整體框架

綠色代表介面/抽象類;藍色代表類,
主要由兩大介面組成,一個是「Collection」介面,另一個是「Map」介面,
前言
以前剛開始學習「集合」的時候,由于沒有好好預習,也沒有學好基礎知識,介面,類,這些基礎知識都沒學好,所以學到這里還是懵懵懂懂的,第一次接觸到「集合」,這兩個字,在我的腦海中,只浮現出數學中學過的「集合」,所以當「集合」在編程語言中出現時,我就沒有繞過來,不過以我現在的視角看,也是和數學中學過的「集合」這種概念是差不多的,
數學中的「集合」:
集合是
確定的一堆東西,集合里的東西則稱為元素,現代的集合一般被定義為:由一個或多個確定的元素所構成的整體,
Java 中的「集合」:在我的理解中,集合可以說是存放 Java 物件的東西,這個東西有人稱為集合,也有人稱為容器,這也是為什么我的標題寫的是 集合(容器),存放在集合中的物件,人們稱為元素,
為什么會有集合的出現呢?
是這樣的,在某些情況下,我們需要創建許多 Java 物件,那么這些物件應該存放在哪里?
需求是這樣的:
- 可以存放物件
- 可以存放不同資料型別
- 可以存放很多物件,沒有限制
那么一開始會想到陣列,陣列可以存放物件,是的,沒錯,但是,陣列有它的缺點,就是一旦創建后,那么陣列長度是不可變的,而且存放的物件的資料型別是固定的,所以陣列不滿足這些條件,此時,集合就出現了,
Java 中的集合
從上面的框架圖中可以看到,主要就兩個介面,分別是 Collection 和 Map,
這兩個介面都抽象了元素的存盤方法,具體有什么區別呢?好吧,不說也知道,Collection 就是用來存盤單一元素的,而 Map 是用來存盤鍵值對的,
下面我將從這兩個介面切入,進而開始好好地回爐重造,哈哈哈哈哈,
可以帶著這些問題去回顧:
- Collection 是怎樣的?Map 又是怎樣的?
- 它們分別還有什么子介面?它們又有哪些實作類呢?
- 提供給我們的API又有哪些呢?具體的 API 用法和效果是怎樣的呢?
Collection
Collection 是最基本的集合介面,一個 Collection 代表一組 Object型別的物件,Java 沒有提供直接實作Collection 的類,只提供繼承該介面的子介面(List、Set、Queue 這些),該介面存盤一組不唯一,無序的物件,這里強調不唯一、無序,那么集合的范圍就很大,想要縮小,比如唯一、有序這些,就可以通過子介面來規定,剛好,它就是這樣來定義子介面的,
- List 介面:元素不唯一且有序,說明可以存盤多個相同的元素,但是存盤是有順序的,即有序可重復,
- Set 介面:元素唯一且無序,說明不能存盤多個相同的元素,存盤的元素沒有順序,即無序不可重復,
我們再來看看 Collection 介面它抽象出來的方法有哪些,

其中,還可以看到有個以 Iterable(可迭代的) 來分類的方法,主要就是 iterator() 這個方法,即迭代器,所謂迭代器,就是用來遍歷集合元素的一個東西,
/**
* Returns an iterator over the elements in this collection. There are no
* guarantees concerning the order in which the elements are returned
* (unless this collection is an instance of some class that provides a
* guarantee).
*
* @return an <tt>Iterator</tt> over the elements in this collection
*/
Iterator<E> iterator();
iterator() 這個方法就是用來回傳對此集合中元素的迭代器,也就是說獲取該集合的迭代器, 這個抽象方法不保證迭代的順序(除非此集合是某個提供保證的類的實體),
再通俗一點,我們想要遍歷集合的元素,那么就需要通過集合物件獲取迭代器物件,通過迭代器物件來遍歷集合中的元素,而且遍歷的順序是跟該集合有關的,

