49

Say I have a class that has many instance variables,. I want to overload the == operator (and hashCode) so I can use instances as keys in maps.

class Foo {
  int a;
  int b;
  SomeClass c;
  SomeOtherClass d;
  // etc.

  bool operator==(Foo other) {
    // Long calculation involving a, b, c, d etc.
  }
}

The comparison calculation may be expensive, so I want to check if other is the same instance as this before making that calculation.

How do I invoke the == operator provided by the Object class to do this ?

Argenti Apparatus
  • 3,857
  • 2
  • 25
  • 35

7 Answers7

67

You're looking for "identical", which will check if 2 instances are the same.

identical(this, other);

A more detailed example?

class Person {
  String ssn;
  String name;

  Person(this.ssn, this.name);

  // Define that two persons are equal if their SSNs are equal
  bool operator ==(Person other) {
    return (other.ssn == ssn);
  }
}

main() {
  var bob = new Person('111', 'Bob');
  var robert = new Person('111', 'Robert');

  print(bob == robert); // true

  print(identical(bob, robert)); // false, because these are two different instances
}
Erik Campobadal
  • 867
  • 9
  • 14
Christophe Herreman
  • 15,895
  • 9
  • 58
  • 86
  • 5
    if you override `operator ==` you will also need to override `hashCode`. See this answer [here](https://stackoverflow.com/a/22999113/9449426) for how to do this – NearHuscarl Dec 02 '19 at 06:14
  • 2
    What if i want to check If a list of model already contains a particular object ? – minato Jan 07 '20 at 11:21
  • @NearlHuscarl pointed to a great thread, but the specific answer is already outdated. See the one regarding `object.hash()` for Dart 2.14 and up – Oded Ben Dov Feb 09 '23 at 06:20
8

You can use identical(this, other).

Alexandre Ardhuin
  • 71,959
  • 15
  • 151
  • 132
3

That's my way how I compare deep 2 Objects they're not the same:

class Foo{
 String uid;
 bool isActiv;
 Foo(this.uid, this.isActiv){}
 Map<String, dynamic> toJson() => _$FooToJson(this);
}

Foo A = Foo("alpha", true);
Foo B = Foo("alpha", true);

print(A.toJson().toString() == B.toJson().toString()); // true
B.uid = "beta";
print(A.toJson().toString() == B.toJson().toString()); // false
Tim Lewis
  • 27,813
  • 13
  • 73
  • 102
Aiiboo
  • 613
  • 2
  • 6
  • 13
2

For completeness, this is a supplemental answer to the existing answers.

If some class Foo does not override ==, then the default implementation is to return whether they are the same object. The documentation states:

The default behavior for all Objects is to return true if and only if this object and other are the same object.

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
2

You can use Equatable library

class Foo extends EquatableMixin{
   int? a;
   int? b;
   SomeClass? c;
   SomeOtherClass? d;
   Foo(this.a,this.b,this.c,this.d);
         
   // this does the job, it overrides the hashcode and equals operator
   // give all properties to this `props`
   @override
   List<Object> get props => [a,b,c,d];
}
        
        
    
class SomeOtherClass with EquatableMixin{
  String name;
          
  SomeOtherClass(this.name);
          
  @override
  List<Object> get props => [name];
}

   
class SomeClass with EquatableMixin{
  String name;
      
  SomeClass(this.name);
      
  @override
  List<Object> get props => [name];
      
}
    


Foo foo = 
   Foo(1,2,SomeOtherClass("roger"),SomeOtherClassObject("mack"));

Foo foo2 = 
   Foo(1,2,SomeOtherClass("roger"),SomeOtherClassObject("mack"));
            
print(foo == foo2) // prints true
    

So, we don't need to manually override == and hashcode() methods the library will do that.

Note : the inner objects (SomeClass and SomeOtherClass) should also use EquatableMixin, we can extends this or use as a mixin too

Balaji
  • 1,773
  • 1
  • 17
  • 30
1

On a different yet similar note, in cases where the framework calls to check the equality among the objects e.g. in case of list.toSet() to get the unique elements from a list, identical(this, other) may not be a choice. That time the class must override the == operator and the hasCode() methods.

However for this case another way could be to use the equatable package. This saves a lot of boiler plate code and is especially handy when you have lot of model classes.

Sisir
  • 4,584
  • 4
  • 26
  • 37
1

When you try to compare two pass-by-reference Objects like class, List, Map, Set or etc... you will need to set variables as const variables to be able to compare them because the compiler compares them by address or hashCode like the below code.

   class Foo {
      final int a;
      final int b;
      final SomeClass c;
      const Foo({required this.a, required this.b, required this.c});
    }
    
    class SomeClass {
      final List values;
      const SomeClass(this.values);
    }
    void main() {
      const foo1 = Foo(a: 1, b: 1, c: SomeClass([]));
      const foo2 = Foo(a: 1, b: 1, c: SomeClass([]));
      final foo3 = Foo(a: 1, b: 1, c: SomeClass([]));
      final foo4 = Foo(a: 1, b: 1, c: SomeClass([]));
      print(foo1 == foo2); // true
      print(foo3 == foo4); //false
      print(foo1.hashCode == foo2.hashCode); // true
      print(foo3.hashCode == foo4.hashCode); // false
    }

But we have a big problem we can assign a constant to variables defined at compile time and we can't do this at runtime, see below solutions :)

solution 1:

will need to override hashCode and == methods like the below code.

void main() {
  const foo1 = Foo(a: 1, b: 1, c: SomeClass([]));
  const foo2 = Foo(a: 1, b: 1, c: SomeClass([]));
  final foo3 = Foo(a: 1, b: 1, c: SomeClass([]));
  final foo4 = Foo(a: 1, b: 1, c: SomeClass([]));
  print(foo1 == foo2); // true
  print(foo3 == foo4); //true
  print(foo1.hashCode == foo2.hashCode); // true
  print(foo3.hashCode == foo4.hashCode); // true
}

class Foo {
  final int a;
  final int b;
  final SomeClass c;
  const Foo({required this.a, required this.b, required this.c});
  @override
  int get hashCode => Object.hash(a.hashCode, b.hashCode, c.hashCode);

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        other is Foo &&
            runtimeType == other.runtimeType &&
            hashCode == other.hashCode;
  }
}

class SomeClass {
  final List values;
  const SomeClass(this.values);
  @override
  int get hashCode => Object.hashAll(values);

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        other is Foo &&
            runtimeType == other.runtimeType &&
            hashCode == other.hashCode;
  }
}

solution 2 (the best solution):

use Equtable package

void main() {
  const foo1 = Foo(a: 1, b: 1, c: SomeClass([]));
  const foo2 = Foo(a: 1, b: 1, c: SomeClass([]));
  final foo3 = Foo(a: 1, b: 1, c: SomeClass([]));
  final foo4 = Foo(a: 1, b: 1, c: SomeClass([]));
  print(foo1 == foo2); // true
  print(foo3 == foo4); //true
  print(foo1.hashCode == foo2.hashCode); // true
  print(foo3.hashCode == foo4.hashCode); // true
}

class Foo extends Equatable {
  final int a;
  final int b;
  final SomeClass c;
  const Foo({required this.a, required this.b, required this.c});

  @override
  List<Object?> get props => [a, b, c];
}

class SomeClass extends Equatable {
  final List values;
  const SomeClass(this.values);

  @override
List<Object?> get props => [values];
}

the sources:

https://www.youtube.com/watch?v=DCKaFaU4jdk

https://api.flutter.dev/flutter/dart-core/Object/hashCode.html

Mahmoud Salah Eldin
  • 1,739
  • 16
  • 21