跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

Java对象的可变性与不可变性分析

不可变对象需同时满足:类为final、所有字段private final、无修改内部状态的方法;final仅保证引用不变,不阻止对象状态改变;适合值对象、配置类和HashMap键,但高频率修改场景需权衡GC开销。 不可变对象必须满足的三个条件 一个 Java 对象要被认定为不可变,不是靠加个
final
关键字就完事的。它得同时满足:类本身用
final
修饰(防止被继承)、所有字段都是
private final
、并且没有暴露可修改内部状态的方法(比如不提供 setter,也不返回可变对象的引用)。漏掉任意一条,都可能被绕过防护。
String
是典型不可变类:它的
value
字段是
private final char[]
,且所有 public 方法(如
substring()
)都返回新对象,不修改原数组 如果字段是
private final List
,但通过 getter 返回了原始
ArrayList
引用,外部就能调用
add()
修改内容——这不算真正不可变 构造器里若直接赋值传入的可变对象(如
this.data = data;
),必须做防御性拷贝:
this.data = new ArrayList(data);
为什么
final
修饰引用不等于对象不可变
final
只保证该变量不能再指向别的对象,但不阻止对象自身状态被修改。这是最常被误解的一点。
public class Person { private final List hobbies; public Person(List h) { this.hobbies = h; // ❌ 危险:hobbies 指向外部传入的 ArrayList } public List getHobbies() { return hobbies; // ✅ 外部拿到后可直接 add/remove } }
上面代码中,
hobbies
是
final
,但
ArrayList
本身是可变的。正确做法是用
Collections.unmodifiableList()
包装,或在构造时深拷贝。 可变对象在并发场景下的典型问题 多线程共享可变对象时,如果没有 同步机制 ,会出现可见性、原子性、重排序三类问题。比如
SimpleDateFormat
就是典型的非线程安全可变类。 Eclipse导入Android或其他的JAVA项目的正确方法 WORD版 本文档主要讲述的是Eclipse导入Android或其他的JAVA项目的正确方法;希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看 下载 立即学习 “ Java免费学习笔记(深入) ”; 多个线程共用同一个
SimpleDateFormat
实例调用
parse()
,可能抛出
java.lang.NumberFormatException
或返回错误日期 解决方式不是加
synchronized
(性能差),而是改用
DateTimeFormatter
(不可变、线程安全),或每次新建
SimpleDateFormat
实例 使用
ThreadLocal
也可行,但要注意内存泄漏风险(尤其在线程池中) 什么时候该设计成不可变对象 不是所有对象都适合不可变。它最适合那些状态一旦创建就不应改变、且会被多处共享的场景。 值对象(value object):如
LocalDate
、
BigInteger
、自定义的
Money
、
Range
配置类:如果整个应用生命周期内配置只读,用不可变对象能避免意外篡改 作为
HashMap
的 key:不可变保证了
hashCode()
值稳定,不会因字段变化导致 key “消失” 但频繁修改的业务实体(如
User
订单状态流转)强行不可变会导致大量临时对象,GC 压力上升 不可变性的代价是每次“修改”都要新建对象,所以关键看修改频次和对象大小。小而稳的数据结构值得不可变;大而频繁变更的状态,得权衡清楚。

相关文章