集合概述
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]