集合概述
Java中的集合,指一系列存儲數據的接口和類,可以解決復雜的數據存儲問題.
導包:import java.util.*;
簡化的集合框架圖如下:
List·列表
ArrayList
List是一個接口:
public interface List<E> extends Collection<E>{...}
ArrayList是最常用的一種List的子類(當然也實現了其他接口,也繼承了父類)。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{...}
ArrayList用法類似於數組,且其容量可按需要動態調整,亦被稱為動態數組。
數組最大的痛點是大小固定(可以改變,但是很麻煩)
ArrayList底層是用數組實現的,所以名字里帶了個數組(Array)。
示例:泰國旅行團
本示例展示了在List中使用泛型的必要性。
設定:泰國旅行團,約定只收Girl,有一個Boy混入,編譯沒問題,接機(輸出)時按Girl接收,會出錯。
import java.util.ArrayList;
import java.util.List;
public class TestArrayList {
public static void main(String[] args) {
// 開團
List _泰國五日游 = new ArrayList();
Girl _g1 = new Girl();
Girl _g2 = new Girl();
Girl _g3 = new Girl();
_泰國五日游.add(_g1);
_泰國五日游.add(_g2);
_泰國五日游.add(_g3);
// 混入
Boy _b = new Boy();
_泰國五日游.add(_b);
System.out.println("...");
// 接機
for (int i = 0; i < _泰國五日游.size(); i++) {
Girl g = (Girl) _泰國五日游.get(i);
}
}
}
class Boy {
}
class Girl {
}
代碼沒錯,運行出錯(對象本是Boy類型,偏要轉成Girl類型---類型轉換異常)
Exception in thread "main"
java.lang.ClassCastException
ArrayList<E>使用泛型
JDK 1.5之后,引入了泛型,可指定列表內元素的類型。類型不符合的元素不允許加入數組,這樣就能再編譯階段發現錯誤,避免運行時出錯的尷尬。
// 開團
List<Girl> _泰國五日游 = new ArrayList<Girl>();
……
// 混入
Boy _b = new Boy();
//提示代碼有錯誤: _泰國五日游.add(_b);
遍歷
import java.util.*;
public class ListTraversal {
public static void main(String[] args) {
m010Traversal();
m020線程安全版();
}
private static void m010Traversal() {
System.out.println("=====遍歷");
List<String> lst = new ArrayList<String>();
lst.add("孫行者");
lst.add("豬八戒");
lst.add("沙悟凈");
// (1)
for (int i = 0; i < lst.size(); i++) {
System.out.println("for遍歷集合:" + lst.get(i));
}
// (2)
for (String s : lst) {
System.out.println("foreach遍歷集合:" + s);
}
// (3)Iterator,迭代器。用於遍歷(迭代訪問)集合中的元素
Iterator<String> it = lst.iterator();
while (it.hasNext()) {
System.out.println("Iterator遍歷:" + it.next());
}
// (4)Java 8:調用forEach()方法遍歷集合
lst.forEach(s -> System.out.println("Lambda表達式遍歷集合:" + s));
}
// API文檔上說ArrayList不是同步的,即多線程環境下不安全
// Collections.synchronizedList(...)將其轉為線程安全的列表
private static void m020線程安全版() {
System.out.println("=====線程安全版");
List<String> lst = new ArrayList<String>();
lst.add("孫行者");
lst.add("豬八戒");
lst.add("沙悟凈");
// 解決線程安全問題
List<String> synList = Collections.synchronizedList(lst);
for (String s : synList) {
System.out.println("foreach遍歷集合:" + s);
}
}
}
更多方法
Collection相關方法
這些方法屬於Collection類,可以被子類繼承,因此通用性較強,不僅List能用,Set也能用。
返回類型 | 方法名稱 | 描述 |
---|---|---|
boolean | add(Object o) | 添加元素 |
int | size() | 獲取元素個數 |
boolean | contains(Object o) | 判斷是否存在指定元素 |
boolean | remove(Object o) | 刪除元素 |
void | clear() | 清空 |
boolean | isEmpty() | 判空 |
Object[] | toArray() | 集合轉數組 |
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
public class TestCollection {
public static void main(String[] args) {
Collection<String> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add("s" + i);
}
System.out.println("size():" + list.size());
// 查找
boolean contains = list.contains("s1");
System.out.println("contains(Object o):" + contains);
boolean empty = list.isEmpty();
System.out.println("isEmpty():" + empty);
// 集合轉數組
Object[] array = list.toArray();
System.out.println("toArray():" + Arrays.toString(array));
// 刪除
list.remove("s1");
System.out.println("remove(Object o):" + list);
list.clear();
System.out.println("clear():" + list);
}
}
List相關方法
List的派生類對象可以使用,Set不可用。
都是和索引相關的方法:
返回類型 | 方法名稱 | 描述 |
---|---|---|
void | add(int index, E element) | 指定位置添加元素 |
int | indexOf(Object o) | 獲取指定元素的索引 |
E | set(int index, E element) | 替換指定位置的元素,返回更新前的元素 |
E | get(int index) | 獲取指定索引的元素 |
E | remove(int index) | 刪除指定索引的元素 |
import java.util.ArrayList;
import java.util.List;
public class TestList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add("s" + i);
}
list.add(3, "舍衛國");
int indexOf = list.indexOf("舍衛國");
System.out.println("List.indexOf(Object o):" + indexOf);
String set = list.set(0, "舍衛國趙長者");// 返回更新前的元素
System.out.println("List.set(int index, E element):" + set);
String get = list.get(0);
System.out.println("List.get(int index):" + get);
String remove = list.remove(3);// 返回被刪除的元素
System.out.println("List.remove(int index):" + remove + list);
}
}
源碼淺析:
ArrayList底層是通過數組實現,查詢快、增刪慢。API文檔上說ArrayList不是同步的,即多線程環境下不安全,但是效率高。
ArrayList每次擴容至1.5倍。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// >>1:右移動1位=除以2
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
Vector
和ArrayList用法一致。
元素超過它的初始大小 | 線程安全 | 效率 | |
---|---|---|---|
ArrayList | *150% | × | 高 |
Vector | *200% | √ | 低 |
Vector是一個比較老的類,在JDK 1.0即已出現,不推薦使用(藍橋杯的練習題中出現過Vector,在那道題中只要知道它的用法和ArrayList一樣就行)。
雖然Vector是線程安全的,但是在線程安全方面也不推薦使用。推薦方案如下:
List<String> synList = Collections.synchronizedList(lst);
LinkedList
ArrayList使用數組實現,查詢快,增刪慢;
LinkedList使用鏈表實現,查詢慢,增刪快,適用於經常插入、刪除大量數據的場合,適合采用迭代器Iterator遍歷。
如果僅僅是在列表末尾插入數據,LinkedList的效率低於ArrayList,因為LinkedList調用add時需要創建對象,而ArrayList只是在容量不夠時才擴容。
LinkedList實現了List和Deque(雙端隊列)接口。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
特色方法(此時不能使用多態):
特色方法 | 解釋 |
---|---|
addFirst() | 頭部添加 |
addLast() | 尾部添加 |
removeFirst() | 頭部刪除 |
removeLast() | 尾部刪除 |
push() | 入棧,等效於addFirst() |
pop() | 出棧,等效於removeFirst() |
offer() | 入隊列,等效於addLast() |
poll() | 出隊列,等效於removeFirst() |
import java.util.LinkedList;
public class TestLinkedList {
public static void main(String[] args) {
LinkedList<String> link = new LinkedList<String>();
// addFirst:頭部添加數據
link.addFirst("A");
link.addFirst("B");
link.addFirst("C");
link.removeFirst();
System.out.println(link);
// addLast:尾部添加數據
link.addLast("A");
link.addLast("B");
link.addLast("C");
link.removeLast();
System.out.println(link);
link.clear();// 清空
// push:將元素推入棧,等效於addFirst()
link.push("A");
link.push("B");
link.push("C");
// pop:出棧,調用的是removeFirst()
link.pop();
System.out.println("棧" + link);
link.clear();// 清空
// 將指定元素添加到此列表的末尾(最后一個元素)。
// offer:入隊列:調用的是add方法,add又調用linkLast,和addLast一樣
link.offer("A");
link.offer("B");
link.offer("C");
// poll:出隊列:調用的是removeFirst()
link.poll();
System.out.println("隊列" + link);
}
}
[B, A]
[B, A, A, B]
棧[B, A]
隊列[B, C]
*ArrayDeque·棧和隊列
- 棧:先進后出
- 隊列:先進先出
Deque(雙端隊列),是Queue的子接口,其實現類ArrayDeque和ArrayList的實現機制相似,使用Object[]數組存儲集合元素,當容量不足時,可以重新分配數組。
ArrayDeque可以當做棧和隊列使用。
import java.util.*;
public class TestArrayDeque {
public static void main(String[] args) {
m030棧();
m040隊列();
}
static void m030棧() {
System.out.println("=====棧");
// push,pop(poll也可以)
Deque<String> stack = new ArrayDeque<String>();
stack.push("A");
System.out.println(stack);// [A]
stack.push("B");
System.out.println(stack);// [B, A]
stack.push("C");
System.out.println(stack);// [C, B, A]
System.out.println("peek()訪問第一個元素:" + stack.peek());// C
System.out.println("pop()彈出:" + stack.pop());// C
System.out.println(stack);// [B, A]
}
static void m040隊列() {
System.out.println("=====隊列");
// offrt,poll(pop也可以)
Deque<String> queue = new ArrayDeque<String>();
queue.offer("A");// [A]
System.out.println(queue);
queue.offer("B");// [A, B]
System.out.println(queue);
queue.offer("C");// [A, B, C]
System.out.println(queue);
System.out.println("peek()訪問第一個元素:" + queue.peek());// A
System.out.println("poll()彈出:" + queue.poll());// A
System.out.println(queue);// [B, C]
}
}
運行結果:
=====棧
[A]
[B, A]
[C, B, A]
peek()訪問第一個元素:C
pop()彈出:C
[B, A]
=====隊列
[A]
[A, B]
[A, B, C]
peek()訪問第一個元素:A
poll()彈出:A
[B, C]