【Java 集合】ArrayList、LinkedList、Stack、Queue、Set、Map, 迭代器 Iterable、Iterator,Collections類

底層細節更推薦看這個 【戀上數據結構】數據結構與算法,這裏是學習一些使用與Java特性

java.util 包中有個集合框架(Collections Framework),提供了一大堆常用的數據結構

  • ArrayListLinkedListQueueStackHashSetHashMap

數據結構是計算機存儲、組織數據的方式:

  • 在實際應用中,根據使場景來選擇最合適的數據結構
    在這裏插入圖片描述

集合框架預覽
下圖:紫色代表接口、藍色是抽象類、綠色是實現類

在這裏插入圖片描述

數組的侷限性

在這裏插入圖片描述
數組的侷限性:

  • 無法動態擴容
  • 操作元素的過程不夠面向對象

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 格式遍歷元素:比如 ListSet 等;
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 遍歷集合元素時,若使用了集合自帶的方法修改集合的長度(比如 addremove 等方法),會拋出 java.util.ConcurrentModificationException 異常。

如果希望在遍歷元素的同時刪除元素

  • 請使用 Iterator 進行遍歷
  • 然後使用 Iteratorremove 方法刪除元素
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

  • LinkedHashSetHashSet 的基礎上(可去重),記錄了元素的添加順序

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

  • LinkedHashMapHashMap 的基礎上,記錄了元素的添加順序
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 是一個常用的集合工具類,提供了很多實用的靜態方法

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章