Java 从入门到精通(九):集合框架入门,List、Set、Map 到底该怎么选?

Java 从入门到精通(九):集合框架入门,List、Set、Map 到底该怎么选?

学完 String、包装类、抽象类、接口这些内容之后,很多人会发现:

Java 语法自己好像懂了不少,但一到真正写业务代码,最常见的问题却变成了另一种:

  • 一组数据到底该用数组,还是集合?
  • List、Set、Map 看起来都能"装东西",到底差别在哪?
  • 为什么有的地方要去重,有的地方又必须保留顺序?
  • ArrayList 和 LinkedList 面试常问,但平时到底怎么选?
  • HashMap 为什么这么常用?它和 Hashtable、TreeMap 又是什么关系?

这些问题,表面上是在选容器,实际上是在训练一种很重要的能力:

根据业务需求,选择合适的数据结构。

集合框架,就是 Java 日常开发里最常用的一套"装数据、管数据、查数据"的工具箱。

这一篇不堆 API,也不背定义,直接把最常用的集合思路讲清楚。

一、为什么数组不够,还要有集合?

很多人最早接触"装一批数据",都是从数组开始:

java 复制代码
int[] nums = new int[5];
nums[0] = 10;
nums[1] = 20;

数组当然很重要,但它有几个明显限制:

  • 长度固定,创建后不能动态扩容
  • 插入、删除不方便
  • 只能处理一类比较基础的存储场景
  • 缺少丰富的方法支持

比如你要保存一批用户名字,而且数量不确定:

java 复制代码
String[] names = new String[100];

如果最后只有 8 个人,就浪费空间;如果来了 101 个人,又不够用。

所以 Java 提供了集合框架,让我们可以:

  • 动态存储数据
  • 方便地增删改查
  • 根据不同场景选不同容器
  • 借助统一接口来写更灵活的代码

可以简单理解为:

  • 数组:更底层、更固定
  • 集合:更灵活、更适合业务开发

二、集合框架整体怎么理解?

Java 集合框架最核心可以分成三大类:

  • List:有序、可重复
  • Set:无重复
  • Map:键值对存储

先看一个最直观的理解方式:

1. List 像"排队名单"

关注的是:

  • 元素有顺序
  • 可以按下标取值
  • 允许重复

例如:课程章节、评论列表、消息记录。

2. Set 像"去重名单"

关注的是:

  • 元素不能重复
  • 通常不强调按下标访问
  • 更适合判重、去重

例如:用户标签、访问过的页面 ID、抽奖去重名单。

3. Map 像"字典/通讯录"

关注的是:

  • 每个值都对应一个键
  • 通过 key 查 value
  • 键不能重复

例如:

  • 学号 -> 学生对象
  • 用户 id -> 用户信息
  • 配置项名称 -> 配置值

所以你先别急着记类名,先记住:

List、Set、Map 不是平级"功能重复"的容器,而是三种完全不同的数据组织方式。

三、List:有序、可重复,最常用的线性集合

List 的特点很简单:

  • 插入顺序通常会保留
  • 可以通过索引访问
  • 允许重复元素

最常用的实现类有:

  • ArrayList
  • LinkedList

1. ArrayList 最常用

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class Demo {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
        fruits.add("苹果");
        fruits.add("香蕉");
        fruits.add("苹果");

        System.out.println(fruits); // [苹果, 香蕉, 苹果]
        System.out.println(fruits.get(1)); // 香蕉
    }
}

从这个例子可以看出来:

  • 顺序保留了
  • 重复元素允许存在
  • 可以通过 get(index) 取指定位置的数据

2. ArrayList 适合什么场景?

大多数业务代码里,如果你只是需要"存一批有顺序的数据",优先就可以想到 ArrayList

它适合:

  • 读取多、随机访问多
  • 末尾追加多
  • 日常大多数列表类需求

例如:

  • 商品列表
  • 搜索结果列表
  • 文章评论列表

3. LinkedList 是什么?

LinkedList 底层思路是链表。

java 复制代码
import java.util.LinkedList;
import java.util.List;

public class Demo {
    public static void main(String[] args) {
        List<String> list = new LinkedList<>();
        list.add("A");
        list.add("B");
        list.add("C");
        System.out.println(list);
    }
}

它也实现了 List,所以用法表面上很像。

但在实际开发里:

  • 如果你经常按索引取值,ArrayList 通常更合适
  • 如果你特别强调头尾插入删除,LinkedList 在某些场景更自然

不过要说一句实话:

现代 Java 业务开发里,ArrayList 的使用频率远高于 LinkedList

很多初学者被"链表插入快"这句话带偏,结果把 LinkedList 用到了完全没必要的地方。

入门阶段先记住一条经验:

默认先用 ArrayList,除非你非常明确地需要链表特性。

四、Set:不允许重复,核心价值是去重

