Skip to content

Instantly share code, notes, and snippets.

@kdmatrosov
Last active May 13, 2020 15:30
Show Gist options
  • Select an option

  • Save kdmatrosov/616343a224b50c105cf1211e71f4d9e9 to your computer and use it in GitHub Desktop.

Select an option

Save kdmatrosov/616343a224b50c105cf1211e71f4d9e9 to your computer and use it in GitHub Desktop.
Перевод "Dart Const Tutorial – All You Need to Know (Const Expressions, Canonical Instances and More)" https://resocoder.com/2020/01/06/dart-const-tutorial-all-you-need-to-know-const-expressions-canonical-instances-and-more/

Dart. Всё, что надо знать про константы

Константы - это не просто противная версия final переменных, которая будет преследовать вас во сне со всеми связанными с ними ошибками. Compile-time константы – это хороший способ повысить производительность вашего приложения, не создавая один и тот же объект несколько раз.

const или final?

Давайте, разберемся с этим вопросом, прежде чем перейти к более глубокому изучению констант. Со стороны кажется, что не имеет значения, есть у вас ключевое слово const или final перед переменной. Эти переменные не могут быть изменены после их объявления.

void run() {
  const myConstNumber = 5;
  final myFinalNumber = 5;

  myConstNumber = 42; // ошибка
  myFinalNumber = 42; // ошибка
}

В большинстве случаев нам подойдет final, в то время как const может использоваться только для глобальных, статических или локальных переменных. Это значит, что никаких const полей в классе.

class MyClass {
  final int myFinalField;
  const int myConstField; // error

  MyClass(this.myFinalField, this.myConstField);
}

Чтобы избежать обременительного процесса принятия решений всякий раз, когда надо создать переменную, многие разработчики просто выбирают final каждый раз, даже не думая о const. Я надеюсь, что это изменится, как только вы увидите преимущества канонических экземпляров (прим. подробнее о канонических экземплярах). Но сначала...

Встроенные константы

Литералы типов в Dart являются константами. Написание строковых литералов таких, как "hello", или числовых литералов таких, как 3.14, в коде естественным образом создает объекты, которые известны во время компиляции.

Более того, даже литералы коллекций присвоены константам.

void run() {
  const myList = [1, 2, 3];
  const myMap = {'one': 1};
  const mySet = {1, 2, 3};
}

Примечание: также внутри литералов коллекций можно использовать генерацию списка, if, spread оператор, проверку и приведение типов.

Взгляд компилятора на константы

Как вы уже могли заметить, с точки зрения программиста между константами const и final практически нет разницы, кроме того, что с константами работать сложнее. Однако у компилятора Dart есть совершенно другая пара глаз, и он видит огромную разницу, которую лучше всего проиллюстрированную на фрагменте кода. Выделенные ниже части рассматриваются компилятором как константы.

Это может быть неочевидно на первый взгляд, но есть большое преимущество в том, когда не только значение константно, но и сама переменная. Такие переменные могут быть использованы далее по коду в местах, требующих констант.

void run() {
  final finalVariable = 123456;
  const constVariable = 123456;

  const notWorking = finalVariable; // ошибка
  const working = constVariable;
}

Возможно, передача значений одних констант другим – это не то, что вы делаете каждый день, но есть место, где это может быть полезным...

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

У многих классов во Flutter есть const конструкторы, например у EdgeInsets, используемый для определения отступов. Это чрезвычайно полезно с точки зрения производительности из-за того, что известно как канонические экземпляры.

Если в приложении вы напишите сотни раз const EdgeInsets.all(8), то ваша память не будет загромождена сотнями различных экземпляров. Вместо этого всякий раз, когда вы определяете одни и те же аргументы для const конструктора или фабрики, будет использован один и тот же канонический экземпляр.

Разумеется, вы можете создать свой собственный класс с const конструктор. Есть только одно правило: все поля таких классов являются final и могут хранить константное значение.

class MyConstClass {
  final int field1;
  final List<String> field2;
  const MyConstClass(this.field1, this.field2);
}

void run() {
  // прокидываем в качестве аргумента константную переменную
  const constVariable = 123456;
  const x = MyConstClass(constVariable, ['hello', 'there']);
}

Это означает, что у вас не может быть поля типа, в котором нет const конструктора.

class MyWannabeConstClass {
  // у Future нет const конструктора или фабрики
  final Future<int> field;
  // Dart позволяет определить, казалось бы, бессмысленный конструктор:
  const MyWannabeConstClass(this.field);
}

void run() {
  // Dart не позволяет нам использовать конструктор const:
  const x = MyWannabeConstClass(Future.value(123)); // ошибка
}

Так почему же Dart позволяет нам определить конструктор, который, по идее, всегда "ломается"? А потому что он отработает без падений, если к нему обратиться иначе. Наличие у класса const конструктора не означает, что вы всегда должны получать канонические экземпляры. Вы также можете создавать обычные экземпляры.

void run() {
  // Передача Future не вызовет ошибки при работе с неконстантным конструктором
  final implicitNew = MyWannabeConstClass(Future.value(123));
  final explicitNew = new MyWannabeConstClass(Future.value(123));
}

Хотя создание неканонических новых экземпляров класса с помощью конструктора const возможно, это несколько расстраивает, поскольку вы теряете все удивительные преимущества производительности при повторном использовании одного и того же экземпляра, возможно, тысячи раз в большом Flutter приложении.

Как вы можете видеть выше, замена ключевого слова const на final автоматически приводит к созданию нового экземпляра.

Так же, как ключевое слово new является необязательным при создании новых экземпляров, ключевое слово const –необязательно при попытке получить существующие канонические экземпляры.

void run() {
  const implicitConst = MyConstClass(1);
  const explicitConst = const MyConstClass(1);
}

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

void run() {
  final regularVariable = const MyConstClass(1);
}

const = 😍

Надеюсь, это руководство смогло прояснить значение const конструктуров и в целом констант в Dart. Попробуйте использовать const, где это возможно, и вы внесете небольшие улучшения по производительности в свои приложения строка за строкой. И вы знаете, как это бывает с небольшими улучшениями, - они дают общий результат.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment