集合類型
底層細節更推薦看這個 【戀上數據結構】數據結構與算法,這裏是學習一些使用與Java特性
java.util
包中有個集合框架(Collections Framework),提供了一大堆常用的數據結構
ArrayList
、LinkedList
、Queue
、Stack
、HashSet
、HashMap
等
數據結構是計算機存儲、組織數據的方式:
- 在實際應用中,根據使場景來選擇最合適的數據結構
集合框架預覽:
下圖:紫色代表接口、藍色是抽象類、綠色是實現類。
數組的侷限性
數組的侷限性:
- 無法動態擴容
- 操作元素的過程不夠面向對象
- …
ArrayList
是 Java 中的動態數組:
- 一個可以動態擴容的數組
- 封裝了各種實用的數組操作
ArrayList(常用方法 + 基本使用)
常用方法:
public int size() // 元素數量
public boolean isEmpty() // 是否爲空
public boolean contains(Object o) // 是否包含元素o
public int indexOf(Object o) // 從前往後查詢元素o的下標
public int lastIndexOf(Object o) // 從後往前查詢元素o的下標
public E get(int index) // 獲取下標爲index的元素
public E set(int index,E element) // 設置下標index的元素爲element
public boolean add(E e) // 往最後添加一個元素e
public void add(int index,E element) // 往下標index處添加一個元素e
public E remove(int index) // 移除index處的元素
public boolean remove(Object o) // 移除元素o
public void clear() // 清空
public Object[] toArray() // 轉爲數組
public <T>T[] toArray(T[] a) // 轉爲數組(指定類型)
public void trimToSize() // 縮容, 縮小到當前容量
public void ensureCapacity(int minCapacity) // 擴容
public boolean addAll(Collection<? extends E>c)
public boolean addAll(int index, Collection<? extends E> c)
public boolean removeAll(Collection<?>c)
public boolean retainAll(Collection<?>c)
public void forEach(Consumer<? super E> action)
public void sort(Comparator<? super E> c)
ArrayList
的基本使用:
// 如果不使用泛型, ArrayList中可以放任何東西, 不推薦這樣使用(不安全)
ArrayList list = new ArrayList();
list.add(11);
list.add(false);
list.add(null);
list.add(3.14);
list.add(0, "jack");
list.add('8');
// 3
System.out.println(list.indexOf(null));
// 6
System.out.println(list.size());
// [jack, 11, false, null, 3.14, 8]
System.out.println(list);
ArrayList — retainAll
list1.retainAll(list2)
: 會從 list1
中刪除掉 list2
中元素以外的所有元素。
List<Integer> list1 = new ArrayList<>();
list1.add(11);
list1.add(22);
list1.add(33);
list1.add(44);
List<Integer> list2 = new ArrayList<>();
list2.add(22);
list2.add(44);
// 從 list1 中刪除掉 list2 中元素以外的所有元素
list1.retainAll(list2);
// [22, 44]
System.out.println(list1);
需要注意的是 public boolean retainAll(Collection<?>c)
接收的參數類型是 Collection
,Java 中大部分集合之間都可以互相調用 retainAll
。
List<Integer> list = new ArrayList<>();
list1.add(11);
list1.add(22);
list1.add(33);
list1.add(44);
Stack<Integer> stack = new Stack<>();
stack.push(11);
// 從 list1 中刪除掉 stack 中元素以外的所有元素
list.retainAll(stack);
// [11]
System.out.println(list);
ArrayList — toArray
Integer[] array0 = (Integer[]) list.toArray();
這段代碼是不可行的,原因是 ArrayList
中不寫泛型是可以任意添加元素的,強轉 list.toArray()
的返回值極其不安全,會拋出 java.lang.ClassCastException
異常。
List<Integer> list = new ArrayList<>();
list.add(11);
list.add(22);
list.add(33);
// 不可以強轉爲Intger[], 會拋出異常: java.lang.ClassCastException
// Integer[] array0 = (Integer[]) list.toArray();
// 可以利用 Object[]來接收, 但是更推薦下面的寫法
Object[] array1 = list.toArray();
// [Ljava.lang.Object;@15db9742
System.out.println(array1);
// [11, 22, 33]
System.out.println(Arrays.toString(array1));
// 比起用 Object[]接收,更推薦這種寫法
Integer[] array2 = list.toArray(new Integer[0]);
// [Ljava.lang.Integer;@6d06d69c
System.out.println(array2);
// [11, 22, 33]
System.out.println(Arrays.toString(array2));
具體爲什麼不能轉,請對比下面一系列情況:
Object obj1 = 11;
// 可以正常轉換
Integer obj2 = (Integer) obj1;
System.out.println(obj2); // 11
Object obj1 = new Object();
// java.lang.ClassCastException
Integer obj2 = (Integer) obj1;
System.out.println(obj2);
Object[] array1 = {11, 22, 33};
// 上一行代碼的本質就是下面一行
// Object[] array1 = new Object[] {11, 22, 33};
// java.lang.ClassCastException
Integer[] array2 = (Integer[]) array1;
System.out.println(array1);
ArrayList 的遍歷(5種)
經典的 for 循環遍歷,可以獲取遍歷元素的下標:
// 經典
int size = list.size();
for (int i = 0; i < size; i++) {
System.out.println(list.get(i));
}
利用迭代器進行遍歷:
// 迭代器
Iterator<Object> it = list.iterator();
while(it.hasNext()) {
Object obj = it.next();
System.out.println(obj);
}
實現了 Iterable
接口的對象,都可以使用 for-each 格式遍歷元素:比如 List
、Set
等;
Iterable
在使用 for-each 格式遍歷元素時,本質是使用了 Iterator
對象(迭代器)。
// for-each 格式
for (Object obj : list) {
System.out.println(obj);
}
集合的 forEach
利用匿名內部類遍歷:
list.forEach(new Consumer<Object>() {
@Override
public void accept(Object t) {
System.out.println();
}
});
利用了 lambda 表達式的 forEach
:
list.forEach((obj) -> {
System.out.println(obj);
});
最精簡的遍歷輸出,瞭解一下即可:
list.forEach(System.out::println);
ArrayList 的擴容原理
具體的在 戀上數據結構(第一季) 已經講得很詳細了,我已爛熟於心…再不濟點進源碼看看就行。
自定義迭代器 Iterable、Iterator
前面說過,實現了 Iterable
接口的對象,都可以使用迭代器和 for-each 格式(本質還是迭代器)遍歷元素。
如何讓自定義對象實現實現可迭代的功能呢?
import java.util.Iterator;
public class ClassRoom implements Iterable<String> {
private String[] students;
public ClassRoom(String... students) { // 可變參數的本質是數組
this.students = students; // 所以可變參數可以用數組來接收
}
@Override
public Iterator<String> iterator() {
return new ClassRoomIterator();
}
private class ClassRoomIterator implements Iterator<String> {
private int index; // 遊標
@Override
public boolean hasNext() {
return index < students.length;
}
@Override
public String next() {
return students[index++];
}
}
}
我們來測試一下它的遍歷:
ClassRoom room = new ClassRoom("Jack", "Rose", "Jerry");
// for-each格式遍歷
for (String string : room) {
System.out.println(string);
}
// 迭代器遍歷
Iterator<String> it = room.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
集合遍歷的注意點(遍歷集合的同時刪除元素)
首先我們先往集合里加點東西:
List<Integer> list = new ArrayList<>();
list.add(11);
list.add(22);
list.add(33);
list.add(44);
然後我們通過遍歷的方式,挨個刪除所有的元素。
可能有人會這麼寫,這樣是錯誤的:拋出異常 java.lang.IndexOutOfBoundsException
int size = list.size();
for (int i = 0; i < size; i++) {
// java.lang.IndexOutOfBoundsException
list.remove(i);
}
或者會這麼寫,這樣寫也是錯誤的:會少刪除元素
for(int i =0; i < list.size(); i++) {
list.remove(i);
}
System.out.println(list); // [22, 44]
下面兩種寫法也是錯誤的:拋出異常 java.util.ConcurrentModificationException
// java.util.ConcurrentModificationException
list.forEach((e) -> {
list.remove(e);
});
// java.util.ConcurrentModificationException
for (Integer e : list) {
list.remove(e);
}
我們會發現,遍歷的集合的方法特別多,但是如果要在遍歷的同時刪除元素,以上都是不可行的。
使用迭代器、forEach
遍歷集合元素時,若使用了集合自帶的方法修改集合的長度(比如 add
、remove
等方法),會拋出 java.util.ConcurrentModificationException
異常。
如果希望在遍歷元素的同時刪除元素:
- 請使用
Iterator
進行遍歷 - 然後使用
Iterator
的remove
方法刪除元素
Iterator<Integer> it = list.iterator();
while(it.hasNext()) {
it.next();
it.remove();
}
// 刪除成功
System.out.println(list); // []
System.out.println(list.size()); // 0
ListIterator(遍歷同時添加元素)
ListIterator
繼承自 Iterator
,在 Iterator
的基礎上增加了一些功能
boolean hasNext(); // 是否有下一個元素
E next(); // 獲取下一個元素
boolean hasPrevious(); // 是否有前一個元素
E previous(); // 獲取前一個元素
int nextIndex();
int previousIndex();
void remove();
void set(E e);
void add(Ee);
基本使用:
List<Integer> list = new ArrayList<>();
list.add(11);
list.add(22);
list.add(33);
ListIterator<Integer> it = list.listIterator();
// 從前往後遍歷
while (it.hasNext()) {
System.out.println(it.next());
} // 11 22 33
// 從後往前遍歷
while (it.hasPrevious()) {
System.out.println(it.previous());
} // 33 22 11
遍歷同時添加元素:
ListIterator<Integer> it = list.listIterator();
while (it.hasNext()) {
it.set(it.next() + 55);
}
System.out.println(list); // [66, 77, 88]
LinkedList
LinkedList
是一個雙向鏈表
- 實現了 List 接口
- API 跟 ArrayList 類似
LinkedList vs ArrayList
Stack
Stack
常用方法:
Stack
使用:
Stack<Integer> stack = new Stack<>();
stack.push(11);
stack.push(22);
stack.push(33);
System.out.println(stack.peek()); // 33
System.out.println(stack.search(11)); // 3
while(!stack.isEmpty()) {
System.out.println(stack.pop());
} // 33 22 11
Queue
Queue
常用方法:
Queue
使用:
Queue<Integer> queue = new LinkedList<>();
queue.add(11);
queue.add(22);
queue.add(33);
System.out.println(queue.element()); // 11
while (!queue.isEmpty()) {
System.out.println(queue.remove());
} // 11 22 33
Set
- 只要是
Set
都可以用作去重。
HashSet
HashSet
簡單使用:
Set<String>set = new HashSet<>();
set.add("Jack");
set.add("Rose");
set.add("Kate");
set.add("Jack");
set.add("Hello");
System.out.println(set.size()); // 4
// 元素的存儲是無序的
System.out.println(set); // [Kate, Hello, Rose, Jack]
set.remove("B");
System.out.println(set); // [Kate, Hello, Rose, Jack]
HashSet
的遍歷:
Set<String> set = new HashSet<>();
set.add("Jack");
set.add("Rose");
set.add("Kate");
// 元素的遍歷是無序的
// 增強for循環遍歷
for (String str : set) {
System.out.println(str);
} // Kate Rose jack
// 迭代器遍歷
Iterator<String> it = set.iterator();
while (it.hasNext()) {
System.out.println(it.next());
} // Kate Rose jack
// forEach遍歷
set.forEach((str) -> {
System.out.println(str);
}); // Kate Rose jack
LinkedHashSet
LinkedHashSet
在HashSet
的基礎上(可去重),記錄了元素的添加順序
LinkedHashSet
簡單使用:
Set<String> set = new LinkedHashSet<>();
set.add("Jack");
set.add("Rose");
set.add("Kate");
set.add("Jack");
set.add("Hello");
System.out.println(set.size()); // 4
// 元素的存儲是有序的(添加順序)
System.out.println(set); // [Jack, Rose, Kate, Hello]
// 元素的遍歷是有序的(添加順序)
for (String str : set) {
System.out.println(str); // Jack Rose Kate
}
set.remove("B");
System.out.println(set); // [Jack, Rose, Kate, Hello]
TreeSet
TreeSet
要求元素必須具備可比較性,默認從小到大的順序遍歷元素(可通過比較器修改)
// 字符串繼承了Comparable, 具有可比較性, 默認按字母順序從小到大
Set<String> set =new TreeSet<>();
set.add("Jack");
set.add("Rose");
set.add("Jim");
set.add("Kate");
set.add("Rose");
set.add("Larry");
System.out.println(set); // [Jack, Jim, Kate, Larry, Rose]
for (String str : set) {
System.out.println(str); // Jack Jim Kate Larry Rose
}
通過比較器自定義比較方式:
// 默認比較方式是從小到大
// 通過比較器修改爲從大到小
Set<Integer> set = new TreeSet<>((i1, i2) -> i2 -i1);
set.add(33);
set.add(11);
set.add(55);
set.add(22);
set.add(44);
System.out.println(set); // [55, 44, 33, 22, 11]
for (Integer i : set) {
System.out.println(i); // 55 44 33 22 11
}
Map
HashMap
HashMap
存儲的是鍵值對(key-value),Map 譯爲 映射,有些編程語言中叫做 字典
HashMap
簡單使用:
HashMap<String, Integer> map = new HashMap<>();
map.put("Jack", 11);
map.put("Rose", 22);
map.put("Jim", 22);
map.put("Jack", 33); // 重複
map.put("Kate", 11);
System.out.println(map.size()); // 4
System.out.println(map.get("Jack")); // 33
System.out.println(map); // {Kate=11, Rose=22, Jack=33, Jim=22}
map.remove("Rose");
System.out.println(map); // {Kate=11, Jack=33, Jim=22}
HashMap — 遍歷
HashMap<String, Integer> map = new HashMap<>();
map.put("Jack", 11);
map.put("Rose", 22);
map.put("Jim", 33);
最簡潔的遍歷方法:通過 forEach
遍歷
// forEach 遍歷, 推薦
map.forEach((key, value) -> {
System.out.println(key + "=" + value);
}); // Rose=22 Jack=11 Jim=33
通過鍵值對 entrySet
遍歷:
// entrySet 是一組鍵值對, 通過鍵值對來遍歷, 推薦
Set<Entry<String, Integer>> entries = map.entrySet();
for (Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey() + "=" + entry.getValue());
} // Rose=22 Jack=11 Jim=33
先取出 value 的集合,再遍歷:無法通過 value 獲取 key,只能遍歷 value
// 只能遍歷 value
Collection<Integer> values = map.values(); // value的集合
for (Integer value : values) {
System.out.println(value);
} // 22 11 33
取出 key 的集合,再遍歷:可以通過 key 獲取 value,但是效率低下
Set<String> keys = map.keySet(); // key的集合
for (String key : keys) {
// 先遍歷key, 又通過key去找到value (整體效率較低)
System.out.println(key + "=" + map.get(key));
} // Rose=22 Jack=11 Jim=33
LinkedHashMap
LinkedHashMap
在HashMap
的基礎上,記錄了元素的添加順序
Map<String, Integer> map = new LinkedHashMap<>();
map.put("Jack", 11);
map.put("Rose", 22);
map.put("Jim", 33);
map.put("Kate", 44);
System.out.println(map); // {Jack=11, Rose=22, Jim=33, Kate=44}
map.forEach((k, v) -> {
System.out.println(k + "=" + v);
}); // Jack=11 Rose=22 Jim=33 Kate=44
map.remove("Rose");
System.out.println(map); // {Jack=11, Jim=33, Kate=44}
TreeMap
TreeMap
要求 key 必須具備可比較性,默認從小到大的順序遍歷 key(可通過比較器修改)
Map<String, Integer> map = new TreeMap<>();
map.put("Jack", 11);
map.put("Rose", 22);
map.put("Jim", 33);
map.put("Kate", 44);
map.put("Larry", 55);
System.out.println(map); // {Jack=11, Jim=33, Kate=44, Larry=55, Rose=22}
map.forEach((k, v) -> {
System.out.println(k + "=" + v);
}); // Jack=11 Jim=33 Kate=44 Larry=55 Rose=22
List vs Set vs Map
java.util.Collections
java.util.Collections
是一個常用的集合工具類,提供了很多實用的靜態方法