ArrayList存的是对象引用而非副本,修改对象属性确实生效;HashSet重复添加失败通常因未重写hashCode()和equals();TreeSet排序优先用Comparator;序列化异常多因serialVersionUID不匹配或类变更。

为什么自定义对象放进 ArrayList 后修改属性,集合里“好像没变”?
不是没变,是变了但你没看到效果——因为 ArrayList 存的是对象引用,不是副本。当你用 list.get(0).setName("xxx") 修改对象内部状态,原对象已被改动,集合中指向它的引用仍有效。
常见误操作:obj = new MyObj(); list.add(obj); obj = new MyObj(); —— 第二个 new 不影响集合,只是把局部变量 obj 指向了新对象,集合里还是老引用。
- 确认是否真在改集合里的对象:用
list.get(i)获取后再调方法,别直接 new 一个新对象去“覆盖” - 如果需要深拷贝行为(比如防止外部篡改),得手动实现克隆或用不可变设计
-
ArrayList、HashSet、HashMap都遵循“存引用”原则,这点和 C++ 的值语义完全不同
HashSet 里自定义对象重复添加失败,是不是没重写 hashCode() 和 equals()?
极大概率是。默认的 Object.hashCode() 返回内存地址哈希值,Object.equals() 是引用比较。两个逻辑相等的对象(如 new User("a", 25) 和 new User("a", 25))会因哈希值不同被分到不同桶,equals() 根本不会被调用。
必须同时重写两者,且满足:相等的对象必须有相同 hashCode();hashCode() 相同不要求一定相等(哈希冲突允许)。
立即学习“Java免费学习笔记(深入)”;
- IDE(如 IntelliJ)可自动生成:右键 → Generate →
equals()andhashCode(),选关键字段(如id或业务主键) - 避免在
hashCode()中使用可变字段(如name),否则对象加入HashSet后改名,就再也找不到了 - 用 Lombok 的
@EqualsAndHashCode可简化,但要注意exclude或of显式指定字段
想按某个字段排序存进 TreeSet,该实现 Comparable 还是传 Comparator?
优先传 Comparator。硬编码 Comparable 会绑定单一排序逻辑,后续想按年龄排、再按姓名排就只能改类,破坏开闭原则。
TreeSet 构造时传入 Comparator 更灵活:
TreeSetset = new TreeSet<>((u1, u2) -> Integer.compare(u1.getAge(), u2.getAge()));
- 如果类天然只有一个“自然顺序”(如
IdCard按卡号升序),才考虑实现Comparable - 用 Lambda 写
Comparator时注意空指针:字段可能为null,推荐用Comparator.nullsLast(Comparator.naturalOrder()) -
TreeSet不允许null元素(除非Comparator显式支持),而ArrayList和HashSet都可以存null
序列化后反序列化,集合里的对象变成 java.lang.ClassCastException?
典型原因是:反序列化时类定义已变更(如删了字段、改了包名),或运行时加载了不同版本的类(比如不同模块引了不同 jar 中的同名类)。JVM 用类的 serialVersionUID 校验兼容性,不匹配就抛异常。
- 显式声明
private static final long serialVersionUID = 1L;,别依赖 IDE 自动生成的随机值 - 字段增减要谨慎:加
transient或提供readObject()自定义反序列化逻辑 - 更稳妥的方式是避开 Java 原生序列化,改用 JSON(
Jackson/Gson)或 Protocol Buffers,它们对字段演进更友好
hashCode() 和 equals() 的一致性约束,以及序列化版本管理——这两个点一旦出问题,往往表现为“数据丢了”或“集合查不到”,但错误信息根本不提它们。










