简体   繁体   English

我是否需要防御性的复制,以* non final *构建一个不可变的类,尽管它是不可变的字段?

[英]Do I need defensive copying constructing an immutable class with *non final* albeit immutable fields?

One should not expose the reference to a non final field if one wants to construct an immutable class - but even for immutable objects like Strings ? 如果要构造一个不可变的类,则不应将引用公开给非最终字段-甚至对于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)
    }
}

In theory it is possible through unsafe publication to see an instance of the Test class with an uninitialised ( null ) s which can also be seen with a correctly initialised s . 从理论上讲,通过不安全的发布,可以看到带有未初始化( nulls Test类的实例,也可以通过正确初始化的s看到该实例。 This is fixable by making s volatile . 这可以通过使s volatile

However, if you've got some callback happening like that, I think you want to take another look at your design. 但是,如果您发生了这样的回调,我想您想再看一下您的设计。

If you were to make the class Serializable then you'd have many more problems. 如果您要使类可Serializable那么您将遇到更多问题。

I don't think it's necessary. 我认为没有必要。 Even in the documentation is said: 甚至在文档中说:

Strings are constant; 字符串是常量; their values cannot be changed after they are created. 它们的值创建后无法更改。 Because String objects are immutable they can be shared. 由于String对象是不可变的,因此可以共享它们。

So once a String object is created, its value is never changed. 因此,一旦创建了String对象,其值就永远不会改变。 If we want to "change" the value of variable a new String object is created. 如果要“更改”变量的值,则会创建一个新的String对象。 Such as in the toUpperCase method the original string is unchanged, but a new copy is created. 例如在toUpperCase方法中,原始字符串不变,但是创建了一个新副本。

EDIT: 编辑:

And when considering strings, literals are put into a shared pool, which means that: 考虑字符串时,将文字放入共享池中,这意味着:

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

both s1 and s2 refer to the same object. s1s2引用相同的对象。

you can try that following code returns true : 您可以尝试以下代码返回true

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

However you could change the value of the String 's value using reflection: 但是,您可以使用反射更改String的值:

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

Technically, if you really want a immutable class in Java you have to make sure that an instance of your class can not be changed after it's created. 从技术上讲,如果您确实想要Java中的不可变类,则必须确保在创建类后不能更改该类的实例。 Therefore all its fields can be final and if they are "exposed" to the world via getters, for example, those fields must either be immutable themselves (as strings are) or not being returned to the outer world (kept private and creating defensive copies of them in getters), so the original field value stays the same. 因此,它的所有字段都可以是最终字段,例如,如果它们通过吸气剂“暴露”在世界上,那么这些字段本身必须是不可变的(就像字符串一样),或者必须不返回给外部世界(保留私有并创建防御性副本) (在getter中使用它们),因此原始字段值保持不变。 This immutability must not be prone to being broken by inheriting from this class as well. 同样,不可变性也不能轻易被此类继承而破坏。

You can read more about it in Effective Java - a book by Joshua Bloch, or take some notes from the internet, like from here . 您可以在《有效的Java》(约书亚·布洛赫(Joshua Bloch)的书)中阅读有关它的更多信息,或从Internet上获得一些注释,例如从此处

Regarding your recent update to the post, here's a suggestion that ensures the initialization was only made once: 关于您最近对帖子的更新,以下建议可确保初始化仅进行一次:

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
}

It does not matter whether this class is immutable (for any definition of immutable). 此类是否不可变(对于不可变的任何定义)都没有关系。 In particular, it does not matter if the reference s is ever changed to point to a different string. 特别是,引用s是否曾经更改为指向其他字符串都没有关系。 The string object is immutable, so you don't need to copy it. 字符串对象是不可变的,因此您无需复制它。 Without defensive copying, callers of getS will get references to the same string object used by Test 's methods and by other callers of getS . 如果没有防御性复制,则getS调用者将获得对Test方法和getS的其他调用者所使用的同一字符串对象的getS That does not matter because nothing 1 they do to this string will affect other referents. 没关系,因为它们对该字符串执行的任何操作1都不会影响其他引用对象。 It'd be a waste of time and memory. 这将浪费时间和内存。

1 I'm ignoring reflection. 1我忽略了反思。 Code that maliciously uses reflection like this can break almost anything, and is not written by accident or hard to spot. 像这样恶意使用反射的代码几乎可以破坏所有内容,并且不会偶然或难以发现。 It is not even remotely practical to worry about this case. 担心这种情况甚至是不切实际的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM