Краткий тур по языку Dart

В мире родился еще один динозаврик и имя ему Dart ? ?

Для начала тура скопипастим предмет обсуждения из Wiki

Dart — язык программирования, созданный Google. Dart позиционируется в качестве замены/альтернативы JavaScript. Один из разработчиков языка Марк Миллер написал, что JavaScript «имеет фундаментальные изъяны», которые невозможно исправить. Поэтому и был создан Dart.

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

Чтобы узнать больше об основных библиотеках Dart, см. Обзор библиотек. Всякий раз, когда вы хотите узнать больше о языковой функции, обратитесь к спецификации языка Dart.

Примечание. С помощью DartPad вы можете экспериментировать с большинством языковых функций Dart (подробнее). Эта страница использует встроенные DartPad редактор с возможностью запускать код в браузере. Если вместо DartPad отображаются пустые поля, перейдите на страницу устранения неполадок DartPad.

Базовая программа на Dart

Следующий код использует многие из основных функций Dart:

// Определяем функцию.
printInteger(int aNumber) {
  print('The number is $aNumber.'); // Print to console.
}

// Это то место, где программа начинает запускаться.
main() {
  var number = 42; // Declare and initialize a variable.
  printInteger(number); // Call a function.
}

Вот что использует эта программа, которая применяется ко всем (или почти ко всем) приложениям Dart:

  • // — с этого начинается любой однострочный комментарий;
  • int — тип целочисленных данных. Есть и другие встроенные типы, к примеру String, List и bool;
  • 42 — числовой литерал. Числовые литералы являются своего рода константой времени компиляции;
  • print() — удобный способ отображения вывода;
  • ‘…’ (или «…») — строковый литерал;
  • $variableName (или ${expression}) — строковая интерполяция: в том числе переменная или строковый эквивалент выражения внутри строкового литерала. Для получения дополнительной информации см. Строки.
  • main() — Специальная обязательная функция верхнего уровня, с которой начинается выполнение приложения. Для получения дополнительной информации см. Функцию main();
  • var — способ объявления переменной без указания ее типа.
Примечание. Код этого сайта соответствует правилам руководства по стилю написания на Dart.

Важные понятия

Когда вы будете изучать язык Dart, помните эти факты и понятия:

  • Все, что вы можете поместить в переменную, является объектом, а каждый объект является экземпляром класса. Четные числа, функции и null являются объектами. Все объекты наследуются от класса Object.
  • Хотя Dart строго типизирован, аннотации типов являются необязательными, потому что Dart может выводить типы. В приведенном выше коде число выводится как тип int. Если вы хотите явно сказать, что тип не ожидается, используйте специальный тип dynamic.
  • Dart поддерживает универсальные типы, такие как List<int> (список целых чисел) или List<dynamic> (список объектов любого типа).
  • Dart поддерживает функции верхнего уровня (такие как main()), а также функции, связанные с классом или объектом (статические свойства и методы экземпляра, соответственно). Вы также можете создавать функции внутри функций (вложенные или локальные функции).
  • Точно так же Dart поддерживает переменные верхнего уровня, а также переменные, привязанные к классу или объекту (статические переменные и переменные экземпляра). Переменные экземпляра иногда называют полями или свойствами.
  • В отличие от Java, в Dart нет ключевых слов public, protected и private. Если идентификатор начинается с подчеркивания (_), он является приватным для своей библиотеки. Для получения дополнительной информации см. Библиотеки и видимость.
  • Идентификаторы могут начинаться с буквы или подчеркивания (_), за которыми следует любая комбинация этих символов плюс цифры.
  • Dart имеет как выражения (которые имеют значения времени выполнения), так и операторы (которые не имеют). Например, условный оператор condition ? expr1 : expr2 имеет значение expr1 или expr2. Сравните это с оператором if-else, который не имеет значения. Инструкция часто содержит одно или несколько выражений, но выражение не может напрямую содержать инструкцию.
  • Инструменты Dart могут сообщать о двух видах проблем: предупреждения и ошибки. Предупреждения являются лишь признаками того, что ваш код может не работать, но они не препятствуют выполнению вашей программы. Ошибки могут быть как во время компиляции, так и во время выполнения. Ошибка во время компиляции вообще не позволяет выполнить код; ошибка во время выполнения приводит к возникновению исключения во время выполнения кода.

Ключевые слова

В следующей таблице перечислены слова, которые язык Dart обрабатывает особым образом

abstract 2 dynamic 2 implements 2 show 1
as 2 else import 2 static 2
assert enum in super
async 1 export 2 interface 2 switch
await 3 extends is sync 1
break external 2 library 2 this
case factory 2 mixin 2 throw
catch false new true
class final null try
const finally on 1 typedef 2
continue for operator 2 var
covariant 2 Function 2 part 2 void
default get 2 rethrow while
deferred 2 hide 1 return with
do if set 2 yield 3

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

  • Слова с верхним индексом 1 являются контекстными ключевыми словами, которые имеют значение только в определенных местах. Они действительные идентификаторы везде;
  • Слова с верхним индексом 2 являются встроенными идентификаторами. Чтобы упростить задачу переноса кода JavaScript в Dart, эти ключевые слова являются допустимыми идентификаторами в большинстве мест, но их нельзя использовать в качестве имен классов или типов или в качестве префиксов импорта;
  • Слова с верхним индексом 3 являются новыми, ограниченными зарезервированными словами, относящимися к поддержке асинхронности, которая была добавлена ​​после выпуска Dart 1.0. Вы не можете использовать await или yield в качестве идентификатора в любом теле функции, помеченном async, async * или sync *.
  • Все остальные слова в таблице являются зарезервированными словами, которые не могут быть идентификаторами.

Переменные

Вот пример создания переменной и ее инициализации:

var name = 'Bob';

Переменные хранят ссылки. Переменная с именем name содержит ссылку на объект String со значением «Bob».

Тип переменной name выводится как String, но вы можете изменить этот тип, указав его. Если объект не ограничен одним типом, укажите объект Object или динамический тип dynamic, следуя рекомендациям по проектированию.

dynamic name = 'Bob';

Другой вариант — явно объявить тип, который будет выведен:

String name = 'Bob';
Примечание. На этой странице соблюдаются рекомендации руководства по стилю, касающиеся использования переменных var вместо аннотаций типов для локальных переменных.
Значение по умолчанию

Неинициализированные переменные имеют начальное значение null. Даже переменные с числовыми типами изначально равны нулю, потому что числа — как и все остальное в Dart — являются объектами.

int lineCount;
assert(lineCount == null);
Примечание: производственный код игнорирует вызов assert(). С другой стороны, во время разработки assert(условие) выдает исключение, если условие ложно. Для получения дополнительной информации см. Assert.
Данные final и const

Если вы никогда не собираетесь менять переменную, используйте final или const, либо вместо var, либо в дополнение к типу. Окончательная переменная может быть установлена ​​только один раз; константная переменная — это константа времени компиляции. (Константные переменные неявно являются окончательными.) Последняя переменная верхнего уровня или класса инициализируется при первом использовании.

Примечание. Переменные экземпляра могут быть final, но не const. Переменные final экземпляра должны быть инициализированы до запуска тела конструктора — при объявлении переменной, параметром конструктора или в списке инициализатора конструктора.

Вот пример создания и установки окончательной переменной:

final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';

Вы не можете изменить значение окончательной переменной:

name = 'Alice'; // Error: a final variable can only be set once.

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

const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere

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

var foo = const [];
final bar = const [];
const baz = []; // Equivalent to `const []`

Вы можете опустить const из инициализирующего выражения объявления const, как для baz выше. Для получения дополнительной информации см. НЕ используйте const с избыточностью.

Вы можете изменить значение неконечной, неконстантной переменной, даже если она имела постоянное значение:

foo = [1, 2, 3]; // Was const []

Вы не можете изменить значение переменной const:

baz = [42]; // Error: Constant variables can't be assigned a value.

Начиная с Dart 2.5, вы можете определять константы, которые используют проверку типов и приведение (is и as), коллекция if и коллекция for и спред — операторы распространения ( и …?):

// Valid compile-time constants as of Dart 2.5.
const Object i = 3; // Where i is a const Object with an int value...
const list = [i as int]; // Use a typecast.
const map = {if (i is int) i: "int"}; // Use is and collection if.
const set = {if (list is List) ...list}; // ...and a spread.

Для получения дополнительной информации об использовании const для создания постоянных значений см. Списки(Lists), Карты(Maps) и Классы(Classes).

Встроенные типы

Язык Dart имеет специальную поддержку для следующих типов:

  • numbers — числа;
  • strings — строки;
  • booleans — булевые значения;
  • lists (also known as arrays) — списки, который также упоминаются массивами;
  • sets — наборы;
  • maps — карты;
  • runes — нужны для выражения символов Юникода в строке;
  • symbols — символы.

Вы можете инициализировать объект любого из этих специальных типов, используя литерал. Например, «это строка» — строковый литерал, true — логический литерал.

Поскольку каждая переменная в Dart ссылается на объект — экземпляр класса — обычно вы можете использовать конструкторы для инициализации переменных. Некоторые из встроенных типов имеют свои собственные конструкторы. Например, вы можете использовать конструктор Map() для создания карты.

Числа — Numbers

Номера дротиков бывают двух видов:

int — целочисленные значения не более 64 бит, в зависимости от платформы. На виртуальной машине Dart значения могут быть от -263 до 263 — 1. В Dart, скомпилированном в JavaScript, используются числа JavaScript, допускающие значения от -253 до 253 — 1.

