Что наследуется от класса Object в Dart?

Ответ

В Dart все классы неявно наследуются от встроенного класса Object. Это означает, что любой экземпляр любого класса имеет набор базовых методов, определенных в Object.

Основные унаследованные члены:

  1. String toString() — возвращает строковое представление объекта. По умолчанию выводит что-то вроде Instance of 'Person'. Я часто переопределяю его для отладки.
  2. int get hashCode — возвращает целочисленный хэш-код объекта, используемый в коллекциях вроде HashMap и HashSet.
  3. bool operator ==(Object other) — оператор равенства. Важнейший момент: по умолчанию он сравнивает ссылки (эквивалентно identical(this, other)), а не содержимое объектов.
  4. Type get runtimeType — возвращает тип объекта во время выполнения (runtime).

Критическая связь между == и hashCode: Если вы переопределяете operator ==, вы обязаны также переопределить hashCode. Объекты, которые равны по ==, должны возвращать одинаковый хэш-код. Нарушение этого контракта приведет к некорректной работе хэш-коллекций.

Пример переопределения в классе модели данных Flutter:

class User {
  final String id;
  final String name;

  const User({required this.id, required this.name});

  @override
  bool operator ==(Object other) {
    // Проверка идентичности ссылок (оптимизация)
    if (identical(this, other)) return true;
    // Проверка типа и сравнение значимых полей
    return other is User &&
        runtimeType == other.runtimeType &&
        id == other.id; // Сравниваем только id, так как он уникален
  }

  @override
  int get hashCode => id.hashCode; // Хэш-код должен зависеть от тех же полей, что и ==

  @override
  String toString() => 'User(id: $id, name: $name)'; // Удобно для логов
}

void main() {
  final user1 = User(id: '1', name: 'Alice');
  final user2 = User(id: '1', name: 'Alice');
  final user3 = User(id: '2', name: 'Alice');

  print(user1 == user2); // true (id одинаковые)
  print(user1 == user3); // false (id разные)

  // Благодаря правильному hashCode, объекты корректно работают в Set
  final usersSet = {user1, user2, user3};
  print(usersSet.length); // 2 (user1 и user3, user2 не добавится, так как равен user1)
}

Понимание этого механизма фундаментально для корректной работы с коллекциями, такими как Set и ключами Map, а также для написания тестов с expect().

Ответ 18+ 🔞

А, ну вот, опять про этот ваш Object заговорили. Ну ладно, раз уж начали, давайте разберемся, что это за хитрая жопа такая.

Короче, слушай сюда. В Dart, если ты создал какой-нибудь свой класс, типа class Cat { ... }, то этот твой Cat уже по умолчанию — полупидор. В смысле, он неявно наследуется от какого-то старого деда по имени Object. Это типа как если бы ты думал, что ты сам по себе, а на самом деле у тебя в роду был какой-нибудь граф, и теперь ты обязан таскать его фамильный герб и геморрой.

И этот дед Object надарил всем своим детям-классам кучу базовых методов. Их всего четыре штуки, но они вездесущие, как назойливые мухи.

  1. toString() — это когда объект пытается рассказать о себе словами. По дефолту он такой тупой, что говорит что-то вроде "я — экземпляр класса 'ХуйВПальто'". Бесполезно, блядь. Нормальные люди его сразу переопределяют, чтобы в логах можно было понять, что за манда с ушами перед тобой.
  2. hashCode — это типа его цифровой отпечаток пальца, целое число. Нужен для всяких хэш-таблиц (HashMap, HashSet), чтобы быстро искать объекты. Если бы его не было, коллекции бы работали со скоростью улитки в сахаре.
  3. operator ==(Object other) — а вот это, ёпта, самое интересное и подлое место. По умолчанию этот оператор сравнивает не то, что внутри объекта, а ссылки на память. То есть он смотрит: "А это один и тот же кусок памяти или два разных?" Если ты создал двух котов с одинаковым именем Cat(name: 'Барсик'), то для Dart это будут два абсолютно разных кота, потому что живут они в разных квартирах-ячейках памяти. Доверия к этому дефолтному сравнению — ебать ноль.
  4. runtimeType — ну это просто тип объекта в рантайме. Спросил — получил, без сюрпризов.

А теперь главный пиздец, на котором все обламываются. Между == и hashCode есть железобетонная связь, нарушив которую ты получишь ебаный хаос.

Правило простое, как три копейки: если ты переопределил operator ==, чтобы сравнивать объекты по смыслу (например, двух пользователей по id), то ты ОБЯЗАН как собака переопределить и hashCode, чтобы он считался от тех же полей. Равные объекты ДОЛЖНЫ иметь одинаковый хэш-код. Если этого не сделать, твои объекты начнут вести себя в Set или как ключи в Map абсолютно непредсказуемо. Коллекции просто сойдут с ума. Это не рекомендация, это закон.

Смотри, как это выглядит в коде, когда ты делаешь всё по-взрослому, для какой-нибудь модели во Flutter:

class User {
  final String id;
  final String name;

  const User({required this.id, required this.name});

  @override
  bool operator ==(Object other) {
    // Сначала быстрая проверка: а не один ли это и тот же объект? (оптимизация)
    if (identical(this, other)) return true;
    // А теперь проверяем: другой объект — это вообще User? И сравниваем ВАЖНЫЕ поля.
    return other is User &&
        runtimeType == other.runtimeType &&
        id == other.id; // Сравниваем только id, потому что он уникален. Name нас не ебёт для равенства.
  }

  @override
  int get hashCode => id.hashCode; // Хэш-код В ТОЧНОСТИ от тех же полей, что и в == (id).

  @override
  String toString() => 'User(id: $id, name: $name)'; // Теперь в логах видно всё, красота.
}

void main() {
  final user1 = User(id: '1', name: 'Alice');
  final user2 = User(id: '1', name: 'Alice'); // Тот же id, но другой объект в памяти.
  final user3 = User(id: '2', name: 'Alice'); // Совсем другой.

  print(user1 == user2); // true! Потому что id одинаковые. Удивление пиздец, да?
  print(user1 == user3); // false. Ну тут логично.

  // А вот магия правильных hashCode и ==:
  final usersSet = {user1, user2, user3}; // Пытаемся запихнуть всех в Set.
  print(usersSet.length); // 2! user2 не добавился, потому что Set увидел, что он РАВЕН user1. Всё работает как часы.
}

Вот и вся магия. Без этого понимания тесты с expect() будут падать с непонятными ошибками, а Set будет хранить дубликаты, как будто на дворе 2002-й год. Так что не ленись, переопределяй эти методы, когда нужно сравнивать объекты по смыслу, а не по месту жительства в оперативке.