Константы — это не просто странная версия 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; // ошибка
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
конструкторов и в целом констант в Dart. Попробуйте использовать const
, где это возможно, и вы внесете небольшие улучшения по производительности в свои приложения строка за строкой. А вы знаете, как это бывает с небольшими улучшениями, — они дают общий результат.
qwert2603
Еще стоит отметить, что при использовании
String.fromEnvironment
илиbool.fromEnvironment
надо присваивать полученное значение именно константам, иначе эти методы будут возвращатьdefaultValue
.Поведение это странное, и, надеюсь, в будущем его починят.
Issue в GitHub