doube — 64-битные (с двойной точностью) числа с плавающей точкой, как указано в стандарте IEEE 754.

И int, и double являются подтипами num. Тип num включает в себя основные операторы, такие как +, -, / и *, а также, где вы найдете abs(), ceil() и floor(), среди других методов. (Битовые операторы, такие как >>, определены в классе int.) Если num и его подтипы не имеют того, что вы ищете, библиотека dart:math имеет.

Целые числа — это числа без десятичной точки. Вот несколько примеров определения целочисленных литералов:

var x = 1;
var hex = 0xDEADBEEF;

Если число включает в себя десятичную, это double. Вот несколько примеров определения двойных литералов:

double z = 1; // Equivalent to double z = 1.0.
Примечание к версии: До Dart 2.1 было ошибкой использовать целочисленный литерал в двойном контексте.

Вот как вы превращаете строку в число или наоборот:

// String -> int
var one = int.parse('1');
assert(one == 1);

// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);

// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');

// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');

Тип int определяет традиционные побитовые операторы сдвига (<<, >>), AND (&) и OR (|). Например:

const msPerSecond = 1000;
const secondsUntilRetry = 5;
const msUntilRetry = secondsUntilRetry * msPerSecond;
Строки — Strings

Строка Dart представляет собой последовательность кодовых единиц UTF-16. Вы можете использовать одинарные или двойные кавычки для создания строки:

var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to use the other delimiter.";

Вы можете поместить значение выражения в строку, используя $ {expression}. Если выражение является идентификатором, вы можете пропустить {}. Чтобы получить строку, соответствующую объекту, Dart вызовет метод объекта toString().

var s = 'string interpolation';
assert('Dart has $s, which is very handy.' ==
    'Dart has string interpolation, ' +
        'which is very handy.');
assert('That deserves all caps. ' +
        '${s.toUpperCase()} is very handy!' ==
    'That deserves all caps. ' +
        'STRING INTERPOLATION is very handy!');
Примечание. Оператор == проверяет, эквивалентны ли два объекта. Две строки эквивалентны, если они содержат одинаковую последовательность единиц кода.

Вы можете объединять строки, используя примыкающие строковые литералы или оператор +:

var s1 = 'String '
    'concatenation'
    " works even over line breaks.";
assert(s1 ==
    'String concatenation works even over '
        'line breaks.');

var s2 = 'The + operator ' + 'works, as well.';
assert(s2 == 'The + operator works, as well.');

Другой способ создания многострочной строки: используйте тройную кавычку с одинарными или двойными кавычками:

var s1 = '''
You can create
multi-line strings like this one.
''';
var s2 = """This is also a
multi-line string.""";

Вы можете создать «сырую» строку, добавив к ней префикс r:

var s = r'In a raw string, not even \n gets special treatment.';

Посмотрите Руны для деталей о том, как выразить символы Unicode в строке.

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

// These work in a const string.
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';

// These do NOT work in a const string.
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = [1, 2, 3];

const validConstString = '$aConstNum $aConstBool $aConstString';
// const invalidConstString = '$aNum $aBool $aString $aConstList';

Для получения дополнительной информации об использовании строк см. Строки и регулярные выражения.

Булевые значения — Booleans

Для представления логических значений Dart имеет тип с именем bool. Только два объекта имеют тип bool: логические литералы true и false, которые являются константами времени компиляции.

Безопасность типов Dart означает, что вы не можете использовать код вроде if (nonbooleanValue) или assert (nonbooleanValue). Вместо этого явно проверьте значения, как тут:

// Check for an empty string.
var fullName = '';
assert(fullName.isEmpty);

// Check for zero.
var hitPoints = 0;
assert(hitPoints <= 0);

// Check for null.
var unicorn;
assert(unicorn == null);

// Check for NaN.
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);
Списки - Lists

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

var list = [1, 2, 3];
Примечание: Dart делает вывод, что список имеет тип List <int>. Если вы попытаетесь добавить нецелые объекты в этот список, анализатор или среда выполнения выдаст ошибку. Для получения дополнительной информации читайте о выводе типа.

Списки используют индексацию с нуля, где 0 - это индекс первого элемента, а list.length - 1 - это индекс последнего элемента. Вы можете получить длину списка и ссылаться на элементы списка так же, как в JavaScript:

var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);

list[1] = 1;
assert(list[1] == 1);

Чтобы создать список, который является константой времени компиляции, добавьте const перед литералом списка:

var constantList = const [1, 2, 3];
// constantList[1] = 1; // Uncommenting this causes an error.

В Dart 2.3 появился оператор распространения spread (...) и оператор распространения с нулевым значением (...?), Которые обеспечивают краткий способ вставки нескольких элементов в коллекцию.

Например, вы можете использовать спред - оператор распространения (...), чтобы вставить все элементы списка в другой список:

var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);

Если выражение справа от оператора распространения может быть нулевым, вы можете избежать исключений, используя оператор распространения с нулевым значением (...?):

var list;
var list2 = [0, ...?list];
assert(list2.length == 1);

Для получения более подробной информации и примеров использования оператора распространения, см. Предложение оператора распространения.

В Dart 2.3 также была представлена collection if и collection for, которые можно использовать для построения коллекций с использованием условий (if) и повторения (for).

Вот пример использования collection if создания списка с тремя или четырьмя элементами в нем:

var nav = [
  'Home',
  'Furniture',
  'Plants',
  if (promoActive) 'Outlet'
];

Вот пример использования collection if для управления элементами списка перед их добавлением в другой список:

var listOfInts = [1, 2, 3];
var listOfStrings = [
  '#0',
  for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');

Дополнительные сведения и примеры использования коллекции if и for см. В предложении коллекции потоков управления.

Тип List имеет много удобных методов для работы со списками. Для получения дополнительной информации о списках см. Генераторы Generics и коллекции Collections.

Наборы - Sets

Набор в Dart - это неупорядоченная коллекция уникальных предметов. Поддержка Dart для наборов обеспечивается литералами set и типом Set.

Примечание к версии: Хотя тип Set всегда был основной частью Dart, литералы набора были введены в Dart 2.2.

Вот простой набор Dart, созданный с использованием литерала набора:

var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
Примечание: Dart делает вывод, что данные в halogens имеют тип Set <String>. Если вы попытаетесь добавить неправильный тип значения в набор, анализатор или среда выполнения выдаст ошибку. Для получения дополнительной информации читайте о выводе типа.

Чтобы создать пустой набор, используйте {} с предшествующим аргументом типа или присвойте {} переменной типа Set:

var names = {};
// Set names = {}; // This works, too.
// var names = {}; // Creates a map, not a set.
Набор или карта? Синтаксис для литералов карты аналогичен синтаксису для литералов наборов. Поскольку литералы карты были на первом месте, {} по умолчанию использует тип карты Map. Если вы забудете аннотацию типа {} или переменную, которой она назначена, Dart создаст объект типа Map <dynamic, dynamic>.

Добавьте элементы в существующий набор, используя методы add() или addAll():

var elements = {};
elements.add('fluorine');
elements.addAll(halogens);

Используйте .length, чтобы получить количество предметов в наборе:

var elements = {};
elements.add('fluorine');
elements.addAll(halogens);
assert(elements.length == 5);

Чтобы создать набор, который является константой времени компиляции, добавьте const перед литералом набора:

final constantSet = const {
  'fluorine',
  'chlorine',
  'bromine',
  'iodine',
  'astatine',
};
// constantSet.add('helium'); // Uncommenting this causes an error.

Начиная с Dart 2.3, наборы поддерживают операторы распространения (... и ...?) И коллекции if и for, как это делают списки. Для получения дополнительной информации см. Обсуждения оператора распространения списка и оператора сбора списка.

Для получения дополнительной информации о наборах см. Обобщения Generics и наборы Sets .

Карты - Maps

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

var gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
};
var nobleGases = {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};
Примечание: Dart делает вывод, что gifts имеет тип Map <String, String>, а nobleGases имеет тип Map <int, String>. Если вы попытаетесь добавить неверный тип значения на карту, анализатор или среда выполнения выдаст ошибку. Для получения дополнительной информации читайте о выводе типа.

Вы можете создать те же объекты, используя конструктор Map:

var gifts = Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var nobleGases = Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';
Примечание. Возможно, вы ожидаете увидеть new Map() вместо простой Map(). Начиная с Dart 2, ключевое слово new является необязательным. Для получения дополнительной информации см. Использование конструкторов.

Добавьте новую пару ключ-значение на существующую карту так же, как в JavaScript:

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds'; // Add a key-value pair

Получить значение из карты так же, как в JavaScript:

var gifts = {'first': 'partridge'};
assert(gifts['first'] == 'partridge');

Если вы ищете ключ, которого нет на карте, вы получите нулевое взамен:

var gifts = {'first': 'partridge'};
assert(gifts['fifth'] == null);

Используйте .length, чтобы получить количество пар ключ-значение на карте:

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);

Чтобы создать карту с постоянной времени компиляции, добавьте const перед литералом карты:

