基于价值的 类 困惑

Value-based Classes confusion

我正在寻求对 definition of Value-based Classes 的一些澄清。我无法想象,最后一个要点 (6) 应该如何与第一个一起使用

Optional就是这样一个class.

Optional a = Optional.of(new ArrayList<String>());
Optional b = Optional.of(new ArrayList<String>());
assertEquals(a, b); // passes as `equals` delegated to the lists

b.get().add("a");

// now bite the last bullet
assertTrue(a.get().isEmpty()); // passes
assertTrue(b.get().isEmpty()); // throws

我是不是读错了,还是需要更精确一些?

更新

Eran 的回答很有道理(他们不再相等),但让我移动目标:

...
assertEquals(a, b); // now, they are still equal
assertEquals(m(a, b), m(a, a)); // this will throw
assertEquals(a, b); // now, they are equal, too

让我们定义一个有趣的方法m,它会进行一些修改并再次撤消:

int m(Optional<ArrayList<String>> x, Optional<ArrayList<String>> y) {
    x.get().add("");
    int result = x.get().size() + y.get().size();
    x.get().remove(x.get().size() - 1);
    return result;
}

我知道这是一种奇怪的方法。但我想,它符合 "any computation or method invocation",不是吗?

they are freely substitutable when equal, meaning that interchanging any two instances x and y that are equal according to equals() in any computation or method invocation should produce no visible change in behavior

一旦执行了b.get().add("a");a就不再是equalsb,所以你没有理由期望assertTrue(a.get().isEmpty());assertTrue(b.get().isEmpty()); 会产生相同的结果。

基于 class 的值是不可变的这一事实并不意味着您不能改变存储在此类 class 实例中的值(如 though may contain references to mutable objects 中所述) .这只意味着一旦你用 Optional a = Optional.of(new ArrayList<String>()) 创建了一个 Optional 实例,你就不能改变 a 来保存对不同 ArrayList.

的引用

第 6 点说,如果 a 和 b 相等,那么它们可以互换使用,也就是说,如果一个方法需要 Class A 的两个实例,并且您已经创建了 a&b 实例,那么如果 a 和 b 通过了第 6 点,您可以发送 (a,a) or (b,b) or (a,b) 所有三个都会给出相同的输出。

当您执行以下行时:

Optional a = Optional.of(new ArrayList<String>());
Optional b = Optional.of(new ArrayList<String>());
assertEquals(a, b); // passes as `equals` delegated to the lists

在 assertEquals(a, b) 中,according to the API :

  1. 将检查参数 ab 是否都是可选的
  2. 物品都没有价值,或者,
  3. 当前值彼此 "equal to" 通过 equals() (在你的例子中,这个等于来自 ArrayList 的那个)。

因此,当您更改 Optional 实例指向的 ArrayList 之一时,断言将在第三点失败。

您可以从您所指的规范中得出您的操作的无效性:

A program may produce unpredictable results if it attempts to distinguish two references to equal values of a value-based class, whether directly via reference equality or indirectly via an appeal to synchronization, identity hashing, serialization, or any other identity-sensitive mechanism. Use of such identity-sensitive operations on instances of value-based classes may have unpredictable effects and should be avoided.

(强调我的)

修改对象身份敏感操作,因为它只影响具有您用于修改的引用所表示的特定身份的对象。

当您调用 x.get().add(""); 时,您正在执行一个操作,允许识别 xy 是否代表相同的实例,换句话说,您正在执行身份敏感操作。

不过,我希望如果未来的 JVM 真正尝试替代基于值的实例,它必须排除引用可变对象的实例,以确保兼容性。如果您执行生成 Optional 的操作,然后提取 Optional,例如… stream. findAny().get(),如果中间操作允许用在中间 Optional 使用点恰好相等的另一个对象替换元素,它将是 disastrous/unacceptable(如果元素不是它本身值类型)…

我觉得比较有意思的例子如下:

void foo() {
    List<String> list = new ArrayList<>();
    Optional<List<String>> a = Optional.of(list);
    Optional<List<String>> b = Optional.of(list);
    bar(a, b);
}

很明显a.equals(b)是真的。此外,由于 Optional 是最终的(不能被 subclassed),不可变的,并且 ab 都引用同一个列表,a.equals(b)总是为真。 (嗯,几乎总是,受制于竞争条件,即另一个线程正在修改列表,而这个线程正在比较它们。)因此,这似乎是 JVM 可以替代 b 的情况对于 a 或反之亦然。

按照今天(Java 8 和 9 和 10)的情况,我们可以写成 a == b,结果将为假。原因是我们知道Optional是一个普通引用类型的实例,而事情目前的实现方式,Optional.of(x)总是会return一个新的实例,两个新的实例是从不 == 彼此。

但是,value-based classes 定义底部的段落说:

A program may produce unpredictable results if it attempts to distinguish two references to equal values of a value-based class, whether directly via reference equality or indirectly via an appeal to synchronization, identity hashing, serialization, or any other identity-sensitive mechanism. Use of such identity-sensitive operations on instances of value-based classes may have unpredictable effects and should be avoided.

换句话说,"don't do that," 或者至少不要依赖结果。原因是 明天 == 操作的语义可能会改变。在假设的未来值类型世界中,== 可能会被重新定义为与 equals 相同的值类型,并且 Optional 可能会从基于值的 class 改变成为一种价值类型。如果发生这种情况,那么 a == b 将是 true 而不是 false。

关于值类型的一个主要思想是它们没有身份的概念(或者它们的身份可能无法被 Java 程序检测到)。在这样的世界里,我们如何判断ab"really"是相同还是不同?

假设我们要通过某种方式(比如调试器)检测 bar 方法,这样我们就可以通过编程语言无法实现的方式检查参数值的属性,例如通过查看机器地址。即使 a == b 为真(请记住,在值类型的世界中,==equals 相同)我们也可以确定 ab 驻留在内存中的不同地址。

现在假设 JIT 编译器编译 foo 并将调用内联到 Optional.of。看到现在有两个代码块 return 两个结果总是 equals,编译器删除了其中一个代码块,然后在 ab 的任何地方使用相同的结果] 用来。现在,在 bar 的检测版本中,我们可能会观察到两个参数值相同。由于第六个项目符号项允许 JIT 编译器执行此操作,它允许替换 equals.

的值

请注意,我们之所以能够观察到这种差异,是因为我们使用了一种额外的语言机制,例如调试器。在 Java 编程语言中,我们根本无法区分,因此这种替换不会影响任何 Java 程序的结果。这让 JVM 选择它认为合适的任何实现策略。 JVM 可以在堆上、堆栈上自由分配 ab,每个分配一个,作为不同的实例,或作为相同的实例,只要 Java 程序可以'不要说出区别。当 JVM 被授予实现选择的自由时,它可以使程序运行得更快很多

这就是第六个项目符号的重点。