【问题标题】:Why doesn't Swift provide hashable for every class implicitly?为什么 Swift 不隐式地为每个类提供哈希值?
【发布时间】:2018-07-17 21:15:38
【问题描述】:

这很容易在 java 中完成 - 哈希码似乎是指向对象或其他东西的指针。为什么 swift 没有为我们提供同样的舒适,并要求我们自己定义函数?

【问题讨论】:

  • 默认的 Java 哈希函数是可怕的,你几乎不想使用它。
  • “哈希码似乎是指向对象或其他东西的指针”后者。

标签: java swift


【解决方案1】:

哈希码似乎是一个指向对象或其他东西的指针。

在 Java 中描述 hashCode() (Object#hashCode()) 的默认实现时确实如此。

Java 提供hashCode() 的默认实现这一事实导致了我遇到的数百个错误。

在 Java 中,hashCode() 和 equals() 需要保持一致才能与基于哈希值的集合(如 HashMap 或 HashSet)一起使用。

在很多很多类中,equals() 的定义方式不是比较指向对象或其他东西的指针。 hashCode() 的默认实现永远不会与 equals() 保持一致,并导致某种难以发现的错误。

Swift 试图以比其他语言更强的方式告诉我们哈希值需要与类型的相等保持一致。

SE-0185 介绍了一种符合Equatable 和Hashable 的简单方法。在合适的条件下,平等是微不足道的,你不需要定义函数。

hashCode() 在 Java 中的默认实现是无用的,当你覆盖 equals() 时必须覆盖它,即使我们忘记覆盖 hashCode(),编译器也不会给我们任何警告。这真的是一种舒适吗?

【讨论】:

    【解决方案2】:

    Java 唯一的值类型是一组固定的“原语”(bool、char、byte、short、int、long、float、double)。 Swift 有一个通用的机制来创建 Java 没有的值类型。使用标识(实例地址)作为 hashValue 的默认值的基础是没有意义的,因为值类型没有标识。

    引用类型(类的实例,称为对象)通过复制指向对象的指针来传递。如果你有一些对象a,取它的默认hashCode,然后将它作为参数p传递给函数p,那么p.hashCode()将与a.hashCode()相同,因为指针值被保留.

    值类型(结构、元组和枚举的实例)通过复制其内容或value 来传递。如果您有一些实例a,并尝试根据a 的成员开始的基地址派生hashCode,您将获得一些价值。但是,当您将它作为参数p 传递给函数f 时,您将导致a 的成员的副本进入堆栈中为p 预留的内存中f 中。这与a 的位置不同。如果您尝试根据p 的成员开始的基地址尝试派生hashCode,您将获得不同的值!

    因此,值类型不存在身份。带有0b00000001 模式的Int8 与其他地方的Int8 模式具有完全相同的值,后者也是0b00000001。这与两个堆对象不同,它们每个都可以具有相同的内容,但通过它们不同(但伪永久)的位置来区分,这构成了它们身份的基础。

    有一个开放的JDK enhancement proposal (#169) 用于引入通用值类型,因为它在减少诸如Optional<T>、Point 等小型短期对象的数量方面具有非常明显的性能优势。我怀疑它们会遇到斯威夫特也遇到了同样的障碍。以下是 JEP 的摘录(重点是我的):

    总结

    为处理不可变和 无引用对象,支持高效的按值计算 使用非原始类型。

    ...

    说明

    将定义一个新的运算符 lockPermanently,它接受一个对象 并将其标记为不可变且不可混淆。

    一般情况下,永久锁定的对象不能受到约束 对任何依赖于参考身份的操作有意义 的对象。一个操作依赖于引用标识,如果 操作产生不同的结果取决于它是否适用 到原始对象或其克隆之一。因此:

    • 字段和数组元素不能更改。
    • 无法同步 执行。
    • 无法调用等待或通知的方法。
    • 无法查询身份哈希码。
    • 不应执行指针相等检查。

    【讨论】:

    • OP 的问题是围绕 Swift 而不是 Java,他还特别提到了类,它们是引用类型。我也想知道为什么 Swift 中的引用类型不会自动符合 Equatable。我通常添加全局相等运算符来添加它。另外,如果您确实需要自定义它,您仍然可以在每种类型的基础上执行此操作,即使使用了默认行为,所以再次像 OP 一样,我找不到默认不包含它的任何理由.
    • @MarkA.Donohoe 我不太确定你的第一句话是什么意思,因为我非常清楚地谈到了 Swift(它的用户可定义值类型)的不同之处,为什么会这样它的行为不同(值类型没有用于哈希码的稳定标识),以及为什么这是一个比 Swift 更普遍的问题(引用当 Java 经历增强以获得对值类型的支持时,它们不会也有一个哈希码。)
    • 特别是引用类型:基于引用的哈希码很少有意义。在将对象与自身进行比较时,它们非常正确,没有任何错误匹配。但是在将对象 A 与不同的对象 B 进行比较时它们是错误的(即使 A 和 B 具有相同的值,并且在业务域中应该是等价的),这是 java 中非常常见的错误来源(忘记覆盖等于和哈希码)。
    • 我很想听听您的用例,因为我从未遇到过将== 实现为=== 的情况。我当然无法想象这是一个明智的默认行为。
    • 根据您的第二条评论,您将不对与该引用类型的另一个实例等效的引用类型使用相同的哈希值。但这意味着您已经明确定义了其他内容以表示“相等”,因此您应该使用相同的内容来计算哈希值。有关该确切场景的详细说明(靠近底部),请参阅我的帖子。 stackoverflow.com/a/60580514/168179。但是...
    【解决方案3】:

    我已经发布了另一个答案 here on StackOverflow,它介绍了如何通过简单地为 == 和 != 定义全局运算符来为所有类类型隐式添加 Equatable 而无需手动执行任何操作。

    此外,我还展示了如何使所有类都实现Hashable,只需在要创建Hashable 的类型上指定协议,而无需实现散列函数。它使用一个扩展来为你实现它。

    两者都已成为我创建的所有新项目的主要内容。

    HTH!

    【讨论】:

      猜你喜欢
      • 2010-09-18
      • 2022-01-15
      • 2015-01-02
      • 1970-01-01
      • 2015-12-04
      • 2011-06-23
      • 1970-01-01
      • 2014-01-02
      • 1970-01-01
      相关资源
      最近更新 更多