我正在寻求一些基于价值类的定义的澄清。我无法想象,最后一个子弹点(6)应该与第一个
一起使用- (1)它们是最终且不可变的(,尽管可能包含对可变对象的引用)
- (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;
}
我知道这很奇怪。但是我想,它有资格为"任何计算或方法调用",不是吗?
当等于时,它们可以自由替换,这意味着在任何计算或方法中,根据Equals()相等的两个实例x和y的互换不应产生可见的行为变化
一旦执行b.get().add("a");
,a
不再是equals
到b
,因此您没有理由期望assertTrue(a.get().isEmpty());
,并且assertTrue(b.get().isEmpty());
会产生相同的结果。
基于价值的类是不可变的事实,并不意味着您不能在此类类的实例中突变存储的值(如though may contain references to mutable objects
中所述)。这仅意味着一旦您使用Optional a = Optional.of(new ArrayList<String>())
创建Optional
实例,就无法突变a
以保持对其他ArrayList
的参考。
您可以得出您所指的规范中的操作无效:
程序如果试图将两个引用与基于价值的类别的相等值区分开,无论是直接通过参考平等还是通过吸引同步,身份散列,序列化,或其他任何其他任何其他任何其他方式,它都可能产生不可预测的结果。身份敏感机制。在基于价值类的实例上使用这种身份敏感的操作可能会产生不可预测的影响,因此应避免。
(强调矿山)
修改对象是 一个身份敏感的操作,因为它仅用您用于修改的参考表示的特定身份影响对象。
当您调用x.get().add("");
时,您正在执行一个操作,该操作允许识别x
和y
是否表示同一实例,换句话说,您正在执行一个身份敏感操作。
仍然,我希望,如果未来的JVM真正尝试替代基于价值的实例,则必须排除参考可变对象的实例,以确保兼容性。如果执行产生Optional
的操作,然后提取Optional
,例如… stream. findAny().get()
,如果允许中间操作用另一个在中间Optional
使用的对象替代元素的元素(如果元素本身不是值类型)…
我认为一个更有趣的例子如下:
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
是最终的(不能分类),不可变化,并且a
和b
都请参考同一列表,因此a.equals(b)
Will Glowers 是正确的。(嗯,几乎总是,要在列表进行比较时要修改列表的种族条件。)因此,这似乎是JVM可以将b
替换为a
或ViceCICE的情况。-versa。
作为今天的事物(Java 8和9和10),我们可以写a == b
,结果将是错误的。原因是我们知道Optional
是普通参考类型的实例,并且当前实现事物的方式,Optional.of(x)
将始终返回新实例,并且两个新实例永远不会彼此相互 ==
。
但是,基于价值类的底部定义的段落说:
一个程序如果试图将两个引用与基于价值的类别的相等值区分开,无论是直接通过参考平等还是通过吸引同步,身份散列,序列化,或任何其他身份敏感性,它可能会产生不可预测的结果机制。在基于价值类的实例上使用这种身份敏感的操作可能会产生不可预测的影响,因此应避免。
换句话说,"不要那样做",或者至少,不要依靠结果。原因是明天 ==
操作的语义可能会改变。在假设的未来价值类型的世界中,可以重新定义==
的价值类型与equals
相同,而Optional
可能会从基于价值的类变为值类型。如果发生这种情况,则a == b
将是正确的。
关于价值类型的主要思想之一是,它们没有身份的概念(或者也许他们的身份无法检测到Java程序)。在这样的世界中,我们怎么能分辨a
和b
"真的"是相同或不同的?
假设我们要通过某些方式来启动bar
方法(例如调试器),以便我们可以通过无法通过编程语言来检查参数值的属性,例如通过查看机器地址。即使a == b
为True(请记住,在一个价值的世界中,==
与equals
相同),我们也许可以确定a
和b
位于内存中的不同地址。
现在假设JIT编译器编译foo
并嵌入到Optional.of
的调用。看到现在有两个代码返回两个始终equals
的结果,编译器消除了其中一个块,然后在使用a
或b
的任何地方使用相同的结果。现在,在我们的bar
仪器版本中,我们可能会观察到两个参数值相同。由于第六个项目符号项目,允许JIT编译器执行此操作,该项目允许替换为equals
。
请注意,我们只能观察到这种差异,因为我们使用了语言外机制,例如调试器。在Java编程语言中,我们根本无法分辨出差异,因此这种替代不会影响任何Java程序的结果。这使JVM可以选择其认为合适的任何实施策略。JVM可以自由在堆上,堆栈上的 a
和 b
分配,每个堆栈上的一个,一个不同的实例,或者是相同的实例,只要Java程序无法说明差异。当授予JVM的实施自由选择时,它可以使程序变得更快。
这就是第六个项目符号项目的重点。
执行行:
Optional a = Optional.of(new ArrayList<String>());
Optional b = Optional.of(new ArrayList<String>());
assertEquals(a, b); // passes as `equals` delegated to the lists
根据API:
- 将检查参数
a
和b
是否可选 - 项目都不存在,或者,
- 现在的值通过equals()(在您的示例中,这个等式是来自arraylist的一个)。
因此,当您更改可选实例指向的阵列列表之一时,断言将在第三点失败。
点6说如果a&amp;b是平等的,那么它们可以互换使用,即说一种方法是否期望A类A类,并且您创建了A&amp; b实例,则如果A&amp; amp;b传递点6您可以发送(a,a) or (b,b) or (a,b)
这三个将给出相同的输出。