先别急着把这两行代码混在一起,它们俩干的事完全不是一回事。list = ... 是给变量重新指路,list.add(...) 是往对象肚子里塞东西。我见过太多新手(甚至干了两三年的开发)在这上面栽跟头,比如把列表拆了、数据串了、改完一个另一个跟着变,最后查半天不知道为什么。这篇文章就直接把这层窗户纸捅破,把“list=和list.add的区别”讲透,顺带把引用赋值、对象操作、拷贝、排序、转 Map 这些高频操作都串起来。不管你是刚学 Java 集合,还是写 C#、Python、JavaScript,这套思维都通用。
1. 先搞清楚等号赋值和 add 方法到底在操作什么
1.1 变量不是盒子,是贴在对象上的标签
很多刚入门的朋友把 List<String> list 想象成一个盒子,往里面装元素。这个直觉在 Python 里叫“变量是标签”,在 Java 里其实也类似:对象变量里面存的不是一个对象本身,而是对象在堆内存中的地址。也就是说,list 这个变量本质是一个引用,它指向堆上的一个 ArrayList 实例。
java复制List<String> list1 = new ArrayList<>();
List<String> list2 = list1; // 这行是在做什么?
list2 = list1 这行代码做的事情,是把 list1 里面存储的地址值复制一份,交给 list2。复制完之后,list1 和 list2 两个变量指向的是同一个 ArrayList 对象。你没听错,不是两个内容相同的列表,而是完完全全同一个对象,就像你把一个人的两个手机号都存在通讯录里,拨哪个号打到的都是同一个人。
这个认知非常关键,因为后面所有的坑都从这里长出来。如果你用 list2.add("hello"),这个 add 操作是在“那个对象”的内部结构上动手,而 list1 指向的就是那个对象,所以 list1.get(0) 也会变成 "hello"。这种情况下,= 改变的不是对象,而是变量指向的对象是谁;add 才是真正改变对象的内容。
1.2 ArrayList 的 add 底层到底怎么干活的
ArrayList.add(E e) 源码的核心逻辑并不复杂,但它能帮你彻底理解“操作对象内部状态”这件事。
java复制public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确保数组容量够
elementData[size++] = e; // 把元素放到数组下标为 size 的位置
return true;
}
ArrayList 内部维护着一个 Object[] elementData 数组和一个 int size 记录当前元素个数。add 的流程是:先检查数组剩余空间,如果不够就扩容,然后把元素赋值到 elementData[size],再把 size 加一。扩容的细节是 newCapacity = oldCapacity + (oldCapacity >> 1),也就是 1.5 倍左右,这也是为什么连续 add 很多元素时会有多次扩容开销。
所以 add 做的事情,是在“已经存在的这个对象”内部做修改。无论你调用多少次 list.add,list 这个变量指向的对象始终没变,变得是对象内部的数组内容和 size 字段。明白了这个区别,你就可以回答这样一个面试题:list = new ArrayList<>() 之后,原来的列表对象去哪里了?答案是:如果没有任何引用指向它,它就成了垃圾回收的候选对象。
1.3 一句话总结:= 改的是引用,add 改的是内容
你可以把这段当成以后判断问题的口诀:等号左边是变量,等号右边是地址来源;点号后面是对象的行为,add 是对象行为,等号是变量指向变更。
java复制List<String> a = new ArrayList<>();
List<String> b = a; // b 和 a 指向同一个对象
b.add("java"); // 在这个对象里添加元素
System.out.println(a.size()); // 输出 1
如果想复制一个真正独立的列表,不能直接 =,要用新建对象的方式。这个在后面的第 3 部分会详细展开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 三个高频踩坑场景:赋值和 add 混用的惨痛教训
2.1 以为 list = otherList 是复制,结果两个变量互相串数据
这是最常见的一个误区。比如下面这段代码:
java复制List<Integer> source = new ArrayList<>();
source.add(1);
source.add(2);
List<Integer> target = source; // 目标:复制一份
target.add(3);
System.out.println(source); // [1, 2, 3]
System.out.println(target); // [1, 2, 3]
写代码的人本意是“把 source 复制给 target”,结果 target 里加了一个 3,source 也跟着多了 3。原因我们已经说过:target 和 source 是同一个对象的两个引用。实际上“复制一份”的正确姿势是 new ArrayList<>(source),这会在堆上新创建一个 ArrayList,把旧列表的元素逐个拷到新数组里,两者从此再无瓜葛。
注意:
new ArrayList<>(source)是浅拷贝。如果列表里存的是可变对象,比如Person,那么新列表和旧列表中
