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 the previous article, in Dart every class is a subclass of Object. In this example, we are using the equality operator (==) to compare 2 student objects. But we didn’t override that equality operator in our Student class, therefore the program will use the default equality operator defined in the 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 objects, so they are not equal to each other. That’s it. We will solve this in the 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 the 2 tests above, even when the map contains the key (and the set contains the value) we provided, it still returns false. The reason is in the 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); // true
}

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)); // true
}

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}'); // Set size: 3 (NOT 5)
  final value = Student('Alice', 20);
  print(studentSet.contains(value)); // true
}

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 the equality operator and hashCode.
– But keep in mind, when you override the equality operator, you always override the 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.

Happy coding!

Tagged : / / / / /
Subscribe
Notify of
guest
3 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Ethirallan
Ethirallan
1 year ago

Nice , these are the things I need

Teddichiiwa
Teddichiiwa
1 year ago

Nice article 😉

3
0
Would love your thoughts, please comment.x
()
x