final constantMap = const {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

// constantMap[2] = 'Helium'; // Uncommenting this causes an error.

Начиная с Dart 2.3, карты поддерживают операторы распространения (... и ...?) И коллекции, если и для, как списки. Для получения подробной информации и примеров см. Предложение оператора распространения и предложение коллекций потока управления.

Для получения дополнительной информации о картах см. Обобщения Generics и Карты Maps.

Руны - Runes

В Dart руны - это кодовые позиции UTF-32 строки.

Unicode определяет уникальное числовое значение для каждой буквы, цифры и символа, используемых во всех мировых системах письма. Поскольку строка Dart представляет собой последовательность единиц кода UTF-16, для выражения 32-битных значений Unicode в строке требуется специальный синтаксис.

Обычный способ выражения кодовой позиции Unicode - это \uXXXX, где XXXX - это шестнадцатеричное 4-значное значение. Например, символ сердца (♥) - это \u2665. Чтобы указать более или менее 4 шестнадцатеричных цифр, поместите значение в фигурные скобки. Например, смеющийся смайлик (?) - это \u{1f600}.

Класс String имеет несколько свойств, которые вы можете использовать для извлечения информации о рунах. Свойства codeUnitAt и codeUnit возвращают 16-битные единицы кода. Используйте свойство runes, чтобы получить руны строки.

Следующий пример иллюстрирует взаимосвязь между рунами, 16-разрядными кодовыми единицами и 32-разрядными кодовыми позициями

main() {
  var clapping = '\u{1f44f}';
  print(clapping);
  print(clapping.codeUnits);
  print(clapping.runes.toList());

  Runes input = new Runes(
      '\u2665  \u{1f605}  \u{1f60e}  \u{1f47b}  \u{1f596}  \u{1f44d}');
  print(new String.fromCharCodes(input));
}
Примечание. Будьте осторожны при манипулировании рунами с помощью операций со списком. Этот подход может легко сломаться, в зависимости от конкретного языка, набора символов и операции. Для получения дополнительной информации см. Как перевернуть строку в дротике? Переполнение стека.
Символы - Symbols

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

Чтобы получить символ для идентификатора, используйте символьный литерал, который просто пишется, как #, за которым следует идентификатор:

#radix
#bar

Символьные литералы являются константами времени компиляции.

Функции

Dart - это настоящий объектно-ориентированный язык, поэтому даже функции являются объектами и имеют тип Function.

Это означает, что функции могут быть назначены переменным или переданы в качестве аргументов другим функциям. Вы также можете вызвать экземпляр класса Dart, как если бы это была функция. Для получения дополнительной информации см. Callable классы.

Вот пример реализации функции:

bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

Хотя Effective Dart рекомендует аннотации типов для общедоступных API-интерфейсов, функция все равно работает, если вы опустите типы:

isNoble(atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

Для функций, которые содержат только одно выражение, вы можете использовать сокращенный синтаксис:

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

Синтаксис => expr является сокращением для {return expr; }. Обозначение => иногда называют синтаксисом стрелки.

Примечание. Между стрелкой (=>) и точкой с запятой (;) может появляться только выражение, но не утверждение. Например, вы не можете поместить туда оператор if, но вы можете использовать условное выражение.

Функция может иметь два типа параметров: обязательные и необязательные. Обязательные параметры перечислены первыми, а затем все необязательные параметры. Необязательные параметры могут быть именованными или позиционными.

Примечание. Некоторые API, в частности конструкторы виджетов Flutter, используют только именованные параметры, даже для обязательных параметров. Смотрите следующий раздел для деталей.
Дополнительные параметры

Необязательные параметры могут быть именованными или позиционными, но не обоими.

Именованные параметры

При вызове функции вы можете указать именованные параметры, используя paramName: value. Например:

enableFlags(bold: true, hidden: false);

При определении функции используйте {param1, param2,…} для указания именованных параметров:

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold, bool hidden}) {...}

Хотя именованные параметры являются своего рода необязательным параметром, их можно аннотировать с помощью @required, чтобы указать, что параметр является обязательным, - что пользователи должны предоставить значение для параметра. Например:

const Scrollbar({Key key, @required Widget child})

Если кто-то пытается создать Scrollbar без указания дочернего аргумента child, то анализатор сообщает о проблеме.

Чтобы использовать аннотацию @required, зависит от пакета meta и импорта: package:meta/meta.dart.

Позиционные параметры

Обертывание набора параметров функции в [ ] помечает их как необязательные позиционные параметры:

String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

Вот пример вызова этой функции без необязательного параметра:

assert(say('Bob', 'Howdy') == 'Bob says Howdy');

И вот пример вызова этой функции с третьим параметром:

assert(say('Bob', 'Howdy', 'smoke signal') ==
    'Bob says Howdy with a smoke signal');
Значения параметров по умолчанию

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

Вот пример установки значений по умолчанию для именованных параметров:

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold = false, bool hidden = false}) {...}

// bold will be true; hidden will be false.
enableFlags(bold: true);
Примечание об устаревании: Старый код может использовать двоеточие (:) вместо = для установки значений по умолчанию именованных параметров. Причина в том, что изначально, только : был поддержан для именованных параметров. Эта поддержка может быть устаревшей, поэтому мы рекомендуем использовать знак =, чтобы указать значения по умолчанию.

В следующем примере показано, как установить значения по умолчанию для позиционных параметров:

String say(String from, String msg,
    [String device = 'carrier pigeon', String mood]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  if (mood != null) {
    result = '$result (in a $mood mood)';
  }
  return result;
}

assert(say('Bob', 'Howdy') ==
    'Bob says Howdy with a carrier pigeon');

Вы также можете передать списки или карты в качестве значений по умолчанию. В следующем примере определяется функция doStuff(), которая задает список по умолчанию для параметра list и карту по умолчанию для параметра gifts.

void doStuff(
    {List list = const [1, 2, 3],
    Map gifts = const {
      'first': 'paper',
      'second': 'cotton',
      'third': 'leather'
    }}) {
  print('list:  $list');
  print('gifts: $gifts');
}
The main() function

Каждое приложение должно иметь функцию main() верхнего уровня, которая служит точкой входа в приложение. Функция main() возвращает void и имеет необязательный параметр List <String> для аргументов.

Вот пример функции main() для веб-приложения:

void main() {
  querySelector('#sample_text_id')
    ..text = 'Click me!'
    ..onClick.listen(reverseText);
}
Примечание. Синтаксис .. в предыдущем коде называется каскадом cascade . С помощью каскадов вы можете выполнять несколько операций над элементами одного объекта.

Вот пример функции main() для приложения командной строки, которое принимает аргументы:

// Run the app like this: dart args.dart 1 test
void main(List arguments) {
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');
}

Вы можете использовать библиотеку args для определения и анализа аргументов командной строки.

Функции как первоклассные объекты

Вы можете передать функцию в качестве параметра другой функции. Например:

void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

// Pass printElement as a parameter.
list.forEach(printElement);

Вы также можете назначить функцию переменной, например:

var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

В этом примере используется анонимная функция. Подробнее об этом в следующем разделе.

Анонимные функции

Большинство функций имеют имена, такие как main() или printElement(). Вы также можете создать анонимную функцию, называемую анонимной функцией, иногда лямбда-выражением или замыканием. Вы можете назначить анонимную функцию переменной, чтобы, например, вы могли добавить или удалить ее из коллекции.

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

Следующий блок кода содержит тело функции:

([[Type] param1[, …]]) {
  codeBlock;
};

В следующем примере определяется анонимная функция с нетипизированным параметром item. Функция, вызываемая для каждого элемента в списке, печатает строку, содержащую значение по указанному индексу.

var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});
void main() {
  var list = ['apples', 'bananas', 'oranges'];
  list.forEach((item) {
    print('${list.indexOf(item)}: $item');
  });
}

Если функция содержит только один оператор, вы можете сократить его, используя обозначение стрелки. Вставьте следующую строку в DartPad и нажмите «Выполнить», чтобы убедиться, что она функционально эквивалентна.

list.forEach(
    (item) => print('${list.indexOf(item)}: $item'));
Лексическая область

Dart - это язык с лексической областью, что означает, что область видимости переменных определяется статически, просто компоновкой кода. Вы можете «следовать за фигурными скобками наружу», чтобы увидеть, находится ли переменная в области видимости.

Вот пример вложенных функций с переменными на каждом уровне области действия:

bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;

    void nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}

Обратите внимание, как nestedFunction() может использовать переменные с каждого уровня, вплоть до верхнего уровня.

Лексические крышки

Замыкание - это объект функции, который имеет доступ к переменным в своей лексической области видимости, даже если функция используется за пределами своей исходной области видимости.

Функции могут закрывать переменные, определенные в окружающих областях. В следующем примере makeAdder() захватывает переменную addBy. Куда бы ни возвращалась возвращаемая функция, она запоминает addBy.

/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

void main() {
  // Create a function that adds 2.
  var add2 = makeAdder(2);

  // Create a function that adds 4.
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}
Проверка функций на равенство

Вот пример тестирования функций верхнего уровня, статических методов и методов экземпляра на равенство:

void foo() {} // A top-level function

class A {
  static void bar() {} // A static method
  void baz() {} // An instance method
}

void main() {
  var x;

  // Comparing top-level functions.
  x = foo;
  assert(foo == x);

  // Comparing static methods.
  x = A.bar;
  assert(A.bar == x);

  // Comparing instance methods.
  var v = A(); // Instance #1 of A
  var w = A(); // Instance #2 of A
  var y = w;
  x = w.baz;

  // These closures refer to the same instance (#2),
  // so they're equal.
  assert(y.baz == x);

  // These closures refer to different instances,
  // so they're unequal.
  assert(v.baz != w.baz);
}
Возвращаемые значения

Все функции возвращают значение. Если возвращаемое значение не указано, инструкция return null; неявно добавляется к телу функции.

foo() {}

assert(foo() == null);

Операторы

Dart определяет операторы, показанные в следующей таблице. Вы можете переопределить многие из этих операторов, как описано в разделе «Переопределяемые операторы».

