欢迎光临
我们一直在努力

21 集合框架新特性与实战——Java 9-21集合增强

目录

  • 🟡 21 集合框架新特性与实战——Java 9-21集合增强
    • 一、不可变集合工厂方法(Java 9+)
      • 1.1 为什么需要不可变集合?
      • 1.2 List.of() / Set.of() / Map.of()
      • 1.3 不可变集合的特性
      • 1.4 注意事项
    • 二、SequencedCollection接口(Java 21)
      • 2.1 什么是SequencedCollection?
      • 2.2 使用示例
      • 2.3 SequencedCollection家族
    • 三、集合的增强方法(Java 8-21)
      • 3.1 Stream API与集合
      • 3.2 Map的computeIfAbsent与merge
      • 3.3 List的常用工厂方法
    • 四、集合选型指南
      • 4.1 选型决策表
      • 4.2 不同场景的推荐
    • 五、综合实战练习
      • 练习1:实现一个简单的词频统计器
      • 练习2:学生管理系统(综合集合应用)
      • 练习3:实现简单的缓存框架
    • 六、常见陷阱与最佳实践
      • 6.1 集合遍历时删除元素
      • 6.2 集合与null值
      • 6.3 集合初始化容量预估
    • 七、总结与下篇预告
      • 本篇核心要点
      • 🤔 互动问题
      • 📖 下篇预告
    • 参考资料

🟡 21 集合框架新特性与实战——Java 9-21集合增强

更新日期:2026年5月 | Java入门到精通系列 · 第三阶段·核心进阶 © 版权声明:本文为原创技术文章,转载请联系作者并注明出处。



一、不可变集合工厂方法(Java 9+)

1.1 为什么需要不可变集合?

在多线程环境下,不可变集合天然线程安全;在函数式编程中,不可变数据避免副作用。

// Java 8之前创建不可变集合的方式(繁琐)
List<String> oldWay = Collections.unmodifiableList(Arrays.asList("A", "B", "C"));

// Java 9+(简洁优雅)
List<String> newWay = List.of("A", "B", "C");

1.2 List.of() / Set.of() / Map.of()

import java.util.*;

public class ImmutableCollectionDemo {
public static void main(String[] args) {
// ===== 不可变List =====
List<String> languages = List.of("Java", "Python", "Go", "Rust");
// languages.add("C++"); // 抛出 UnsupportedOperationException!
// languages.set(0, "Kotlin"); // 抛出 UnsupportedOperationException!
System.out.println(languages);

// ===== 不可变Set =====
Set<Integer> numbers = Set.of(1, 2, 3, 4, 5);
// numbers.add(6); // 抛出 UnsupportedOperationException!
System.out.println(numbers);

// ===== 不可变Map =====
Map<String, Integer> scores = Map.of(
"Alice", 95,
"Bob", 88,
"Charlie", 92
);
// scores.put("Dave", 77); // 抛出 UnsupportedOperationException!
System.out.println(scores);

// ===== Map.ofEntries()(超过10个键值对时使用)=====
Map<String, Integer> largeMap = Map.ofEntries(
Map.entry("Java", 1),
Map.entry("Python", 2),
Map.entry("JavaScript", 3),
Map.entry("TypeScript", 4),
Map.entry("Go", 5),
Map.entry("Rust", 6),
Map.entry("C++", 7),
Map.entry("C#", 8),
Map.entry("Swift", 9),
Map.entry("Kotlin", 10),
Map.entry("Ruby", 11),
Map.entry("PHP", 12)
);
System.out.println(largeMap.size()); // 12
}
}

1.3 不可变集合的特性

特性说明
不可修改 add/remove/set等操作会抛出UnsupportedOperationException
不允许null List.of()不允许null元素,会抛出NullPointerException
序列化支持 实现了Serializable
内存优化 内部使用紧凑的专用实现
线程安全 不可变天然线程安全
值相等 List.of(1, 2, 3).equals(List.of(1, 2, 3))为true

1.4 注意事项

// 陷阱1:不允许null
List.of(null); // NullPointerException!

