97

I find myself wanting to override hashcode and == for an object, and I'm wondering if there are best practices for how to implement a hashcode that depends on multiple attributes, and it seems like there are some Dart-specific considerations.

The simplest answer would be to XOR the hashes of all the attributes together, and it's probably not too bad. There's also an example in Dart Up and Running at https://www.dartlang.org/docs/dart-up-and-running/contents/ch03.html

  // Override hashCode using strategy from Effective Java, Chapter 11.
 int get hashCode {
   int result = 17;
   result = 37 * result + firstName.hashCode;
   result = 37 * result + lastName.hashCode;
   return result;
 }

but that seems like it expects truncating integer semantics and in Dart overflowing the range of JS integers seems bad for hashing.

We could also do that and just truncate to 32 bits after each operation.

For my application the expected size of the set is very small and almost anything would do, but I'm surprised not to see a standard recipe for the general case. Does anyone have any experience or strong experience with this?

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Alan Knight
  • 2,759
  • 1
  • 15
  • 13
  • 1
    Almost -1 from me for saying "recipe", but since the quality of your question is good, no -1 from me. Surely the term you should be using in your title, is Algorithm, or even "way"(as in, a good way, or a good algorithm) rather than "a good recipe". I can understand if you think the term algorithm sounds like showing off. You could say 'way', but using the term "recipe" is below lame – barlop Dec 13 '13 at 23:41
  • Perhaps look for examples from another language with arbitrary precision integers. Smalltalk has these I believe. – Greg Lowe Dec 14 '13 at 10:15
  • 1
    Dart's integers are funny in that they're arbitrary on the VM, but if you're compiling to JavaScript you get Javascript's limits. So you'd want to truncate, and the interesting question is where and how to do that best. It's presumably some combination of shift/multiply, XOR and truncate. That's also what Smalltalk tends to do. The devil is in the details. – Alan Knight Dec 16 '13 at 20:44

6 Answers6

78

The quiver package provides helper functions hash2, hash3, etc., which simplify the task of implementing hashCode, with some assurance that it works properly under the Dart VM and when compiled to JavaScript.

import 'package:quiver/core.dart';

class Person {
  String name;
  int age;

  Person(this.name, this.age);

  bool operator ==(o) => o is Person && name == o.name && age == o.age;
  int get hashCode => hash2(name.hashCode, age.hashCode);
}

Also see this post for a slightly lengthier discussion.

Patrice Chalin
  • 15,440
  • 7
  • 33
  • 44
66

Since version 2.14, Dart language added support for Object.hash(), along with Object.hashAll() and Object.hashAllUnordered()

hash() doc:

Creates a combined hash code for a number of objects.

Example:

class SomeObject {
  final Object a, b, c;
  SomeObject(this.a, this.b, this.c);

  bool operator==(Object other) =>
      other is SomeObject && a == other.a && b == other.b && c == other.c;

  int get hashCode => Object.hash(a, b, c); // <----- here
}

Note from doc on implementation:

The hash value generated by this function is not guranteed to be stable over different runs of the same program, or between code run in different isolates of the same program. The exact algorithm used may differ between different platforms, or between different versions of the platform libraries, and it may depend on values that change on each program execution.

Nathaniel Johnson
  • 4,731
  • 1
  • 42
  • 69
manikanta
  • 8,100
  • 5
  • 59
  • 66
  • 8
    This should be upvoted. It is the recommended method: https://dart.dev/guides/libraries/library-tour#implementing-map-keys – BbL Feb 02 '22 at 10:22
  • 1
    What if the class has a parent? Can I do `Object.hash(a, b, c, super)`? – nuynait Oct 22 '22 at 22:40
  • 1
    @nuynait No, you can't use `super` on its own as a valid expression. I think the best option is: `Object.hash(super.hashCode, a, b, c)`. – Anakhand Dec 28 '22 at 16:23
42

In the interests of minimizing dependencies, if you are already depending on flutter, but not depending on something like quiver, the dart:ui library contains utilites, hashValues and hashList for creating and combining hash values. If combining list values, one must be careful to ensure the equality operator and hashcode match behaviour. If the hash code computes it's hash deeply, then use deep equality, else, use shallow equality.

class Example {
    final String value1;
    final Object value2;
    final List<Object> deep;
    final List<Object> shallow;