Описание Оператор
одинарный постфикс expr++ expr-- () [] . ?.
одинарный префикс -expr! expr ~ expr ++ expr --expr
мультипликативный / % ~/
адитивный+ -
сдвиг << >> >>>
побитовое И &
побитовый XOR ^
побитовое ИЛИ |
реляционное и испытание типа = > <= < as is is!
равенство == !=
логическое И &&
логическое ИЛИ ||
если null ??
условный expr1 ? expr2 : expr3
каскад ..
назначение = * = / = + = - = = ^ и т.д.
Предупреждение. Приоритет оператора - это пример поведения анализатора Dart. Для окончательных ответов обратитесь к грамматике в спецификации языка Dart.

Когда вы используете операторы, вы создаете выражения. Вот несколько примеров выражений операторов:

a++
a + b
a = b
a == b
c ? a : b
a is T

В таблице операторов каждый оператор имеет более высокий приоритет, чем операторы в строках, следующих за ним. Например, мультипликативный оператор % имеет более высокий приоритет, чем (и, следовательно, выполняется раньше) оператор равенства ==, который имеет более высокий приоритет, чем логический оператор AND &&. Этот приоритет означает, что следующие две строки кода выполняются одинаково:

// Parentheses improve readability.
if ((n % i == 0) && (d % i == 0)) ...

// Harder to read, but equivalent.
if (n % i == 0 && d % i == 0) ...
Предупреждение. Для операторов, работающих с двумя операндами, крайний левый операнд определяет, какая версия оператора используется. Например, если у вас есть объект Vector и объект Point, aVector + aPoint использует версию Vector вектора +.
Арифметические операторы

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

ОператорЗначение
+добавлять
вычитать
-expr унарный минус, также известный как отрицание
*умножение
/деление
~/ деление, возвращая целочисленный результат
% получить остаток от целочисленного деления

Пример:

assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // Result is a double
assert(5 ~/ 2 == 2); // Result is an int
assert(5 % 2 == 1); // Remainder

assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');

Dart также поддерживает префиксные и постфиксные операторы увеличения и уменьшения.

ОператорЗначение
++var var = var + 1 (значение выражения var + 1)
var++ var = var + 1 (значение выражения var)
--var var = var - 1 (значение выражения var - 1)
var--var = var - 1 (значение выражения var)

Пример:

var a, b;

a = 0;
b = ++a; // Increment a before b gets its value.
assert(a == b); // 1 == 1

a = 0;
b = a++; // Increment a AFTER b gets its value.
assert(a != b); // 1 != 0

a = 0;
b = --a; // Decrement a before b gets its value.
assert(a == b); // -1 == -1

a = 0;
b = a--; // Decrement a AFTER b gets its value.
assert(a != b); // -1 != 0
Равенство и реляционные операторы

В следующей таблице перечислены значения равенства и реляционных операторов.

ОператорЗначение
== равное, см. обсуждение ниже
!= не равный
>больше, чем
<меньше, чем
>=больше, чем или равно
<=меньше, чем или равно

Чтобы проверить, представляют ли два объекта x и y одно и то же, используйте оператор ==. (В редком случае, когда вам нужно знать, являются ли два объекта одним и тем же объектом, используйте вместо него функцию identifier().) Вот как работает оператор ==:

  1. Если x или y равен null, верните true, если оба имеют значение null, и false, если только один равен нулю.
  2. Вернуть результат вызова метода x. == (y). (Это верно, операторы, такие как ==, являются методами, которые вызываются для их первого операнда. Вы даже можете переопределить многие операторы, включая ==, как вы увидите в переопределенных операторах.)

Вот пример использования каждого из операторов равенства и отношений:

assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(2 <= 3);
Тип тестовых операторов

Как as, is, и is! Операторы удобны для проверки типов во время выполнения.

ОператорЗначение
as Typecast (также используется для указания префиксов библиотеки)
is True, если объект имеет указанный тип
is! False, если объект имеет указанный тип

Результатом obj is T - true, если obj реализует интерфейс, указанный в T. Например, obj is Object - всегда true.

Используйте оператор as для приведения объекта к определенному типу. В общем, вы должны использовать его как сокращение для теста is для объекта, за которым следует выражение, использующее этот объект. Например, рассмотрим следующий код:

if (emp is Person) {
  // Type check
  emp.firstName = 'Bob';
}

Вы можете сделать код короче, используя оператор as:

(emp as Person).firstName = 'Bob';
Примечание. Код не эквивалентен. Если emp является нулем или не Person, первый пример (с is) ничего не делает; вторая (с as) выдает исключение.
Операторы присваивания

Как вы уже видели, вы можете присваивать значения с помощью оператора =. Чтобы назначить, только если переменная назначенного значения пуста, используйте оператор ?? =.

// Assign value to a
a = value;
// Assign value to b if b is null; otherwise, b stays the same
b ??= value;

Составные операторы присваивания, такие как +=, объединяют операцию с присваиванием.

=–=/=%=>>=^=
+=*=~/=<<=&=|=

Вот как работают составные операторы присваивания:

Сложное назначениеЭквивалентное выражение
Для оператора op: a op= b a = a op b
Пример: a += b a = a + b

В следующем примере используются операторы присваивания и составного присваивания:

var a = 2; // Assign using =
a *= 3; // Assign and multiply: a = a * 3
assert(a == 6);
Логические операторы

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

ОператорЗначение
!exprинвертирует следующее выражение (изменяет ложь на истину и наоборот)
|| логическое ИЛИ
&& логическое И

Вот пример использования логических операторов:

if (!done && (col == 0 || col == 3)) {
  // ...Do something...
} 
Побитовые и сдвиговые операторы

Вы можете манипулировать отдельными битами чисел в Dart. Обычно вы используете эти побитовые и сдвиговые операторы с целыми числами.

ОператорЗначение
&AND
|OR
^XOR
~expr Унарное побитовое дополнение (0 становятся 1; 1 становятся 0)
<<Сдвиг влево
>>Сдвиг вправо

Вот пример использования побитовых и сдвиговых операторов:

final value = 0x22;
final bitmask = 0x0f;

assert((value & bitmask) == 0x02); // AND
assert((value & ~bitmask) == 0x20); // AND NOT
assert((value | bitmask) == 0x2f); // OR
assert((value ^ bitmask) == 0x2d); // XOR
assert((value << 4) == 0x220); // Shift left
assert((value >> 4) == 0x02); // Shift right
Условные выражения

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

условие ? expr1: expr2

Если условие истинно, вычисляет expr1 (и возвращает его значение); в противном случае вычисляет и возвращает значение expr2.

expr1 ?? expr2

Если expr1 не равен NULL, возвращает его значение; в противном случае вычисляет и возвращает значение expr2.

Когда вам нужно присвоить значение на основе логического выражения, рассмотрите возможность использования ?: .

var visibility = isPublic ? 'public' : 'private';

Если логическое выражение проверяет наличие нуля, рассмотрите возможность использования ??.

String playerName(String name) => name ?? 'Guest';

Предыдущий пример мог быть написан как минимум двумя другими способами, но не так кратко:

// Slightly longer version uses ?: operator.
String playerName(String name) => name != null ? name : 'Guest';

// Very long version uses if-else statement.
String playerName(String name) {
  if (name != null) {
    return name;
  } else {
    return 'Guest';
  }
}
Каскадная запись (..)

Каскады (..) позволяют выполнять последовательность операций над одним и тем же объектом. В дополнение к вызовам функций вы также можете получить доступ к полям этого же объекта. Это часто избавляет вас от необходимости создавать временную переменную и позволяет писать более гибкий код.

Рассмотрим следующий код:

querySelector('#confirm') // Get an object.
  ..text = 'Confirm' // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

Первый вызов метода querySelector() возвращает объект селектора. Код, который следует за каскадной нотацией, работает с этим объектом селектора, игнорируя любые последующие значения, которые могут быть возвращены.

Предыдущий пример эквивалентен:

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

Вы также можете вкладывать свои каскады. Например:

final addressBook = (AddressBookBuilder()
      ..name = 'jenny'
      ..email = 'jenny@example.com'
      ..phone = (PhoneNumberBuilder()
            ..number = '415-555-0100'
            ..label = 'home')
          .build())
    .build();

Будьте осторожны, чтобы построить каскад на функции, которая возвращает реальный объект. Например, следующий код завершается ошибкой:

var sb = StringBuffer();
sb.write('foo')
  ..write('bar'); // Error: method 'write' isn't defined for 'void'.

Вызов sb.write() возвращает void, и вы не можете создать каскад void.

Примечание. Строго говоря, нотация «двойная точка» для каскадов не является оператором. Это всего лишь часть синтаксиса Dart.
Другие операторы

Вы видели большинство оставшихся операторов в других примерах:

ОператорИмяЗначение
( ) Применение функции Представляет вызов функции
[ ] Доступ к списку Относится к значению по указанному индексу в списке
. Доступ к свойствуОтносится к свойству выражения. Пример: foo.bar выбирает bar свойство из выражения foo
? . Условный доступ к свойствуКак и . , Но самый левый операнд может быть нулевым. Пример: foo? .bar выбирает bar свойство из выражения foo, если foo не равно нулю (в этом случае значение foo? .bar равно нулю)

Для получения дополнительной информации об операторах . , ?. И .. см. Классы.

Операторы управления потоком

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

  • if и else
  • циклы for
  • циклы while и do-while
  • break и continue
  • switch и case
  • assert

Вы также можете влиять на поток управления, используя try-catch и throw, как описано в разделе «Исключения».

Условные операторы if и else

Dart поддерживает операторы if с дополнительными операторами else, как показано в следующем примере. Также см. Условные выражения.