// 陷阱2:Map.of()最多10个键值对
Map.of("k1", "v1", "k2", "v2", ..., "k10", "v10"); // 最多10对
// 超过10个用 Map.ofEntries()

// 陷阱3:不可变集合仍然可以修改内部元素(如果是可变对象)
List<StringBuilder> builders = List.of(new StringBuilder("A"), new StringBuilder("B"));
builders.get(0).append("X"); // 允许!元素本身可变
System.out.println(builders); // [AX, B]

// 正确做法:确保元素也是不可变的
List<String> safeList = List.of("A", "B"); // String是不可变的


二、SequencedCollection接口(Java 21)

2.1 什么是SequencedCollection?

Java 21引入了SequencedCollection接口,统一了"有序集合"的操作方式。

public interface SequencedCollection<E> extends Collection<E> {
// 在首尾添加
void addFirst(E e);
void addLast(E e);

// 获取首尾元素
E getFirst();
E getLast();

// 移除首尾元素
E removeFirst();
E removeLast();

// 返回反序视图
SequencedCollection<E> reversed();
}

2.2 使用示例

import java.util.*;

public class SequencedCollectionDemo {
public static void main(String[] args) {
// ArrayList 支持 SequencedCollection
SequencedCollection<String> list = new ArrayList<>();
list.addFirst("First");
list.addLast("Last");
list.add("Middle");
System.out.println(list); // [First, Middle, Last]
System.out.println(list.getFirst()); // First
System.out.println(list.getLast()); // Last

// 反转视图
SequencedCollection<String> reversed = list.reversed();
System.out.println(reversed); // [Last, Middle, First]

// TreeMap 的 SequencedMap
SequencedMap<String, Integer> map = new TreeMap<>();
map.putFirst("low", 1);
map.putLast("high", 100);
System.out.println(map.firstEntry()); // low=1
System.out.println(map.lastEntry()); // high=100
System.out.println(map.reversed()); // {high=100, low=1}
}
}

2.3 SequencedCollection家族

SequencedCollection<E>
├── List<E>(ArrayList, LinkedList等)
├── Deque<E>(ArrayDeque, LinkedList等)
└── SortedSet<E>(TreeSet等)

SequencedSet<E> extends SequencedCollection<E>, Set<E>

SequencedMap<K,V>
├── SortedMap<K,V>(TreeMap等)
└── LinkedHashMap<K,V>


三、集合的增强方法(Java 8-21)

3.1 Stream API与集合

import java.util.*;
import java.util.stream.*;

public class StreamWithCollection {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Charlie", "David", "Eve", "Frank");

// 过滤
List<String> longNames = names.stream()
.filter(name -> name.length() > 3)
.collect(Collectors.toList());
System.out.println(longNames); // [Alice, Charlie, David, Frank]

// 映射
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(nameLengths); // [5, 3, 7, 5, 3, 5]

// 分组
Map<Integer, List<String>> grouped = names.stream()
.collect(Collectors.groupingBy(String::length));
System.out.println(grouped);
// {3=[Bob, Eve], 5=[Alice, David, Frank], 7=[Charlie]}

// 统计
IntSummaryStatistics stats = names.stream()
.mapToInt(String::length)
.summaryStatistics();
System.out.println("平均长度: " + stats.getAverage()); // 4.666
System.out.println("最长: " + stats.getMax()); // 7

// 收集到不可变集合
List<String> immutableList = names.stream()
.filter(n -> n.startsWith("A") || n.startsWith("B"))
.collect(Collectors.toUnmodifiableList());
System.out.println(immutableList); // [Alice, Bob]
}
}

3.2 Map的computeIfAbsent与merge

import java.util.*;

public class MapEnhancements {
public static void main(String[] args) {
Map<String, List<String>> classStudents = new HashMap<>();

// computeIfAbsent:如果key不存在,则计算并放入
classStudents.computeIfAbsent("Math", k -> new ArrayList<>()).add("Alice");
classStudents.computeIfAbsent("Math", k -> new ArrayList<>()).add("Bob");
classStudents.computeIfAbsent("English", k -> new ArrayList<>()).add("Charlie");
System.out.println(classStudents);
// {Math=[Alice, Bob], English=[Charlie]}

// merge:合并值
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 80);
scores.merge("Alice", 10, Integer::sum); // Alice: 80 + 10 = 90
scores.merge("Bob", 85, Integer::sum); // Bob: 85(新增)
System.out.println(scores); // {Alice=90, Bob=85}

// getOrDefault
int score = scores.getOrDefault("Charlie", 0);
System.out.println("Charlie: " + score); // 0
}
}

