Dart/Flutter – How to compare 2 objects

In the last article, we learned How to print an object in Dart. In this article, we will play around with another thing: how to compare 2 objects.

Example 1Compare 2 students without overriding operator == :

Let’s create a class Student with 2 attributes: name and age:

class Student {
  final String name;
  final int age;

  Student(this.name, this.age);
}


Now, we create 2 students with exactly the same info, then compare them:

void compareObjects() {
  final s1 = Student('Alice', 20);
  final s2 = Student('Alice', 20);

  print(s1 == s2); // -> false
}


As mentioned in previous article, in Dart every class is a subclass of Object. In this example, we are using equality operator (==) to compare 2 student objects. But we didn’t override that equality operator in our Student class, therefore the program will use default equality operator defined in Object class. If we look at the document, it says:

/**
   * The equality operator.
   *
   * The default behavior for all [Object]s is to return true if and
   * only if `this` and [other] are the same object.
   ...
*/
external bool operator ==(other);


s1 and s2 are not the same object, so they are not equal to each other. That’s it. We will solve this in next example.

Example 2 Compare 2 students by overriding operator == :

Let’s update the Student class, we override and implement operator ==. Here is the updated version:

class Student {
  final String name;
  final int age;

  Student(this.name, this.age);

  @override
  bool operator ==(other) {
    return (other is Student)
        && other.name == name
        && other.age == age;
  }
}

void compareObjects() {
  final s1 = Student('Alice', 20);
  final s2 = Student('Alice', 20);

  print(s1 == s2); // -> true
}


Now s1 == s2 returns true because we define the algorithm to check the equality of them, even when they are not the same object. It’s good so far but not enough, as in the document, it also mentions another thing:

/**
... 
* If a subclass overrides the equality operator it should override
* the [hashCode] method as well to maintain consistency.
*/
external bool operator ==(other);


It says if we override the equality operator (==) in our subclass, we SHOULD override the hashCode method too. Okay, we will override hashCode, but when should we override it? Because in the last example, it works perfectly. To clarify this, let’s move to the next example.

Example 3 – Object in hash-based collections (Map, Set…):

We still keep using the Student class in example 2 above, adding 2 other methods to see how Student class will work in Map and Set:

class Student {
  final String name;
  final int age;

  Student(this.name, this.age);

  @override
  bool operator ==(other) {
    return (other is Student)
        && other.name == name
        && other.age == age;
  }
}

void testObjectsInMap() {
  final alice = Student('Alice', 20);
  final bob = Student('Bob', 25);
  final chris = Student('Chris', 22);

  // Map each student to a unique id
  final studentsMap = {alice: 1, bob: 2, chris: 3};

  final key = Student('Alice', 20);
  print(studentsMap.containsKey(key)); // -> false
}

void testObjectsInSet() {
  final alice = Student('Alice', 20);
  final bob = Student('Bob', 25);
  final chris = Student('Chris', 22);

  // Put students to a Set
  final studentSet = {alice, bob, chris, alice, chris};
  print('Set size: ${studentSet.length}'); // -> 3 (NOT 5)
  final value = Student('Alice', 20);
  print(studentSet.contains(value)); // false
}


In 2 tests above, even when the map contains the key (and the set contains the value) we provided, but it still returns false. The reason is in hash-based collection, both equality operator (==) and hashCode are used when doing the comparison.

To fix this, we modify the code in example 3 by overriding the hashCode method. The latest code is here:

void main() {
  compareObjects();
  testObjectsInMap();
  testObjectsInSet();
}

void compareObjects() {
  final s1 = Student('Alice', 20);
  final s2 = Student('Alice', 20);

  print(s1 == s2);
}

void testObjectsInMap() {
  final alice = Student('Alice', 20);
  final bob = Student('Bob', 25);
  final chris = Student('Chris', 22);

  // Map each student to a unique id
  final studentsMap = {alice: 1, bob: 2, chris: 3};

  final key = Student('Alice', 20);
  print(studentsMap.containsKey(key)); // -> false
}

void testObjectsInSet() {
  final alice = Student('Alice', 20);
  final bob = Student('Bob', 25);
  final chris = Student('Chris', 22);

  // Put students to a Set
  final studentSet = {alice, bob, chris, alice, chris};
  print('Set size: ${studentSet.length}'); // -> 3 (NOT 5)
  final value = Student('Alice', 20);
  print(studentSet.contains(value)); // false
}

class Student {
  final String name;
  final int age;

  Student(this.name, this.age);

  @override
  bool operator ==(other) {
    return (other is Student) && other.name == name && other.age == age;
  }

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


And here is the output:

true
true
Set size: 3
true


Conclusion:
– It depends on your situation, we may (or may not) need to override equality operator and hashCode.
– But keep in mind, when you override equality operator, you always override hashCode method too. (And when you override hashCode, you always override equality operator).
– Programming is always interesting like this!

This post is also available on medium.

Disclaimer: My English is not perfect, please don’t hesitate to point out any mistakes in the post.

Happy coding!

Tagged : / / / / /
5 3 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x