關于這個迭代器,后續再來講吧,
下面開始說一下基本的介面實作類,基本的API,加上一些自己的見解,最主要是先回顧 API 的使用,畢竟還有好多知識,這些知識需要建立在我們會用的前提下,所以這里淺入淺出~
List 介面下的實作類
List 介面下的實作類有 ArrayList、LinkedList、Vector、Stack

這里簡要介紹下 List 介面,List 介面是一個有序的 Collection,使用此介面能夠精確的控制每個元素插入的位置,能夠通過「索引」(即元素在 List 中位置,類似于陣列的下標,從0開始)來訪問 List 中的元素,它存盤一組不唯一、有序(插入順序)的物件,
ArrayList
我們看下 ArrayList 原始碼,它是這樣定義的:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
...
}
可以看到,它
- 繼承了
AbstractList - 實作了
List,RandomAccess,Cloneable,Serializable
ArrayList 是動態陣列,所謂動態陣列,即可以動態的修改,隨時出入、洗掉元素,還可以動態擴容,也就是沒有固定的容量限制,可以存放很多元素,直到你的記憶體爆炸,
初始化是這樣的:
// 1. 以多型的方式寫,介面不能實體化,所以通過其實作類對介面實體化,
List<E> list = new ArrayList<>();
// 2. 直接ArrayList
ArrayList<E> list = new ArrayList<>();
這兩種寫法有什么區別呢?
第1種寫法:此時的 List 的物件 list,可以通過這個物件呼叫 List 介面宣告的方法,但是不能呼叫 ArrayList 獨有的方法,換句話說,List 這個介面規定了一些抽象的方法,具體實作不關心,你可以直接呼叫,這里「具體實作不關心」就是說,你是使用 ArrayList 來實體化 List 介面或者使用 LinkedList 來實體化 List 介面,List 介面它都不關心,外界使用的時候,知道 List 提供這些 API 就夠了,另一個角度理解,即該 list 物件擁有 List 的屬性和方法,沒有 ArrayList 獨有的屬性和方法,
第2種寫法:此時 ArrayList 的物件 list,可以呼叫所有方法,畢竟 ArrayList 實作了 List 介面,那么 List 有的方法,ArrayList 的物件 list也有,
進入正題