if (isRaining()) {
  you.bringRainCoat();
} else if (isSnowing()) {
  you.wearJacket();
} else {
  car.putTopDown();
}

В отличие от JavaScript, условия должны использовать логические значения, больше ничего. Посмотрите тип данных Booleans для получения дополнительной информации.

Циклы for

Вы можете выполнять итерации со стандартным циклом for. Например:

var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
  message.write('!');
}

Замыкания внутри циклов for в Dart фиксируют значение индекса, избегая распространенной ошибки, обнаруженной в JavaScript. Например, рассмотрим:

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());

Выход 0, а затем 1, как и ожидалось. Напротив, в примере будет напечатано 2, а затем 2 в JavaScript.

Если объект, который вы перебираете, является Iterable, вы можете использовать метод forEach(). Использование forEach() является хорошим вариантом, если вам не нужно знать текущий счетчик итераций:

candidates.forEach((candidate) => candidate.interview());

Итерируемые классы, такие как List и Set, также поддерживают форму итерации for-in:

var collection = [0, 1, 2];
for (var x in collection) {
  print(x); // 0 1 2
}
Циклы While и do-while

Цикл while оценивает условие перед циклом:

while (!isDone()) {
  doSomething();
}

Цикл do-while оценивает условие после цикла:

do {
  printLine();
} while (!atEndOfPage());
Конструкции break и continue

Используйте break, чтобы остановить цикл:

while (true) {
  if (shutDownRequested()) break;
  processIncomingRequests();
}

Используйте continue, чтобы перейти к следующей итерации цикла:

for (int i = 0; i < candidates.length; i++) {
  var candidate = candidates[i];
  if (candidate.yearsExperience < 5) {
    continue;
  }
  candidate.interview();
}

Вы можете написать этот пример по-другому, если вы используете Iterable, такой как список или набор:

candidates
    .where((c) => c.yearsExperience >= 5)
    .forEach((c) => c.interview());
Конструкции switch и case

Операторы switch в Dart сравнивают целочисленные, строковые или константы времени компиляции, используя ==. Все сравниваемые объекты должны быть экземплярами одного и того же класса (а не каких-либо его подтипов), и класс не должен переопределять ==. Перечисленные типы хорошо работают в операторах switch.

Примечание: операторы switch в Dart предназначены для ограниченных обстоятельств, таких как переводчики или сканеры.

Каждое непустое предложение case, как правило, заканчивается оператором break. Другими допустимыми способами завершить непустое предложение case являются операторы continue, throw или return.

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

var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClosed();
    break;
  case 'PENDING':
    executePending();
    break;
  case 'APPROVED':
    executeApproved();
    break;
  case 'DENIED':
    executeDenied();
    break;
  case 'OPEN':
    executeOpen();
    break;
  default:
    executeUnknown();
}

В следующем примере в операторе case пропущен оператор break, что приводит к ошибке:

var command = 'OPEN';
switch (command) {
  case 'OPEN':
    executeOpen();
    // ERROR: Missing break

  case 'CLOSED':
    executeClosed();
    break;
}

Тем не менее, Dart поддерживает пустые предложения case, допуская форму провала:

var command = 'CLOSED';
switch (command) {
  case 'CLOSED': // Empty case falls through.
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

Если вы действительно хотите потерпеть неудачу, вы можете использовать оператор continue и метку:

var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed;
  // Continues executing at the nowClosed label.

  nowClosed:
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

Предложение case может иметь локальные переменные, которые видны только внутри области действия этого предложения.

Утверждения assert

Во время разработки используйте оператор утверждения - assert (condition, optionalMessage); - нарушить нормальное выполнение, если логическое условие ложно. Вы можете найти примеры утверждений в течение всего данного тура. Вот еще немного:

// Make sure the variable has a non-null value.
assert(text != null);

// Make sure the value is less than 100.
assert(number < 100);

// Make sure this is an https URL.
assert(urlString.startsWith('https'));

Чтобы прикрепить сообщение к утверждению, добавьте строку в качестве второго аргумента для утверждения assert.

assert(urlString.startsWith('https'),
    'URL ($urlString) should start with "https".');

Первым аргументом assert может быть любое выражение, которое разрешается в логическое значение. Если значение равно false, утверждение не выполняется и генерируется исключение (AssertionError).

Когда именно работают утверждения? Это зависит от используемых вами инструментов и инфраструктуры:

  • Flutter включает утверждения в режиме отладки.
  • Инструменты только для разработки, такие как dartdevc, обычно включают утверждения по умолчанию.
  • Некоторые инструменты, такие как dart и dart2js, поддерживают утверждения через флаг командной строки: --enable-asserts.

В рабочем коде утверждения игнорируются, а аргументы assert не оцениваются.

Исключения

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

В отличие от Java, все исключения Dart являются непроверенными исключениями. Методы не объявляют, какие исключения они могут генерировать, и вы не обязаны перехватывать какие-либо исключения.

Dart предоставляет типы Exception и Error, а также множество предопределенных подтипов. Вы можете, конечно, определить свои собственные исключения. Тем не менее, программы Dart могут выбрасывать любой ненулевой объект - не только объекты Exception и Error - в качестве исключения.

Throw

Вот пример выброса или повышения риска исключения:

throw FormatException('Expected at least 1 section');

Вы также можете бросать произвольные объекты:

throw 'Out of llamas!';
Примечание. Код производственного качества обычно генерирует типы, в которых реализована ошибка Error или исключение Exception.

Поскольку генерирование исключения является выражением, вы можете генерировать исключения в операторах =>, а также в любом другом месте, где разрешены выражения:

void distanceTo(Point other) => throw UnimplementedError();
Catch

Поймать или захватить исключение останавливает распространение исключения (если вы не сбросите исключение). Поймать исключение дает вам возможность обработать это:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}

Для обработки кода, который может генерировать более одного типа исключений, вы можете указать несколько предложений catch. Первое предложение catch, соответствующее типу брошенного объекта, обрабатывает исключение. Если в предложении catch не указан тип, это предложение может обрабатывать любой тип брошенного объекта:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');
}

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

Вы можете указать один или два параметра для catch(). Первое - это исключение, а второе - трассировка стека (объект StackTrace).

try {
  // ···
} on Exception catch (e) {
  print('Exception details:\n $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

Чтобы частично обработать исключение и разрешить его распространение, используйте ключевое слово rethrow.

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // Runtime error
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // Allow callers to see the exception.
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}
Finally

Чтобы убедиться, что какой-то код выполняется независимо от того, было ли выброшено исключение, используйте предложение finally. Если ни одно предложение catch не соответствует исключению, исключение распространяется после выполнения предложения finally:

try {
  breedMoreLlamas();
} finally {
  // Always clean up, even if an exception is thrown.
  cleanLlamaStalls();
}

Предложение finally запускается после любых соответствующих предложений catch:

try {
  breedMoreLlamas();
} catch (e) {
  print('Error: $e'); // Handle the exception first.
} finally {
  cleanLlamaStalls(); // Then clean up.
}

Узнайте больше, прочитав раздел «Исключения» в туре по библиотеке.

Классы

Dart - это объектно-ориентированный язык с классами и смешанным наследованием. Каждый объект является экземпляром класса, и все классы происходят от объекта Object. Смешанное наследование означает, что, хотя каждый класс (кроме Object) имеет ровно один суперкласс, тело класса может быть повторно использовано в нескольких иерархиях классов.

Использование членов классов

У объектов есть члены, состоящие из функций и данных (методы и переменные экземпляра соответственно). Когда вы вызываете метод, вы вызываете его для объекта: у метода есть доступ к функциям и данным этого объекта.

Используйте точку (.) Для ссылки на переменную или метод экземпляра:

var p = Point(2, 2);

// Set the value of the instance variable y.
p.y = 3;

// Get the value of y.
assert(p.y == 3);

// Invoke distanceTo() on p.
num distance = p.distanceTo(Point(4, 4));

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

// If p is non-null, set its y value to 4.
p?.y = 4;
Использование конструкторов

Вы можете создать объект, используя конструктор. Имена конструктора могут быть ClassName или ClassName.identifier. Например, следующий код создает объекты Point с помощью конструкторов Point() и Point.fromJson():

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

Следующий код имеет тот же эффект, но использует необязательное ключевое слово new перед именем конструктора:

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
Примечание к версии: новое ключевое слово стало необязательным в Dart 2.

Некоторые классы предоставляют константные конструкторы. Чтобы создать постоянную времени компиляции с помощью конструктора констант, поместите ключевое слово const перед именем конструктора:

var p = const ImmutablePoint(2, 2);

Построение двух идентичных констант времени компиляции приводит к одному, каноническому экземпляру:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // They are the same instance!

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

// Lots of const keywords here.
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

Вы можете опустить все, кроме первого использования ключевого слова const:

// Only one const, which establishes the constant context.
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

Если константный конструктор находится вне константного контекста и вызывается без const, он создает неконстантный объект:

var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant

assert(!identical(a, b)); // NOT the same instance!
Примечание к версии: ключевое слово const стало необязательным в постоянном контексте в Dart 2.
Получение типа объекта

Чтобы получить тип объекта во время выполнения, вы можете использовать свойство объектов runtimeType, которое возвращает объект Type.

print('The type of a is ${a.runtimeType}');

До этого вы видели, как использовать классы. В оставшейся части этого раздела показано, как реализовать классы.

Переменные экземпляра

Вот как вы объявляете переменные экземпляра:

class Point {
  num x; // Declare instance variable x, initially null.
  num y; // Declare y, initially null.
  num z = 0; // Declare z, initially 0.
}

Все неинициализированные переменные экземпляра имеют значение null.

Все переменные экземпляра генерируют неявный метод получения. Не финальные переменные экземпляра также генерируют неявный метод установки. Для получения дополнительной информации см. геттеры и сеттеры.

class Point {
  num x;
  num y;
}

void main() {
  var point = Point();
  point.x = 4; // Use the setter method for x.
  assert(point.x == 4); // Use the getter method for x.
  assert(point.y == null); // Values default to null.
}

Если вы инициализируете переменную экземпляра там, где она объявлена ​​(а не в конструкторе или методе), значение устанавливается при создании экземпляра, то есть перед выполнением конструктора и его списка инициализатора.

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

Объявите конструктор, создав функцию с тем же именем, что и его класс (плюс, необязательно, дополнительный идентификатор, как описано в Именованных конструкторах). Наиболее распространенная форма конструктора, порождающий конструктор, создает новый экземпляр класса:

class Point {
  num x, y;

  Point(num x, num y) {
    // There's a better way to do this, stay tuned.
    this.x = x;
    this.y = y;
  }
}

Ключевое слово this ссылается на текущий экземпляр.

Примечание. Используйте this только в случае конфликта имен. В противном случае стиль Dart опускает это.

Шаблон назначения аргумента конструктора для переменной экземпляра настолько распространен, что у Dart есть синтаксический сахар, чтобы упростить его:

class Point {
  num x, y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}
Конструкторы по умолчанию

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

Конструкторы не наследуются

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

Именованные конструкторы

Используйте именованный конструктор для реализации нескольких конструкторов для класса или для дополнительной ясности:

class Point {
  num x, y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin() {
    x = 0;
    y = 0;
  }
}

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

Вызов конструктора суперкласса не по умолчанию

По умолчанию конструктор в подклассе вызывает неназванный конструктор суперкласса без аргументов. Конструктор суперкласса вызывается в начале тела конструктора. Если также используется список инициализаторов, он выполняется до вызова суперкласса. Таким образом, порядок выполнения выглядит следующим образом:

  1. список инициализаторов
  2. конструктор суперкласса без аргументов
  3. конструктор основного класса без аргументов

Если у суперкласса нет безымянного конструктора без аргументов, то вы должны вручную вызвать один из конструкторов в суперклассе. Укажите конструктор суперкласса после двоеточия (:), непосредственно перед телом конструктора (если есть).

В следующем примере конструктор для класса Employee вызывает именованный конструктор для своего суперкласса Person. Нажмите Run, чтобы выполнить код.

class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // Prints:
  // in Person
  // in Employee
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
}

