0

如果一个人想要构造一个不可变的类,不应该公开对非 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

4 回答 4

2

理论上,通过不安全的发布可以看到Test带有未初始化 ( null)的类的实例,s也可以通过正确初始化的s. 这可以通过制作来解决s volatile

但是,如果你有一些这样的回调发生,我想你想再看看你的设计。

如果你要上课,Serializable那么你会有更多的问题。

于 2013-12-02T04:43:12.527 回答
1

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

字符串是常量;它们的值在创建后无法更改。因为 String 对象是不可变的,所以它们可以被共享。

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

编辑:

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

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

两者都s1引用s2同一个对象。

您可以尝试以下代码返回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()); 
于 2013-11-30T15:23:56.917 回答
1

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

您可以在 Effective Java(Joshua Bloch 的一本书)中阅读有关它的更多信息,或者从 Internet 上做一些笔记,比如从这里

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

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
}
于 2013-11-30T15:56:29.613 回答
1

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

1我忽略了反思。像这样恶意使用反射的代码几乎可以破坏任何东西,而且不是偶然编写的,也不是很难发现的。担心这种情况甚至是不切实际的。

于 2013-11-30T16:52:50.113 回答