Java 为什么我们需要不可变类?

Java 为什么我们需要不可变类?,java,design-patterns,immutability,Java,Design Patterns,Immutability,我无法获得需要不可变类的场景。 你有没有遇到过这样的要求?或者你能给我们举一个我们应该使用这种模式的真实例子吗。Java实际上是一个完整的参考。有时一个实例被多次引用。如果您更改这样一个实例,它将反映到它的所有引用中。有时,您根本不希望使用此功能来提高健壮性和线程安全性。然后,一个不可变类很有用,这样就可以强制创建一个新的实例,并将其重新分配给当前引用。这样,其他引用的原始实例保持不变 想象一下,如果字符串是可变的,Java会是什么样子。哈希映射就是一个典型的例子。映射的键必须是不可变的。如果键

我无法获得需要不可变类的场景。

你有没有遇到过这样的要求?或者你能给我们举一个我们应该使用这种模式的真实例子吗。

Java实际上是一个完整的参考。有时一个实例被多次引用。如果您更改这样一个实例,它将反映到它的所有引用中。有时,您根本不希望使用此功能来提高健壮性和线程安全性。然后,一个不可变类很有用,这样就可以强制创建一个新的实例,并将其重新分配给当前引用。这样,其他引用的原始实例保持不变


想象一下,如果字符串是可变的,Java会是什么样子。

哈希映射就是一个典型的例子。映射的键必须是不可变的。如果键不是不可变的,并且您更改了键上的一个值,使得hashCode()将产生一个新值,则映射现在被破坏(键现在位于哈希表中的错误位置)。

不可变类通常更易于设计、实现和正确使用。一个例子是String: java .Lang.String 的实现明显比C++中的<>代码:ST::String 简单,主要是因为它的不可更改性。 不可变的一个特别重要的方面是并发性:不可变的对象可以在多个线程之间安全地共享,而可变的对象必须通过仔细的设计和实现使线程安全-通常这不是一项简单的任务

更新:详细解决此问题-请参见第15项:最小化可变性

另见这些相关员额:


从本质上讲,我们不需要不可变的类,但它们确实可以使一些编程任务变得更容易,特别是当涉及多个线程时。访问不可变对象不必执行任何锁定,您已经确定的关于此类对象的任何事实在将来都将继续适用。

Joshua Bloch的《有效Java》概述了编写不可变类的几个原因:

  • 简单性-每个类仅处于一种状态
  • 线程安全-由于状态无法更改,因此不需要同步
  • 以不变的风格编写代码可以产生更健壮的代码。想象一下,如果字符串不是不可变的;任何返回字符串的getter方法都需要实现在返回字符串之前创建防御副本,否则客户端可能会意外或恶意破坏对象的状态

一般来说,最好的做法是使对象不可变,除非结果导致严重的性能问题。在这种情况下,可变生成器对象可用于构建不可变对象,例如StringBuilder。不可变的原因有很多:

  • 线程安全:不可变对象不能更改,其内部状态也不能更改,因此无需同步
  • 它还保证我通过(网络)发送的内容必须与之前发送的内容处于相同的状态。这意味着没有人(窃听者)可以来我的不可变集合中添加随机数据
  • 开发起来也比较简单。如果对象是不可变的,则可以保证不存在子类。例如,一个
    字符串

因此,如果您希望通过网络服务发送数据,并且您希望有一种感觉,即保证您的结果与发送的结果完全相同,请将其设置为不可变。

让我们看一个极端情况:整数常量。如果我写了一个像“x=x+1”这样的语句,我想成为100%的知己,无论程序中其他地方发生了什么,数字“1”都不会以某种方式变成2

整数常量不是一个类,但概念是一样的。假设我写:

String customerId=getCustomerId();
String customerName=getCustomerName(customerId);
String customerBalance=getCustomerBalance(customerid);
看起来很简单。但是,如果字符串不是不可变的,那么我必须考虑GETCuffError可以改变客户ID的可能性,这样当我调用GETCuuleStalk时,我就得到了不同客户的余额。现在你可能会说,“为什么有人编写getCustomerName函数让它更改id?这毫无意义。”但这正是你可能遇到麻烦的地方。编写上述代码的人可能会认为函数不会更改参数是显而易见的。然后,有人不得不修改该功能的另一个用途,以处理客户拥有多个同名帐户的情况。他说,“哦,这是一个方便的getCustomer name函数,它已经在查找名称了。我会让它自动将id更改为下一个同名帐户,并将其放入循环…”然后你的程序神秘地开始不工作。那会是一种糟糕的编码风格吗?可能但在副作用不明显的情况下,这恰恰是一个问题

不变性仅仅意味着某类对象是常量,我们可以将它们视为常量

(当然,用户可以为变量指定不同的“常量对象”。有人可以编写 字符串s=“hello”; 然后再写 s=“再见”;
除非我将变量设为final,否则我无法确定它是否在我自己的代码块中没有被更改。就像整数常量一样,我可以确保“1”始终是相同的数字,但不是“x=1”永远不会通过写入“x=2”而被更改但是我可以告诉你,如果我有一个不可变对象的句柄,我传递给它的任何函数都不能对我进行更改,或者如果我复制了两个副本,对保存一个副本的变量的更改不会更改另一个副本。等等。

其他答案似乎过于侧重于解释为什么不可变性是好的。它非常好,我使用了它无论何时
public class Scratchpad {
    public static void main(String[] args) throws Exception {
        SomeData sd = new SomeData("foo");
        System.out.println(sd.data); //prints "foo"
        voodoo(sd, "data", "bar");
        System.out.println(sd.data); //prints "bar"
    }

    private static void voodoo(Object obj, String fieldName, Object value) throws Exception {
        Field f = SomeData.class.getDeclaredField("data");
        f.setAccessible(true);
        Field modifiers = Field.class.getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL);
        f.set(obj, "bar");
    }
}

class SomeData {
    final String data;
    SomeData(String data) {
        this.data = data;
    }
}
class Person {
    public string getName() { ... }
    public void registerForNameChange(NameChangedObserver o) { ... }
}
void foo(Person p) {
    p.getName().prepend("Mr. ");
}