Java: 何时选择将类设为不可变的而不是可变的?
Java: 何时选择将类设为不可变的而不是可变的?
我了解不可变对象和可变对象之间的利弊,但在设计时如何决定使用哪个。我之所以问这个问题是因为如果对象是可变的,你可以通过将对象恢复到初始状态来重复使用它,这样既高效又经济。而不可变对象在执行多步操作时性能会受到影响,因为它在每一步都生成一个新对象。<- 就内存而言
以下是一个例子:
//可变
A a = new A();
for(int j = 0 ; j < 1000 ; j++){
a.setP1(j);
//做一些操作
}
//不可变
for(int j = 0 ; j < 1000 ; j++){
A a = new A(j);
//做一些操作
}
我知道不可变对象是线程安全的,无需同步,简单且共享内部状态,但除此之外还有其他特定的原因吗?什么时候应该将我们的类声明为final(不可变)?
在Java中,当我们需要选择创建一个不可变的类(immutable class)还是可变的类(mutable class)时,通常有以下两种情况需要考虑:
1. 在多线程环境中工作时,不可变对象非常适合线程间通信(消息传递)。由于不可变对象的状态不会发生改变,所以多个线程可以安全地访问和共享这些对象,而无需担心并发访问的问题。
2. 当一个对象不需要持有状态时,即其成员变量在应用程序的生命周期内不会发生变化,那么选择创建一个不可变的类会更合适。例如,用户凭证是一个典型的不需要变化的对象,一旦创建后,其内容就不会再被修改。
在Java中,创建一个不可变的类可以采取以下几种解决方法:
1. 将类的所有成员变量声明为`final`,这样一旦初始化后就无法再修改。
public class ImmutableClass { private final int id; private final String name; public ImmutableClass(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public String getName() { return name; } }
2. 不提供任何修改类成员变量的方法,例如不提供setter方法,从而确保类的状态不会被修改。
public class ImmutableClass { private int id; private String name; public ImmutableClass(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public String getName() { return name; } }
3. 如果类的成员变量是可变对象,那么在获取这些可变对象时需要进行防御性复制(defensive copy),以确保不可变类的对象不会因为外部修改而发生变化。
public class ImmutableClass { private final Listdata; public ImmutableClass(List data) { this.data = new ArrayList<>(data); } public List getData() { return new ArrayList<>(data); } }
不可变类在多线程环境下能够提供更好的线程安全性,同时也能够避免对象状态的修改。通过在Java中使用`final`关键字、不提供修改方法或进行防御性复制等方法,我们可以轻松地创建不可变类。这样一来,我们就能够更好地控制类的行为,提高代码的可靠性和可维护性。
在Java中,选择将类设置为不可变的还是可变的取决于对象池与创建之间的权衡。这是一个将对象设置为可变的很差的理由,除非出于某些非常特殊的原因,创建对象非常昂贵,或者实例的数量需要严格控制(比如数据库连接和线程池)。
这是在Java早期使用的一种技术,现在已经被认为是不好的。让虚拟机和垃圾回收器完成它的工作,并根据使用情况设计代码,忽略这种通常是“虚构”的优化。
所以,为什么要选择将类设置为不可变的呢?原因是不可变类具有许多优点。首先,不可变类在多线程环境中是线程安全的,这意味着不需要使用同步机制来保护对象的状态。其次,不可变类可以被自由地共享,因为它们的状态不会发生变化。这使得在对象之间共享数据变得更加容易和高效。此外,不可变类还可以更容易地进行测试和调试,因为它们的行为是可预测的。
那么如何将一个类设置为不可变的呢?首先,类的所有字段应该被声明为私有的,并且没有公共的setter方法。这样可以防止外部代码直接修改对象的状态。其次,如果需要修改对象的状态,应该返回一个新的对象,而不是直接修改原始对象。这可以通过使用不可变的字段和返回新对象的方法来实现。最后,要注意避免在构造函数中使用可变的参数,因为这可能导致对象在创建后发生变化。
下面是一个示例代码,展示了如何将一个类设置为不可变的:
public final class ImmutableClass { private final int value; private final String name; public ImmutableClass(int value, String name) { this.value = value; this.name = name; } public int getValue() { return value; } public String getName() { return name; } public ImmutableClass withValue(int newValue) { return new ImmutableClass(newValue, this.name); } public ImmutableClass withName(String newName) { return new ImmutableClass(this.value, newName); } }
在上面的示例中,ImmutableClass类具有两个私有的不可变字段value和name。它的构造函数接受这两个字段的值,并在对象创建时初始化它们。getValue()和getName()方法用于获取字段的值。withValue()和withName()方法用于返回一个新的ImmutableClass对象,其中一个字段的值被修改为新的值,而另一个字段的值保持不变。
总之,选择将类设置为不可变的是一个好的实践,它提供了许多优点,如线程安全、容易共享和可预测的行为。为了将类设置为不可变的,应该遵循一些规则,如将字段声明为私有的,不提供公共的setter方法,并返回一个新的对象来修改对象的状态。