我是否需要使用* non final *尽管不可变字段构建不可变类的防御性复制?

时间:2013-11-30 15:09:50

标签: java security immutability encapsulation defensive-copy

如果想要构造一个不可变的类,那么不应该将引用暴露给非final字段 - 但是对于像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)
    }
}

4 个答案:

答案 0 :(得分:2)

理论上,通过不安全的发布可以查看Test类的实例,其中包含未初始化的null} s,也可以通过正确初始化{{1 }}。这可以通过制作s s来解决。

但是,如果你发生了类似的回调,我想你想再看一下你的设计。

如果你要上课volatile那么你还会遇到更多问题。

答案 1 :(得分:1)

我认为没必要。即使在文档中说:

  

字符串是不变的;他们的价值观无法改变   创建。因为String   对象是不可变的,可以共享。

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

修改

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

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

s1s2都指向同一个对象。

您可以尝试以下代码返回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()); 

答案 2 :(得分:1)

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

你可以在有效的Java中阅读更多关于它的信息 - 约书亚布洛赫的书,或者从互联网上做一些笔记,比如from here

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

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
}

答案 3 :(得分:1)

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

1 我忽略了反思。恶意使用这样的反射的代码几乎可以破坏任何东西,并且不是偶然写入或难以发现的。担心这种情况甚至不太可行。