Dart Tutorial

Dart Lesson 56 of 102 3 min read

Object Equality in Dart: ==, hashCode and identical

Learn how equality works in Dart: identity versus value equality, overriding == and hashCode correctly, and why it matters for Set and Map.

On this page

Are two objects “the same”? There are two different questions hiding in that word:

  • Identity: are they the very same object in memory?
  • Equality: do they represent the same value?

The default is identity #

For your own classes, == compares identity unless you say otherwise.

class Point {
  final int x, y;
  Point(this.x, this.y);
}

void main() {
  var a = Point(1, 2);
  var b = Point(1, 2);
  var c = a;

  print(a == b);         // different objects
  print(a == c);         // same object
  print(identical(a, c));
}
false
true
true

Two points with the same coordinates are not equal. That is rarely what you want for data classes.

Built-in types compare by value #

Numbers, strings and booleans already behave sensibly. Collections do not: two lists are equal only if they are the same list.

void main() {
  print('dart' == 'dart');
  print(3 == 3.0);
  print([1, 2] == [1, 2]);
}
true
true
false

Overriding == and hashCode #

To get value equality, override the == operator. You must override hashCode as well.

class Point {
  final int x, y;
  const Point(this.x, this.y);

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Point && other.x == x && other.y == y;

  @override
  int get hashCode => Object.hash(x, y);

  @override
  String toString() => 'Point($x, $y)';
}

void main() {
  var a = Point(1, 2);
  var b = Point(1, 2);

  print(a == b);
  print(identical(a, b));
  print(a.hashCode == b.hashCode);
}
true
false
true

Reading the == method: if other is the very same object, they are equal. Otherwise, other must be a Point with matching fields.

Why hashCode matters #

Set and Map use hashCode to find items quickly, and only then call ==. The rule is:

If a == b, then a.hashCode must equal b.hashCode.

Break the rule and sets and maps misbehave.

class Point {
  final int x, y;
  const Point(this.x, this.y);

  @override
  bool operator ==(Object other) =>
      other is Point && other.x == x && other.y == y;

  @override
  int get hashCode => Object.hash(x, y);
}

void main() {
  var visited = {Point(0, 0), Point(1, 1), Point(0, 0)};
  print(visited.length);

  var labels = {Point(0, 0): 'origin'};
  print(labels[Point(0, 0)]);
}
2
origin

Without the overrides, the set would hold three points and the map lookup would return null.

Use Object.hash(a, b, c) for up to twenty fields and Object.hashAll(list) for a collection.

Equality with collections inside #

If a field is a list, == on it compares identity. Compare the contents with helpers from the collection package.

import 'package:collection/collection.dart';

class Playlist {
  final String name;
  final List<String> songs;
  Playlist(this.name, this.songs);

  static const _eq = ListEquality<String>();

  @override
  bool operator ==(Object other) =>
      other is Playlist && other.name == name && _eq.equals(other.songs, songs);

  @override
  int get hashCode => Object.hash(name, _eq.hash(songs));
}

Ways to avoid writing it by hand #

  • Records have value equality built in: (1, 2) == (1, 2) is true. See records.
  • const objects with the same values are identical, so they are also equal.
  • The equatable package lets you list the fields once.
  • Code generators such as freezed write ==, hashCode, toString and copyWith for you.

Rules for a correct == #

  • Only use fields that never change. If an object’s hash changes while it is inside a set, the set can no longer find it. Value-equal classes should be immutable.
  • Always override hashCode together with ==.
  • Use the same fields in both.
  • The parameter type must be Object, not your own class.

Try it yourself #

Create a Money class with amount and currency. Before overriding anything, put two equal-looking values in a Set and print its length. Then add == and hashCode and run it again.

Practise in the playground Updated by Santosh Adhikari