我是否需要防御性复制来构建一个带有非最终字段的不可变类



如果想要构建一个不可变的类,就不应该公开对非最终字段的引用,但即使是对于像Strings这样的不可变对象?

public final class Test { // Test class is meant to be immutable
    private String s; // CAN'T MAKE THIS FINAL
    void onCreate(String s) { // a callback called ONCE after construction
        this.s = new String(s); // do I need to do this ? (protect me from me)
    }
    public String getS() {
        return new String(s); //do I need to do this ?(protect me from the world)
    }
}

理论上,通过不安全的发布可以看到具有未初始化(nullsTest类的实例,也可以看到具有正确初始化的s的实例。这可以通过使s成为volatile来解决。

然而,如果你遇到了这样的回调,我想你应该重新审视一下你的设计。

如果你要使类Serializable,那么你会遇到更多的问题。

这个类是否是不可变的并不重要(对于任何不可变的定义)。特别地,引用s是否被改变为指向不同的字符串并不重要。字符串对象是不可变的,因此您不需要复制它。如果没有防御性复制,getS的调用方将获得对Test的方法和getS的其他调用方使用的相同字符串对象的引用。这并不重要,因为他们对这个字符串所做的任何事情都不会影响其他引用。那将是浪费时间和记忆。

1我忽略了反射。像这样恶意使用反射的代码几乎可以破坏任何东西,而且不是偶然或难以发现的。担心这个案子根本不现实。

我认为没有必要。甚至在文件中说:

字符串是常量;它们的值在创建。因为字符串对象是不可变的,它们可以共享。

因此,一旦创建了String对象,它的值就永远不会改变。如果我们想"更改"变量的值,就会创建一个新的String对象。例如在toUpperCase方法中,原始字符串保持不变,但会创建新的副本。

编辑:

当考虑字符串时,文字被放入一个共享池中,这意味着:

String h = "HELLO";
String h1 = "HELLO";

CCD_ 13和CCD_。

您可以尝试以下代码返回true:

String h = "HELLO";
String h1 = "HELLO";
boolean r = (h==h1);
System.out.println(r);

但是,您可以使用反射更改String的值:

java.lang.reflect.Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
valueField.set("Original", "Modified".toCharArray()); 

从技术上讲,如果你真的想要一个Java中的不可变类,你必须确保类的实例在创建后不会被更改。因此,它的所有字段都可以是最终字段,例如,如果它们通过getter"暴露"给世界,这些字段本身必须是不可变的(就像字符串一样),或者不返回到外部世界(保持私有并在getter中创建它们的防御副本),因此原始字段值保持不变。这种不变性决不能因为从这个类继承而被破坏。

你可以在乔舒亚·布洛赫的《高效Java》一书中阅读更多关于它的内容,或者从互联网上做一些笔记,比如在这里。

关于你最近对帖子的更新,这里有一个建议,可以确保初始化只进行一次:

private String s; // CAN'T MAKE THIS FINAL
private boolean stringWasSet = false;
public void onCreate(String s) { // a callback called ONCE after construction
    if (!stringWasSet) {
        this.s = s; // No need for defensive copy here, if the variable itself is immutable, like String
        stringWasSet = true;
    }
}
public String getS() {
    return s; // No need for defensive copy here, if the variable itself is immutable, like String
}

最新更新