Ответ
Generics в Dart — это реализация параметрического полиморфизма. Они позволяют писать код, который может работать с разными типами данных, оставаясь типобезопасным. Один алгоритм или структура данных становится полиморфной по отношению к типу своих элементов.
Без Generics (отсутствие полиморфизма или небезопасно):
// 1. Код дублируется для каждого типа (нет полиморфизма).
class IntBox { int value; IntBox(this.value); }
class StringBox { String value; StringBox(this.value); }
// 2. Использование dynamic/Object (полиморфно, но небезопасно).
class DynamicBox {
dynamic value; // Потеряна безопасность типов.
DynamicBox(this.value);
}
С Generics (параметрический полиморфизм с безопасностью типов):
// Один полиморфный класс для множества типов.
class Box<T> {
final T value;
Box(this.value);
T getValue() => value;
}
void main() {
// Компилятор выводит конкретный тип для каждого экземпляра.
final intBox = Box<int>(42); // Box<int>
final stringBox = Box('Hello'); // Box<String> (тип выведен)
// Безопасность типов сохраняется.
int number = intBox.getValue(); // OK
// String text = intBox.getValue(); // Ошибка компиляции: type 'int' is not a subtype of 'String'
}
Связь с другими видами полиморфизма в Dart:
- Полиморфизм подтипов (наследование):
class SiberianHusky extends Dog—SiberianHuskyможет использоваться везде, где ожидаетсяDog. - Параметрический полиморфизм (Generics):
List<Dog>может содержатьSiberianHusky,Beagleи т.д., потому что они — подтипыDog. Это показывает, как Generics взаимодействуют с полиморфизмом подтипов.
Практический пример из Flutter:
// Виджет FutureBuilder полиморфен относительно типа данных Future.
FutureBuilder<List<Post>>(
future: _fetchPosts(), // Future<List<Post>>
builder: (context, snapshot) {
if (snapshot.hasData) {
// snapshot.data имеет тип List<Post>? благодаря generics.
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (ctx, i) => PostWidget(snapshot.data![i]), // Тип Post известен.
);
}
return CircularProgressIndicator();
},
)
Таким образом, Generics делают код полиморфным (работает со многими типами), строго типизированным (ошибки ловятся на этапе компиляции) и избегающим дублирования.
Ответ 18+ 🔞
Э, слушай, вот объясняю про дженерики в Dart, как будто это не какая-то академическая хуйня, а реально полезная штука. Представь себе, что ты пытаешься запихнуть в одну коробку и бутерброд, и молоток, и кота. Без дженериков у тебя есть три пути, и все они — пиздец.
Путь первый, идиотский: заведи отдельную коробку под каждый предмет. Это как если бы ты для целых чисел писал один класс, а для строк — другой, абсолютно такой же, но с другим названием. Ну, ёпта, реально? Овердохуища одинакового кода, а толку — ноль. Чистое безумие.
class IntBox { int value; IntBox(this.value); }
class StringBox { String value; StringBox(this.value); }
Путь второй, опасный: возьми одну коробку, но кинь туда всё, что попало.
Типа dynamic или Object. Вроде бы полиморфизм есть — одна коробка на все случаи жизни. Но безопасность типов накрылась медным тазом. Вытащишь ты оттуда, ожидая бутерброд, а там — кот, который цапнет тебя за руку уже в рантайме. Доверия к такому коду — ебать ноль.
class DynamicBox {
dynamic value; // Типа "а хрен его знает, что тут лежит"
DynamicBox(this.value);
}
А теперь, внимание, путь третий, магический — дженерики. Вот это уже хитрая жопа. Ты создаёшь одну умную коробку, но говоришь ей: «Слушай, коробка, я пока не знаю, что в тебя положат. Может, число, может, строка. Но когда положат — запомни этот тип и больше с ним не хулигань».
class Box<T> { // Смотри-ка, буква T. Это типа "тип, который придёт позже"
final T value;
Box(this.value);
T getValue() => value; // И отдавать будешь именно его, а не какую-то хуйню
}
void main() {
final intBox = Box<int>(42); // Всё, коробка запечатана. Только для int.
final stringBox = Box('Hello'); // Тут компилятор сам догадался, что это String.
int number = intBox.getValue(); // Всё ок, мы договорились, что тут число.
// String text = intBox.getValue(); // А вот это — НИХУЯ! Ошибка компиляции. Спасибо, дженерики!
}
Как это связано с другими плюшками Dart?
Ну, смотри. Есть полиморфизм через наследование: SiberianHusky — это подтип Dog. Так вот, дженерики с этим дружат. Если у тебя есть List<Dog>, то ты можешь запихнуть туда и SiberianHusky, и Beagle. Потому что они все — собаки. Дженерики не ломают иерархию типов, а используют её. Это, блядь, элегантно.
Ну и где это в Flutter'е? Да везде, ебать копать!
Самый простой пример — FutureBuilder. Без дженериков он был бы просто FutureBuilder, который возвращает dynamic, и ты бы каждый раз гадал, что там в snapshot.data. А так — красота.
FutureBuilder<List<Post>>( // Говорим прямо: ждём мы Future, который вернёт List<Post>
future: _fetchPosts(),
builder: (context, snapshot) {
if (snapshot.hasData) {
// Ура! Компилятор и IDE знают, что snapshot.data — это List<Post>?
// И автодополнение работает, и если ошибся — сразу подчеркнёт.
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (ctx, i) => PostWidget(snapshot.data![i]), // Тип Post известен, всё безопасно.
);
}
return CircularProgressIndicator();
},
)
Короче, суть в чём: дженерики — это не про сложность, а про простоту. Один раз написал умный код, который работает с любым типом, но при этом не превращается в тыкву с дырой вместо безопасности. И дублировать ничего не надо. Пизда рулю, а не фича.