Поскольку аргументы конструктора суперкласса оцениваются перед вызовом конструктора, аргумент может быть выражением, таким как вызов функции:

class Employee extends Person {
  Employee() : super.fromJson(getDefaultData());
  // ···
}
Предупреждение: аргументы конструктора суперкласса не имеют доступа к this. Например, аргументы могут вызывать статические методы, но не методы экземпляра.
Список инициализаторов

Помимо вызова конструктора суперкласса, вы также можете инициализировать переменные экземпляра до запуска тела конструктора. Разделяйте инициализаторы запятыми.

// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}
Предупреждение: правая часть инициализатора не имеет доступа к этому.

Во время разработки вы можете проверить входные данные, используя assert в списке инициализатора.

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}

Списки инициализатора удобны при настройке конечных полей. В следующем примере инициализируются три последних поля в списке инициализатора. Пример этого показан ниже

import 'dart:math';

class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = new Point(2, 3);
  print(p.distanceFromOrigin);
}
Перенаправление конструкторов

Иногда единственной целью конструктора является перенаправление в другой конструктор того же класса. Тело перенаправляющего конструктора пустое, а вызов конструктора появляется после двоеточия (:).

class Point {
  num x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(num x) : this(x, 0);
}
Константные конструкторы

Если ваш класс создает объекты, которые никогда не меняются, вы можете сделать эти объекты константами времени компиляции. Для этого определите конструктор const и убедитесь, что все переменные экземпляра являются финальными final.

class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final num x, y;

  const ImmutablePoint(this.x, this.y);
}

Константные конструкторы не всегда создают константы. Подробности смотрите в разделе об использовании конструкторов.

Фабричные конструкторы

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

В следующем примере демонстрируется конструктор фабрики, возвращающий объекты из кэша:

class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map _cache =
      {};

  factory Logger(String name) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}
Примечание: фабричные конструкторы не имеют доступ к this.

Вызовите конструктор фабрики так же, как любой другой конструктор:

var logger = Logger('UI');
logger.log('Button clicked');

Методы

Методы - это функции, которые обеспечивают поведение объекта.

Методы экземпляра

Методы экземпляра на объектах могут обращаться к переменным экземпляра, и this. Метод distanceTo() в следующем примере является примером метода экземпляра:

import 'dart:math';

class Point {
  num x, y;

  Point(this.x, this.y);

  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}
Геттеры и сеттеры

Методы получения и установки - это специальные методы, обеспечивающие доступ на чтение и запись к свойствам объекта. Напомним, что каждая переменная экземпляра имеет неявный метод получения, а также, в случае необходимости, метод установки. Вы можете создать дополнительные свойства, реализовав методы получения и установки, используя ключевые слова get и set:

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

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

Примечание. Такие операторы, как increment (++), работают ожидаемым образом, независимо от того, определен ли получатель явно. Чтобы избежать неожиданных побочных эффектов, оператор вызывает геттер ровно один раз, сохраняя его значение во временной переменной.
Абстрактные методы

Методы экземпляра, геттер и сеттер могут быть абстрактными, определяя интерфейс, но оставляя его реализацию другим классам. Абстрактные методы могут существовать только в абстрактных классах.

Чтобы сделать метод абстрактным, используйте точку с запятой (;) вместо тела метода:

abstract class Doer {
  // Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide an implementation, so the method is not abstract here...
  }
}
Абстрактные классы

Используйте модификатор abstract для определения абстрактного класса - класса, который не может быть создан. Абстрактные классы полезны для определения интерфейсов, часто с некоторой реализацией. Если вы хотите, чтобы ваш абстрактный класс казался инстанцируемым, определите конструктор фабрики.

Абстрактные классы часто имеют абстрактные методы. Вот пример объявления абстрактного класса, который имеет абстрактный метод:

// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
  // Define constructors, fields, methods...

  void updateChildren(); // Abstract method.
}
Неявные интерфейсы

Каждый класс неявно определяет интерфейс, содержащий все элементы экземпляра класса и любые интерфейсы, которые он реализует. Если вы хотите создать класс A, который поддерживает API класса B, не наследуя реализацию B, класс A должен реализовать интерфейс B.

Класс реализует один или несколько интерфейсов, объявив их в предложении Implements, а затем предоставив API-интерфейсы, требуемые интерфейсами. Например:

// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

  // In the interface.
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// An implementation of the Person interface.
class Impostor implements Person {
  get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}

Вот пример указания, что класс реализует несколько интерфейсов:

class Point implements Comparable, Location {...}
Расширение класса

Используйте extends для создания подкласса, и super для ссылки на суперкласс:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}
Переопределяющие члены

Подклассы могут переопределять методы экземпляра, методы получения и установки. Вы можете использовать аннотацию @override, чтобы указать, что вы намеренно переопределяете член:

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}

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

<+|[]
>/^[]=
<=~/&~
>=*<<==
%>> 
Примечание. Возможно, вы заметили, что != Не является переопределенным оператором. Выражение e1! = E2 является просто синтаксическим сахаром для !(E1 == e2).

Вот пример класса, который переопределяет операторы + и -:

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // Operator == and hashCode not shown. For details, see note below.
  // ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

Если вы переопределите ==, вам также следует переопределить метод получения hashCode объекта. Пример переопределения == и hashCode см. В разделе «Реализация ключей карты».

Для получения дополнительной информации о переопределении, в общем, см. Расширение класса.

noSuchMethod()

Чтобы обнаружить или отреагировать всякий раз, когда код пытается использовать несуществующий метод или переменную экземпляра, вы можете переопределить noSuchMethod():

class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

Вы не можете вызвать нереализованный метод, если не выполнено одно из следующих условий:

  • Приемник имеет статический тип динамический dynamic.
  • Получатель имеет статический тип, который определяет нереализованный метод (аннотация в порядке), а динамический тип получателя имеет реализацию noSuchMethod(), которая отличается от реализации в классе Object.

Для получения дополнительной информации см. Неофициальную спецификацию пересылки noSuchMethod.

Перечисляемые типы

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

Использование перечислений

Объявите перечислимый тип, используя ключевое слово enum:

enum Color { red, green, blue }

Каждое значение в перечислении имеет получатель индекса, который возвращает позицию значения, начинающуюся с нуля, в объявлении перечисления. Например, первое значение имеет индекс 0, а второе значение имеет индекс 1.

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

Чтобы получить список всех значений в перечислении, используйте константу значений values перечисления.

List colors = Color.values;
assert(colors[2] == Color.blue);

Вы можете использовать enums в выражениях switch и вы получите предупреждение, если вы не обработаете все значения enum:

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // Without this, you see a WARNING.
    print(aColor); // 'Color.blue'
}

Перечисляемые типы имеют следующие ограничения:

  • Вы не можете создавать подклассы, смешивать или реализовывать перечисление.
  • Вы не можете явно создать экземпляр enum.

Для получения дополнительной информации см. Спецификацию языка Dart.

Добавление возможностей в класс: mixins

Миксины - это способ многократного использования кода класса в нескольких иерархиях классов.

Чтобы использовать миксин, используйте ключевое слово with, за которым следует одно или несколько имен миксинов. В следующем примере показаны два класса, которые используют mixins:

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

