Java 14中Record文档中“shallowly immutable”的含义是什么?

30
我正在阅读Records的文档,不理解术语“shallowly immutable”的含义。我们所说的“shallowly immutable”是什么意思?如果它是不可变的,为什么我们需要一个复制构造函数?为什么有两个“Hello Worlds!”?
对于所有记录类,以下不变量必须成立:如果记录R的组件是c1、c2、……cn,则如果记录实例按以下方式复制:
 R copy = new R(r.c1(), r.c2(), ..., r.cn());  // copy constructor ?

那么必须成立的是 r.equals(copy)

我已经为Java Records实现了检测深度不可变性。高级别内容在此StackOverflow答案中得到了捕获(甚至更详细的处理在其中提到的开源项目中):https://dev59.com/IloU5IYBdhLWcg3wzZLw#75043881 - chaotic3quilibrium
2个回答

34

浅不可变指,如果一个类有字段,那么这些字段被视为final。但是,它们的字段(即字段的字段)不需要是final

您不需要实现构造函数,这已经为您实现了。但是,如果您选择自己实现它,例如进行参数验证,则应保持此不变性。


5
“浅不可变性”的一个例子可能是一个记录实例,它有一个ArrayList实例作为其字段。虽然该记录字段不能引用任何其他ArrayList列表实例,但列表的元素仍然可以改变(由于代码持有对包含在记录中的该列表实例的引用)。因为列表实例仍然是可变的,尽管现在它被包含在一个不可变的记录实例中 - 因此,您只能从记录中获得“浅度不可变性”。 - Ivo Mori
1
@IvoMori 是的,那是一个很好的例子,谢谢。另一个与记录无关的例子是调用 Collections.unmodifiableList:您不能更改列表,但如果它们不是不可变的,则仍然可以更改元素。 - Alex R
顺便提一下,记录文档中使用“值”这个词表明记录组件本身应该是不可变的——否则它们就不会是“值”。特别是当“Object.equals(Object)、Object.hashCode()和Object.toString()方法的隐式声明源自所有组件字段”时,您很可能不希望这些方法依赖于可变数据。 - Ivo Mori
2
@IvoMori 我严重怀疑这是真的,因为这会极大地限制它们的实用性。将这些方法依赖于可变数据并没有什么问题。你只是不能在需要深度不可变性的上下文中使用记录类型(例如作为 Map 键)。我认为“值”一词只是指引用,而不是其背后的对象。 - HTNW
@HTNW 有道理,我在那个上下文中错误地过分解读了“value”这个词的使用。感谢您指出来。 - Ivo Mori

15

如果您将一个类视为其他类和基元(int、数组等)的组合或层次结构,则浅不可变性是仅涉及第一级的不可变性(常数性)。

这与术语“深不可变性”形成对比,后者指整个层次结构的不可变性。大多数关于不可变性的实际好处,例如隐式线程安全,仅适用于深度不可变的对象。

考虑这个类

class Foo {
    private final MutableBar bar;

    //ctor, getter
}

这个类是浅不可变的。它不能直接更改,但可以间接更改,例如

foo.getBar().setSomeProperty(5);

因此它并不是完全不可变的。

另一个只使用基本类型的浅不可变性示例

class Foo {
    private final int[] ints;

    Foo(int[] ints) {
        this.ints = ints;
    }
}

这个可以像这样改变

int[] ints = {1};
Foo foo = new Foo(ints);
ints[0] = 2;

对于一个小的层次结构,有时将浅不可变类转换为深度不可变类很简单。通常涉及到防御性拷贝,或将可变类切换为不可变变体。

class Foo {
    private final int[] ints; 

    Foo(int[] ints) {
        // copy to protect against the kind of mutation shown above
        this.ints = Arrays.copyOf(ints, ints.length);
    }

    // if you must have a getter for an array, make sure not to return the array itself, 
    // otherwise the caller can change it.
    // for performance reasons, consider an immutable List instead - no copy required
    int[] getInts() {
        return Arrays.copyOf(ints, ints.length);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接