集合类型
Java笔记目录可以点这里: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
是一个常用的集合工具类,提供了很多实用的静态方法