基于价值的 类 困惑
Value-based Classes confusion
我正在寻求对 definition of Value-based Classes 的一些澄清。我无法想象,最后一个要点 (6) 应该如何与第一个一起使用
- (1) 它们是最终的且不可变的(尽管可能包含对可变对象的引用)
- (6) 它们在相等时 可自由替换 ,这意味着在任何计算或方法调用中根据 equals() 交换任何两个相等的实例 x 和 y 应该产生行为没有明显变化。
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
就不再是equals
到b
,所以你没有理由期望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 :
- 将检查参数
a
和 b
是否都是可选的
- 物品都没有价值,或者,
- 当前值彼此 "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("");
时,您正在执行一个操作,允许识别 x
和 y
是否代表相同的实例,换句话说,您正在执行身份敏感操作。
不过,我希望如果未来的 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),不可变的,并且 a
和 b
都引用同一个列表,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 程序检测到)。在这样的世界里,我们如何判断a
和b
"really"是相同还是不同?
假设我们要通过某种方式(比如调试器)检测 bar
方法,这样我们就可以通过编程语言无法实现的方式检查参数值的属性,例如通过查看机器地址。即使 a == b
为真(请记住,在值类型的世界中,==
与 equals
相同)我们也可以确定 a
和 b
驻留在内存中的不同地址。
现在假设 JIT 编译器编译 foo
并将调用内联到 Optional.of
。看到现在有两个代码块 return 两个结果总是 equals
,编译器删除了其中一个代码块,然后在 a
或 b
的任何地方使用相同的结果] 用来。现在,在 bar
的检测版本中,我们可能会观察到两个参数值相同。由于第六个项目符号项允许 JIT 编译器执行此操作,它允许替换 equals
.
的值
请注意,我们之所以能够观察到这种差异,是因为我们使用了一种额外的语言机制,例如调试器。在 Java 编程语言中,我们根本无法区分,因此这种替换不会影响任何 Java 程序的结果。这让 JVM 选择它认为合适的任何实现策略。 JVM 可以在堆上、堆栈上自由分配 a
和 b
,每个分配一个,作为不同的实例,或作为相同的实例,只要 Java 程序可以'不要说出区别。当 JVM 被授予实现选择的自由时,它可以使程序运行得更快很多。
这就是第六个项目符号的重点。
我正在寻求对 definition of Value-based Classes 的一些澄清。我无法想象,最后一个要点 (6) 应该如何与第一个一起使用
- (1) 它们是最终的且不可变的(尽管可能包含对可变对象的引用)
- (6) 它们在相等时 可自由替换 ,这意味着在任何计算或方法调用中根据 equals() 交换任何两个相等的实例 x 和 y 应该产生行为没有明显变化。
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
就不再是equals
到b
,所以你没有理由期望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 :
- 将检查参数
a
和b
是否都是可选的 - 物品都没有价值,或者,
- 当前值彼此 "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("");
时,您正在执行一个操作,允许识别 x
和 y
是否代表相同的实例,换句话说,您正在执行身份敏感操作。
不过,我希望如果未来的 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),不可变的,并且 a
和 b
都引用同一个列表,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 程序检测到)。在这样的世界里,我们如何判断a
和b
"really"是相同还是不同?
假设我们要通过某种方式(比如调试器)检测 bar
方法,这样我们就可以通过编程语言无法实现的方式检查参数值的属性,例如通过查看机器地址。即使 a == b
为真(请记住,在值类型的世界中,==
与 equals
相同)我们也可以确定 a
和 b
驻留在内存中的不同地址。
现在假设 JIT 编译器编译 foo
并将调用内联到 Optional.of
。看到现在有两个代码块 return 两个结果总是 equals
,编译器删除了其中一个代码块,然后在 a
或 b
的任何地方使用相同的结果] 用来。现在,在 bar
的检测版本中,我们可能会观察到两个参数值相同。由于第六个项目符号项允许 JIT 编译器执行此操作,它允许替换 equals
.
请注意,我们之所以能够观察到这种差异,是因为我们使用了一种额外的语言机制,例如调试器。在 Java 编程语言中,我们根本无法区分,因此这种替换不会影响任何 Java 程序的结果。这让 JVM 选择它认为合适的任何实现策略。 JVM 可以在堆上、堆栈上自由分配 a
和 b
,每个分配一个,作为不同的实例,或作为相同的实例,只要 Java 程序可以'不要说出区别。当 JVM 被授予实现选择的自由时,它可以使程序运行得更快很多。
这就是第六个项目符号的重点。