🍣

hashcodeについて

2023/05/23に公開

hashcodeについて無知だったので調べてみました。


All objects have hash codes. The default hash code implemented by Object represents only the identity of the object, the same way as the default operator == implementation only considers objects equal if they are identical

hashcodeは、オブジェクトの同一性を表す。これは、==operatorと同様のモノ。

Objects that are not equal are allowed to have the same hash code. It is even technically allowed that all instances have the same hash code, but if clashes happen too often, it may reduce the efficiency of hash-based data structures like HashSet or HashMap.

hashcodeは、オブジェクトの同一性を確認するために用いられるが、イコールではないオブジェクト同士が同一のhashcodeを持つことは仕様上可能。全てのオブジェクトが同一のhashcodeを持つことも。

dartのイミュータブルなオブジェクトは、同じ値であれば同一のインスタンスを再利用し、同じhashCodeを持ちます。

void main() {
  runApp(MyApp());
  final monster1 = const Monster('Slime');
  final monster2 = const Monster('Slime');
  print(monster1.hashCode);
  print(monster2.hashCode);
  print(monster1 == monster2);
}


class Monster {
  final String name;
  const Monster(this.name);
}

実行結果

866102355
866102355
true

参考記事に結果がありますが、Monsterクラスがimmutableでないのなら、hashcodeも違うものになり、==operatorの結果もfalseとなります。

hashcodeと==演算子

等しいオブジェクトは常に同じ hashCode を持つべきです。逆に2つのオブジェクトの hashCode が異なる場合、それらのオブジェクトは等しくないと見なされます。
しかし、2つのオブジェクトが等しくても(== 演算子で比較した結果が真)、それらの hashCode が異なる可能性があります。これはハッシュコリジョンと呼ばれ、ハッシュ関数の性質上避けられません。ただし、等しいオブジェクトは常に同じ hashCode を持つべきで、このルールを遵守することで、ハッシュベースのコレクション(例えば、Dartの Set または Map)で期待する振る舞いを得ることができます。
したがって、== 演算子をオーバーライドする際は hashCode も同時にオーバーライドし、これらの規則に従うことが重要です。

オーバーライドする際の注意点

どのフィールドを比較に使うべきかを決定するには、そのオブジェクトの等価性がどのように意味を持つかを理解する必要があります。その上で、hashCodeの実装も等価性の判断と一致するように実装する必要があります。つまり、x == yがtrueを返す場合、x.hashCodeとy.hashCodeも同じ値を返すようにする必要があります。
たとえば、ある種のオブジェクトでは、ある特定のフィールド(たとえばIDフィールド)が同じであれば、2つのオブジェクトは等しいと考えるかもしれません。そのような場合、それらの特定のフィールドだけを比較すればよいでしょう。一方、他のオブジェクトでは、すべてのフィールドが一致して初めて等しいと考えるかもしれません。


class Person {
  const Person({required this.id, required this.name});
  final String id;
  final String name;

  
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Person &&
          runtimeType == other.runtimeType &&
          name == other.name &&
          id == other.id;

  
  int get hashCode => id.hashCode ^ name.hashCode;
}

hashcodeだけでも等価性の判断には十分かもしれませんが、主要なフィールドの一つも比較した方がより安全かもしれません。また、プロパティの hashCode を排他的論理和(XOR)する方法は、 hashCode をオーバーライドするための一般的なアプローチの一つのようです。

XORを使う理由は、簡単で、異なるプロパティの hashCode を組み合わせることができ、結果として広範なハッシュコードの範囲を提供するからです。これはハッシュベースのデータ構造(例えば、ハッシュマップ)で良いパフォーマンスを得るのに役立ちます。

他の一般的なアプローチとしては、個々のプロパティの hashCode を加算する方法や、 hashCode をビットシフトして組み合わせる方法などがあります。

関連記事

https://zenn.dev/articles/3e5315f9912698/edit

参考記事

https://api.flutter.dev/flutter/dart-core/Object/hashCode.html
https://zenn.dev/iwaku/articles/2020-12-18-iwaku

Discussion