【问题标题】:Compatibility issues while converting Classes to Records将类转换为记录时的兼容性问题
【发布时间】:2020-07-02 22:28:29
【问题描述】:

我一直在使用以下名为City 的类

@ToString
@AllArgsConstructor
public class City {
    Integer id;
    String name;
}

并尝试将其转换为名为@9​​87654324@ 的record

record CityRecord(Integer id, String name) {} // much cleaner!

但是转向这样的表示,我们的一个单元测试开始失败。测试在内部处理从 JSON 文件读取的城市列表并映射到进一步计算城市的对象,同时将它们分组到 Map 下。简化为:

List<City> cities = List.of(
        new City(1, "one"),
        new City(2, "two"),
        new City(3, "three"),
        new City(2, "two"));
Map<City, Long> cityListMap = cities.stream()
        .collect(Collectors.groupingBy(Function.identity(),
                Collectors.counting()));

上面的代码断言 true 包含 4 个键,每个键占其出现的 1 个。使用记录表示,生成的Map 中的键不超过 3 个。是什么原因造成的,应该采取什么方法来解决这个问题?

【问题讨论】:

  • 附带说明,使用@lombok.EqualsAndHashCode public class City 也不能解决问题!学习——必须有单元测试!
  • Lombok 代码没有将 (Integer id, String name) 对建模为数据;它只是自动化一些语法样板。它缺少适当的 equals/hashCode 方法,因此它不会根据它们的状态考虑两个 (id, name) 等价物。记录确实如此。当然,这取决于你想要什么。在大多数情况下,Lombok 版本可能已“损坏”,但您没有说明您的要求,因此我们无法知道。

标签: java compatibility java-14 java-record


【解决方案1】:

原因

观察到的行为背后的原因如 java.lang.Record 中所述

对于所有记录类,必须满足以下不变量:如果记录 R的组件是c1,c2,...cn,那么如果一个记录实例被复制 如下:

 R copy = new R(r.c1(), r.c2(), ..., r.cn());   then it must be the case that r.equals(copy).

简而言之,您的CityRecord 类现在有一个equals(和哈希码)实现,它比较两个属性并确保它们是否相等,由这些组件组成的记录也相等。作为该评估的结果,具有相同属性的两个记录对象将被组合在一起。

因此,推断/断言应该有三个这样的键的结果是正确的,其中一个具有 id=2, name="two" 的键被计数两次。

立即补救

对此的直接临时解决方案是在您的记录表示中创建一个自定义(有缺陷 - 原因稍后解释)equals 实现。这看起来像:

record CityRecord(Integer id, String name) {

    // WARNING, BROKEN CODE
    // Does not adhere to contract of `Record::equals`
    @Override
    public boolean equals(Object o) {
        return this == o;
    }

    @Override
    public int hashCode() {
        return System.identityHashCode(this);
    }
}

现在比较将在两个对象之间进行,就像在使用现有的 City 类时一样,您的测试就可以正常工作了。但在使用任何此类补救措施之前,您必须注意以下注意事项。

注意

正如JEP-359 所说,记录更像是“数据载体”,在选择迁移现有类时,您必须了解记录自动获取的标准成员。 p>

计划迁移一个必须了解当前实施的完整细节,例如在您引用的示例中,当您按City 分组时,应该没有理由让两个城市具有相同的idname data 以不同的方式列出。它们应该相等,在所有重复 两次 之后应该是 相同的数据,因此计数正确。

在这种情况下,您现有的实现(如果表示数据模型)可以通过覆盖equals 实现来修正以匹配record 的方式,以考虑比较各个属性,这就是直接补救措施所述的地方以上是矛盾的,应该避免。

【讨论】:

  • 随时欢迎专家提供更好的答案。
  • 您不仅应该“永远不必这样做”,而且事实上,这会使您的代码损坏,因为它不遵守java.lang.Record 中的equals() 的精炼合约。如果您想要身份平等,那么您不需要记录。记录首先是语义工具,其次才是语法工具。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-05
  • 1970-01-01
  • 1970-01-01
  • 2010-11-13
相关资源
最近更新 更多