基于价值的课程混乱

时间:2017-11-28 09:44:33

标签: java java-8 immutability value-class

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

  • (1)它们是final和immutable(虽然可能包含对可变对象的引用
  • (6)当它们相等时,它们是可自由替换的,这意味着在任何计算或方法调用中根据equals()交换任意两个x和y实例应该不会产生明显的行为变化

Optional就是这样一个类。

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;
}
我知道,这是一种奇怪的方法。但我想,它符合&#34;任何计算或方法调用&#34;,不是吗?

5 个答案:

答案 0 :(得分:11)

  

它们可以自由替代当等于时,意味着在任何计算或方法调用中根据equals()交换任意两个实例x和y应该不会产生行为的可见变化

执行b.get().add("a");后,a不再是equalsb,因此您没有理由期待assertTrue(a.get().isEmpty());assertTrue(b.get().isEmpty());会产生相同的结果。

基于值的类是不可变的这一事实并不意味着您不能改变存储在这些类的实例中的值(如though may contain references to mutable objects中所述)。这仅表示使用Optional创建Optional a = Optional.of(new ArrayList<String>())实例后,您无法改变a以保留对其他ArrayList的引用。

答案 1 :(得分:7)

您可以从您所指的规范中推导出您的行为的无效性:

  

如果程序试图将两个引用区分为基于值的类的相等值,无论是直接通过引用相等还是间接通过引发同步,身份哈希,序列化或任何其他类型,程序可能会产生不可预测的结果身份敏感机制。对基于值的类的实例使用此类身份敏感操作可能会产生不可预测的影响,应该避免使用。

(强调我的)

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

当您呼叫x时,您正在执行允许识别yOptional是否代表同一实例的操作,换句话说,您正在执行身份敏感操作。

但是,我希望如果未来的JVM真正尝试替换基于值的实例,它必须排除引用可变对象的实例,以确保兼容性。如果您执行的操作产生Optional,然后提取… stream. findAny().get(),例如Optional,如果中间操作允许用另一个在中间{{1}}使用点碰巧相等的对象替换该元素,那将是灾难性的/不可接受的(如果该元素本身不是一个值型)...

答案 2 :(得分:3)

我认为一个更有趣的例子如下:

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是最终的(不能是子类),不可变的,ab都引用相同的列表,a.equals(b) 总是是真的。 (好吧,几乎总是,受到竞争条件的影响,另一个线程正在修改列表,而这个正在比较它们。)因此,这似乎是JVM可以替换b的情况。 a或反之亦然。

由于今天的情况(Java 8和9和10),我们可以编写a == b,结果将为false。原因是我们知道Optional是普通引用类型的实例,以及当前实现的方式,Optional.of(x)将始终返回一个新实例,并且两个新实例永远不会{{1相互之间。

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

  

如果程序试图将两个引用区分为基于值的类的相等值,无论是直接通过引用相等还是间接通过对同步,身份哈希,序列化或任何其他身份敏感的诉求,都可能产生不可预测的结果机制。对基于值的类的实例使用此类身份敏感操作可能会产生不可预测的影响,应该避免使用。

换句话说,“不要那样做”,或者至少不要依赖于结果。原因是明天 ==操作的语义可能会发生变化。在假设的未来值类型世界中,==可能会重新定义值类型与==相同,而equals可能会从基于值的类更改为值类型。如果发生这种情况,那么Optional将为真而不是假。

关于价值类型的一个主要观点是,他们没有身份概念(或者他们的身份可能无法被Java程序检测到)。在这样的世界中,我们如何判断a == ba“真的”是相同还是不同?

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

现在假设JIT编译器编译a并内联对b的调用。看到现在有两个代码返回两个始终为foo的结果,编译器会消除其中一个,然后在使用Optional.ofequals的地方使用相同的结果。现在,在a的检测版本中,我们可能会发现两个参数值是相同的。由于第六个项目允许替换b的值,因此允许JIT编译器执行此操作。

请注意,我们只能观察到这种差异,因为我们使用的是语言外机制,例如调试器。在Java编程语言中,我们根本无法区分,因此这种替换不会影响任何Java程序的结果。这使JVM可以选择它认为合适的任何实现策略。只要Java程序无法区分,JVM就可以自由地在堆上,堆栈上,每个上一个,作为不同的实例或相同的实例分配barequals。 。当JVM被授予实现自由选择权时,它可以使程序更快地批次

这是第六个项目的重点。

答案 3 :(得分:2)

执行以下行时:

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. 现值等于&#34;等于&#34;彼此通过 equals()(在您的示例中,此等于是ArrayList中的那个)。
  4. 因此,当您更改Optional实例所指向的ArrayList之一时,断言将在第三点失败。

答案 4 :(得分:1)

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