Set 最大的特点就是:

不允许重复元素。

最常用的是:

  • HashSet
  • LinkedHashSet
  • TreeSet

1. HashSet 的基本用法

java 复制代码
import java.util.HashSet;
import java.util.Set;

public class Demo {
    public static void main(String[] args) {
        Set<String> cities = new HashSet<>();
        cities.add("北京");
        cities.add("上海");
        cities.add("北京");

        System.out.println(cities);
    }
}

虽然加了两次"北京",但 Set 里只会保留一个。

这就非常适合做:

  • 数据去重
  • 成员判断
  • 标签汇总

例如,你想统计一篇文章中出现过哪些不同的单词,就很适合用 Set。

2. Set 为什么不能用索引?

因为 Set 关注的重点不是"第几个元素",而是"某个元素是否存在"。

它和 List 的设计目标不同:

  • List 更像线性表
  • Set 更像数学集合

所以你不应该一边用 Set,一边还想着 get(0)get(1) 这种操作。

如果你特别关心顺序,应该先想清楚自己真正要的是不是 List。

3. HashSet、LinkedHashSet、TreeSet 怎么区分?

可以先用一句话记:

  • HashSet:去重,通常不保证遍历顺序
  • LinkedHashSet:去重,同时保留插入顺序
  • TreeSet:去重,并且自动排序

例如:

java 复制代码
import java.util.Set;
import java.util.TreeSet;

public class Demo {
    public static void main(String[] args) {
        Set<Integer> scores = new TreeSet<>();
        scores.add(90);
        scores.add(75);
        scores.add(88);
        scores.add(90);

        System.out.println(scores); // [75, 88, 90]
    }
}

所以如果需求是:

  • 只去重:先想 HashSet
  • 去重且保留加入顺序:想 LinkedHashSet
  • 去重且排序:想 TreeSet

五、Map:真正高频的键值对容器

如果说 List 是"最常见的线性容器",那 Map 就是"最常见的查找容器"。

它保存的是:

key -> value

最常用实现类:

  • HashMap
  • LinkedHashMap
  • TreeMap

1. HashMap 基本用法

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) {
        Map<Integer, String> students = new HashMap<>();
        students.put(1001, "张三");
        students.put(1002, "李四");
        students.put(1003, "王五");

        System.out.println(students.get(1002)); // 李四
    }
}

这里就不是"存一排数据",而是"通过 key 找 value"。

所以 Map 非常适合:

  • id 到对象的映射
  • 配置项存储
  • 统计计数
  • 缓存结构

2. Map 和 List 最本质的区别

很多人初学时容易混:

  • List 是通过位置找元素
  • Map 是通过找元素

比如:

java 复制代码
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
System.out.println(list.get(1));

这里你是按位置取第二个人。

而 Map 是:

java 复制代码
Map<Integer, String> map = new HashMap<>();
map.put(1001, "张三");
map.put(1002, "李四");
System.out.println(map.get(1002));

这里你是按学号取人。

所以当你的需求里天然存在"编号、名称、唯一标识"这类键时,Map 通常更合适。

3. 键不能重复是什么意思?

java 复制代码
Map<String, Integer> map = new HashMap<>();
map.put("apple", 3);
map.put("banana", 5);
map.put("apple", 10);

System.out.println(map); // apple 对应的值会被覆盖

Map 中 key 不能重复。

如果重复 put 同一个 key,旧值会被新值覆盖。

这其实很合理:

因为一个 key 代表的是同一个"索引入口"。

4. 一个非常常见的计数场景

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) {
        String[] words = {"java", "spring", "java", "mysql", "java", "spring"};
        Map<String, Integer> countMap = new HashMap<>();

        for (String word : words) {
            if (countMap.containsKey(word)) {
                countMap.put(word, countMap.get(word) + 1);
            } else {
                countMap.put(word, 1);
            }
        }

        System.out.println(countMap);
    }
}

这个例子特别典型。

以后你一看到:

  • 统计词频
  • 统计用户访问次数
  • 统计商品销量次数

脑子里就要想到:

这大概率是 Map 的场景。

六、到底该选 List、Set 还是 Map?

这是整篇最关键的问题。

可以按下面这套思路判断:

1. 你要的是"一列数据",还是"键值映射"?

  • 如果是"一批元素排起来",优先想 List
  • 如果是"键对应值",优先想 Map

2. 这一批元素能不能重复?

  • 能重复:List
  • 不能重复:Set

3. 你关不关心顺序?

  • 关心插入顺序:ArrayList / LinkedHashSet / LinkedHashMap
  • 关心排序结果:TreeSet / TreeMap
  • 不关心顺序,只关心查找和存储:HashSet / HashMap

4. 你是不是要按下标访问?

  • 是:通常应该用 List
  • 不是,而是判断元素是否存在:更像 Set
  • 不是,而是通过 key 查 value:更像 Map

