Копирование значений и ссылочная модель

Типы хранения данных

В языке Dart есть два способа хранения данных:

Простые типы в Dart — это:
 intdoubleboolStringnum
 enum (перечисления, о них поговорим позже)

Копирование простых данных

Когда ты присваиваешь значение одной переменной в другую, создаётся копия данных.
Оригинальная переменная не изменяется.

Каждая переменная хранит своё значение отдельно!

Dart - Копирование по значению

Светлая тема Темная тема
void main() {
  int a = 10;
  int b = a; // b получает КОПИЮ значения a
  print("a = $a, b = $b"); // a = 10, b = 10

  b = 20; // Изменяем b, но a не трогается
  print("a = $a, b = $b"); // a = 10, b = 20
}

Как работают ссылки в объектах?

Объекты (например, ListMapSetclassне копируются, а передаются по ссылке.

Это значит, что если одна переменная изменит объект, то изменения отразятся на всех переменных, которые ссылаются на этот объект.

Пример с List (списками)
Объект изменяется, потому что обе переменные указывают на одну область памяти!

Dart - Передача по ссылке (List)

Светлая тема Темная тема
void main() {
  List<int> list1 = [1, 2, 3];
  List<int> list2 = list1; // list2 получает ссылку на тот же объект!

  print("list1 = $list1, list2 = $list2"); // [1, 2, 3], [1, 2, 3]

  list2[0] = 42; // Меняем элемент через list2

  print("list1 = $list1, list2 = $list2"); // [42, 2, 3], [42, 2, 3]
}

Сравнение своих объектов

Dart - Ссылки на один объект

Светлая тема Темная тема
class Player {
  String name;
  Player(this.name);
}

void main() {
  Player p1 = Player("Игрок");
  Player p2 = p1;

  p2.name = "Новый Игрок";

  print(p1.name); // "Новый Игрок"
  print(p2.name); // "Новый Игрок"

  // Значния не копируются
  // p1 и p2 ссылаются на один и тот же объект в памяти
}

Dart - Ссылки на разные объекты

Светлая тема Темная тема
class Player {
  String name;
  Player(this.name);
}

void main() {
  Player p1 = Player("Игрок");
  Player p2 = Player("Игрок");

  print(p1 == p2); // false (разные объекты в памяти)
  
  // Или воспользоваться методом identical
  print(identical(p1, p2)); // false (разные объекты в памяти)
}

Даже если у объектов одинаковые данные (name), они хранятся в разных местах памяти, и Dart считает их разными.

Как сравнить объекты по содержимому?

Чтобы сравнить объекты по их полям, нужно переопределить метод operator == и хеш-функцию hashCode.

- Каждый объект в Dart автоматически предоставляет целочисленный хэш-код
- Однако его можно переопределить, чтобы сгенерировать собственный
- Если это сделать, нужно обязательно переопределить и оператор ==
- Равные объекты (==) должны иметь одинаковые хэш-коды

Dart - Переопределение == и hashCode

Светлая тема Темная тема
class Player {
  String name;

  Player(this.name);

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    return other is Player && other.name == name;
  }

  // Переопределение hashCode используя алгоритм из книги "Effective Java"
  @override
  int get hashCode {
    int result = 17;
    return 37 * result + name.hashCode;
  }
}

void main() {
  Player p1 = Player("Игрок");
  Player p2 = Player("Игрок");

  print(p1 == p2); // true (сравниваем по имени)
}

Константные конструкторы (const)

Обычные объекты создаются каждый раз заново, но константные объекты с одинаковыми значениями могут переиспользоваться.

- Константные объекты хранятся в специальной памяти и не дублируются.
- Если объект неизменяемый, его выгодно делать const для экономии памяти.

Обычный конструктор

Dart - Обычный конструктор

Светлая тема Темная тема
class Point {
  int x, y;
  Point(this.x, this.y);
}

void main() {
  var p1 = Point(10, 20);
  var p2 = Point(10, 20);
  
  print(identical(p1, p2)); // false (разные объекты в памяти)
}

Константный конструктор

Dart - Константный конструктор

Светлая тема Темная тема
class Point {
  // 👉 Свойства обязательно final
  final int x, y;
  // 👉 Добавляем перед конструктором const
  const Point(this.x, this.y);
}

void main() {
  var p1 = const Point(10, 20);
  var p2 = const Point(10, 20);
  
  print(identical(p1, p2)); // true (один объект на всю программу!)
}

Стек (Stack) и Куча (Heap)

В Стек попадают все простые типы данных: числа, функции,  и т.п.
Здесь компилятору понятно, сколько места он занимает, сколько использует памяти и работает очень шустро

Куча — это огромная область памяти, в которой можно создать объекты.
Доступ в эту память имеется по всему приложению.
Она работает медленнее + накладные расходы на работу "Сборщика Мусора"

В случае с простыми типами данных переменные копируются.
В случае с объектами работает ссылочная модель данных.

Stack (Стек) — для простых типов

Быстрая память, предназначенная для временных данных (локальных переменных).

Тут хранятся:
 intdoubleboolStringenum
Локальные переменные внутри функций

Как это работает?

Dart - Работа со стеком

Светлая тема Темная тема
void myFunction() {
  int a = 10; // Хранится в Stack
  double b = 20.5; // Хранится в Stack
  print(a + b);
}

void main() {
  myFunction(); // После выполнения данные a и b удалятся
}

Простые переменные живут только внутри своей функции, а потом исчезают.

Heap (Куча) — для объектов

Большая память для хранения объектов

Тут хранятся:
Все объекты (classListMapSet)
Данные, которые живут дольше функции

Dart - Работа с кучей

Светлая тема Темная тема
class Player {
  String name;
  Player(this.name);
}

void main() {
  Player p1 = Player("Игрок 1");
  Player p2 = p1;

  p2.name = "Изменённый Игрок";

  print(p1.name); // "Изменённый Игрок" (оба указывают на один объект!)
}

Что такое static и где оно используется?

static - относится ко всему классу, а не к конкретному объекту

Где используется static?

Например, можно посчитать сколько объектов определенного класса было создано

Dart - Статические свойства

Светлая тема Темная тема
class Character {
  String name;
  int hp;

  static int count = 0;

  Character(this.name, this.hp) {
    count++;
  }
}

void main() {
  Character knight = Character("Воин", 100);
  Character mage = Character("Маг", 80);
  Character paladin = Character("Паладин", 120);

  // Выводит общее количество созданных персонажей
  // static вызывается у самого класса!
  // объект создавать не нужно!
  print(Character.count);
}

Перечисления Enum

Перечисления enum создаёт фиксированные наборы значений, удобные для работы со статусами, направлениями и режимами.

Используются, когда есть фиксированный список возможных значений:

Например, мы часто будем работать с состоянием во Flutter Приложениях, используя статусы:

Dart - Enum

Светлая тема Темная тема
enum Status { loading, success, error }

void checkStatus(Status status) {
  switch (status) {
    case Status.loading:
      print("Загружается...");
      break;
    case Status.success:
      print("Успешно!");
      break;
    case Status.error:
      print("Ошибка!");
      break;
  }
}

void main() {
  checkStatus(Status.loading);  // Загружается...
  checkStatus(Status.success);  // Успешно!
}