Если вы посмотрите на документацию API для базового типа массива List
, вы увидите, что тип на самом деле List<E>
. Нотация <…>
помечает List
как универсальный (или параметризованный) тип — тип, имеющий параметры формального типа. По соглашению, большинство переменных типа имеют однобуквенные имена, такие как E
, T
, S
, K
и V
.
Зачем использовать дженерики?
Обобщения часто требуются для обеспечения безопасности типов, но они имеют больше преимуществ, чем просто запуск кода:
- Правильное указание универсальных типов приводит к лучшему сгенерированному коду.
- Вы можете использовать дженерики для уменьшения дублирования кода.
Если вы хотите, чтобы список содержал только строки, вы можете объявить его как List<String>
(читается как «список строк»). Таким образом, вы, ваши коллеги-программисты и ваши инструменты можете обнаружить, что присвоение нестрокового списка, вероятно, является ошибкой. Вот пример:
var names = List<String>();
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> {
T getByKey(String key);
void setByKey(String key, T value);
}
В этом коде T
является типом stand-in. Это заполнитель, который вы можете рассматривать как тип, который разработчик определит позже.
Использование литералов коллекции
Список, набор и литералы карты могут быть параметризованы. Параметризованные литералы аналогичны литералам, которые вы уже видели, за исключением того, что вы добавляете <
type
>
(для списков и наборов) или <
keyType
,
valueType
>
(для карт) перед открывающей скобкой. Вот пример использования типизированных литералов:
var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
Использование параметризованных типов с конструкторами
Чтобы указать один или несколько типов при использовании конструктора, поместите типы в угловые скобки (<...>
) сразу после имени класса. Например:
var nameSet = Set<String>.from(names);
Следующий код создает карту с целочисленными ключами и значениями типа View:
var views = Map<int, View>();
Коллекции обобщений и типы, которые они содержат
Универсальные типы Dart являются реализованными, что означает, что они переносят информацию своего типа во время выполнения. Например, вы можете проверить тип коллекции:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
Ограничение параметризованного типа
При реализации универсального типа вы можете захотеть ограничить типы его параметров. Вы можете сделать это, используя extends
.
class Foo<t extends="" somebaseclass=""> {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
Можно использовать SomeBaseClass
или любой из его подклассов в качестве универсального аргумента:
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<extender>();
Также можно указать общий аргумент:
var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'
Указание любого типа, отличного от SomeBaseClass, приводит к ошибке:
var foo = Foo<Object>();
Использование методов обобщений
Первоначально общая поддержка Dart была ограничена классами. Более новый синтаксис, называемый обобщенными методами, допускает аргументы типа для методов и функций:
T first<T>(List<T> 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
).
Для получения дополнительной информации о дженериках см. Использование универсальных методов.