刚学 Java 集合框架的时候,我被这几个基础问题卡了很久 📅 2026/7/7 14:59:20 我刚开始学 Java 集合框架的时候脑子里是一团浆糊。ArrayList、LinkedList、HashMap、HashSet、TreeMap……一堆名字砸过来每个看起来都能存东西但好像又不太一样。更别说还有 Collection 和 Collections 这种长得差不多、实际两码事的东西。那时我在想这么多东西我到底要从哪开始后来我发现其实不需要一口气全搞明白。集合框架是有套路的——先搞清楚那几个最基础的概念后面那些实现类自然就能串起来。这篇就是我当初最希望有人能跟我讲清楚的东西。数组和集合到底差在哪学集合之前Java 里存数据最基础的方式是数组。我们最开始写代码的时候大概都写过类似的东西String[]namesnewString[10];看起来没什么问题对吧但很快你会发现这个长度 10 是定死的。数据超过 10 个存不进去少于 10 个又浪费空间。更难受的是你一开始根本不知道到底要存多少个。我印象很深有次写个小程序要记录用户输入的单词想先声明一个足够大的数组但又怕太小会溢出最后给了一个 1000 的容量。后来一想这不就是瞎蒙嘛。这就是数组最大的问题长度固定不灵活。集合就是来解决这个的。它像个能自动伸缩的容器你往里放东西它会自己长大拿出去它也不闹。用 ArrayList 的话上面那段代码就变成了ListStringnamesnewArrayList();想加几个加几个不用提前算。除了长度还有两个区别我当时也觉得挺有意思的。一个是数组啥都能装基本类型也好、对象也好都能往里塞int[]numbersnewint[5];但集合就不行。你不能写ArrayListint只能写ArrayListInteger。这个后面细说。另一个是访问方式。数组用下标直接取names[0]就行。集合得通过迭代器或者一些工具方法——当然这只是写法上的区别不是啥大问题。所以如果让我总结数组像个定制的储物柜每个格子大小固定、位置固定。集合像个编织袋能撑能缩装啥都行就是拿东西的时候得翻一翻。日常开发里除了那种确定长度、频繁按索引访问的场景大部分情况下集合用起来更顺手。Collection 和 Collections我被这个名字骗过我第一次看到这两个名字的时候以为 Collection 是接口Collections 是它下面的某个实现类。错了。Collection 是接口。它是所有集合类List、Set、Queue 这些的老祖宗,定义了一组通用的方法比如 add、remove、isEmpty、size。不管是 ArrayList 还是 HashSet它们都实现了这个接口所以你才能用一样的方法往里面加东西、删东西。Collections 是工具类。注意有个 s 在尾巴上。它是一堆静态方法的集合用来操作集合的排序、查找、打乱顺序、反转、包装成不可变的……它本身不是集合是个工具箱。我当时怎么搞清楚这个的是我写了一段代码Collections.sort(list);然后回去找 Collection 接口发现里面根本没有 sort 方法。顺着往下翻才看到 Collections 这个类。这俩名字就差一个字母但一个是我要继承/实现的规范一个是我可以调用的工具。知道这个之后很多困惑就解开了。每次看到一个新类先问自己它是 Collection 接口的实现类比如 ArrayList、HashSet还是 Collections 这个工具类提供的功能比如排序、不可变集合包装搞清楚这个分类集合框架就不会那么乱了。遍历一个集合有多少种方式讲遍历之前先说说我一开始犯过的傻。我曾以为遍历只有一种方式就是 for 循环按索引取。后来发现根本不是这么回事——Java 集合的遍历方式多得超乎想象。我当时把几个常见的方式都试了一遍每个都有自己的使用场景。普通 for 循环这个只对 List 有用因为 List 有索引for(inti0;ilist.size();i){Stringitemlist.get(i);}这种写法的好处是你能控制索引。但 Set 和 Map 没有索引这就用不了了。增强 for 循环for-each这个当时让我觉得挺神奇的——不用管索引直接把每个元素拿出来for(Stringitem:list){System.out.println(item);}大部分需要遍历所有元素的时候我都用这个。简洁、直观、不容易错。Iterator 迭代器这个我一开始不理解既然 for-each 已经能遍历了为什么还要迭代器后来遇到一个问题就知道了我想在遍历的过程中删掉某些元素。用 for-each 这么写是会报错的for(Stringitem:list){if(item.equals(xxx)){list.remove(item);// 并发修改异常}}但用 Iterator 就可以IteratorStringitlist.iterator();while(it.hasNext()){Stringitemit.next();if(item.equals(xxx)){it.remove();}}原理是Iterator 的 remove 方法会同步更新集合的修改状态不会触发并发修改检查。当时搞明白这个的时候有一种哦原来是这样的感觉。ListIterator 列表迭代器Iterator 的升级版。它除了能往后走next,还能往前走previous。而且可以在遍历的时候添加和修改元素。当时我写了一个小程序需要在列表中间插入几个元素ListIterator 帮了大忙ListIteratorStringlitlist.listIterator();while(lit.hasNext()){Stringitemlit.next();if(item.equals(某个条件)){lit.add(新元素);// 在当前迭代位置插入}}不过实际开发中用的不算多知道有这么个东西就行真需要了再回来翻。forEach 方法Java 8JDK 8 之后集合可以直接调 forEach 方法传一个 Lambda 表达式进去list.forEach(item-System.out.println(item));我当时看到这个写法觉得挺酷的代码一下子短了好多。不过一开始 Lambda 看着不太习惯写多了就好了。Stream API这个说实话我学了一段时间才真正敢在代码里用。Stream 不只是遍历它提供了一套流水线操作你可以过滤、映射、排序、收集用链式调用串起来。list.stream().filter(item-item.length()3).sorted().collect(Collectors.toList());最开始我觉得这写法很怪习惯了之后发现有些场景确实用起来很舒服尤其是需要对集合做多步处理的时候。所以总结一下这 6 种方式普通 for 和 for-each 最常用需要边遍历边删就用 Iterator需要双向遍历用 ListIterator喜欢函数式风格就用 forEach 和 Stream。没有哪个是最好的看场景选。为什么 ArrayListint 会报错我学集合早期被编译报错卡住过的一个问题是为什么不能new ArrayListint()上网查了之后大概明白了——Java 的泛型是编译时的假把式。编译完之后泛型的类型信息会被擦除掉官方叫类型擦除。什么意思呢你写ArrayListString和ArrayListInteger在编译后的字节码里它俩都是ArrayListObject。String 和 Integer 是引用类型擦掉之后变成 Object没问题。但 int 是基本类型不是对象擦掉之后没法变成 Object。解决方案就是用包装类。Java 为每个基本类型都准备了一个包装类int → Integer、char → Character、double → Double、boolean → Boolean……以此类推。而且 JDK 5 之后Java 有自动装箱和自动拆箱机制。你写ListIntegerlistnewArrayList();list.add(10);// 自动装箱int → Integerintnlist.get(0);// 自动拆箱Integer → int表面上你写的是 int实际上编译器偷偷帮你转了。代码看起来没啥区别但如果你追求性能大量的装箱拆箱是有开销的。当然这是后面才会关心的事了。刚开始学知道泛型不能用基本类型、用包装类就行。List 和数组互相转换踩过的坑日常开发里经常需要在数组和 List 之间倒腾。看起来简单但稍不注意就容易踩坑。List 转数组String[]arrlist.toArray(newString[0]);JDK 1.8 之后官方推荐传一个长度为 0 的数组性能更好。原理是 toArray 方法内部会判断如果传进来的数组长度不够就反射创建一个新的。传 0 长度的数组它会直接创建一个恰好够用的新数组返回。数组转 List这个坑更多一些。最直接的方式是Arrays.asList()ListStringlistArrays.asList(arr);看起来一切正常对吧但如果你后面写了list.add(xxx)就等着抛异常吧。Arrays.asList 返回的 List 是不可变的。不能 add不能 remove。它只是把数组包装成了一个看起来像 List的东西底层还是那个数组。我当时第一次遇到这个问题的时候对着报错看了半天心想这不是 List 吗怎么不能加元素解决办法是包一层ListStringlistnewArrayList(Arrays.asList(arr));这样才是真正的可变 ArrayList。还有另一个坑是基本类型数组。如果你写int[]arr{1,2,3};Listint[]listArrays.asList(arr);// 注意泛型是 int[]不是 Integer它不会得到一个有三个元素的 List而是得到一个有一个元素的 List——这个元素就是整个 int 数组。因为泛型不支持 int,所以 Arrays.asList 把整个 int[] 当成了一个对象。解决方案有两种。一种是用 Integer 包装类的数组Integer[]arr{1,2,3};ListIntegerlistArrays.asList(arr);// 正常了另一种是用 Streamint[]arr{1,2,3};ListIntegerlistArrays.stream(arr).boxed().collect(Collectors.toList());这个坑我第一次踩到的时候真的有点无语——心想这也行后来只能说记住就好基本类型在集合的世界里是个特殊的存在。最后说几句写这篇的时候我一直想着当初学集合框架的我自己。那时候看到那么多类名、接口、方法真的很想有人能告诉我先搞清楚这几块基础——数组 vs 集合、Collection vs Collections、怎么遍历、类型的问题、转换的问题——后面的具体实现类学起来会顺畅很多。我就是按这个路子过来的。先打基础再看细节。ArrayList 和 LinkedList 有啥区别、HashMap 的哈希冲突怎么解决的、TreeMap 为什么有序……这些是下一篇的事。先把地基踩实了后面走起来才不晃。而且说实话学的时候觉得哇这么多东西怎么记得住写代码写多了就会发现常用的也就那几个。新手不用慌慢慢来搞懂一个算一个。