【问题标题】:Do you need to override hashCode() and equals() for records?您是否需要为记录覆盖 hashCode() 和 equals()?
【发布时间】:2020-08-26 09:31:52
【问题描述】:

假设如下示例:

public record SomeRecord(int foo, byte bar, long baz)
{ }

如果我要将所述对象添加到HashMap,是否需要覆盖hashCodeequals

【问题讨论】:

  • 不,我相当肯定编译器会为您做到这一点。见这篇文章:aboullaite.me/java-14-records。 javap 揭示了 equals、hashCode、toString、getter 和 setter 都是为你而设计的。太棒了
  • 这是可能的,因为在某些极端情况下您需要这样做,但总的来说,您不需要——而且可能不想这样做。如果您这样做,请确保实现java.lang.Record 中概述的equals()hashCode() 的精炼语义。
  • @BrianGoetz 当你在这里时,是否有理由让像 record 这样大声尖叫“值类型”的东西获得一个公共构造函数,比如 IntegerDouble 的构造函数等刚刚被标记为已弃用,而不是像valueOf(…)?那样允许由JRE自行决定共享的工厂方法
  • @Holger 是的,但我怀疑这是一个更长的对话。你能把它带到 OpenJDK amber-dev 列表吗?

标签: java java-14 java-record


【解决方案1】:

不,您不需要定义自己的hashCodeequals。如果您希望覆盖默认实现,您可以这样做。

详见规范第 8.10.3 节https://docs.oracle.com/javase/specs/jls/se14/preview/specs/records-jls.html#jls-8.10

请注意,具体来说,实现您自己的版本的注意事项:

从 java.lang.Record 继承的所有成员。除非明确 在记录体中被覆盖,R 已经隐式声明了以下方法 覆盖 equals、hashCode 和 toString 方法 java.lang.Record.

这些来自 java.lang.Record 的方法是否应该明确 在记录正文中声明,实现应满足 java.lang.Record 中指定的预期语义。

特别是,自定义equals 实现必须满足记录副本必须等于记录的预期语义。这通常不适用于类(例如,两个 Car 对象可能是 equals,如果它们的 VIN 值相同,即使 owner 字段不同)但对于记录必须是正确的。这个限制意味着几乎没有理由覆盖equals

【讨论】:

  • 计算所有记录组件的hashCode。
  • @sc0der 从技术上讲,Record 如何实现 hashCode 是留给实现的细节(记住 Java 是一种语言规范)。但是在实践中,我希望实现者通常只会散列所有成员。在集合的实现中处理碰撞。 HashMap 的 openjdk 实现有一个相当复杂的处理,它会根据冲突的数量改变结构。
  • 查看Record.hashCode()的javadoc
  • hashCode() 的有效(但愚蠢)实现是 return 1;。它遵守 Object.hashCode() 和 Record.hashCode() 的约定。 (有一个标志使得 System.identityHashCode(Object) 总是返回 1:-XX:hashCode=2
  • 另见 Javadoc 上的注释以供记录: 实现要求:隐式提供的实现返回一个哈希码值,该哈希码值是根据 Object.hashCode() 为所有组件组合哈希码值得出的类型为引用类型的组件,或类型为原始类型的组件的原始包装哈希码。
【解决方案2】:

您是否需要它的答案确实是 - 这取决于您决定创建为Record 的实体的实现。在编译或运行时也没有约束你这样做的限制,而且对于扩展 Object 的类总是如此。

另一方面,主要的motivations for the proposal 之一是“低价值、重复、容易出错的代码:构造函数、访问器、equals()hashCode()toString() 等”。在数据载体中,这在当今的 Java 编程中很常见。因此,进一步说明的决定是更喜欢语义目标和

...:将数据建模为数据。(如果 语义是正确的,样板将自行处理。)它 声明浅不可变应该简单、清晰、简洁, 表现良好的名义数据聚合。

尾巴

因此,样板文件已得到处理,但请注意,出于某种原因,您可能仍希望您的记录组件之一不被视为两个不同对象之间比较过程的一部分,这就是您可能需要的地方想要覆盖equalshashCode 提供的默认实现。此外,毫无疑问,我认为有时需要 toString 的幻想,因此也需要覆盖它。

上述情况大多不能归类为编译或运行时失败,但提案本身读取了它带来的风险:

从状态自动派生的任何成员 description 也可以显式声明。然而,一不小心 实现 accessors 或 equals/hashCode 可能会破坏 记录的语义不变量。

(注意:后者主要是我的意见,这样消费者会希望有各种灵活性,以便他们可以使用最新的功能,但在某种程度上,现有的实现曾经可以工作。你看,backward compatibility 在升级过程中也很重要。)

【讨论】:

    【解决方案3】:

    什么是 Java 记录? 关于 Java 最常见的抱怨之一是您需要编写大量代码才能使一个类成为 有用。很多时候您需要编写以下内容:

    1. toString()
    2. hashCode()
    3. 等于()
    4. Getter 方法
    5. 公共构造函数

    对于简单的领域类,这些方法通常很无聊, 重复的,以及很容易产生的那种东西 机械地(并且 IDE 通常提供此功能),但截至目前, 语言本身并没有提供任何方法来做到这一点。

    记录的目标是扩展 Java 语言语法并创建一个 可以说一个类是“字段,只是字段,什么都没有 但田野。”通过你对一个班级做出这样的陈述, 编译器可以通过自动创建所有方法并拥有 所有字段都参与hashCode()等方法。

    记录带有一个记录内所有属性的hashCode()equals()toString() 的默认实现

    hashCode()的默认实现

    记录将使用记录内所有属性的哈希码

    默认实现 equals()

    记录将使用所有属性来决定两个记录是否相等

    所以任何哈希实现,例如HashSet、LinkedHashSet、HashMap、LinkedHashMap、

    etc 将使用hashCode(),如果发生任何冲突,将使用equals()

    默认实现还是自定义实现?

    如果您想使用hashCode()equals() 中的所有属性,请不要覆盖

    是否需要为记录重写 hashCode() 和 equals()?

    由您决定保留默认实现或仅选择一些属性

    任何东西,但如果你想要自定义属性,你可以覆盖来决定哪个 属性决定相等,属性决定hashCode

    默认实现中hashCode是如何计算的?

    将像这样使用整数和字符串的 hashCode:

        int hashCode = 1 * 31;
        hashCode = (hashCode + "a".hashCode()) & 0x7fffffff;
    

    ,下面的代码带有hashCode()equals()默认实现

    toString()

    HashSet 没有添加它们两个,因为这两个记录具有相同的 hashCode 和 equals

        static record Record(int id, String name) {
    
        }
    
        public static void main(String[] args) {
            Record r1 = new Record(1, "a");
            Record r2 = new Record(1, "a");
    
            Set<Record> set = new HashSet<>();
            set.add(r1);
            set.add(r2);
            System.out.println(set);
    
            System.out.println("Hashcode for record1: " + r1.hashCode());
            System.out.println("Hashcode for record2: " + r2.hashCode());
    
            int hashCode = 1 * 31;
            hashCode = (hashCode + "a".hashCode()) & 0x7fffffff;
            System.out.println("The hashCode: " + hashCode);
        }
    

    ,输出

    [Record[id=1, name=a]]
    Hashcode for record1: 128
    Hashcode for record2: 128
    The hashCode: 128
    

    ,资源:

    blogs oracle

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-06-02
      • 1970-01-01
      • 2019-01-22
      • 1970-01-01
      相关资源
      最近更新 更多