這些 API 的使用,需要熟悉,畢竟演算法題也會用到,
public void apiOfArrayList() {
int idx;
List<Integer> list = new ArrayList<>();
// 添加元素
list.add(23);
list.add(30);
// 根據下標(索引)獲取元素
idx = list.get(0);
idx = list.get(1);
// 更新元素值,在某個位置重新賦值
list.set(1, 32);
List<String> list2 = new ArrayList<>();
list2.add("god23bin");
list2.add("LeBron");
list2.add("I love Coding");
// 移除下標(索引)為2的元素
list2.remove(2);
// 移除指定元素
list2.remove("god23bin");
// 獲取集合長度,遍歷會用到
int len = list2.size();
// 判斷某個元素是否在集合中,演算法題會用到的
boolean flag = list2.contains("god23bin");
// 判斷集合是否為空,演算法題會用到的
boolean flag2 = list2.isEmpty();
}
排序:
- ArrayList 中的 sort() 方法
Random random = new Random();
List<Integer> numList = new ArrayList<>();
for (int i = 0; i < 10; ++i) {
numList.add(random.nextInt(100));
}
// 將numList升序排序
numList.sort(Comparator.naturalOrder());
// 將numList降序排序
numList.sort(Comparator.reverseOrder());
- Collections 工具類的 sort() 方法
// 將numList升序排序
Collections.sort(numList);
關于 Collections 工具類的排序
看看這兩個方法,這兩個方法都是泛型方法,
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
第一個方法需要「待排序類」實作 Comparable 介面,這樣才能使用這個方法,
第二個方法需要「待排序類」有一個比較器,即 Comparator,換句話說需要有一個比較器類實作 Comparator 介面,這樣才能使用這個方法,
扯到 Comparable 和 Comparator
所以,如果你想要某個類的物件支持排序,那么你就需要讓這個類實作 Comparable 介面,這個介面只有一個抽象方法 compareTo(),我們需要實作它,它的規則是:若 當前值 較大則回傳正值,若相等則回傳0,若 當前值 較小則回傳負值,
這里我們可以看到,Collections 是可以對 numList 進行排序的,因為這個 numList 集合的元素型別是 Integer,為什么 Integer 型別的元素支持排序?我們可以從原始碼中看到 Integer 是實作了 Comparable 介面的,所以 Integer 型別的元素才支持排序,
public final class Integer extends Number implements Comparable<Integer> {
...
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
...
}
回到第一句話,如果你想要某個類的物件支持排序,那么你就需要讓這個類實作 Comparable 介面,不然是不支持排序的,
下面,我這里就分別使用兩種方式(實作 Comparable 或 Comparator)讓某個類支持排序,
搞定 Comparable
舉個栗子:我這里有一個 Game 類(待排序類,本身不支持排序,我們的任務是讓 Game 具有可排序的能力),當你把多個 Game 物件放到集合中使用 Collections 這個工具類進行排序時,Collections 是不知道如何給 Game 排序的,直到 Game 實作了 Comparable 介面后,Collections 才知道 Game 該如何排序,
Game 類實作 Comparable 介面,重寫 compareTo() 方法,
public class Game implements Comparable<Game> {
public String name;
public Double price;
// 省略 getter setter 構造方法
@Override
public int compareTo(Game o) {
return comparePrice(this.price, o.price);
}
public int comparePrice(double p1, double p2) {
return p1 > p2 ? 1 : (p1 == p2 ? 0 : -1);
}
}
這樣,我們就可以使用 Collections 對 Game 進行排序,
List<Game> gameList = new ArrayList<>();
gameList.add(new Game("GTA", 58.0));
gameList.add(new Game("FC", 118.0));
gameList.add(new Game("2K", 199.0));
Collections.sort(gameList); // 進行排序
System.out.println(gameList); // 列印排序結果
搞定 Comparator
同理,我這里有一個 Game 類
public class Game {
public String name;
public Double price;
// 省略 getter setter 構造方法
}
寫一個 Game 的比較器類 GameComparator,讓這個類實作 Comparator 介面,重寫 compare() 方法
public class GameComparator implements Comparator<Game> {
@Override
public int compare(Game g1, Game g2) {
return g1.getPrice() - g2.getPrice();
}
}
這樣,我們就可以使用 Collections 對 Game 進行排序,
List<Game> gameList = new ArrayList<>();
gameList.add(new Game("GTA", 58.0));
gameList.add(new Game("FC", 118.0));
gameList.add(new Game("2K", 199.0));
Collections.sort(gameList, new GameComparator()); // 使用比較器進行排序
System.out.println(gameList); // 列印排序結果
總結排序
你可以選擇兩種方式(實作 Comparable 或 Comparator)中的其中一個,讓某個類支持排序,
- 選擇 Comparable,那么該類需要實作該介面
- 選擇 Comparator,那么需要定義一個比較器,實作該介面
最后通過 Collections.sort() 進行排序,
LinkedList
LinkedList 是鏈表,屬于線性表,學過資料結構的我們也是知道的,有指標域和資料域,雖然說 Java 里沒有指標,但是有指標的思想,這里我也說不太清楚,反正是可以按指標來理解的,(如有更好的描述,歡迎幫我補充啦!)
在 Java 中,這個 LinkedList 是 List 介面下面的實作類,也是很常用的一種集合容器,演算法題也會用到它,
我們看下 LinkedList 原始碼,它是這樣定義的:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
...
}
可以看到,它
- 繼承了
AbstractSequentialList - 實作了
List,Deque,Cloneable,Serializable
同樣,LinkedList 需要掌握的方法和 ArrayList 差不多,可以說基本是一樣的,只是底層實作不一樣,

