5 Container and Generics
1 Array
在 Java 中,数组是一种用于存储固定大小的同类型元素的线性数据结构
| 声明 |
|---|
| int[] numbers;
String[] names;
double[] scores;
|
初始化:
| 静态初始化 |
|---|
| // 完整格式
int[] arr1 = new int[]{1, 2, 3, 4, 5};
// 简化格式(最常用)
int[] arr2 = {1, 2, 3, 4, 5};
String[] strArr = {"Hello", "World"};
|
| 动态初始化 |
|---|
| int[] arr = new int[5]; // 创建一个长度为5的int数组,默认值全是0
|
| 声明后再初始化 |
|---|
| int[] arr;
// arr = {1, 2, 3}; // 错误!不能这样写
arr = new int[]{1, 2, 3}; // 正确
|
Array 在 Java 中被视为对象,它继承自 java.lang.Object,因此两个 Array 变量可以相互赋值
| int[] a = new int[10];
a[0] = 5;
int[] b = a;
b[0] = 16;
System.out.println(a[0]); // 输出:16
|
1.1 访问 Array
使用 {arrayName.length} 属性获取长度,注意这不是方法
| 声明后再初始化 |
|---|
| int[] arr = {1, 2, 3};
System.out.println(arr.length); // 输出:3
|
遍历数组可以使用 foreach 或者普通的 for 循环
| 声明后再初始化 |
|---|
| int[] arr = {1, 2, 3, 4, 5};
for (int num : arr) {
System.out.println(num);
}
// var:编译器自动推断类型
for (var num : arr) {
System.out.println(num);
}
|
bounds checking
编译器本身不会验证你使用的数组下标是否在数组的有效范围内,为了避免程序运行时导致问题,Java 语言在运行时负责进行边界检查。这意味着当程序执行到访问数组元素的代码时,JVM 会自动检查你提供的下标是否在合法的范围内。如果检查发现下标越界,Java 会抛出一个 ArrayIndexOutOfBoundsException 运行时异常
| public class Test {
public static void main(String[] args) {
int[] nums = {1, 2, 3, 4, 5};
System.out.println(nums[5]);
}
}
|
| $ javac Test.java # 不会报错
$ java Test # 运行时检查
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
at Test.main(Test.java:4)
|
1.2 Array 方法
| 方法 |
说明 |
返回值 |
String Arrays.toString(array) |
将数组转换为字符串表示形式 |
|
String Arrays.deepToString(array) |
将多维数组转换为字符串表示形式 |
|
void Arrays.sort(array) |
对数组进行升序排序 |
|
void Arrays.sort(array, fromIndex, toIndex) |
对数组指定范围进行排序 |
|
int Arrays.binarySearch(array, key) |
在已排序数组中二分查找元素 |
找到则返回索引,否则返回负值 |
T[] Arrays.copyOf(original, newLength) |
复制数组,可指定新长度 |
|
T[] Arrays.copyOfRange(original, from, to) |
复制数组的指定范围 |
|
boolean Arrays.equals(array1, array2) |
比较两个数组是否相等 |
|
boolean Arrays.deepEquals(array1, array2) |
比较两个多维数组是否相等 |
|
void Arrays.fill(array, value) |
用指定值填充整个数组 |
|
void Arrays.fill(array, fromIndex, toIndex, value) |
用指定值填充数组的指定范围 |
|
List<T> Arrays.asList(elements...) |
将元素列表转换为固定大小的 List |
|
Stream<T> Arrays.stream(array) |
将数组转换为 Stream 流 |
|
void Arrays.parallelSort(array) |
并行排序数组 |
大数据量时更高效 |
void Arrays.setAll(array, generator) |
使用生成函数设置所有元素的值 |
|
void Arrays.parallelSetAll(array, generator) |
并行使用生成函数设置所有元素的值 |
|
void Arrays.parallelPrefix(array, op) |
并行计算数组的前缀 |
|
int Arrays.mismatch(array1, array2) |
查找两个数组第一个不匹配的索引 |
|
int Arrays.compare(array1, array2) |
按字典顺序比较两个数组 |
|
void System.arraycopy(...) |
高效复制数组 |
系统级底层方法 |
Object array.clone() |
克隆数组 |
浅拷贝 |
1.3 多维数组
Java 中的多维数组本质上就是数组的数组。这意味着数组中的每个元素本身也是一个数组
| // 声明一个 2 行 3 列的整数数组
int[][] matrix = new int[2][3];
// 声明并初始化一个 2 行 3 列的数组
int[][] matrix = {
{1, 2, 3}, // 第 0 行
{4, 5, 6} // 第 1 行
};
|
还可以定义不规则数组,多维数组的每一行可以有不同的长度
| // 声明一个二维数组,但只指定第一维(行数)
int[][] jaggedArray = new int[3][];
// 然后为每一行分别分配不同长度的数组
jaggedArray[0] = new int[1]; // 第0行有1列
jaggedArray[1] = new int[2]; // 第1行有2列
jaggedArray[2] = new int[3]; // 第2行有3列
// 也可以用字面量初始化不规则数组
int[][] jaggedArray2 = {
{1}, // 第0行,长度1
{2, 3}, // 第1行,长度2
{4, 5, 6} // 第2行,长度3
};
|
2 Collection
Collection 是 Java 集合框架的根接口,它代表了一组对象的容器,定义了集合类的基本操作
| 集合框架根接口
├── Collection (接口)
│ ├── List (接口 - 有序、可重复)
│ │ ├── ArrayList (类)
│ │ ├── LinkedList (类)
│ │ └── Vector (类)
│ │ └── Stack (类)
│ ├── Set (接口 - 无序、不可重复)
│ │ ├── HashSet (类)
│ │ ├── LinkedHashSet (类)
│ │ └── TreeSet (类)
│ └── Queue (接口 - 队列)
│ ├── LinkedList (类)
│ ├── PriorityQueue (类)
│ └── Deque (接口)
│ └── ArrayDeque (类)
│
└── Map (接口)
├── HashMap
├── TreeMap
├── LinkedHashMap
└── Hashtable
|
collection 类是 generic 类,在定义变量时,要指定两种类型:集合本身的类型和存储在集合中的元素的类型
| ArrayList<String> stringList = new ArrayList<String>();
// 简化写法 - 编译器自动推断类型
ArrayList<String> list1 = new ArrayList<>(); // 推断为 String
|
2.1 Iterate
迭代(遍历)Collection 是指按顺序访问集合中的每一个元素的过程
1.简单的 for 循环(仅 List 可用)
| List<String> list = Arrays.asList("Apple", "Banana", "Orange");
for (int i = 0; i < list.size(); i++) {
String fruit = list.get(i);
System.out.println("索引 " + i + ": " + fruit);
}
|
2.for-each 循环(最常用)
| List<String> list = Arrays.asList("Apple", "Banana", "Orange");
for (String fruit : list) {
System.out.println(fruit);
}
|
3.使用 Iterator 迭代器
| List<String> list = Arrays.asList("Apple", "Banana", "Orange");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String fruit = iterator.next();
System.out.println(fruit);
// 可以在迭代时安全删除元素
if ("Banana".equals(fruit)) {
iterator.remove(); // 安全删除当前元素
}
}
|
3 List
List 代表一个有序的、可重复的元素序列
ArrayList 是基于动态数组的 List 接口实现
LinkedList 是基于双向链表的 List 接口实现
ArrayList 对比 LinkedList
| 特性 |
ArrayList |
LinkedList |
| 底层结构 |
动态数组 |
双向链表 |
| 内存占用 |
较小(仅数组) |
较大(每个元素包含前后指针) |
| 随机访问 |
O(1) - 极快 |
O(n) - 慢 |
| 头部插入 |
O(n) - 慢 |
O(1) - 极快 |
| 尾部插入 |
平均 O(1) - 快 |
O(1) - 极快 |
| 中间插入 |
O(n) - 慢 |
O(n) - 慢(需要遍历) |
| 内存局部性 |
好(连续内存) |
差(分散内存) |
| 实现接口 |
List, RandomAccess |
List, Deque, Queue |
ArrayList 对比 Vector
| 特性 |
ArrayList |
Vector |
| 同步性 |
非线程安全 |
线程安全(所有方法同步) |
| 性能 |
较快 |
较慢(同步开销) |
| 使用推荐 |
推荐使用 |
不推荐在新代码中使用 |
3.1 ArrayList 方法
| 方法 |
说明 |
返回值 |
| Add |
|
|
boolean add(E e) |
将指定元素追加到此列表的末尾 |
总是返回 true |
void add(int index, E element) |
将指定元素插入此列表中的指定位置 |
|
boolean addAll(Collection<? extends E> c) |
将指定集合中的所有元素按顺序追加到此列表的末尾 |
如果列表更改则返回 true |
boolean addAll(int index, Collection<? extends E> c) |
从指定位置开始,将指定集合中的所有元素插入此列表 |
如果列表更改则返回 true |
| Remove |
|
|
E remove(int index) |
移除此列表中指定位置的元素 |
被移除的元素 E |
boolean remove(Object o) |
从此列表中移除第一次出现的指定元素(如果存在) |
如果列表包含元素则返回 true |
boolean removeAll(Collection<?> c) |
移除此列表中所有也包含在指定集合中的元素 |
如果列表更改则返回 true |
boolean removeIf(Predicate<? super E> filter) |
移除此列表中满足给定谓词的所有元素 |
如果任何元素被移除则返回 true |
protected void removeRange(int fromIndex, int toIndex) |
移除列表中索引在 fromIndex(含)和 toIndex(不含)之间的所有元素 |
|
void clear() |
移除此列表中的所有元素 |
|
boolean retainAll(Collection<?> c) |
仅保留此列表中包含在指定集合中的元素 |
如果列表更改则返回 true |
| Modify |
|
|
E set(int index, E element) |
将此列表中指定位置的元素替换为指定元素 |
被替换的旧元素 E |
void replaceAll(UnaryOperator<E> operator) |
将该列表的每个元素替换为将该运算符应用于该元素的结果 |
|
| Search |
|
|
E get(int index) |
返回此列表中指定位置的元素 |
元素 E |
int indexOf(Object o) |
返回此列表中第一次出现的指定元素的索引;如果列表不包含该元素,则返回 -1 |
|
int lastIndexOf(Object o) |
返回此列表中最后一次出现的指定元素的索引;如果列表不包含该元素,则返回 -1 |
|
boolean contains(Object o) |
如果此列表包含指定的元素,则返回 true |
|
boolean containsAll(Collection<?> c) |
如果此列表包含指定集合中的所有元素,则返回 true |
|
| Info |
|
|
boolean isEmpty() |
如果此列表不包含任何元素,则返回 true |
|
int size() |
返回此列表中的元素数 |
|
| Capacity |
|
|
void ensureCapacity(int minCapacity) |
如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数指定的元素数 |
|
void trimToSize() |
将此 ArrayList 实例的容量修剪为列表的当前大小 |
|
| Generate |
|
|
Object[] toArray() |
返回一个包含此列表中所有元素的数组 |
|
<T> T[] toArray(T[] a) |
返回一个包含此列表中所有元素的数组;返回数组的运行时类型是指定数组的类型 |
|
Object clone() |
返回此 ArrayList 实例的浅表副本 |
|
void sort(Comparator<? super E> c) |
根据指定比较器产生的顺序对此列表进行排序 |
|
List<E> subList(int fromIndex, int toIndex) |
返回此列表中在 fromIndex(含)和 toIndex(不含)之间的部分的视图 |
|
| Iterator |
|
|
Iterator<E> iterator() |
以正确的顺序返回此列表中元素的迭代器 |
|
ListIterator<E> listIterator() |
返回此列表中元素的列表迭代器(按正确顺序) |
|
ListIterator<E> listIterator(int index) |
从列表中的指定位置开始,返回列表中元素的列表迭代器(按正确顺序) |
|
| Stream |
|
|
Stream<E> stream() |
以此列表作为源返回一个顺序 Stream |
|
Stream<E> parallelStream() |
以此列表作为源返回一个可能并行的 Stream |
|
void forEach(Consumer<? super E> action) |
对 Iterable 的每个元素执行给定的操作,直到所有元素都被处理或操作引发异常 |
|
4 Set
Set 代表一个不包含重复元素的集合
HashSet 是基于哈希表(HashMap)的 Set 接口实现
TreeSet 是基于红黑树(TreeMap)的 Set 接口实现,元素按顺序存储
LinkedHashSet 维护一个贯穿所有条目的双向链表,从而保持了元素的插入顺序
| 需求 |
推荐实现 |
理由 |
| 一般去重需求 |
HashSet |
性能最好 |
| 需要有序集合 |
TreeSet |
自动排序 |
| 保持插入顺序 |
LinkedHashSet |
插入顺序 + 去重 |
| 范围查询 |
TreeSet |
高效的导航方法 |
| 大量数据快速查找 |
HashSet |
O(1) 查找性能 |
需要 null 元素 |
HashSet |
允许 null 元素 |
当自定义一个类实现 Set 接口时,必须正确实现 equals 和 hashCode
equals 规则
- 自反性:
x.equals(x) == true
- 对称性:
x.equals(y) == y.equals(x)
- 传递性:
x.equals(y) && y.equals(z) == x.equals(z)
- 一致性:多次调用结果不变
- 与
null 比较必须返回 false
hashCode 规则
相等对象必须返回相同哈希值
| public class CustomSet<E> implements Set<E> {
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Set)) return false;
Set<?> other = (Set<?>) obj;
return this.size() == other.size() &&
this.containsAll(other);
}
@Override
public int hashCode() {
int hashCode = 0;
for (E element : this) {
if (element != null) {
hashCode += element.hashCode();
}
}
return hashCode;
}
}
|
使用 TreeSet 时需要实现 Comparable<T>,或者提供外部 Comparator
| // 实现 `Comparable<T>`
class Person implements Comparable<Person> {
String name;
int age;
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age);
}
}
// 提供外部 `Comparator`
Set<Person> treeSet = new TreeSet<>(Comparator.comparing(p -> p.name));
|
4.1 Set 方法
| 方法 |
说明 |
boolean add(E e) |
向集合添加元素,如果元素已存在则返回 false |
boolean remove(Object o) |
从集合中移除指定元素 |
boolean contains(Object o) |
判断集合是否包含指定元素 |
int size() |
返回集合中的元素个数 |
boolean isEmpty() |
判断集合是否为空 |
void clear() |
清空集合中的所有元素 |
Iterator<E> iterator() |
返回集合的迭代器 |
Object[] toArray() |
将集合转换为数组 |
<T> T[] toArray(T[] a) |
将集合转换为指定类型的数组 |
boolean containsAll(Collection<?> c) |
判断集合是否包含指定集合的所有元素 |
boolean addAll(Collection<? extends E> c) |
添加指定集合中的所有元素 |
boolean retainAll(Collection<?> c) |
仅保留集合中包含在指定集合中的元素 |
boolean removeAll(Collection<?> c) |
移除集合中所有包含在指定集合中的元素 |
boolean equals(Object o) |
比较集合与指定对象是否相等 |
int hashCode() |
返回集合的哈希码值 |
default boolean removeIf(Predicate<? super E> filter) |
移除满足条件的元素 |
default Spliterator<E> spliterator() |
返回集合的可分割迭代器 |
default Stream<E> stream() |
返回集合的顺序流 |
default Stream<E> parallelStream() |
返回集合的并行流 |
5 Map
Map 代表一个键值对(Key-Value) 的映射集合
HashMap:最常用,无序
LinkedHashMap:保持插入顺序
TreeMap:按键的自然顺序或自定义比较器排序
Hashtable:线程安全但较老,不推荐使用
使用 HashMap 时,作为键的对象必须必须同时重写 hashCode 和 equals 方法
| class ProperKey {
private String id;
private String name;
public ProperKey(String id, String name) {
this.id = id;
this.name = name;
}
// 必须重写 equals()
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProperKey properKey = (ProperKey) o;
return Objects.equals(id, properKey.id) &&
Objects.equals(name, properKey.name);
}
// 必须重写 hashCode()
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
|
| 使用场景 |
推荐实现 |
关键特性 |
性能特点 |
| 通用键值存储 |
HashMap |
快速查找,无序 |
O(1) 操作 |
| 保持插入顺序 |
LinkedHashMap |
插入顺序迭代 |
接近 HashMap |
| 需要键排序 |
TreeMap |
键的自然顺序或定制顺序 |
O(log n) 操作 |
| 高并发环境 |
ConcurrentHashMap |
线程安全,分段锁 |
高并发性能 |
| 遗留系统/同步 |
Hashtable |
线程安全(全表锁) |
性能较差 |
| 配置文件 |
Properties |
字符串键值对,IO 支持 |
专用场景 |
5.1 遍历 Map
遍历所有键值对:
| // 方法 1:使用 entrySet()
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + ": " + value);
}
// 方法 2:使用 forEach (Java 8+)
map.forEach((key, value) -> {
System.out.println(key + ": " + value);
});
|
遍历所有键:
| for (String key : map.keySet()) {
System.out.println("Key: " + key);
}
|
遍历所有值:
| for (Integer value : map.values()) {
System.out.println("Value: " + value);
}
|
5.2 Map 方法
| 方法 |
说明 |
返回值 |
V put(K key, V value) |
添加键值对,如果键已存在则替换旧值 |
旧值或 null |
V get(Object key) |
根据键获取对应的值 |
|
V remove(Object key) |
根据键移除键值对 |
被移除的值 |
boolean containsKey(Object key) |
判断是否包含指定键 |
|
boolean containsValue(Object value) |
判断是否包含指定值 |
|
int size() |
返回键值对的数量 |
|
boolean isEmpty() |
判断 Map 是否为空 |
|
void clear() |
清空所有键值对 |
|
void putAll(Map<? extends K,? extends V> m) |
添加另一个 Map 的所有键值对 |
|
Set<K> keySet() |
返回所有键的 Set 视图 |
|
Collection<V> values() |
返回所有值的 Collection 视图 |
|
Set<Map.Entry<K,V>> entrySet() |
返回所有键值对的 Set 视图 |
|
boolean equals(Object o) |
比较 Map 与指定对象是否相等 |
|
int hashCode() |
返回 Map 的哈希码值 |
|
default V getOrDefault(Object key, V defaultValue) |
获取值,如果键不存在返回默认值 |
|
default void forEach(BiConsumer<? super K,? super V> action) |
对每个键值对执行操作 |
|
default void replaceAll(BiFunction<? super K,? super V,? extends V> function) |
替换所有值 |
|
default V putIfAbsent(K key, V value) |
键不存在时才放入值 |
|
default boolean remove(Object key, Object value) |
键值都匹配时才删除 |
|
default boolean replace(K key, V oldValue, V newValue) |
键值都匹配时才替换 |
|
default V replace(K key, V value) |
键存在时替换值 |
|
default V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) |
键不存在时计算新值 |
|
default V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) |
键存在时重新计算值 |
|
default V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) |
计算指定键的新值 |
|
default V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) |
合并指定键的值 |
|
5.3 Map.Entry 方法
| 方法 |
说明 |
返回值 |
K getKey() |
返回条目的键 |
|
V getValue() |
返回条目的值 |
|
V setValue(V value) |
设置条目的值 |
旧值 |
boolean equals(Object o) |
比较条目与指定对象是否相等 |
|
int hashCode() |
返回条目的哈希码值 |
|
static <K,V> Map.Entry<K,V> copyOf(Map.Entry<? extends K,? extends V> e) |
创建条目的不可变副本 |
|
static <K,V> Comparator<Map.Entry<K,V>> comparingByKey() |
返回按键比较的比较器 |
|
static <K,V> Comparator<Map.Entry<K,V>> comparingByValue() |
返回按值比较的比较器 |
|
static <K,V> Comparator<Map.Entry<K,V>> comparingByKey(Comparator<? super K> cmp) |
返回使用指定比较器按键比较的比较器 |
|
static <K,V> Comparator<Map.Entry<K,V>> comparingByValue(Comparator<? super V> cmp) |
返回使用指定比较器按值比较的比较器 |
|
6 Generic
Java 泛型在底层的实现机制:
- 代码复用,无多份副本:与 C++ 模板不同,Java 泛型不会为每种具体类型生成多份代码副本
- 一次性编译:泛型类只被编译一次,生成单个类文件。这是通过类型擦除实现的,在编译后,泛型类型信息被擦除,替换为它们的边界或
Object 类型
6.1 Generic Classes
泛型类是在类定义时使用类型参数的类,允许在创建对象时指定具体的类型
| 类型参数 |
通常含义 |
示例 |
T |
Type(类型) |
Box<T> |
E |
Element(元素) |
List<E> |
K |
Key(键) |
Map<K, V> |
V |
Value(值) |
Map<K, V> |
N |
Number(数字) |
Calculator<N> |
R |
Return type(返回类型) |
Function<T, R> |
| public class Box<T> {
private T content;
public Box(T content) {
this.content = content;
}
public T getContent() {
return content;
}
public void setContent(T content) {
this.content = content;
}
public String getContentType() {
return content.getClass().getSimpleName();
}
}
|
6.2 Generic Methods
泛型函数是在方法级别使用类型参数的函数,允许在调用方法时推断或指定具体的类型
| public class GenericMethodExamples {
// 简单的泛型函数
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
// 返回泛型类型的函数
public static <T> T getFirstElement(T[] array) {
if (array == null || array.length == 0) {
return null;
}
return array[0];
}
// 多个类型参数的泛型函数
public static <T, U> void printPair(T first, U second) {
System.out.println("First: " + first + " (" + first.getClass().getSimpleName() + ")");
System.out.println("Second: " + second + " (" + second.getClass().getSimpleName() + ")");
}
}
|
6.3 Generic and Subtyping
泛型类型本身没有继承关系
| class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
List<Dog> dogs = new ArrayList<>();
// List<Animal> animals = dogs; // 编译错误!
// List<Object> objects = dogs; // 编译错误!
|
可以使用 wildcard(通配符)
- 无界通配符
<?>:未知类型(图片中展示的)
- 上界通配符
<? extends T>:T 或 T 的子类型
- 下界通配符
<? super T>:T 或 T 的父类型
| 无界通配符 |
|---|
| void printCollection(Collection<?> c) {
for (Object e : c) {
System.out.println(e);
}
}
|
| 上界通配符 |
|---|
| public class UpperBoundedWildcard {
// 接受 Animal 及其任何子类的 List
public static void processAnimals(List<? extends Animal> animals) {
for (Animal animal : animals) {
System.out.println("Animal: " + animal.getClass().getSimpleName());
}
// 不能添加元素(除了 null)
// animals.add(new Dog()); // 编译错误!
// animals.add(new Animal()); // 编译错误!
}
// 可以读取元素为 Animal 类型
public static Animal getFirstAnimal(List<? extends Animal> animals) {
return animals.get(0); // 安全 - 总是Animal
}
public static void main(String[] args) {
List<Dog> dogs = Arrays.asList(new Dog(), new Dog());
List<Cat> cats = Arrays.asList(new Cat(), new Cat());
List<Animal> animals = Arrays.asList(new Dog(), new Cat());
processAnimals(dogs); // List<Dog> 是 List<? extends Animal>
processAnimals(cats); // List<Cat> 是 List<? extends Animal>
processAnimals(animals); // List<Animal> 是 List<? extends Animal>
Animal firstDog = getFirstAnimal(dogs);
Animal firstCat = getFirstAnimal(cats);
}
}
|
| 下界通配符 |
|---|
| public class LowerBoundedWildcard {
// 接受 Dog 及其任何父类的 List
public static void addDogs(List<? super Dog> list) {
list.add(new Dog()); // 可以添加 Dog
// list.add(new Animal()); // 不能添加 Animal(如果 list 是 List<Dog>)
// 读取时只能得到 Object
Object obj = list.get(0);
// Dog dog = list.get(0); // 编译错误!
}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = new ArrayList<>();
List<Object> objects = new ArrayList<>();
addDogs(dogs); // List<Dog> 是 List<? super Dog>
addDogs(animals); // List<Animal> 是 List<? super Dog>
addDogs(objects); // List<Object> 是 List<? super Dog>
// 不允许
List<Cat> cats = new ArrayList<>();
// addDogs(cats); // List<Cat> 不是 List<? super Dog>
}
}
|
6.4 类型擦除
| // 源代码(编译时)
public class Pair<T> {
private T first;
private T second;
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
}
// 编译后(运行时) - 概念上的等价代码
public class Pair {
private Object first; // T 被擦除为 Object
private Object second; // T 被擦除为 Object
public Pair(Object first, Object second) {
this.first = first;
this.second = second;
}
public Object getFirst() {
return first;
}
public void setFirst(Object first) {
this.first = first;
}
}
|
编译时的类型检查:
| Pair<String> stringPair = new Pair<>("Hello", "World");
String first = stringPair.getFirst();
// Integer num = stringPair.getFirst(); // 编译错误
|
运行时的类型擦除:
| // 运行时:所有类型信息都被擦除
Pair stringPair = new Pair("Hello", "World");
String first = (String) stringPair.getFirst(); // 编译器插入的强制转换
// 相当于以下代码:
Object rawFirst = stringPair.getFirst(); // 实际返回 Object
String first = (String) rawFirst; // 编译器生成的转换
|
这就导致无法在运行时检查泛型类型
| public class RuntimeTypeCheck {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
// 编译错误
// if (stringList instanceof List<String>) {}
// 只能检查原始类型
if (stringList instanceof List) {
System.out.println("It's a List");
}
// 两个列表的运行时类型相同
System.out.println(stringList.getClass() == intList.getClass()); // true
}
}
|
7 Stream
Stream(流)是一个来自数据源的元素队列,并支持聚合操作。它本身不存储数据,而是对数据源(如集合、数组、I/O channel等)进行计算处理
- 不是数据结构:它不存储数据,而是通过管道操作源数据
- 函数式编程:它的操作不会修改源数据。例如,对 Stream 的过滤操作会生成一个新的 Stream,而不是删除源集合中的元素
- 惰性执行:许多操作(如
filter, map)是惰性的,只有在终端操作执行时,中间操作才会开始
- 可消费性:Stream 在终端操作执行后就被消费掉了,不能再被使用。你必须重新创建 Stream 才能再次操作
Java 提供了三套专门的基本类型流:
| 流类型 |
元素类型 |
对应的包装类流 |
IntStream |
int |
Stream<Integer> |
LongStream |
long |
Stream<Long> |
DoubleStream |
double |
Stream<Double> |
这些流实现 BaseStream 接口,与 Stream<T> 并列,有着专用的终端操作
其他基本类型
float:使用 DoubleStream
char:使用 IntStream
boolean:使用 IntStream 或 Stream<Boolean>
Stream 的操作遵循一个标准流程:创建流 → 中间操作 → 终端操作
7.1 创建 Stream
| 创建方式 |
方法 |
示例 |
| 从集合 |
Collection.stream() / parallelStream() |
list.stream() |
| 从数组 |
Arrays.stream(T[] array) |
Arrays.stream(new String[]{"a", "b"}) |
| 静态工厂 |
Stream.of(T... values) |
Stream.of("a", "b", "c") |
| 无限流 |
Stream.iterate() / Stream.generate() |
Stream.iterate(0, n -> n + 2) |
| 空流 |
Stream.empty() |
Stream.empty() |
| 文件行流 |
Files.lines(Path path) |
Files.lines(Paths.get("file.txt")) |
| // 从集合创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> streamFromList = list.stream();
// 从数组创建
String[] array = {"a", "b", "c"};
Stream<String> streamFromArray = Arrays.stream(array);
// 使用 Stream.of
Stream<String> streamOf = Stream.of("a", "b", "c");
// 创建无限流
// iterate: 从种子开始,通过函数迭代生成
Stream<Integer> evenNumbers = Stream.iterate(0, n -> n + 2); // 0, 2, 4, 6...
// generate: 通过 Supplier 不断生成
Stream<Double> randomNumbers = Stream.generate(Math::random);
|
IntStream.range()
IntStream.range(start, end):不包含结束值
IntStream.rangeClosed(start, end):包含结束值
| // 生成 1 到 4 (不包含 5)
IntStream.range(1, 5)
.forEach(System.out::print);
// 输出: 1234
|
7.2 中间操作
中间操作返回一个新的 Stream,是惰性的,它们不会立即执行,而是等到终端操作时一起执行
| 操作 |
方法 |
描述 |
| 过滤 |
filter(Predicate<T>) |
排除不满足条件的元素 |
| 映射 |
map(Function<T, R>) |
将元素转换成其他形式或提取信息 |
| 扁平化映射 |
flatMap(Function<T, Stream<R>>) |
将每个元素转换成一个流,然后把所有流连接成一个流 |
| 去重 |
distinct() |
通过 hashCode() 和 equals() 去重 |
| 排序 |
sorted() / sorted(Comparator) |
产生一个按自然顺序或比较器排序的新流 |
| 截取 |
limit(long maxSize) |
截取流的前 N 个元素 |
| 跳过 |
skip(long n) |
跳过流的前 N 个元素 |
| 遍历 |
peek(Consumer<T>) |
对每个元素执行操作,主要用于调试 |
| List<String> words = Arrays.asList("Hello", "World", "Java", "Stream");
// filter: 过滤长度大于4的字符串
List<String> longWords = words.stream()
.filter(s -> s.length() > 4)
.collect(Collectors.toList());
// [Hello, World, Stream]
// map: 将每个字符串转换为大写
List<String> upperCaseWords = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// [HELLO, WORLD, JAVA, STREAM]
// flatMap: 将每个单词拆分成字母,然后合并成一个流
List<String> letters = words.stream()
.flatMap(word -> Arrays.stream(word.split("")))
.collect(Collectors.toList());
// [H, e, l, l, o, W, o, r, l, d, J, a, v, a, S, t, r, e, a, m]
// distinct & sorted
List<Integer> numbers = Arrays.asList(5, 3, 1, 2, 3, 4, 5);
List<Integer> processedNumbers = numbers.stream()
.distinct() // 去重: [5, 3, 1, 2, 4]
.sorted() // 排序: [1, 2, 3, 4, 5]
.collect(Collectors.toList());
// limit & skip
List<Integer> limited = numbers.stream()
.skip(2) // 跳过前2个: [1, 2, 3, 4, 5]
.limit(3) // 只取3个: [1, 2, 3]
.collect(Collectors.toList());
|
7.3 终端操作
终端操作会从流的流水线生成结果或副作用。执行后,Stream 就被消费掉了,不能再使用
| 操作 |
方法 |
描述 |
| 遍历 |
forEach(Consumer<T>) forEachOrdered(Consumer<T>) |
对每个元素执行操作(forEachOrdered 在并行流中能够保持顺序) |
| 收集 |
collect(Collector) |
将流转换为其他形式(如 List, Set, Map) |
| 匹配 |
allMatch / anyMatch / noneMatch(Predicate) |
检查流中元素是否全部 / 任一 / 没有匹配谓词 |
| 查找 |
findFirst() / findAny() |
返回第一个 / 任意一个元素(返回 Optional) |
| 计数 |
count() |
返回流中元素的总数 |
| 规约 |
reduce(T identity, BinaryOperator) |
将流中元素反复结合,得到一个值 |
| 聚合 |
max(Comparator) / min(Comparator) |
返回流中最大 / 最小的元素(返回 Optional) |
匹配操作和查找操作都是 short-circuiting(短路)操作,一旦找到满足条件的元素就会立即停止处理
| List<String> list = Arrays.asList("a", "b", "c", "d");
// forEach: 遍历并打印
list.stream().forEach(System.out::println);
// collect: 转换为 List, Set
List<String> collectedList = list.stream().collect(Collectors.toList());
Set<String> collectedSet = list.stream().collect(Collectors.toSet());
// collect: 转换为 Map (注意:键不能重复)
Map<String, Integer> map = list.stream()
.collect(Collectors.toMap(s -> s, String::length)); // {a=1, b=1, c=1, d=1}
// count: 计数
long count = list.stream().filter(s -> s.startsWith("a")).count(); // 1
// anyMatch / allMatch / noneMatch
boolean anyStartsWithA = list.stream().anyMatch(s -> s.startsWith("a")); // true
boolean allStartsWithA = list.stream().allMatch(s -> s.startsWith("a")); // false
boolean noneStartsWithZ = list.stream().noneMatch(s -> s.startsWith("z")); // true
// findFirst / findAny
Optional<String> first = list.stream().findFirst(); // Optional["a"]
Optional<String> any = list.parallelStream().findAny(); // 可能返回任意一个元素
// reduce: 将元素组合起来
Optional<String> concatenated = list.stream().reduce((s1, s2) -> s1 + "-" + s2); // Optional["a-b-c-d"]
// 带初始值的 reduce
String resultWithIdentity = list.stream().reduce("Prefix:", (s1, s2) -> s1 + "-" + s2); // "Prefix:-a-b-c-d"
// max / min
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> max = numbers.stream().max(Integer::compareTo); // Optional[5]
Optional<Integer> min = numbers.stream().min(Integer::compareTo); // Optional[1]
|
short-circuiting
anyMatch():找到第一个匹配就返回
allMatch():找到第一个不匹配就返回
noneMatch():找到第一个匹配就返回
findFirst():找到第一个元素就返回,在并行流中仍保持顺序
findAny():找到第一个元素就返回,在并行流中可能返回任意一个
| List<Integer> numbers = Arrays.asList(1, 3, 5, 6, 7, 9);
// 检查是否没有偶数
boolean result = numbers.stream()
.peek(n -> System.out.println("检查: " + n))
.noneMatch(n -> n % 2 == 0);
System.out.println("结果: " + result);
|
| output |
|---|
| 检查: 1
检查: 3
检查: 5
检查: 6
结果: false
|
基本类型流的专用终端操作
sum() average() summaryStatistics()
| 以 IntStream 为例 |
|---|
| int[] numbers = {1, 2, 3, 4, 5};
IntStream stream = IntStream.of(numbers);
int sum = stream.sum(); // 求和: 15
OptionalDouble avg = IntStream.of(numbers).average(); // 平均值: 3.0
// 统计信息
IntSummaryStatistics stats = IntStream.of(numbers).summaryStatistics();
System.out.println("Count: " + stats.getCount()); // 5
System.out.println("Sum: " + stats.getSum()); // 15
System.out.println("Min: " + stats.getMin()); // 1
System.out.println("Max: " + stats.getMax()); // 5
System.out.println("Average: " + stats.getAverage()); // 3.0
|
第 6、9 行这里不能再使用 stream 这个变量了,因为在第 5 行被终端操作消费掉了
7.3.1 Collectors
java.util.stream.Collectors 类提供了大量静态方法,用于创建常见的收集器,是 collect 操作的核心
| 方法 |
描述 |
toList() |
收集到 List |
toSet() |
收集到 Set |
toCollection(Supplier) |
收集到特定的集合,如 toCollection(LinkedList::new) |
toMap(Function k, Function v) |
收集到 Map,需指定键和值的映射函数 |
joining() / joining(delimiter) |
连接字符串 |
groupingBy(Function) |
根据分类函数分组,返回 Map<K, List<T>> |
partitioningBy(Predicate) |
根据 true/false 分区,返回 Map<Boolean, List<T>> |
counting() |
计数 |
summingInt / averagingInt |
求和 / 求平均值 |
summarizingInt |
获取统计信息(数量、总和、最小值、最大值、平均值) |
| List<Person> people = Arrays.asList(
new Person("Alice", 25, "London"),
new Person("Bob", 30, "New York"),
new Person("Charlie", 25, "London")
);
// groupingBy: 按城市分组
Map<String, List<Person>> peopleByCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity));
// {New York=[Bob], London=[Alice, Charlie]}
// groupingBy with downstream: 按城市分组,并计算每组的平均年龄
Map<String, Double> averageAgeByCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity,
Collectors.averagingInt(Person::getAge)));
// {New York=30.0, London=25.0}
// partitioningBy: 按年龄是否大于26分区
Map<Boolean, List<Person>> partitioned = people.stream()
.collect(Collectors.partitioningBy(p -> p.getAge() > 26));
// {false=[Alice, Charlie], true=[Bob]}
// joining: 连接所有名字
String names = people.stream()
.map(Person::getName)
.collect(Collectors.joining(", "));
// "Alice, Bob, Charlie"
|
7.4 并行 Stream
通过 parallelStream() 方法或 stream().parallel()可以创建并行流,利用多核处理器提高大数据集的处理效率
| List<String> list = Arrays.asList("a", "b", "c", "d", "e");
// 使用并行流
long count = list.parallelStream()
.filter(s -> s.startsWith("a"))
.count();
|
- 并行流不总是更快,它有一定的开销(线程池、拆分、合并)
- 确保操作是无状态且不干扰的
- 在需要共享可变状态时,要特别小心线程安全问题