Dart литералы это ссылки?

Литералы в Dart это объекты т.к. мы можем сделать так: print(5.runtimeType); Получается что 5-это ссылка на ячейку памяти а не константа, хранящаяся прямо в коде?


Ответы (1 шт):

Автор решения: MiT

В Dart все является объектами, но фишка в реализации и оптимизации компилятора и Dart VM. Давайте все посмотрим на примерах.

var a = 5;
var b = true;

В Dart эти литералы действительно являются объектами, но компилятор и runtime оптимизируют их работу:

  • Для частых значений может использоваться пул констант
  • Примитивные типы могут оптимизироваться до машинных значений
  • JIT/AOT-компиляторы могут выполнять дополнительные оптимизации
var str = "Hello";

Строки в Dart неизменяемы (immutable) и также могут оптимизироваться через пул констант.

var list = [1, 2, 3];
var map = {'a': 1, 'b': 2};

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

Таким образом:

  • Да, литералы в Dart - это объекты
  • Но их реализация оптимизирована
  • Малые целые числа и строки часто используют пул констант
  • Компилятор может оптимизировать работу с примитивными типами
  • Const литералы всегда являются одним и тем же объектом
void main() {
  var a = 5;
  var b = 5;
  print(identical(a, b)); // true - одна и та же ссылка
  
  b = a; // b теперь ссылается на тот же объект, что и a
  print(identical(a, b)); // true - так как a и b ссылаются на один объект
  
  b = 6; // b теперь ссылается на новый объект
  print(identical(a, b)); // false - так как a и b ссылаются на разные объекты
  
  // Компилятор может оптимизировать это
  a = 5 + 1;
  print(identical(a, b)); // true - так как a и b ссылаются на один объект
  
  var x = 1000000;
  const y = 1000000;
  print(identical(x, y)); // true - одна и та же ссылка
  
  var s1 = "hello";
  var s2 = "hello";
  print(identical(s1, s2)); // true - строки из пула
  
  const c = 5;
  const d = 5;
  print(identical(c, d)); // true - одна и та же ссылка
  
  var list1 = [1, 2, 3];
  var list2 = [1, 2, 3];
  print(identical(list1, list2)); // false - из-за мутабельности
  
  const list3 = [1, 2, 3];
  const list4 = [1, 2, 3];
  print(identical(list3, list4)); // true - из-за иммутабельности (UnmodifiableListView)
  
  var map1 = {'a': 1, 'b': 2};
  var map2 = {'a': 1, 'b': 2};
  print(identical(map1, map2)); // false - из-за мутабельности
  
  const map3 = {'a': 1, 'b': 2};
  const map4 = {'a': 1, 'b': 2};
  print(identical(map3, map4)); // true - из-за иммутабельности (UnmodifiableMapView)
}

Также в данном вопросе уместно учитывать и передачу данных. В Dart передача данных зависит от типа данных:

  • Для примитивных типов (как мы узнали они тоже объекты) - передача по значению
  • Для объектов - передача по ссылке
class Person1 {
  Person1(this.name);
  String name;
}

class Person2 {
  const Person2(this.name);
  
  final String name;
}

void modifyValues(int number, List<int> list, String str, Person1 p1, Person2 p2) {
  number = 10;        // Не изменит оригинал
  list.add(4);        // Изменит оригинал
  str = "world";      // Не изменит оригинал
  p1.name = "Bob";    // Изменит оригинал
  // p2.name = "Bob"; // Не даст изменить из-за final
}

void modifyValues2(
    final int number, final List<int> list, final String str,
    final Person1 p1, final Person2 p2) {
  // number = 10;     // Не даст изменить из-за final
  list.add(8);        // Изменит оригинал
  // str = "world";   // Не даст изменить из-за final
  p1.name = "Bib";    // Изменит оригинал
  // p2.name = "Bob"; // Не даст изменить из-за final
}

void main() {
  int num = 5;
  var lst = [1, 2, 3];
  String text = "hello";
  var p1 = Person1("John1");
  var p2 = Person2("John2");
  
  modifyValues(num, lst, text, p1, p2);
  
  print(num);  // 5
  print(lst);  // [1, 2, 3, 4]
  print(text); // "hello"
  print(p1.name); // "Bob"
  print(p2.name); // "John2"
  
  modifyValues2(num, lst, text, p1, p2);
  
  print(num);  // 5
  print(lst);  // [1, 2, 3, 4, 8]
  print(text); // "hello"
  print(p1.name); // "Bib"
  print(p2.name); // "John2"
  
  
  List<int> list1 = [1, 2, 3];
  List<int> list2 = list1; // list2 теперь ссылается на тот же список, что и list1

  list1.add(4); // Изменяем список, на который ссылается list1 (и list2)

  print(list1); // [1, 2, 3, 4]
  print(list2); // [1, 2, 3, 4] (list2 тоже изменился!)
}

В Dart всегда передаются ссылки. Однако, из-за неизменяемости базовых типов, поведение при работе с ними похоже на передачу по значению. Важно понимать разницу и помнить, что при работе с иммутабельными объектами изменения, сделанные через одну переменную, отразятся на всех переменных, ссылающихся на тот же объект. Чтобы обезопасить себя запомните следующее:

// Делайте копию, если не хотите чтобы мутация отразилась на всех ссылках
var newList = List.from(originalList);
var newMap = Map.from(originalMap);

// Делайте глубокую копию (нужно реализовать самостоятельно),
// если не хотите чтобы мутация отразилась на всех ссылках
var deepCopyList = originalList.deepCopy().toList();
var deepMap = originalMap.deepCopy();

// Используйте UnmodifiableListView/UnmodifiableMapView для getter
List get l => List.unmodifiable(originalList);
Map get m => Map.unmodifiable(originalList);

// Думайте о передаче данных в конструкторах
class Box {
  // Сохраняет ссылку на переданный список
  const Box(this.items);
  
  // Безопасный вариант - создание копии
  const Box.safe(List<int> items) : items = List.from(items);

  final List<int> items;
}

// Используйте иммутабельные объекты
class Address {
  const Address(this.street);

  final String street;

  Address clone() {
    return Address(street);
  }
}

// Чтобы модифицировать иммутабельные объекты используйте clone/copyWith
class User {
  const User(this.name, this.address);

  final String name;
  final Address address;
  
  User clone() {
    return User(name, address.clone());
  }

  User copyWith({final String? name, final Address? address}) {
    return User(name ?? this.name, address ?? this.address.clone());
  }
}
→ Ссылка