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á


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.

DART
abstract class DataTransformer<T, R> {
  R transform(T input);
  List``<R>`` batch(List``<T>`` inputs) => inputs.map(transform).toList();
}
TEXT 📖 Somente leitura
> **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. Fundamentos de Generics

(1) Por Que Generics São Necessários

▶ Exemplo

TEXT 📖 Somente leitura
> **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

DART
// 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
  }
}
TEXT 📖 Somente leitura
> **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

TEXT 📖 Somente leitura
> **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

DART
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
}
TEXT 📖 Somente leitura
> **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

TEXT 📖 Somente leitura
> **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

DART
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
}
TEXT 📖 Somente leitura
> **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

TEXT 📖 Somente leitura
> **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

DART
// 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
}
TEXT 📖 Somente leitura
> **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

TEXT 📖 Somente leitura
> **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

DART
// 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]
}
TEXT 📖 Somente leitura
> **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

TEXT 📖 Somente leitura
> **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

DART
// 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
}
TEXT 📖 Somente leitura
> **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

TEXT 📖 Somente leitura
> **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

DART
// 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]
}
TEXT 📖 Somente leitura
> **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

TEXT 📖 Somente leitura
> **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

DART
// 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}
}
TEXT 📖 Somente leitura
> **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

100%
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
TEXT 📖 Somente leitura
> **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

TEXT 📖 Somente leitura
> **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

DART
// 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');
  }
}
TEXT 📖 Somente leitura
> **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

DART
// ============================================
// 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');
}
TEXT 📖 Somente leitura
> **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:

TEXT 📖 Somente leitura
=== 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 & para 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 dynamic revela erros apenas em runtime. Use dynamic apenas 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>`` e List<T>```?** R: `List aceita 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 tipos List`````.

P: Há diferença entre extends e implements em restrições genéricas? R: Em restrições, extends corresponde tanto à herança de classe quanto à implementação de interface. T extends Comparable significa que T pode ser uma subclasse ou uma classe que implementa Comparable.

P: Para que serve a palavra-chave covariant? R: covariant permite 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


📝 Exercícios

  1. Básico (Dificuldade ⭐): Implemente uma classe genérica Pair<T, U> com campos first e second, e um método swap() que retorna um novo Pair com os valores trocados. Teste com Pair<int, String> e Pair<double, bool>.
  2. Intermediário (Dificuldade ⭐⭐): Implemente um tipo Result<T> (similar ao Result do Rust) com isSuccess, data, error, e um método map<R>(). Use-o para reescrever uma função de parsing que pode falhar.
  3. Desafio (Dificuldade ⭐⭐⭐): Implemente uma classe genérica Pipeline<T, R> que suporte o encadeamento de múltiplos DataTransformers, inferindo automaticamente os tipos intermediários, e finalmente chame process() para executar todo o pipeline. Dica: Você pode usar tipos recursivos ou o padrão builder.

← Lição Anterior | Próxima Lição →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%