Что такое оператор двойного равно (==) в Dart?

Ответ

Оператор == (двойное равно) в Dart — это оператор проверки равенства значений (эквивалентности) объектов. Он сравнивает содержимое объектов, а не их идентичность в памяти (для сравнения ссылок используется функция identical()).

Как работает по умолчанию: Для экземпляров классов (не примитивов) оператор == по умолчанию сравнивает ссылки. Это означает, что два разных объекта с одинаковыми полями будут считаться неравными.

void main() {
  var list1 = [1, 2];
  var list2 = [1, 2];
  var list3 = list1;

  print(list1 == list2); // false (разные экземпляры в памяти)
  print(identical(list1, list2)); // false
  print(list1 == list3); // true (одна и та же ссылка)
  print(identical(list1, list3)); // true

  // Для примитивов (int, String, bool) == сравнивает значения
  var a = 'hello';
  var b = 'hello';
  print(a == b); // true (значения одинаковы, строка интернирована)
}

Переопределение оператора == для своих классов: Чтобы сравнение работало по значению полей, необходимо переопределить оператор == и метод hashCode. Это критически важно для корректной работы с коллекциями (Set, Map).

class Person {
  final String name;
  final int age;

  Person(this.name, this.age);

  @override
  bool operator ==(Object other) {
    // Проверяем идентичность, тип и сравниваем поля
    return identical(this, other) ||
        other is Person &&
        runtimeType == other.runtimeType &&
        name == other.name &&
        age == other.age;
  }

  @override
  int get hashCode => Object.hash(name, age); // Генерация хэш-кода на основе полей
}

void main() {
  var p1 = Person('Alice', 30);
  var p2 = Person('Alice', 30);
  var p3 = Person('Bob', 25);

  print(p1 == p2); // true (поля одинаковы)
  print(p1 == p3); // false

  var personSet = {p1};
  print(personSet.contains(p2)); // true (благодаря правильным == и hashCode)
}

Null-безопасность: Оператор == не защищает от null. Для безопасного сравнения с null используйте object == null или оператор ?.

String? nullableString;
print(nullableString == null); // true
print(nullableString?.isEmpty ?? true); // Безопасный доступ с проверкой на null

Ответ 18+ 🔞

А, ну вот, опять про эти двойные равно. Слушай, тут история такая, что если не разберёшься — потом сам от себя охуеешь, когда что-то в Map не находится, хотя ты вроде бы туда положил. Так что давай по полочкам, без паники.

Вот смотри, == в Dart — это как будто ты спрашиваешь: «Эй, а вы, ребята, по значению одинаковые?». Но тут есть подвох, ёпта. По умолчанию, для всяких своих кастомных классов (не для встроенных примитивов), этот оператор работает как полный пидарас шерстяной. Он сравнивает не то, что внутри, а то, где в памяти этот объект лежит. То есть, две абсолютно одинаковые коробки, но стоящие на разных полках, он посчитает разными. Вот тебе наглядный пиздец:

void main() {
  var list1 = [1, 2];
  var list2 = [1, 2];
  var list3 = list1;

  print(list1 == list2); // false (разные экземпляры в памяти)
  print(identical(list1, list2)); // false
  print(list1 == list3); // true (одна и та же ссылка)
  print(identical(list1, list3)); // true

  // Для примитивов (int, String, bool) == сравнивает значения
  var a = 'hello';
  var b = 'hello';
  print(a == b); // true (значения одинаковы, строка интернирована)
}

Видишь? list1 и list2 — ну один в один, как близнецы, но для языка это два разных уёбка в пальто, потому что ссылки разные. А вот list1 и list3 — это один и тот же чувак, просто на него две таблички с именем повесили. Поэтому == говорит «да».

Теперь, если ты пишешь свой класс и хочешь, чтобы сравнение было по смыслу, а не по месту жительства в оперативке, тебе придётся немного поработать руками. Надо переопределить сам оператор == и, что критически важно, метод hashCode. Иначе все твои Set и Map превратятся в манду с ушами — ничего работать не будет. Вот смотри, как это делается без геморроя:

class Person {
  final String name;
  final int age;

  Person(this.name, this.age);

  @override
  bool operator ==(Object other) {
    // Проверяем идентичность, тип и сравниваем поля
    return identical(this, other) ||
        other is Person &&
        runtimeType == other.runtimeType &&
        name == other.name &&
        age == other.age;
  }

  @override
  int get hashCode => Object.hash(name, age); // Генерация хэш-кода на основе полей
}

void main() {
  var p1 = Person('Alice', 30);
  var p2 = Person('Alice', 30);
  var p3 = Person('Bob', 25);

  print(p1 == p2); // true (поля одинаковы)
  print(p1 == p3); // false

  var personSet = {p1};
  print(personSet.contains(p2)); // true (благодаря правильным == и hashCode)
}

Вот теперь p1 и p2 — это уже не просто два разных объекта в памяти, а две сущности, которые мы договорились считать равными, если у них совпадают имя и возраст. И коллекции это понимают. Если бы hashCode не переопределили, то Set бы просто послал тебя нахуй, и contains вернул бы false, доверия ебать ноль.

И последнее, про null. Оператор == сам по себе от null не защищает, он тупо сравнивает. Если тебе нужно проверить, не null ли твоя переменная, делай явно object == null. А если хочешь безопасно что-то у не-null переменной спросить, используй ?. — это твой лучший друг, чтобы не получить NullPointerException в самый неподходящий момент.

String? nullableString;
print(nullableString == null); // true
print(nullableString?.isEmpty ?? true); // Безопасный доступ с проверкой на null

Вот и вся магия. Главное — не забывай про hashCode, когда переопределяешь ==, иначе будет тебе хиросима, а не работающий код.