玩转 Java 集合:底层原理与业务实战全解析
在Java开发中,数组作为基础的数据存储容器,存在长度固定、只能存储同类型元素等局限性。而集合框架则提供了更灵活、高效的数据存储方案,能适配不同业务场景的存储需求。本文将结合实战代码,从体系结构、核心API、底层原理到业务实战,全面拆解Java集合框架。
一、集合框架整体体系
Java集合框架主要分为**单列集合(Collection)和双列集合(Map)**两大体系,其核心结构如下:
- 单列集合:每个元素仅存储一个数据,顶层接口为
Collection,分为List和Set两大分支 - 双列集合:每个元素以键值对(Key-Value)形式存储,顶层接口为
Map
1.1 单列集合:Collection家族
-
List:有序、可重复、有索引
ArrayList:基于数组实现,查询快、增删慢LinkedList:基于双向链表实现,增删快、查询慢
-
Set:无序、不重复、无索引
HashSet:基于哈希表,查询快LinkedHashSet:基于哈希表+链表,有序TreeSet:基于红黑树,自动排序
1.2 双列集合:Map家族
-
Map:键值对存储,键唯一、值可重复
HashMap:基于哈希表,查询快LinkedHashMap:基于哈希表+链表,有序TreeMap:基于红黑树,自动排序
1.3单列集合:List与Set的核心差异
单列集合的特性由List和Set的子实现类决定,我们可以通过一段代码直观感受两者区别:
// List集合:有序、可重复、有索引
List<String> list = new ArrayList<>();
list.add("java");
list.add("java");
list.add("C");
System.out.println(list); // 输出:[java, java, C]
System.out.println(list.get(0)); // 输出:java(支持索引访问)
// Set集合:无序、不重复、无索引
Set<String> set = new HashSet<>();
set.add("鸿蒙");
set.add("java");
set.add("java");
System.out.println(set); // 输出:[鸿蒙, java](自动去重,顺序与添加无关)
List系列(ArrayList/LinkedList):侧重有序存储,支持索引操作,允许元素重复Set系列(HashSet/LinkedHashSet/TreeSet):侧重去重存储,无索引,其中TreeSet支持排序、LinkedHashSet可保证插入顺序
1.4双列集合:Map的键值对存储
Map集合以“键唯一、值可重复”为核心特性,常用实现类包括HashMap(无序)、LinkedHashMap(有序)、TreeMap(排序)。示例代码如下:
Map<String, Integer> map = new HashMap<>();
map.put("张三", 18);
map.put("张三", 19); // 键重复会覆盖旧值
map.put("李四", 20);
System.out.println(map); // 输出:{李四=20, 张三=19}
二、Collection单列集合核心操作
Collection作为单列集合的顶层接口,定义了通用API,所有子实现类均可继承使用。
2.1 Collection通用API实战
我们可以通过ArrayList实现类,验证add、remove、contains等核心方法:
Collection<String> coll = new ArrayList<>();
// 1. 添加元素
coll.add("张三");
coll.add("李四");
// 2. 获取元素个数
System.out.println(coll.size()); // 输出:2
// 3. 删除元素
coll.remove("李四");
// 4. 判断元素是否存在
System.out.println(coll.contains("张三")); // 输出:true
// 5. 转数组
Object[] arr = coll.toArray();
System.out.println(Arrays.toString(arr)); // 输出:[张三]
2.2 集合的三种遍历方式
Collection支持迭代器、增强for、Lambda表达式三种遍历方式,不同方式适配不同场景:
- 迭代器遍历:集合专属遍历工具,支持遍历中删除元素
Collection<String> names = new ArrayList<>();
names.add("张三");
names.add("李四");
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String name = it.next();
System.out.println(name);
// 迭代器自身remove方法可避免并发修改异常
if (name.equals("李四")) it.remove();
}
- 增强for遍历:简化版迭代器,仅适用于读取数据
for (String name : names) {
System.out.println(name);
}
- Lambda遍历:JDK8+特性,代码更简洁
names.forEach(System.out::println);
2.3 避坑指南:并发修改异常
当遍历集合时直接调用集合的remove方法,会触发并发修改异常。以下是两种解决方案:
ArrayList<String> list = new ArrayList<>();
list.add("宁夏枸杞");
list.add("黑枸杞");
list.add("人字拖");
// 方案1:索引遍历+删除后i--
for (int i = 0; i < list.size(); i++) {
if (list.get(i).contains("枸杞")) {
list.remove(i);
i--; // 修正索引偏移
}
}
// 方案2:迭代器remove方法
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().contains("枸杞")) it.remove();
}
三、List集合:有序存储的专属实现
List集合因支持索引,拥有add(int index, E e)、remove(int index)等特有方法,同时其两个核心实现类(ArrayList/LinkedList)底层原理差异显著。
3.1 List特有API实战
List<String> names = new ArrayList<>();
names.add("张三");
names.add("李四");
// 指定索引插入元素
names.add(1, "王二");
// 修改指定索引元素
names.set(2, "孙七");
// 删除指定索引元素
names.remove(0);
System.out.println(names); // 输出:[王二, 孙七]
3.2 ArrayList与LinkedList底层原理
- ArrayList:基于数组实现,查询快(通过索引直接定位)、增删慢(需移动后续元素)
- LinkedList:基于双向链表实现,查询慢(需遍历链表)、增删快(仅需修改节点指针),且可直接实现队列/栈结构:
// LinkedList实现队列(先进先出)
LinkedList<String> queue = new LinkedList<>();
queue.addLast("张三");
queue.addLast("李四");
System.out.println(queue.removeFirst()); // 输出:张三
// LinkedList实现栈(后进先出)
LinkedList<String> stack = new LinkedList<>();
stack.push("张三");
stack.push("李四");
System.out.println(stack.pop()); // 输出:李四
四、Set集合:去重存储的核心方案
Set集合的核心价值是元素去重,不同实现类的去重与排序逻辑依赖不同底层机制。
4.1 HashSet:基于哈希表的高效去重
HashSet底层基于**哈希表(数组+链表+红黑树,JDK8+)**实现,其去重逻辑依赖两个方法:
hashCode():计算元素的哈希值,确定存储位置equals():判断同一哈希位置的元素是否重复
若要实现自定义对象的去重,需重写这两个方法。以Student类为例:
public class Student {
private String name;
private int age;
// 重写equals:判断属性是否一致
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
// 重写hashCode:保证属性一致的对象哈希值相同
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
// 测试去重效果
Set<Student> studentSet = new HashSet<>();
studentSet.add(new Student("张三", 18));
studentSet.add(new Student("张三", 18));
System.out.println(studentSet.size()); // 输出:1(自动去重)
4.2 TreeSet:支持排序的去重集合
TreeSet底层基于红黑树实现,默认支持数值/字符串的升序排序。若要存储自定义对象,需指定排序规则,有两种实现方式:
- 自定义类实现
Comparable接口
public class Teacher implements Comparable<Teacher> {
private double salary;
@Override
public int compareTo(Teacher o) {
return Double.compare(this.salary, o.salary); // 按薪资升序
}
}
- TreeSet构造时传入
Comparator比较器
Set<Teacher> teachers = new TreeSet<>((o1, o2) ->
Double.compare(o2.getSalary(), o1.getSalary())); // 按薪资降序
teachers.add(new Teacher(10000.7));
teachers.add(new Teacher(13000.1));
System.out.println(teachers); // 输出:[13000.1, 10000.7]
五、Map双列集合:键值对的高效存储
Map集合以“键值对”形式存储数据,其核心操作围绕“键”展开,同时支持三种遍历方式。
5.1 Map核心API实战
Map<String, Integer> map = new HashMap<>();
// 添加键值对
map.put("张三", 18);
// 根据键取值
System.out.println(map.get("张三")); // 输出:18
// 判断键是否存在
System.out.println(map.containsKey("李四")); // 输出:false
// 获取所有键
Set<String> keys = map.keySet();
// 获取所有值
Collection<Integer> values = map.values();
5.2 Map的三种遍历方式
- 键找值:先获取所有键,再遍历取值
for (String key : map.keySet()) {
System.out.println(key + "=" + map.get(key));
}
- 键值对遍历:将键值对作为整体遍历,效率更高
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
- Lambda遍历:JDK8+简化写法
map.forEach((k, v) -> System.out.println(k + "=" + v));
5.3 实战案例:景点投票统计
利用Map的键值对特性,可快速实现“80人秋游景点投票统计”功能:
// 模拟80人投票数据
List<String> locations = new ArrayList<>();
String[] spots = {"玉龙雪山","故宫","黄鹤楼","外滩"};
Random r = new Random();
for (int i = 0; i < 80; i++) {
locations.add(spots[r.nextInt(spots.length)]);
}
// 统计投票结果
Map<String, Integer> voteMap = new HashMap<>();
for (String spot : locations) {
voteMap.put(spot, voteMap.containsKey(spot) ? voteMap.get(spot)+1 : 1);
}
// 输出结果
voteMap.forEach((k, v) -> System.out.println(k + "获票" + v + "张"));
六、业务实战:基于集合的电影管理系统
结合List集合的有序存储特性,我们可以实现一个简易的电影管理系统,支持电影上架、查询、封杀明星关联电影等功能。
6.1 核心实体类:Movie
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Movie {
private String name;
private double score;
private String actor;
private double price;
}
6.2 核心业务逻辑:MovieService
public class MovieService {
private static List<Movie> movies = new ArrayList<>();
private static Scanner sc = new Scanner(System.in);
// 上架电影
public void addMovie() {
Movie movie = new Movie();
System.out.println("输入电影名称:");
movie.setName(sc.next());
System.out.println("输入电影评分:");
movie.setScore(sc.nextDouble());
movies.add(movie);
System.out.println("上架成功!");
}
// 封杀明星关联电影
public void deleteStar() {
System.out.println("输入封杀明星姓名:");
String star = sc.next();
for (int i = 0; i < movies.size(); i++) {
if (movies.get(i).getActor().contains(star)) {
movies.remove(i);
i--;
}
}
System.out.println("封杀完成!");
}
}
七、集合框架选型建议
不同集合的特性决定了其适用场景,选型时可遵循以下原则:
- 有序可重复+频繁查询:选
ArrayList - 有序可重复+频繁增删:选
LinkedList(尤其首尾操作) - 去重+无序存储:选
HashSet - 去重+有序存储:选
LinkedHashSet - 去重+排序存储:选
TreeSet - 键值对存储+高效查询:选
HashMap - 键值对存储+排序需求:选
TreeMap
总结
Java集合框架是开发中的必备工具,其核心价值在于灵活适配不同存储场景。从底层原理来看,ArrayList的数组、LinkedList的链表、HashSet的哈希表、TreeSet的红黑树,分别对应了不同的数据结构思想;从实战角度,掌握API操作、避坑并发异常、结合业务选型,才能真正发挥集合的高效性。