    Example({this.value1, this.value2, this.deep, this.shallow});

    @override
    operator ==(o) =>
        o is Example &&
        o.value1 == value1 &&
        o.value2 == value2 &&
        listEquals(o.deep, deep) &&
        o.shallow == shallow;

    @override
    int get hashCode => hashValues(value1, value2, hashList(deep), shallow);
}

Flutter API docs for hashValues

Flutter API docs for hashList

Daniel Brotherston
  • 1,954
  • 4
  • 18
  • 28
  • 3
    Awesome, minimizing dependencies is always in my interest! – WSBT Aug 12 '21 at 16:55
  • 1
    Really your answer saved me thank you so much – poonam kalra Sep 02 '21 at 07:17
  • 1
    Is it guaranteed that `hashValues` will return a different hash for two different arguments? Or does it a good job minimizing this risk drastically? – SametSahin Oct 01 '21 at 20:17
  • There is no guarantee that `hashValues` will return a different has for two different arguments, no hash function can make this guarantee because the key space is generally smaller than value space of the object. But as far as I understand, the function seeks to minimize the chances of a collision, while minimizing computational complexity. – Daniel Brotherston Oct 02 '21 at 21:47
  • 2
    Note that now both hashValues and hashList are deprecated. Use Object.hashAll() and Object.hashAllUnordered() instead: https://api.flutter.dev/flutter/dart-ui/hashList.html – TarHalda Sep 06 '22 at 19:02
19

The equatable package can help

import 'package:equatable/equatable.dart';

class Person extends Equatable {
  final String name;
  final int age;

  Person(this.name, this.age);

  @override
  List<Object> get props => [name, age];
}

Now Person will use == and hashCode from Equatable, which takes props list that you give

Pavel
  • 5,374
  • 4
  • 30
  • 55
  • Unfortunately this doesn't work with models with extends from ChangeNotifier when you use provider StateManagement – NiklasLehnfeld Aug 11 '20 at 07:35
  • 2
    @NiklasLehnfeld if you need to extend another class, you can use mixin. E.g. `class MyModel extends ChangeNotifier with EquatableMixin` – Pavel Aug 11 '20 at 13:10
  • 1
    This works only with immutable objects so all member variables must be final. Not so useful if your class objects change over time. – Teh Sunn Liu Jul 12 '21 at 08:49
  • 1
    @NiklasLehnfeld you shoudn't extend models from ChangeNotifier. You're doing something wrong. Models should be immutable. If you want reactive fields, consider MobX or Get. – Sergey Molchanovsky Sep 10 '21 at 07:05
12

I recomend "equatable" plugin

https://pub.dev/packages/equatable

Example:

Raw mode:

class Person {
  final String name;

  const Person(this.name);

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

  @override
  int get hashCode => name.hashCode;
}

With equatable :

import 'package:equatable/equatable.dart';

class Person extends Equatable {
  final String name;

  Person(this.name);

  @override
  List<Object> get props => [name];
}
-1

Since Dart is so similar to Java, you can surely find good references on hashCodes for Java that are applicable for Dart too.

A little googling took me to the Wikipedia page on Java's Object.hashCode(). Has a very basic example for the hashcode of a simple object. A popular methodology is to perform a multiplication with a prime number (different ones) and adding some value for each property of the object.

This question f.e. explains why the number 31 is chosen for multiplication for the String.hashCode() method.

More detailed examples of hashcode implementations can be easily found using Google.

Community
  • 1
  • 1
Steven Roose
  • 2,731
  • 4
  • 29
  • 46
  • 3
    I don't know that Dart is so similar to Java. In particular, it differs in its handling of integer types, and the common hash for Java would, if applied to a large number of things, overflow out of Javascript integer range, which would be bad. – Alan Knight Dec 16 '13 at 20:40
  • Well, Java ints overflow as well. You may want to use int32 or int64 from the dart:typed_data package so that your hashcodes are consistent. – Steven Roose Dec 17 '13 at 19:42
  • 1
    Java ints overflow by wrapping around. Javascript ints (and Dart ints when compiled to Javascript) overflow by turning into doubles, which is much worse for hashing. Using an int32 datatype would help, but dart:typed_data doesn't have a single int type, it only has collections. – Alan Knight Dec 17 '13 at 20:31