Dart: Generics em Dart
Última atualização: 2026-08-26
Generics são templates para código — escreva uma vez, reutilize para muitos tipos, e o compilador garante a segurança de tipos para você.
1. O que Você Aprenderá
- Definindo classes genéricas e funções genéricas
- Restrições genéricas:
extendse múltiplos limites - Colaboração entre generics e coleções
- Covariância e contravariância (limites de tipo)
- Cenário do Bob: Transformador genérico de dados
DataTransformer<T, R>no DataPipeline
2. A História Real de um Desenvolvedor
(1) A Dor: Pesadelo de Manutenção por Código Duplicado
Charlie escreveu parsers separados para cada tipo de dados no DataPipeline: OrderParser, ProductParser, CustomerParser. A lógica em todos os três parsers era quase idêntica (ler → validar → transformar), diferindo apenas nos tipos de entrada/saída. Toda vez que a lógica de análise precisava de modificação, era necessário alterar em 3 lugares. Uma vez, uma alteração esquecida fez com que o parser de Product não aplicasse as novas regras de validação.
(2) A Solução com Generics
Unificar os três parsers usando uma classe genérica DataTransformer<T, R>. Os parâmetros de tipo permitem que o compilador garanta a segurança de tipos, e o código precisa ser mantido em apenas um lugar.
abstract class DataTransformer<T, R> {
R transform(T input);
List``<R>`` batch(List``<T>`` inputs) => inputs.map(transform).toList();
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
(3) Os Benefícios
- 3 parsers mesclados em 1 classe genérica, reduzindo o volume de código em 70%
- Modificar a lógica de validação requer alteração em apenas um lugar; todos os tipos são atualizados automaticamente
- O compilador garante a segurança de tipos, impedindo a mistura de Orders com Products
3. Fundamentos de Generics
(1) Por Que Generics São Necessários
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: Problemas sem generics
// Sem generics - List``<dynamic>`` perde a segurança de tipos
void main() {
List amounts = [1500, 3200, 890]; // List``<dynamic>``
amounts.add('not a number'); // Sem erro de compilação!
for (final a in amounts) {
print((a as int) * 2); // Crash em runtime com 'not a number'
}
}
// Com generics - segurança de tipos em tempo de compilação
void main() {
List``<int>`` amounts = [1500, 3200, 890];
// amounts.add('not a number'); // Erro de compilação!
for (final a in amounts) {
print(a * 2); // Seguro - o tipo é conhecido
}
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
4. Classes Genéricas
(1) Definição de Classe Genérica
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: Classe container genérica
class Result``<T>`` {
final T? data;
final String? error;
final bool isSuccess;
Result.success(this.data)
: error = null,
isSuccess = true;
Result.failure(this.error)
: data = null,
isSuccess = false;
// Método que retorna o tipo genérico
T get dataOrThrow {
if (isSuccess && data != null) return data;
throw Exception(error ?? 'Unknown error');
}
// Transformar o valor de sucesso
Result``<R>`` map``<R>``(R Function(T) fn) {
if (isSuccess && data != null) {
return Result.success(fn(data));
}
return Result.failure(error);
}
}
void main() {
final success = Result.success(1500.0);
final failure = Result``<double>``.failure('Network timeout');
print(success.data); // 1500.0
print(failure.error); // Network timeout
// Map transforma o valor de sucesso
final formatted = success.map((v) => '\$${v.toStringAsFixed(2)} USD');
print(formatted.data); // $1500.00 USD
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: Classe de cache genérica
class Cache``<T>`` {
final Map<String, T> _store = {};
final Duration _ttl;
Cache({Duration ttl = const Duration(minutes: 5)}) : _ttl = ttl;
void put(String key, T value) => _store[key] = value;
T? get(String key) => _store[key];
bool contains(String key) => _store.containsKey(key);
void remove(String key) => _store.remove(key);
void clear() => _store.clear();
}
void main() {
final orderCache = Cache<Map<String, dynamic>>();
orderCache.put('ORD-001', {'amount': 1500.0, 'status': 'completed'});
print(orderCache.get('ORD-001')); // {amount: 1500.0, status: completed}
print(orderCache.contains('ORD-002')); // false
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
5. Funções Genéricas
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: Definição de função genérica
// Função genérica com um único parâmetro de tipo
T firstOrNull``<T>``(List``<T>`` items) => items.isEmpty ? throw StateError('Empty list') : items.first;
// Função genérica com dois parâmetros de tipo
R transform<T, R>(T input, R Function(T) converter) => converter(input);
// Função genérica com restrição
double sumNumbers<T extends num>(List``<T>`` items) =>
items.fold(0.0, (sum, item) => sum + item.toDouble());
void main() {
print(firstOrNull(['ORD-001', 'ORD-002'])); // ORD-001
print(firstOrNull([1500, 3200])); // 1500
final formatted = transform(1500.0, (v) => '\$${v} USD');
print(formatted); // $1500.0 USD
print(sumNumbers([1, 2, 3, 4, 5])); // 15.0
print(sumNumbers([1.5, 2.5, 3.0])); // 7.0
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: Funções utilitárias genéricas
// Utilitário de cast seguro
T? tryCast``<T>``(dynamic value) {
if (value is T) return value;
return null;
}
// Particionar uma lista em dois grupos
(List``<T>``, List``<T>``) partition``<T>``(List``<T>`` items, bool Function(T) predicate) {
final matching = ``<T>``[];
final notMatching = ``<T>``[];
for (final item in items) {
(predicate(item) ? matching : notMatching).add(item);
}
return (matching, notMatching);
}
void main() {
// Cast seguro
final intVal = tryCast``<int>``('hello'); // null
final strVal = tryCast``<String>``('hello'); // hello
// Particionar
final (high, low) = partition([1500.0, 50.0, 3200.0, 890.0], (v) => v >= 1000);
print('High: $high'); // [1500.0, 3200.0]
print('Low: $low'); // [50.0, 890.0]
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
6. Restrições Genéricas
(1) Restrição extends Única
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: Restrição extends
// Aceita apenas tipos que extendem num
double average<T extends num>(List``<T>`` values) {
if (values.isEmpty) return 0;
final sum = values.fold``<num>``(0, (a, b) => a + b);
return sum / values.length;
}
// Aceita apenas tipos que implementam Comparable
T findMax<T extends Comparable>(List``<T>`` items) {
if (items.isEmpty) throw StateError('Empty list');
return items.reduce((a, b) => a.compareTo(b) >= 0 ? a : b);
}
void main() {
print(average([10, 20, 30])); // 20.0
print(average([1.5, 2.5, 3.0])); // 2.333...
print(findMax(['banana', 'apple', 'cherry'])); // cherry
print(findMax([10, 5, 8])); // 10
// average(['a', 'b']); // Erro de compilação! String não extends num
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
(2) Múltiplas Restrições
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: Múltiplas restrições
// Múltiplas restrições usando interseção
class SortedCollection<T extends Comparable``<T>``> {
final List``<T>`` _items = [];
void add(T item) {
final index = _items.indexWhere((e) => item.compareTo(e) <= 0);
if (index == -1) {
_items.add(item);
} else {
_items.insert(index, item);
}
}
List``<T>`` get items => List.unmodifiable(_items);
}
void main() {
final sorted = SortedCollection``<String>``();
sorted.add('cherry');
sorted.add('apple');
sorted.add('banana');
print(sorted.items); // [apple, banana, cherry]
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
7. Colaboração entre Generics e Coleções
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: Operações tipo-seguras em coleções
// Armazenamento chave-valor tipo-seguro
class KeyValueStore<K, V> {
final Map<K, V> _data = {};
void put(K key, V value) => _data[key] = value;
V? get(K key) => _data[key];
// Converter todos os valores
Map<K, R> mapValues``<R>``(R Function(V) converter) {
return _data.map((key, value) => MapEntry(key, converter(value)));
}
// Filtrar por tipo de chave
Map<K, V> whereKey(bool Function(K) predicate) {
return Map.fromEntries(
_data.entries.where((e) => predicate(e.key)),
);
}
}
void main() {
final store = KeyValueStore<String, double>();
store.put('Electronics', 3600.0);
store.put('Books', 170.0);
store.put('Clothing', 890.0);
// Mapear valores para strings formatadas
final formatted = store.mapValues((v) => '\$${v.toStringAsFixed(2)} USD');
print(formatted); // {Electronics: $3600.00 USD, Books: $170.00 USD, Clothing: $890.00 USD}
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
8. Cenário do Bob: Transformador de Dados Genérico
classDiagram
class DataTransformer~T, R~ {
+transform(T input) R
+batch(List~T~ inputs) List~R~
+validate(T input) bool
}
class OrderParser {
+transform(String) Order
}
DataTransformer <|-- OrderParser
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: Framework genérico DataTransformer
// Interface base do transformador
abstract class DataTransformer<T, R> {
R transform(T input);
String get name;
List``<R>`` batch(List``<T>`` inputs) {
final results = ``<R>``[];
final errors = <(int, String)>[];
for (var i = 0; i < inputs.length; i++) {
try {
if (validate(inputs[i])) {
results.add(transform(inputs[i]));
}
} catch (e) {
errors.add((i, e.toString()));
}
}
if (errors.isNotEmpty) {
print('$name: ${errors.length} errors during batch transform');
}
return results;
}
bool validate(T input) => true;
}
// Transformador concreto: String CSV line → Order
class Order {
final String id;
final double amount;
Order({required this.id, required this.amount});
@override
String toString() => 'Order($id, \$${amount.toStringAsFixed(2)})';
}
class CsvOrderTransformer extends DataTransformer<String, Order> {
@override
String get name => 'CsvOrderTransformer';
@override
bool validate(String input) {
final parts = input.split(',');
return parts.length >= 2;
}
@override
Order transform(String input) {
final parts = input.split(',');
return Order(
id: parts[0].trim(),
amount: double.parse(parts[1].trim()),
);
}
}
void main() {
final transformer = CsvOrderTransformer();
final csvLines = [
'ORD-001, 1500.0',
'ORD-002, 3200.0',
'ORD-003, 890.0',
];
final orders = transformer.batch(csvLines);
print('Transformed ${orders.length} orders:');
for (final order in orders) {
print(' $order');
}
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
9. Exemplo Completo: Framework de Processamento Genérico do DataPipeline
// ============================================
// DataPipeline Generic Processing Framework
// Pipeline de transformação de dados tipo-seguro
// ============================================
// Tipo Result para tratamento seguro de erros
class Result``<T>`` {
final T? data;
final String? error;
Result.success(this.data) : error = null;
Result.failure(this.error) : data = null;
bool get isSuccess => data != null;
Result``<R>`` map``<R>``(R Function(T) fn) {
if (isSuccess && data != null) return Result.success(fn(data!));
return Result.failure(error);
}
}
// Transformador genérico
abstract class DataTransformer<T, R> {
R transform(T input);
String get name;
bool validate(T input) => true;
Result``<R>`` safeTransform(T input) {
try {
if (!validate(input)) {
return Result.failure('$name: Validation failed');
}
return Result.success(transform(input));
} catch (e) {
return Result.failure('$name: $e');
}
}
List<Result``<R>``> batch(List``<T>`` inputs) =>
inputs.map(safeTransform).toList();
}
// Agregador genérico
abstract class DataAggregator<T, R> {
R aggregate(List``<T>`` items);
String get name;
}
// Agregador de soma para tipos numéricos
class SumAggregator<T extends num> extends DataAggregator<T, double> {
@override
String get name => 'SumAggregator';
@override
double aggregate(List``<T>`` items) =>
items.fold(0.0, (sum, item) => sum + item.toDouble());
}
// Agregador de média
class AverageAggregator<T extends num> extends DataAggregator<T, double> {
@override
String get name => 'AverageAggregator';
@override
double aggregate(List``<T>`` items) =>
items.isEmpty ? 0 : items.fold(0.0, (s, i) => s + i.toDouble()) / items.length;
}
// Pipeline que encadeia transformadores
class Pipeline<I, M, O> {
final DataTransformer<I, M> _first;
final DataTransformer<M, O> _second;
Pipeline(this._first, this._second);
List<Result``<O>``> process(List``<I>`` inputs) {
final midResults = _first.batch(inputs);
final midValues = midResults.where((r) => r.isSuccess).map((r) => r.data!).toList();
return _second.batch(midValues);
}
}
// Tipos concretos
class Order {
final String id;
final double amount;
Order({required this.id, required this.amount});
@override
String toString() => 'Order($id, \$${amount.toStringAsFixed(2)})';
}
class OrderSummary {
final String id;
final String tier;
OrderSummary({required this.id, required this.tier});
@override
String toString() => 'OrderSummary($id, $tier)';
}
class CsvToOrder extends DataTransformer<String, Order> {
@override
String get name => 'CsvToOrder';
@override
Order transform(String input) {
final parts = input.split(',');
return Order(id: parts[0].trim(), amount: double.parse(parts[1].trim()));
}
@override
bool validate(String input) => input.split(',').length >= 2;
}
class OrderToSummary extends DataTransformer<Order, OrderSummary> {
@override
String get name => 'OrderToSummary';
@override
OrderSummary transform(Order input) {
final tier = input.amount >= 1000 ? 'Premium' : 'Standard';
return OrderSummary(id: input.id, tier: tier);
}
}
void main() {
final csvLines = [
'ORD-001, 1500.0',
'ORD-002, 50.0',
'ORD-003, 3200.0',
];
// Transformador simples
final parser = CsvToOrder();
final orders = parser.batch(csvLines);
print('=== Parsed Orders ===');
for (final result in orders) {
if (result.isSuccess) {
print(' ${result.data}');
} else {
print(' Error: ${result.error}');
}
}
// Pipeline: CSV → Order → Summary
final pipeline = Pipeline<String, Order, OrderSummary>(
CsvToOrder(), OrderToSummary());
final summaries = pipeline.process(csvLines);
print('\n=== Order Summaries ===');
for (final result in summaries) {
if (result.isSuccess) print(' ${result.data}');
}
// Agregação
final validAmounts = orders
.where((r) => r.isSuccess)
.map((r) => r.data!.amount)
.toList();
final total = SumAggregator().aggregate(validAmounts);
final avg = AverageAggregator().aggregate(validAmounts);
print('\nRevenue: \$${total.toStringAsFixed(2)} USD');
print('Average: \$${avg.toStringAsFixed(2)} USD');
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
Saída:
=== Parsed Orders ===
Order(ORD-001, $1500.00)
Order(ORD-002, $50.00)
Order(ORD-003, $3200.00)
=== Order Summaries ===
OrderSummary(ORD-001, Premium)
OrderSummary(ORD-002, Standard)
OrderSummary(ORD-003, Premium)
Revenue: $4750.00 USD
Average: $1583.33 USD
❓ Perguntas Frequentes
P: Os parâmetros de tipo genérico existem em runtime? R: Sim, os generics do Dart são reificados. Você pode obter informações de tipo em runtime. `list is List``
``` retorna true em runtime, ao contrário da type erasure do Java.
P: Uma restrição genérica pode ter múltiplos limites? R: Sim, você pode usar a sintaxe
T extends A & B(embora Dart não suporte¶ múltiplos limites diretamente atualmente). Você pode alcançar isso indiretamente fazendo o tipo restringido implementar múltiplas interfaces, ou usar limites como `T extends Comparable`````.
P: Quando devo usar generics vs. dynamic? R: Use generics sempre que possível. Generics verificam tipos em tempo de compilação, enquanto
dynamicrevela erros apenas em runtime. Usedynamicapenas quando o tipo é verdadeiramente desconhecido (como parsing de JSON).
P: Um método genérico pode ter parâmetros de tipo diferentes dos de sua classe? R: Sim. Por exemplo,
<T, R> R transform(T input, R Function(T) fn)tem dois parâmetros de tipo. Os parâmetros de tipo de um método são independentes dos parâmetros de tipo de sua classe.
**P: Qual a diferença entre
List``<dynamic>`` eList<T>```?** R: `Listaceita elementos de qualquer tipo, perdendo a segurança de tipos. `List``<T>aceita apenas elementos do tipo T, com segurança de tipos garantida pelo compilador.List``<dynamic>``` não é um supertipo de outros tiposList`````.
P: Há diferença entre
extendseimplementsem restrições genéricas? R: Em restrições,extendscorresponde tanto à herança de classe quanto à implementação de interface.T extends Comparablesignifica que T pode ser uma subclasse ou uma classe que implementa Comparable.
P: Para que serve a palavra-chave
covariant? R:covariantpermite que um subtipo use um tipo mais específico para um parâmetro (covariância). É frequentemente usada para parâmetros em padrões consumer, mas enfraquece a segurança de tipos — use com cautela.
📖 Resumo
- Generics permitem que o código seja reutilizado para múltiplos tipos via parâmetros de tipo
<T>, com o compilador garantindo a segurança de tipos. - Classes genéricas (
class Box<T>) e funções genéricas (T first<T>(List<T>)) são as formas mais comuns. - A restrição
extendslimita o alcance dos parâmetros de tipo, por exemplo,<T extends num>aceita apenas tipos numéricos. - Coleções genéricas (
List<T>, `Map<K, V>) são mais seguras queList``. - O
DataTransformer<T, R>do DataPipeline é uma aplicação típica de design genérico.
📝 Exercícios
- Básico (Dificuldade ⭐): Implemente uma classe genérica
Pair<T, U>com camposfirstesecond, e um métodoswap()que retorna um novo Pair com os valores trocados. Teste comPair<int, String>ePair<double, bool>. - Intermediário (Dificuldade ⭐⭐): Implemente um tipo
Result<T>(similar ao Result do Rust) comisSuccess,data,error, e um métodomap<R>(). Use-o para reescrever uma função de parsing que pode falhar. - Desafio (Dificuldade ⭐⭐⭐): Implemente uma classe genérica
Pipeline<T, R>que suporte o encadeamento de múltiplosDataTransformers, inferindo automaticamente os tipos intermediários, e finalmente chameprocess()para executar todo o pipeline. Dica: Você pode usar tipos recursivos ou o padrão builder.