3.3 List的常用工厂方法

import java.util.*;

public class ListFactoryMethods {
public static void main(String[] args) {
// copyOf(Java 10+)— 创建不可变副本
List<String> original = new ArrayList<>(Arrays.asList("A", "B", "C"));
List<String> copy = List.copyOf(original);
original.add("D");
System.out.println(original); // [A, B, C, D]
System.out.println(copy); // [A, B, C] 不受影响

// toList()(Java 16+)
List<Integer> evenNumbers = IntStream.rangeClosed(1, 20)
.filter(n -> n % 2 == 0)
.boxed()
.toList(); // 等同于 .collect(Collectors.toUnmodifiableList())
System.out.println(evenNumbers);
}
}


四、集合选型指南

4.1 选型决策表

需求推荐选择理由
有序、可重复、随机访问 ArrayList 数组底层,O(1)随机访问
有序、可重复、频繁头尾操作 LinkedList / ArrayDeque 链表O(1)头尾操作
无序、不重复 HashSet HashMap底层,O(1)查找
排序、不重复 TreeSet 红黑树,O(log n)
插入顺序、不重复 LinkedHashSet 保持插入顺序
键值对、无序 HashMap 最通用的Map
键值对、排序 TreeMap 按key排序
键值对、插入顺序 LinkedHashMap 保持插入顺序
线程安全、高并发读写 ConcurrentHashMap 分段锁/CAS
线程安全、读多写少 CopyOnWriteArrayList 写时复制
不可变集合 List.of() / Map.of() 工厂方法
固定大小、高性能 EnumMap / EnumSet 枚举专用

4.2 不同场景的推荐

// 场景1:缓存(LRU策略)
LinkedHashMap<String, Object> cache = new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
return size() > 100; // 最多100个条目
}
};

// 场景2:词频统计
Map<String, Long> wordCount = words.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

// 场景3:多值Map
Map<String, List<String>> multimap = new HashMap<>();
multimap.computeIfAbsent("key", k -> new ArrayList<>()).add("value");

// 场景4:枚举映射
enum Color { RED, GREEN, BLUE }
Map<Color, String> colorNames = new EnumMap<>(Color.class);
colorNames.put(Color.RED, "红色");

// 场景5:只读配置
Map<String, String> config = Map.of(
"host", "localhost",
"port", "8080",
"debug", "true"
);


五、综合实战练习

练习1:实现一个简单的词频统计器

import java.util.*;
import java.util.stream.*;

public class WordFrequencyCounter {
public static Map<String, Integer> countWords(String text) {
Map<String, Integer> frequency = new TreeMap<>(); // 用TreeMap实现自动排序
String[] words = text.toLowerCase()
.replaceAll("[^a-zA-Z\\\\s]", "")
.split("\\\\s+");

for (String word : words) {
if (!word.isEmpty()) {
frequency.merge(word, 1, Integer::sum);
}
}
return frequency;
}

public static void printTopN(Map<String, Integer> frequency, int n) {
frequency.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.limit(n)
.forEach(entry -> System.out.printf("%-15s %d次%n", entry.getKey(), entry.getValue()));
}

public static void main(String[] args) {
String text = "Java is great. Java is powerful. Java is widely used. " +
"Python is also popular. Python is easy to learn.";

Map<String, Integer> freq = countWords(text);
System.out.println("=== 词频统计 ===");
freq.forEach((word, count) -> System.out.printf("%-15s %d次%n", word, count));

System.out.println("\\n=== Top 5 高频词 ===");
printTopN(freq, 5);
}
}

练习2:学生管理系统(综合集合应用)

import java.util.*;
import java.util.stream.*;

public class StudentManagementSystem {
private Map<String, Student> students = new HashMap<>();
private Map<String, List<Student>> classIndex = new TreeMap<>();
private Map<String, TreeSet<Student>> scoreRanking = new TreeMap<>();

public void addStudent(Student student) {
students.put(student.getId(), student);

// 按班级索引
classIndex.computeIfAbsent(student.getClassName(), k -> new ArrayList<>())
.add(student);

// 按成绩排名
scoreRanking.computeIfAbsent(student.getClassName(), k ->
new TreeSet<>((a, b) -> Double.compare(b.getScore(), a.getScore()))
).add(student);
}

// 按班级查询学生
public List<Student> getStudentsByClass(String className) {
return classIndex.getOrDefault(className, Collections.emptyList());
}

// 获取某班级成绩前N名
public List<Student> getTopN(String className, int n) {
TreeSet<Student> ranking = scoreRanking.get(className);
if (ranking == null) return Collections.emptyList();
return ranking.stream().limit(n).collect(Collectors.toList());
}

// 按成绩范围查询
public List<Student> getStudentsByScoreRange(double min, double max) {
return students.values().stream()
.filter(s -> s.getScore() >= min && s.getScore() <= max)
.sorted(Comparator.comparingDouble(Student::getScore).reversed())
.collect(Collectors.toList());
}

// 获取各班级平均分
public Map<String, Double> getClassAverages() {
return students.values().stream()
.collect(Collectors.groupingBy(
Student::getClassName,
Collectors.averagingDouble(Student::getScore)
));
}

// 生成报表
public void printReport() {
System.out.println("=== 学生管理系统报表 ===");
System.out.println("总人数: " + students.size());

System.out.println("\\n— 各班级学生列表 —");
classIndex.forEach((cls, list) -> {
System.out.println(cls + " (" + list.size() + "人):");
list.forEach(s -> System.out.println(" " + s));
});

System.out.println("\\n— 各班级成绩前3名 —");
scoreRanking.forEach((cls, ranking) -> {
System.out.println(cls + ":");
ranking.stream().limit(3).forEach(s ->
System.out.printf(" %s – %.1f%n", s.getName(), s.getScore())
);
});

System.out.println("\\n— 各班级平均分 —");
getClassAverages().forEach((cls, avg) ->
System.out.printf("%s: %.1f%n", cls, avg)
);
}

// 内部类
static class Student {
private String id;
private String name;
private String className;
private double score;

public Student(String id, String name, String className, double score) {
this.id = id;
this.name = name;
this.className = className;
this.score = score;
}

public String getId() { return id; }
public String getName() { return name; }
public String getClassName() { return className; }
public double getScore() { return score; }

@Override
public String toString() {
return String.format("[%s] %s – %s (%.1f分)", id, name, className, score);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
return id.equals(((Student) o).id);
}

@Override
public int hashCode() { return Objects.hash(id); }
}

public static void main(String[] args) {
StudentManagementSystem system = new StudentManagementSystem();

system.addStudent(new Student("001", "Alice", "一班", 92));
system.addStudent(new Student("002", "Bob", "一班", 85));
system.addStudent(new Student("003", "Charlie", "一班", 88));
system.addStudent(new Student("004", "David", "二班", 95));
system.addStudent(new Student("005", "Eve", "二班", 78));
system.addStudent(new Student("006", "Frank", "二班", 90));
system.addStudent(new Student("007", "Grace", "一班", 96));

system.printReport();

System.out.println("\\n— 成绩90以上的学生 —");
system.getStudentsByScoreRange(90, 100).forEach(System.out::println);
}
}