Чтобы реализовать миксин, создайте класс, который расширяет Object и не объявляет конструкторов. Если вы не хотите, чтобы ваш миксин использовался как обычный класс, используйте ключевое слово mixin вместо class. Например:

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

Чтобы указать, что только некоторые типы могут использовать миксин - например, чтобы ваш миксин мог вызывать метод, который он не определяет - используйте on для указания требуемого суперкласса:

mixin MusicalPerformer on Musician {
  // ···
}
Примечание к версии: Поддержка ключевого слова mixin была введена в Dart 2.1. Код в более ранних выпусках обычно использовал abstract class. Дополнительную информацию об изменениях в миксинах 2.1 см. В журнале изменений Dart SDK и спецификации миксинов 2.1.

Переменные и методы класса

Используйте ключевое слово static для реализации общеклассовых переменных и методов.

Статические переменные

Статические переменные (переменные класса) полезны для общеклассового состояния и констант:

class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}

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

[pre] Примечание. На этой странице соблюдается рекомендация руководства по стилю о предпочтении lowerCamelCase для постоянных имен. [/pre]

Статические методы

Статические методы (методы класса) не работают с экземпляром и, следовательно, не имеют доступа к this. Например:

import 'dart:math';

class Point {
  num x, y;
  Point(this.x, this.y);

  static num distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}

[pre] Примечание: рассмотрите возможность использования функций верхнего уровня вместо статических методов для общих или широко используемых утилит и функций. [/pre]

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

Обобщения - Generics

Если вы посмотрите на документацию API для базового типа массива List, вы увидите, что тип на самом деле List<E>. Нотация <…> помечает List как универсальный (или параметризованный) тип - тип, имеющий параметры формального типа. По соглашению, большинство переменных типа имеют однобуквенные имена, такие как E, T, S, K и V.

Зачем использовать дженерики?

Обобщения часто требуются для обеспечения безопасности типов, но они имеют больше преимуществ, чем просто запуск кода:

  • Правильное указание универсальных типов приводит к лучшему сгенерированному коду.
  • Вы можете использовать дженерики для уменьшения дублирования кода.

Если вы хотите, чтобы список содержал только строки, вы можете объявить его как List <String> (читается как «список строк»). Таким образом, вы, ваши коллеги-программисты и ваши инструменты можете обнаружить, что присвоение нестрокового списка, вероятно, является ошибкой. Вот пример:

var names = List();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error

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

abstract class ObjectCache {
  Object getByKey(String key);
  void setByKey(String key, Object value);
}

Вы обнаружите, что вам нужна специфичная для строки версия этого интерфейса, поэтому вы создаете другой интерфейс:

abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}

Позже вы решите, что вам нужна специфичная для номера версия этого интерфейса ... Вы поняли идею.

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

abstract class Cache {
  T getByKey(String key);
  void setByKey(String key, T value);
}

В этом коде T является типом stand-in. Это заполнитель, который вы можете рассматривать как тип, который разработчик определит позже.

Использование литералов коллекции

Список, набор и литералы карты могут быть параметризованы. Параметризованные литералы аналогичны литералам, которые вы уже видели, за исключением того, что вы добавляете <type> (для списков и наборов) или <keyTypevalueType> (для карт) перед открывающей скобкой. Вот пример использования типизированных литералов:

var names = ['Seth', 'Kathy', 'Lars'];
var uniqueNames = {'Seth', 'Kathy', 'Lars'};
var pages = {
  'index.html': 'Homepage',
  'robots.txt': 'Hints for web robots',
  'humans.txt': 'We are people, not machines'
};

Использование параметризованных типов с конструкторами

Чтобы указать один или несколько типов при использовании конструктора, поместите типы в угловые скобки (<...>) сразу после имени класса. Например:

var nameSet = Set.from(names);

Следующий код создает карту с целочисленными ключами и значениями типа View:

var views = Map();

Коллекции обобщений и типы, которые они содержат

Универсальные типы Dart являются реализованными, что означает, что они переносят информацию своего типа во время выполнения. Например, вы можете проверить тип коллекции:

var names = List();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List); // true
Примечание. В отличие от универсальных в Java используется стирание, что означает, что параметры универсального типа удаляются во время выполнения. В Java вы можете проверить, является ли объект списком, но вы не можете проверить, является ли он списком List<String>.

Ограничение параметризованного типа

При реализации универсального типа вы можете захотеть ограничить типы его параметров. Вы можете сделать это, используя extends.

class Foo {
  // Implementation goes here...
  String toString() => "Instance of 'Foo<$T>'";
}

class Extender extends SomeBaseClass {...}

Можно использовать SomeBaseClass или любой из его подклассов в качестве универсального аргумента:

var someBaseClassFoo = Foo();
var extenderFoo = Foo();

Также можно указать общий аргумент:

var foo = Foo();
print(foo); // Instance of 'Foo'

Указание любого типа, отличного от SomeBaseClass, приводит к ошибке:

var foo = Foo<Object>();

Использование методов обобщений

Первоначально общая поддержка Dart была ограничена классами. Более новый синтаксис, называемый обобщенными методами, допускает аргументы типа для методов и функций:

T first(List ts) {
  // Do some initial work or error checking, then...
  T tmp = ts[0];
  // Do some additional checking or processing...
  return tmp;
}

Здесь параметр общего типа для first (<T>) позволяет вам использовать аргумент типа T в нескольких местах:

  • В возвращаемом типе функции (T).
  • В типе аргумента (List <T>).
  • В типе локальной переменной (T tmp).

Для получения дополнительной информации о дженериках см. Использование универсальных методов.

Библиотеки и видимость

Директивы import и library могут помочь вам создать модульную и разделяемую базу кода. Библиотеки не только предоставляют API-интерфейсы, но и являются элементом конфиденциальности: идентификаторы, которые начинаются со знака подчеркивания (_), видны только внутри библиотеки. Каждое приложение Dart является библиотекой, даже если оно не использует директиву библиотеки library.

Библиотеки могут распространяться с использованием пакетов.

Использование библиотек

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

Например, веб-приложения Dart обычно используют библиотеку dart: html, которую они могут импортировать следующим образом:

import 'dart:html';
Примечание: URI обозначает унифицированный идентификатор ресурса. URL-адреса (унифицированные указатели ресурсов) являются распространенным типом URI.
Указание префикса библиотеки

Если вы импортируете две библиотеки с конфликтующими идентификаторами, вы можете указать префикс для одной или обеих библиотек. Например, если library1 и library2 оба имеют класс Element, то у вас может быть такой код:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// Uses Element from lib1.
Element element1 = Element();

// Uses Element from lib2.
lib2.Element element2 = lib2.Element();
Импорт только части библиотеки

Если вы хотите использовать только часть библиотеки, вы можете выборочно импортировать библиотеку. Например:

// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
Библиотека с отложенной загрузкой(lazily loading)

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

  • Чтобы уменьшить начальное время запуска веб-приложения.
  • Выполнить A / B-тестирование - например, попробовать альтернативные реализации алгоритма.
  • Для загрузки редко используемых функций, таких как дополнительные экраны и диалоги.
Только dart2js поддерживает отложенную загрузку. Flutter, Dart VM и dartdevc не поддерживают отложенную загрузку. Для получения дополнительной информации см. Выпуск № 33118 и выпуск № 27776.

Чтобы отложено загрузить библиотеку, вы должны сначала импортировать ее, используя deferred as.

import 'package:greetings/hello.dart' deferred as hello;

Когда вам нужна библиотека, вызовите loadLibrary(), используя идентификатор библиотеки.

Future greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}

В предыдущем коде ключевое слово await приостанавливает выполнение до загрузки библиотеки. Для получения дополнительной информации об async и await см. Поддержку асинхронности.

Вы можете вызывать loadLibrary() несколько раз в библиотеке без проблем. Библиотека загружается только один раз.

При использовании отложенной загрузки помните следующее:

  • Константы отложенной библиотеки не являются константами в импортируемом файле. Помните, что эти константы не существуют, пока не будет загружена отложенная библиотека.
  • Вы не можете использовать типы из отложенной библиотеки в импортируемом файле. Вместо этого рассмотрите возможность перемещения типов интерфейса в библиотеку, импортированную как отложенной библиотекой, так и импортирующим файлом.
  • Dart неявно вставляет loadLibrary() в пространство имен, которое вы определяете, используя deferred as namespace. Функция loadLibrary() возвращает Future.

Реализация библиотек

Посмотрите Создание Библиотечных пакетов для совета относительно того, как реализовать пакет библиотеки, включая:

  • Как организовать исходный код библиотеки.
  • Как использовать директиву export.
  • Когда использовать директиву part.
  • Когда использовать директиву library.

Поддержка асинхронности

Библиотеки Dart полны функций, которые возвращают объекты Future или Stream. Эти функции являются асинхронными: они возвращаются после настройки, возможно, трудоемкой операции (например, ввода-вывода), не дожидаясь завершения этой операции.

Ключевые слова async и await поддерживают асинхронное программирование, позволяя вам писать асинхронный код, который похож на синхронный код.

Обработка фьючерсов

Когда вам нужен результат завершенного будущего, у вас есть два варианта:

Код, который использует async и await, является асинхронным, но очень похож на синхронный код. Например, вот код, который использует await для ожидания результата асинхронной функции:

await lookUpVersion();

Чтобы использовать await, код должен быть в функции async - функции, помеченной как async:

Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}
Примечание. Хотя функция async может выполнять длительные операции, она не ожидает этих операций. Вместо этого функция async выполняется только до тех пор, пока не встретит свое первое выражение ожидания (подробности). Затем он возвращает объект Future, возобновляя выполнение только после завершения выражения await.

