1. Java学习日记——DAY22:深入理解集合框架与泛型应用
今天是我系统学习Java的第22天,决定把重点放在集合框架(Collection Framework)和泛型(Generics)这两个核心概念上。作为Java语言中最基础也最强大的特性之一,集合框架几乎出现在所有Java项目中,而泛型则是保证类型安全的利器。记得刚开始接触时,我对为什么需要这么多不同的集合类感到困惑,ArrayList和LinkedList到底有什么区别?HashMap和HashTable又该如何选择?通过今天的系统梳理,终于把这些知识点真正串联起来了。
2. 集合框架的体系结构解析
2.1 集合框架的三大分支
Java集合框架主要分为三大类接口:List、Set和Queue。每种接口都有其特定的应用场景和实现类:
List接口:有序集合,允许重复元素
- ArrayList:基于动态数组,随机访问快(O(1)),但插入删除慢(O(n))
- LinkedList:基于双向链表,插入删除快(O(1)),但随机访问慢(O(n))
- Vector:线程安全的ArrayList,但性能较差
Set接口:不允许重复元素
- HashSet:基于哈希表,无序,查找快(O(1))
- LinkedHashSet:保持插入顺序的HashSet
- TreeSet:基于红黑树,自动排序(O(log n))
Queue接口:先进先出(FIFO)的队列
- PriorityQueue:优先级队列
- ArrayDeque:双端队列实现
2.2 集合的选择策略
在实际开发中,选择哪种集合取决于具体需求:
- 如果需要快速随机访问,选择ArrayList
- 如果频繁在集合中间插入/删除元素,选择LinkedList
- 如果需要去重且不关心顺序,选择HashSet
- 如果需要保持插入顺序,选择LinkedHashSet
- 如果需要自动排序,选择TreeSet
- 如果需要线程安全,考虑Collections.synchronizedList()或ConcurrentHashMap
提示:在Java 8之后,Stream API可以很方便地对集合进行操作,如过滤、映射、归约等,这大大简化了集合处理代码。
3. Map接口及其实现类详解
3.1 HashMap的工作原理
HashMap是Java中最常用的Map实现,它基于哈希表实现键值对存储。其核心机制包括:
- 哈希函数:通过hashCode()方法计算键的哈希值
- 冲突解决:Java 8之后采用链表+红黑树的混合结构
- 扩容机制:默认负载因子0.75,当元素数量超过容量*负载因子时自动扩容
// HashMap的基本使用示例 Map<String, Integer> wordCount = new HashMap<>(); wordCount.put("Java", 1); wordCount.put("Python", 2); wordCount.put("C++", 3); // Java 8新增的forEach方法 wordCount.forEach((k, v) -> System.out.println(k + ": " + v));3.2 HashMap与HashTable的对比
虽然两者都实现了Map接口,但有几点关键区别:
| 特性 | HashMap | HashTable |
|---|---|---|
| 线程安全 | 不安全 | 安全 |
| 允许null | 键值都可以为null | 键值都不能为null |
| 性能 | 更高 | 较低 |
| 迭代器 | fail-fast | 不是fail-fast |
| 继承关系 | 继承AbstractMap | 继承Dictionary |
在现代Java开发中,HashTable已经被ConcurrentHashMap取代,后者提供了更好的并发性能。
4. 泛型在集合中的应用
4.1 泛型的基本概念
泛型是Java 5引入的重要特性,它允许在编译时检查类型安全,避免了运行时的ClassCastException。在集合中使用泛型可以明确指定集合中元素的类型:
// 不使用泛型(Java 5之前) List list = new ArrayList(); list.add("Hello"); String s = (String) list.get(0); // 需要强制类型转换 // 使用泛型 List<String> genericList = new ArrayList<>(); genericList.add("Hello"); String s = genericList.get(0); // 自动类型推断4.2 泛型通配符
Java泛型支持三种通配符:
- 无界通配符:
<?>表示未知类型 - 上界通配符:
<? extends Number>表示Number或其子类 - 下界通配符:
<? super Integer>表示Integer或其父类
// 上界通配符示例 public static double sum(List<? extends Number> list) { double sum = 0; for (Number n : list) { sum += n.doubleValue(); } return sum; } // 下界通配符示例 public static void addNumbers(List<? super Integer> list) { for (int i = 1; i <= 10; i++) { list.add(i); } }5. 集合的线程安全与并发控制
5.1 同步集合与并发集合
Java提供了多种方式来实现线程安全的集合操作:
Collections.synchronizedXXX:包装普通集合使其线程安全
List<String> syncList = Collections.synchronizedList(new ArrayList<>());Concurrent集合:Java 5引入的java.util.concurrent包中的专门实现
- ConcurrentHashMap
- CopyOnWriteArrayList
- ConcurrentLinkedQueue
不可变集合:Java 9引入的List.of(), Set.of()等工厂方法
5.2 ConcurrentHashMap的实现原理
ConcurrentHashMap是HashMap的线程安全版本,但实现方式与HashTable完全不同:
- 分段锁:Java 7采用分段锁机制,将整个Map分成多个Segment
- CAS操作:Java 8改为使用CAS(Compare-And-Swap)和synchronized优化并发性能
- 扩容机制:支持并发扩容,不会阻塞所有操作
// ConcurrentHashMap使用示例 ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); map.put("one", 1); map.putIfAbsent("one", 100); // 不会覆盖已有值 // 原子更新 map.compute("one", (k, v) -> v == null ? 1 : v + 1);6. 集合的性能优化实践
6.1 初始化容量设置
集合类在创建时指定初始容量可以避免频繁扩容带来的性能开销:
// 不好的做法:使用默认初始容量(16),可能频繁扩容 Map<String, String> map1 = new HashMap<>(); // 好的做法:预估元素数量设置初始容量 Map<String, String> map2 = new HashMap<>(1000);6.2 遍历集合的最佳实践
Java提供了多种遍历集合的方式,性能差异明显:
- for循环:适合ArrayList等随机访问快的集合
- 迭代器:通用性强,适合所有集合
- forEach:Java 8引入,语法简洁
- Stream API:Java 8引入,支持函数式编程
List<String> languages = Arrays.asList("Java", "Python", "C++"); // 传统for循环 for (int i = 0; i < languages.size(); i++) { System.out.println(languages.get(i)); } // 增强for循环 for (String lang : languages) { System.out.println(lang); } // 使用迭代器 Iterator<String> it = languages.iterator(); while (it.hasNext()) { System.out.println(it.next()); } // Java 8 forEach languages.forEach(System.out::println); // 使用Stream API languages.stream().forEach(System.out::println);7. 实际开发中的集合使用技巧
7.1 避免ConcurrentModificationException
这个异常通常发生在使用迭代器遍历集合时,同时修改集合结构:
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); // 错误示例:会抛出ConcurrentModificationException for (String s : list) { if (s.equals("b")) { list.remove(s); // 直接修改原集合 } } // 正确做法1:使用迭代器的remove方法 Iterator<String> it = list.iterator(); while (it.hasNext()) { if (it.next().equals("b")) { it.remove(); // 通过迭代器删除 } } // 正确做法2:Java 8 removeIf list.removeIf(s -> s.equals("b"));7.2 自定义对象作为Map的键
当自定义类作为HashMap的键时,必须正确重写hashCode()和equals()方法:
class Student { private String id; private String name; // 构造函数、getter/setter省略 @Override public int hashCode() { return Objects.hash(id, name); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Student student = (Student) obj; return Objects.equals(id, student.id) && Objects.equals(name, student.name); } } // 使用示例 Map<Student, Integer> scoreMap = new HashMap<>(); Student s1 = new Student("001", "Alice"); scoreMap.put(s1, 90);8. Java 8对集合的增强
8.1 Stream API的引入
Java 8的Stream API为集合操作带来了革命性的变化:
List<String> languages = Arrays.asList("Java", "Python", "C++", "JavaScript", "Ruby"); // 过滤出长度大于3的语言,并转换为大写 List<String> result = languages.stream() .filter(lang -> lang.length() > 3) .map(String::toUpperCase) .collect(Collectors.toList()); // 分组统计 Map<Integer, List<String>> lengthMap = languages.stream() .collect(Collectors.groupingBy(String::length)); // 并行流处理 long count = languages.parallelStream() .filter(lang -> lang.startsWith("J")) .count();8.2 新的集合工厂方法
Java 9引入了方便的集合工厂方法,可以创建不可变集合:
// Java 9之前创建不可变集合 List<String> oldList = Collections.unmodifiableList( Arrays.asList("a", "b", "c")); // Java 9工厂方法 List<String> newList = List.of("a", "b", "c"); Set<String> newSet = Set.of("a", "b", "c"); Map<String, Integer> newMap = Map.of("a", 1, "b", 2);经过今天的学习,我对Java集合框架有了更系统、更深入的理解。特别是通过实际编码验证了各种集合类的性能特点和使用场景,这对我以后写出更高效、更健壮的代码有很大帮助。在实际项目中,选择正确的集合类型往往能显著提升程序性能,而合理使用泛型则能大大减少运行时错误。明天我计划继续深入研究Java的并发编程部分,特别是与集合相关的并发工具类。