练习3:实现简单的缓存框架

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class SimpleCache<K, V> {
private final Map<K, CacheEntry<V>> cache;
private final int maxSize;
private final long defaultTtl; // 默认过期时间(毫秒)

public SimpleCache(int maxSize, long defaultTtl) {
this.cache = new ConcurrentHashMap<>();
this.maxSize = maxSize;
this.defaultTtl = defaultTtl;
}

public void put(K key, V value) {
put(key, value, defaultTtl);
}

public void put(K key, V value, long ttl) {
if (cache.size() >= maxSize) {
evict(); // 淘汰过期和最旧的条目
}
cache.put(key, new CacheEntry<>(value, System.currentTimeMillis() + ttl));
}

public V get(K key) {
CacheEntry<V> entry = cache.get(key);
if (entry == null) return null;
if (entry.isExpired()) {
cache.remove(key);
return null;
}
return entry.value;
}

public void remove(K key) {
cache.remove(key);
}

public int size() {
// 清理过期条目
cache.entrySet().removeIf(e -> e.getValue().isExpired());
return cache.size();
}

private void evict() {
// 移除所有过期条目
cache.entrySet().removeIf(e -> e.getValue().isExpired());

// 如果仍然超过容量,移除最旧的
if (cache.size() >= maxSize) {
cache.entrySet().stream()
.min(Comparator.comparingLong(e -> e.getValue().expireTime))
.ifPresent(e -> cache.remove(e.getKey()));
}
}

private static class CacheEntry<V> {
V value;
long expireTime;

CacheEntry(V value, long expireTime) {
this.value = value;
this.expireTime = expireTime;
}

boolean isExpired() {
return System.currentTimeMillis() > expireTime;
}
}

public static void main(String[] args) throws InterruptedException {
SimpleCache<String, String> cache = new SimpleCache<>(3, 2000);

cache.put("name", "Alice");
cache.put("age", "25");
System.out.println(cache.get("name")); // Alice

Thread.sleep(2500);
System.out.println(cache.get("name")); // null(已过期)
}
}


六、常见陷阱与最佳实践

6.1 集合遍历时删除元素

// ❌ 错误:增强for循环中删除元素
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
try {
for (String s : list) {
if ("B".equals(s)) {
list.remove(s); // ConcurrentModificationException!
}
}
} catch (ConcurrentModificationException e) {
System.out.println("不能在增强for循环中删除元素!");
}

// ✅ 正确1:使用Iterator
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if ("B".equals(it.next())) {
it.remove(); // 安全删除
}
}

// ✅ 正确2:使用removeIf(Java 8+)
list.removeIf(s -> "B".equals(s));

// ✅ 正确3:使用Stream过滤
List<String> filtered = list.stream()
.filter(s -> !"B".equals(s))
.collect(Collectors.toList());

6.2 集合与null值

集合类型允许null键/元素说明
ArrayList 可以存储null
LinkedList 可以存储null
HashSet 允许1个null
HashMap 允许1个null键
TreeMap null键导致NullPointerException
TreeSet null元素导致NullPointerException
ConcurrentHashMap null键或值都导致NullPointerException
List.of() null元素导致NullPointerException

6.3 集合初始化容量预估

// 如果知道大致大小,预先指定容量
List<String> list = new ArrayList<>(1000); // 避免多次扩容
Map<String, Integer> map = new HashMap<>(1024); // 预估容量

// HashMap的容量公式:预期元素数 / 负载因子
int expectedSize = 100;
int capacity = (int) (expectedSize / 0.75) + 1; // 134
Map<String, String> map2 = new HashMap<>(capacity);


七、总结与下篇预告

本篇核心要点

要点说明
不可变集合 List.of() / Set.of() / Map.of() 工厂方法
SequencedCollection Java 21统一的有序集合接口
集合选型 根据场景选择最合适的集合类型
遍历删除 使用Iterator.remove()或removeIf()
null安全 注意不同集合对null的处理差异

🤔 互动问题

  • List.of("A", "B", "C")和Arrays.asList("A", "B", "C")有什么区别?
  • 如何优雅地将一个List按照某个属性分组?
  • 为什么ConcurrentHashMap不允许null键值?
  • 📖 下篇预告

    下一篇我们将学习**《异常处理》**,深入了解Java的异常体系结构、try-catch-finally的最佳实践、自定义异常的使用,以及异常处理的设计模式。


    参考资料

    • JEP 269: Convenience Factory Methods for Collections
    • JEP 431: Sequenced Collections
    • Java 21 Collections新特性
    赞(0)
    未经允许不得转载:171主机测评 » 21 集合框架新特性与实战——Java 9-21集合增强
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址