有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java我是否需要构造一个带有*非final*但不可变字段的不可变类?

如果想要构造一个不可变的类,就不应该公开对非final字段的引用,但即使对于字符串之类的不可变对象也是如此

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) 个答案

  1. # 1 楼答案

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

    然而,如果您遇到了这样的回调,我想您应该再看看您的设计

    如果要使类Serializable,则会有更多的问题

  2. # 2 楼答案

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

    你可以在约书亚·布洛赫(Joshua Bloch)的一本书《高效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. # 3 楼答案

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

    1我忽略了反射。恶意使用这种反射的代码几乎可以破坏任何东西,并且不是偶然编写的,也不是难以发现的。担心这个案子一点也不现实

  4. # 4 楼答案

    我认为没有必要。即使在文件中也提到:

    Strings are constant; their values cannot be changed after they are created. Because String objects are immutable they can be shared.

    因此,一旦创建了字符串对象,它的值就永远不会更改。如果我们想“更改”变量的值,将创建一个新的字符串对象。例如在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());