七、开发里最常见的选择口诀

如果你现在还没有完全形成感觉,可以先背一个非常实用的版本:

  • 默认列表数据:ArrayList
  • 默认去重集合:HashSet
  • 默认键值映射:HashMap

这三个,是 Java 集合里最常用的"默认起手式"。

很多真实项目里,你写的大部分集合代码,绕不开它们。

等你后面遇到更明确的需求,再升级成:

  • 要保序:LinkedHashMap / LinkedHashSet
  • 要排序:TreeMap / TreeSet
  • 要线程安全:并发集合(后面会讲)

八、几个初学者最容易踩的坑

1. 以为集合什么都能装,结果类型混乱

不要这样写:

java 复制代码
List list = new ArrayList();
list.add("hello");
list.add(123);
list.add(true);

这会让代码非常难维护。

更好的写法是明确类型:

java 复制代码
List<String> list = new ArrayList<>();

这里的泛型我们下一篇会详细讲,但你现在先记住:

集合尽量带类型。

2. 一边遍历一边乱删元素

有些人会在遍历集合时直接删除元素,结果容易出问题。

这类问题后面讲迭代器和并发修改时会系统展开。

你现在先建立一个意识:

集合在遍历、修改时要格外小心。

3. 把 Set 当成有顺序的列表来用

如果你的业务明确依赖"第一个、第二个、第三个",那大概率不该用普通 HashSet

4. 把 Map 理解成"高级 List"

Map 不是按位置访问的容器,它的核心是通过 key 建立索引。

只要你想清楚这一点,很多场景就不会选错。

九、一个简单的业务例子,把三者连起来

假设你在做一个课程系统:

  • List 保存课程章节,因为章节有顺序
  • Set 保存学生标签,因为标签要去重
  • Map 保存学号和学生对象的映射,因为要按学号快速找到人

示意代码:

java 复制代码
import java.util.*;

public class Demo {
    public static void main(String[] args) {
        List<String> chapters = new ArrayList<>();
        chapters.add("第一章:Java 入门");
        chapters.add("第二章:流程控制");
        chapters.add("第三章:集合框架");

        Set<String> tags = new HashSet<>();
        tags.add("认真");
        tags.add("活跃");
        tags.add("认真");

        Map<Integer, String> studentMap = new HashMap<>();
        studentMap.put(1001, "张三");
        studentMap.put(1002, "李四");

        System.out.println(chapters);
        System.out.println(tags);
        System.out.println(studentMap.get(1001));
    }
}

只要你能看懂这个例子,说明你对集合框架最核心的第一层已经建立起来了。

十、这一篇你真正该记住什么?

不要试图一次把所有集合类全背下来。

入门阶段,先把下面这几条吃透:

  1. List :有序、可重复,典型实现是 ArrayList
  2. Set :无重复,典型实现是 HashSet
  3. Map :键值对存储,典型实现是 HashMap
  4. 顺序、去重、映射,是三种不同需求,不要混着选
  5. 大多数业务开发里,先会正确选容器,比背底层源码更重要

十一、结语

集合框架是 Java 真正进入"写业务代码"阶段的分水岭。

前面学类、对象、字符串,更多是在理解语言本身。

而从集合开始,你会越来越频繁地思考:

  • 数据怎么组织?
  • 查找效率怎么保证?
  • 哪种结构更贴近业务需求?

这时候,Java 就不只是"会写语法",而开始进入"会设计数据"的阶段了。

先把 List、Set、Map 三者分清楚,后面再学泛型、迭代器、并发集合、Stream,都会顺很多。

下一篇,我们继续:Java 从入门到精通(十):泛型与集合实战,为什么 Java 一定要有泛型?

相关推荐
东离与糖宝2 小时前
Java 26惰性常量+HTTP/3:AI微服务启动提速5倍实战
java·人工智能
郝学胜-神的一滴2 小时前
从线程栈到表达式求值:栈结构的核心应用与递归实现
开发语言·数据结构·c++·算法·面试·职场和发展·软件工程
姓蔡小朋友2 小时前
Agent Skill设计模式
开发语言·javascript·设计模式
爱码少年2 小时前
Springboot 工程中快速判断web应用服务器类型
java·spring boot
敲代码的嘎仔2 小时前
Java后端开发——多线程面试题
java·开发语言·面试·多线程·八股·threadlocal·
sonnet-10292 小时前
交换排序算法
java·c语言·开发语言·数据结构·笔记·算法·排序算法
NGC_66112 小时前
深度解析 ConcurrentHashMap 1.8:put 与 get 核心流程全解
java·开发语言
杭州杭州杭州2 小时前
J2EE实验
java·java-ee
福运常在2 小时前
股票数据API如何获取(20)炸板股池数据
java·python·maven