Используйте try, catch и finally для обработки ошибок и очистки в коде, который использует await:

try {
  version = await lookUpVersion();
} catch (e) {
  // React to inability to look up the version
}

Вы можете использовать await несколько раз в функции async. Например, следующий код трижды ожидает результатов функций:

var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);

В await expression, значение expression обычно является будущим; если это не так, то значение автоматически переносится в будущее. Этот объект Future указывает на обещание вернуть объект. Значением await epression является возвращаемый объект. Выражение await останавливает выполнение до тех пор, пока этот объект не станет доступен.

Если вы получаете ошибку времени компиляции при использовании await, убедитесь, что await находится в функции async. Например, чтобы использовать await в функции main() вашего приложения, тело main() должно быть помечено как async:

Future main() async {
  checkVersion();
  print('In main: version is ${await lookUpVersion()}');
}

Объявление асинхронных функций

Функция async - это функция, тело которой помечено модификатором асинхронности async.

Добавление ключевого слова async в функцию возвращает Future. Например, рассмотрим эту синхронную функцию, которая возвращает строку:

String lookUpVersion() => '1.0.0';

Если вы измените его на асинхронную функцию через async- например, потому что будущая реализация будет занимать много времени - возвращаемое значение будет Future:

Future lookUpVersion() async => '1.0.0';

Обратите внимание, что телу функции не нужно использовать Future API. Dart создает объект Future, если это необходимо. Если ваша функция не возвращает полезное значение, сделайте возвращаемый тип Future <void>.

Интерактивное введение в использование futures, async и await см. В разделе кодовой метки асинхронного программирования.

Обработка потоков

Когда вам нужно получить значения из потока, у вас есть два варианта:

  • Используйте async и асинхронный цикл for (await for).
  • Используйте Stream API, как описано в обзоре библиотеки.
Примечание. Перед использованием await for убедитесь, что это делает код более понятным и что вы действительно хотите дождаться всех результатов потока. Например, вам обычно не следует использовать await for для слушателей событий пользовательского интерфейса, поскольку платформы пользовательского интерфейса отправляют бесконечные потоки событий.

Асинхронный цикл for имеет следующую форму:

await for (varOrType identifier in expression) {
  // Executes each time the stream emits a value.
}

Значение выражения expression должно иметь тип Stream. Исполнение происходит следующим образом:

  1. Подождите, пока поток не выдаст значение.
  2. Выполнить тело цикла for с переменной, установленной на это значение.
  3. Повторите 1 и 2, пока поток не будет закрыт.

Чтобы прекратить прослушивание потока, вы можете использовать оператор break или return, который выходит из цикла for и отписывается от потока.

Если вы получили ошибку во время компиляции при реализации асинхронного цикла for, убедитесь, что await for находится в асинхронной функции async. Например, чтобы использовать асинхронный цикл for в функции main() вашего приложения, тело main() должно быть помечено как async:

Future main() async {
  // ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  // ...
}

Для получения дополнительной информации об асинхронном программировании, в общем, смотрите раздел dart:async в обзоре библиотеки.

Генераторы

Когда вам нужно лениво создать последовательность значений, подумайте об использовании функции генератора. Дарт имеет встроенную поддержку двух видов функций генератора:

  • Синхронный генератор: возвращает итерируемый объект Iterable.
  • Асинхронный генератор: возвращает потоковый объект Stream Stream.

Чтобы реализовать функцию синхронного генератора, пометьте тело функции как sync * и используйте операторы yield для доставки значений:

Iterable naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

Чтобы реализовать функцию асинхронного генератора, пометьте тело функции как async * и используйте операторы yield для доставки значений:

Stream asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

Если ваш генератор рекурсивный, вы можете улучшить его производительность, используя yield *:

Iterable naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

Вызываемые классы

Чтобы позволить экземпляру вашего класса Dart вызываться как функция, реализуйте метод call().

В следующем примере класс WannabeFunction определяет функцию call(), которая принимает три строки и объединяет их, разделяя каждую пробелом и добавляя восклицательный знак. Ниже представлен код, в виде примера

class WannabeFunction {
  call(String a, String b, String c) => '$a $b $c!';
}

main() {
  var wf = new WannabeFunction();
  var out = wf("Hi", "there,", "gang");
  print('$out');
}

Изоляты

Большинство компьютеров, даже на мобильных платформах, имеют многоядерные процессоры. Чтобы воспользоваться всеми этими ядрами, разработчики традиционно используют потоки совместно используемой памяти, работающие одновременно. Однако параллелизм общего состояния подвержен ошибкам и может привести к сложному коду.

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

Для получения дополнительной информации см. Следующее:

Определения типов Typedefs

В Dart функции являются объектами, так же как строки и числа являются объектами. Typedef, или псевдоним типа функции, дает типу функции имя, которое вы можете использовать при объявлении полей и возвращаемых типов.

Рассмотрим следующий код, который не использует typedef:

class SortedCollection {
  Function compare;

  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}

// Initial, broken implementation.
int sort(Object a, Object b) => 0;

void main() {
  SortedCollection coll = SortedCollection(sort);

  // All we know is that compare is a function,
  // but what type of function?
  assert(coll.compare is Function);
}

Информация о типе теряется при назначении f для compare. Тип f - (Object, Object)int (где → означает возврат), но тип сравнения compare - Function. Если мы изменим код, чтобы использовать явные имена и сохранили информацию о типе, и разработчики, и инструменты могут использовать эту информацию.

typedef Compare = int Function(Object a, Object b);

class SortedCollection {
  Compare compare;

  SortedCollection(this.compare);
}

// Initial, broken implementation.
int sort(Object a, Object b) => 0;

void main() {
  SortedCollection coll = SortedCollection(sort);
  assert(coll.compare is Function);
  assert(coll.compare is Compare);
}
Примечание. В настоящее время typedefs ограничены типами функций. Мы ожидаем, что это изменится.

Поскольку typedefs являются просто псевдонимами, они предлагают способ проверки типа любой функции. Например:

typedef Compare = int Function(T a, T b);

int sort(int a, int b) => a - b;

void main() {
  assert(sort is Compare); // True!
}

Метаданные

Используйте метаданные для предоставления дополнительной информации о вашем коде. Аннотация метаданных начинается с символа @, за которым следует либо ссылка на константу времени компиляции ( такие как deprecated), либо обращение к конструктору констант.

Для всего кода Dart доступны две аннотации: @deprecated и @override. Примеры использования @override см. В разделе Расширение класса. Вот пример использования аннотации @deprecated:

class Television {
  /// _Deprecated: Use [turnOn] instead._
  @deprecated
  void activate() {
    turnOn();
  }

  /// Turns the TV's power on.
  void turnOn() {...}
}

Вы можете определить свои собственные аннотации метаданных. Вот пример определения аннотации @todo, которая принимает два аргумента:

library todo;

class Todo {
  final String who;
  final String what;

  const Todo(this.who, this.what);
}

И вот пример использования этой аннотации @todo:

import 'todo.dart';

@Todo('seth', 'make this do something')
void doSomething() {
  print('do something');
}

Метаданные могут появляться перед объявлением library, class, typedef, параметра типа, конструктора, фабрики, функции, поля, параметра или переменной, а также перед директивой импорта или экспорта. Вы можете получить метаданные во время выполнения, используя отражение.

Комментарии

Dart поддерживает однострочные комментарии, многострочные комментарии и комментарии к документации.

Однострочные комментарии

Однострочный комментарий начинается с //. Все между // и концом строки игнорируется компилятором Dart.

void main() {
  // TODO: refactor into an AbstractLlamaGreetingFactory?
  print('Welcome to my Llama farm!');
}

Многострочные комментарии

Многострочный комментарий начинается с /* и заканчивается */. Все между /* и */ игнорируется компилятором Dart (если комментарий не является комментарием к документации; см. Следующий раздел). Многострочные комментарии могут быть вложенными.

void main() {
  /*
   * This is a lot of work. Consider raising chickens.

  Llama larry = Llama();
  larry.feed();
  larry.exercise();
  larry.clean();
   */
}

Комментарии к документации

Комментарии к документации - это многострочные или однострочные комментарии, начинающиеся с /// или / **. Использование /// в последовательных строках имеет тот же эффект, что и многострочный комментарий к документу.

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

Вот пример комментариев документации со ссылками на другие классы и аргументы:

/// A domesticated South American camelid (Lama glama).
///
/// Andean cultures have used llamas as meat and pack
/// animals since pre-Hispanic times.
class Llama {
  String name;

  /// Feeds your llama [Food].
  ///
  /// The typical llama eats one bale of hay per week.
  void feed(Food food) {
    // ...
  }

  /// Exercises your llama with an [activity] for
  /// [timeLimit] minutes.
  void exercise(Activity activity, int timeLimit) {
    // ...
  }
}

В сгенерированной документации [Food] становится ссылкой на документы API для класса Food.

Для анализа кода Dart и генерации HTML-документации вы можете использовать инструмент генерации документации SDK. Пример сгенерированной документации см. В документации по Dart API. Для получения советов о том, как структурировать ваши комментарии, см. Рекомендации для комментариев Dart Doc.

Резюме

На этой странице представлены наиболее часто используемые функции языка дартс. Реализуются дополнительные функции, но мы ожидаем, что они не нарушат существующий код. Для получения дополнительной информации см. Спецификацию языка Dart и Effective Dart.

Чтобы узнать больше об основных библиотеках Dart, см. Тур по библиотекам Dart.