目前這里就不演示基本的使用方法了,你可以自己動手試試啦!
Vector
我們看下 Vector 原始碼,它是這樣定義的:
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
...
}
可以看到,它
- 繼承了
AbstractList - 實作了
List,RandomAccess,Cloneable,Serializable
這樣一看,它和 ArrayList 的定義,簡直是一模一樣,那它們之間有什么區別嗎?那當然是有啦!
區別就是 Vector 是執行緒安全的,在多執行緒操作下不會出現并發問題,因為 Vector 在每個方法上都加上了 synchronized 關鍵字,保證多個執行緒操作方法時是同步的,
Stack
Stack 顧名思義,就是堆疊,它是 Vector 的子類,實作了標準的堆疊這種資料結構,
public class Stack<E> extends Vector<E> {
...
}
它里面包括了 Vector 的方法,也有自己的方法,
- empty():判斷堆疊是否為空
- peek():查看堆疊頂元素
- push():入堆疊
- pop():出堆疊
- search():搜索元素,回傳元素所在位置
public void apiOfStack() {
Stack<Integer> stack = new Stack<>();
// 入堆疊
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
// 獲取堆疊的大小 == 堆疊中元素個數 == 堆疊的長度
int size = stack.size();
// 查看(回傳)堆疊頂元素
Integer peek = stack.peek();
// 出堆疊
stack.pop();
// 判斷堆疊是否為空
boolean empty = stack.empty();
// 搜索 元素1 此時堆疊中元素為 1 2 3,堆疊頂是3,堆疊底是1
// 從堆疊頂往下找,第一個元素的位置記為1
int search = stack.search(1);
}
但是目前這個已經官方不推薦使用了,而是選擇使用 LinkedList 來用作堆疊,
這里就要扯到佇列 Queue 啦!
Queue 介面
Java 中的 Queue 是一個介面,和上面的 Stack 不同,Stack 是類,
我們看下 Queue 介面原始碼,它是這樣定義的:
public interface Queue<E> extends Collection<E> {
...
}
這個介面就抽象了 6 個方法:
- add():入隊,即隊尾插入元素
- offer():入隊,即隊尾插入元素
- peek():查看隊頭元素
- poll():查看隊頭元素
- remove():出隊,即移除隊頭元素
- element():出隊,即移除隊頭元素
很大的疑問來了!這些方法有什么區別??我們看看原始碼怎么說的,這個原始碼說明也不怕,下面我有翻譯~
add() 和 offer() 的區別
/**
* Inserts the specified element into this queue if it is possible to do so
* immediately without violating capacity restrictions, returning
* {@code true} upon success and throwing an {@code IllegalStateException}
* if no space is currently available.
*
* @param e the element to add
* @return {@code true} (as specified by {@link Collection#add})
* @throws IllegalStateException if the element cannot be added at this
* time due to capacity restrictions
* @throws ClassCastException if the class of the specified element
* prevents it from being added to this queue
* @throws NullPointerException if the specified element is null and
* this queue does not permit null elements
* @throws IllegalArgumentException if some property of this element
* prevents it from being added to this queue
*/
/**
* 將指定的元素插入此佇列(如果可以立即執行此操作而不違反容量限制),成功則回傳 true
* 如果容量不夠,則失敗,拋出 IllegalStateException
*/
boolean add(E e);
/**
* Inserts the specified element into this queue if it is possible to do
* so immediately without violating capacity restrictions.
* When using a capacity-restricted queue, this method is generally
* preferable to {@link #add}, which can fail to insert an element only
* by throwing an exception.
*
* @param e the element to add
* @return {@code true} if the element was added to this queue, else
* {@code false}
* @throws ClassCastException if the class of the specified element
* prevents it from being added to this queue
* @throws NullPointerException if the specified element is null and
* this queue does not permit null elements
* @throws IllegalArgumentException if some property of this element
* prevents it from being added to this queue
*/
/**
* 如果可以在不違反容量限制的情況下立即將指定的元素插入到此佇列中,
* 使用容量受限的佇列時,通常此方法是最好的入隊方法 ,只有當引發例外時才可能無法插入元素,
* 成功回傳 true,失敗回傳 false
*/
boolean offer(E e);
所以區別就是:在容量有限制的佇列中,add() 超過限制會拋出例外,而 offer() 不會,只會回傳 false
remove() 和 poll() 的區別
/**
* Retrieves and removes the head of this queue. This method differs
* from {@link #poll poll} only in that it throws an exception if this
* queue is empty.
*
* @return the head of this queue
* @throws NoSuchElementException if this queue is empty
*/
/**
* 檢索并洗掉此佇列的隊頭元素, 這個方法與 poll 的區別僅僅是當佇列為空時洗掉會拋出例外,
*/
E remove();
/**
* Retrieves and removes the head of this queue,
* or returns {@code null} if this queue is empty.
*
* @return the head of this queue, or {@code null} if this queue is empty
*/
/**
* 檢索并洗掉此佇列的隊頭元素,如果隊空,則回傳 null
* or returns {@code null} if this queue is empty.
*
* @return the head of this queue, or {@code null} if this queue is empty
*/
E poll();
所以區別是:當隊空時洗掉元素,那么 remove() 會拋出例外, poll() 會回傳null
element() 和 peek() 的區別
/**
* Retrieves, but does not remove, the head of this queue. This method
* differs from {@link #peek peek} only in that it throws an exception
* if this queue is empty.
*
* @return the head of this queue
* @throws NoSuchElementException if this queue is empty
*/
/**
* 佇列為空時會拋出例外
*/
E element();
/**
* Retrieves, but does not remove, the head of this queue,
* or returns {@code null} if this queue is empty.
*
* @return the head of this queue, or {@code null} if this queue is empty
*/
/**
* 佇列為空時會回傳 null
*/
E peek();
所以區別是:當隊空查看隊頭元素時,那么 element() 會拋出例外, peek() 會回傳null
總的來說,就是失敗的區別:
| 拋出例外 | 回傳特殊值 | |
|---|---|---|
| 入隊 | add() | offer() 回傳false |
| 出隊 | remove() | poll() 回傳 null |
| 查看隊頭元素 | element() | peek() 回傳 null |
雙端佇列和優先級佇列

