基于值的类混淆更新

2022-09-03 10:11:33

我正在寻求对基于值的类的定义进行一些澄清。我无法想象,最后一个要点(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;
}

这是奇怪的方法,我知道。但我想,它有资格被称为“任何计算或方法调用”,不是吗?


答案 1

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

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

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


答案 2

您可以从所引用的规范中推断出操作的无效性:

如果程序尝试区分对基于值的类的相等值的两个引用,则可能会产生不可预知的结果,无论是直接通过引用相等还是通过对同步、标识哈希、序列化或任何其他标识敏感机制的调用间接区分。在基于值的类的实例上使用此类标识敏感操作可能会产生不可预知的影响,应避免使用。

(强调我的)

修改对象一种标识敏感操作,因为它仅影响具有由用于修改的引用所表示的特定标识的对象。

调用时,您正在执行一个允许识别和表示同一实例的操作,换句话说,您正在执行标识敏感操作。x.get().add("");xy

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