Queue 有個子介面 Deque,就是雙端佇列,需要掌握的 Deque 實作類為 LinkedList 和 ArrayDeque,然后我們可以發現 Deque 這個介面抽象出來的方法,在原有的 Queue 上,多出了 First、Last 這些方法,對應著就是從佇列的頭部和尾部進行操作(入隊、出隊等等),
有個抽象類 AbstractQueue 實作了 Queue 介面,然后 PriorityQueue 繼承了 AbstractQueue ,
演示下基本的 API,大部分操作都是大同小異,
public void apiOfDeque() {
// 通過 LinkedList 創建 Deque 物件
Deque<Integer> deque = new LinkedList<>();
// 正常隊尾入隊 => 完成入隊后 [1,2,3]
deque.addLast(1);
deque.addLast(2);
deque.addLast(3);
// 從隊頭入隊 => [4,1,2,3]
deque.addFirst(4);
// 獲取隊頭元素 => 4
// 這里 get 和 peek 的區別就是,get 如果隊慷訓拋出例外
Integer first = deque.getFirst();
// 使用 offer 入隊 => [4,1,2,3,5]
deque.offerLast(5);
// 使用 poll 出隊 => [1,2,3,5]
Integer integer = deque.pollFirst();
// 剩下的操作也是差不多的...
}
至于優先級佇列的呢?之后再寫啦!這個坑等著后面補回來??
Set 介面
對于 Set 介面,我們需要知道它的 3 個實作類,分別是 HashSet、LinkedHashSet、TreeSet,
HashSet 是基于 HashMap 的,
TreeSet 是基于 TreeMap 的,
至于 Map,后面再說啦,
HashSet 和 LinkedHashSet

HashSet 和 LinkedHashSet 的API使用方法基本一模一樣,你會一個,等于會兩個,會兩個,等于會好多,好吧,說實話,熟悉了 Collection 介面的方法,就會好多了好吧哈哈哈哈,
public void apiOfHashSet() {
Set<Integer> hashSet = new HashSet<>();
// 添加元素
hashSet.add(1);
hashSet.add(2);
hashSet.add(3);
// 獲取集合大小
int size = hashSet.size();
// 判斷是否包含某個元素
if (hashSet.contains(3)) {
System.out.println("包含3");
}
// 移除元素
hashSet.remove(2);
// 清空集合
hashSet.clear();
}
public void apiOfLinkedHashSet() {
Set<Integer> linkedHashSet = new LinkedHashSet<>();
// 添加元素
linkedHashSet.add(2);
linkedHashSet.add(3);
linkedHashSet.add(4);
// 獲取集合大小
int size = linkedHashSet.size();
// 判斷是否包含某個元素
if (linkedHashSet.contains(3)) {
System.out.println("包含3");
}
// 移除元素
linkedHashSet.remove(2);
// 清空集合
linkedHashSet.clear();
}
TreeSet
這個坑等著后面補回來??
Map
Map 介面,它提供了一種映射關系,用于存盤具有映射關系的元素,以鍵值對的形式存盤,鍵和值可以是任意型別的,以 Entry 物件存盤,
看 Map 介面的原始碼,可以發現 Entry 是定義在 Map 介面里的一個「內部介面」,

需要知道的是:
- Key 是不可以重復的,Value 是可以重復的,Key 和 Value 都可以為 null,不過只能有一個 Key 為 null
- Map 介面提供了分別回傳 Key 值集合、Value 值集合以及 Entry(鍵值對)集合的方法
// 回傳 Key 值集合
Set<K> keySet();
// 回傳 Value 值集合
Collection<V> values();
// 回傳 Entry 集合
Set<Map.Entry<K, V>> entrySet();
看看完整的 Map 介面提供的抽象方法,當然,還是一樣,并不需要全部掌握啦,

HashMap

HashMap,就是我們學過的哈希表的實作,是經常使用的一個資料結構,演算法題也有它的影子,HashMap 中的Key 值和 Value值都可以為 null,但是一個 HashMap 只能有一個 Key 值為 null 的映射(Key 值不可重復)
還是一樣,這里先回顧 API 的使用,
public void apiOfHashMap() {
Map<Integer, String> hashMap = new HashMap<>();
// 存盤鍵值對
hashMap.put(1, "LeBron");
hashMap.put(2, "Chris");
hashMap.put(3, "god23bin");
// 根據Key獲取Value
String s = hashMap.get(3);
// 獲取某個鍵,如果沒有這個鍵,那么獲取默認值
String defaultValue = https://www.cnblogs.com/god23bin/p/hashMap.getOrDefault(4,"默認值");
// 獲取集合大小
int size = hashMap.size();
// 判斷是否包含某個Key
if (hashMap.containsKey(2)) {
System.out.println("包含值為2的Key");
}
// 判斷是否包含某個Value
if (hashMap.containsValue("LeBron")) {
System.out.println("包含值為LeBron的Value");
}
// 獲取鍵集合
Set<Integer> integers = hashMap.keySet();
for (Integer key : integers) {
System.out.println(key);
}
// 獲取值集合
Collection<String> values = hashMap.values();
for (String val : values) {
System.out.println(val);
}
// 獲取鍵值對集合
Set<Map.Entry<Integer, String>> entries = hashMap.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
}
LinkedHashMap
這個坑等著后面補回來??
HashTable
這個坑等著后面補回來??
TreeMap
這個坑等著后面補回來??
最后
好吧,這方面還是不太會描述,給自己留下了好些坑,那就等著我后面補充吧!集合重造進度 20%!
最后的最后
由本人水平所限,難免有錯誤以及不足之處, 螢屏前的靚仔靚女們 如有發現,懇請指出!
最后,謝謝你看到這里,謝謝你認真對待我的努力,希望這篇博客對你有所幫助!
你輕輕地點了個贊,那將在我的心里世界增添一顆明亮而耀眼的星!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/482143.html
標籤:Java
上一篇:執行緒池的簡介說明
