Se você consultar a documentação da API do tipo array básico List, vai descobrir que seu tipo real é List<E>. Os símbolos de menor e maior <...> representam um tipo genérico (tipo parametrizado), ou seja, um tipo que possui parâmetros de tipo formais. Por convenção, quase todas as variáveis de tipo recebem nomes com uma única letra, como E, T, S, K, V.
Por que usar genéricos?
- A principal função dos genéricos é garantir a segurança de tipos
- Especificar os genéricos corretamente gera um código de execução mais otimizado;
- O uso de genéricos reduz drasticamente a repetição de código.
Se quiser que uma lista armazene apenas textos, declare ela como List<String>: essa é uma lista de strings.
Uma vez definida como lista exclusiva para textos, inserir valores de tipos diferentes causará um erro.
// Erro de verificação estática de tipos
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Erro de compilação: 42 não é uma stringCode language: PHP (php)
D:\dartdemo\firstdart>dart run
Building package executable...
Failed to build firstdart:firstdart:
bin/firstdart.dart:5:12: Error: The argument type 'int' can't be assigned to the parameter type 'String'.
names.add(42); // Erro de compilação: 42 não é uma stringCode language: PHP (php)
Eliminar redundância de código
Suponha que você precise de uma interface abstrata para um cache de objetos:
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
Code language: JavaScript (javascript)
Mais tarde, se precisar de um cache que armazene apenas strings, terá que criar uma interface separada:
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
Code language: Dart (dart)
Depois você vai precisar de caches exclusivos para números… e terá que reescrever códigos quase idênticos repetidamente.
Por exemplo, você acabaria escrevendo algo assim:
abstract class IntCache {
int getByKey(String key);
void setByKey(String key, int value);
}Code language: Dart (dart)
Os genéricos resolvem esse problema de uma única vez: basta definir uma interface genérica universal com parâmetros de tipo:
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}Code language: Dart (dart)
Aqui o T é um placeholder de tipo, representa qualquer tipo concreto passado posteriormente pelo desenvolvedor.
Exemplo prático
// Classe base abstrata de cache genérico
abstract class Cache<T> {
T? getByKey(String key);
void setByKey(String key, T value);
void remove(String key);
}
// Implementação de cache em memória (baseado em Map)
class MemoryCache<T> implements Cache<T> {
final Map<String, T> _storage = {};
@override
T? getByKey(String key) {
return _storage[key];
}
@Override
void setByKey(String key, T value) {
_storage[key] = value;
}
@Override
void remove(String key) {
_storage.remove(key);
}
}
void main() {
// 1. Exemplo de cache de strings
final Cache<String> strCache = MemoryCache<String>();
strCache.setByKey("name", "Tom");
print(strCache.getByKey("name")); // Tom
// 2. Cache de inteiros int
final Cache<int> intCache = MemoryCache<int>();
intCache.setByKey("age", 26);
print(intCache.getByKey("age")); // 26
// 3. Cache de objetos personalizados
final Cache<User> userCache = MemoryCache<User>();
userCache.setByKey("u1", User("Jack", 18));
final User? user = userCache.getByKey("u1");
print(user?.name); // Jack
// Teste de exclusão
userCache.remove("u1");
print(userCache.getByKey("u1")); // null
}
// Classe de entidade personalizada
class User {
final String name;
final int age;
User(this.name, this.age);
}Code language: Dart (dart)

Usar literais de coleção genéricos
Os literais List, Set e Map suportam genéricos parametrizados:
- List / Set: escreva
<Tipo>antes do colchete aberto - Map: escreva
<TipoChave, TipoValor>antes da chave aberta
Exemplos:
// Lista de strings
var names = <String>['Seth', 'Kathy', 'Lars'];
// Conjunto de strings (remove duplicatas automaticamente)
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
// Dicionário com chave e valor do tipo string
var pages = <String, String>{
'index.html': 'Página inicial',
'robots.txt': 'Arquivo de orientação para crawlers',
'humans.txt': 'Arquivo explicativo para desenvolvedores humanos',
};
Code language: PHP (php)
Executar o exemplo
void main() {
// 1. List<String> Lista de strings: ordenada, aceita duplicatas, acesso por índice
var names = <String>['Seth', 'Kathy', 'Lars', 'Seth'];
print("=== List Lista ===");
print(names); // [Seth, Kathy, Lars, Seth] duplicatas permitidas
print(names[0]); // Pegar primeiro elemento pelo índice: Seth
names.add("Mike"); // Adicionar elemento no final
print("Após adição: $names");
// 2. Set<String> Conjunto de strings: desordenado, remove duplicatas, sem índice
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars', 'Seth'};
print("\n=== Set Conjunto (remoção automática de duplicatas) ===");
print(uniqueNames); // {Seth, Kathy, Lars} o Seth duplicado é excluído
print(uniqueNames.contains("Kathy")); // Verificar existência: true
uniqueNames.remove("Lars");
print("Após remover Lars: $uniqueNames");
// 3. Map<String, String> Dicionário chave-valor: chaves únicas, busca por chave
var pages = <String, String>{
'index.html': 'Página inicial',
'robots.txt': 'Arquivo de orientação para crawlers',
'humans.txt': 'Arquivo explicativo para desenvolvedores humanos',
};
print("\n=== Map Dicionário ===");
print(pages);
print(pages['index.html']); // Pegar valor pela chave: Página inicial
pages["about.html"] = "Página Sobre"; // Adicionar par chave-valor
print("Após adicionar about.html: $pages");
// Exemplos comuns de iteração
print("\n=== Iterar sobre List ===");
for (var name in names) {
print("- $name");
}
print("\n=== Iterar todas as entradas do Map ===");
for (var entry in pages.entries) {
print("Arquivo: ${entry.key} → Descrição: ${entry.value}");
}
}Code language: Dart (dart)
Usar parâmetros genéricos em construtores
Ao chamar um construtor, especifique o tipo genérico dentro dos símbolos <> após o nome da classe:
var nameSet = Set<String>.of(names);Code language: JavaScript (javascript)
O código abaixo cria um dicionário com chave do tipo int e valor do tipo View:
var views = SplayTreeMap<int, View>();
Code language: HTML, XML (xml)
import 'dart:collection';
void main() {
var names = <String>['Seth', 'Kathy', 'Lars', 'Seth'];
var nameSet = Set<String>.of(names);
print("Lista original: $names");
print("Set sem duplicatas: $nameSet");
// SplayTreeMap precisa do import dart:collection
var views = SplayTreeMap<int, String>();
views[5] = "Página de configurações";
views[2] = "Página inicial";
views[9] = "Minha página";
views[1] = "Tela de splash";
print("\nTreeMap ordenado:");
for (var e in views.entries) {
print("${e.key} : ${e.value}");
}
}Code language: PHP (php)
Tipos de tempo de execução das coleções genéricas
Reificação de tipos (reified)
Os genéricos do Dart são reificados: todas as informações de tipo genérico são mantidas em tempo de execução.
Você pode verificar diretamente o tipo genérico concreto de uma coleção durante a execução:
void main() {
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // Imprime true
}Code language: PHP (php)
Comparação complementar: Os genéricos do Java usam apagamento de tipos, os parâmetros genéricos são removidos em tempo de execução. No Java só é possível verificar se um objeto é uma
List, não dá para diferenciarList<String>eList<int>.
* Restringir tipos de parâmetros genéricos
Limite (bound). Se ainda não aprendeu classes, basta entender o conceito básico desta seção.
Ao definir genéricos, você pode restringir que os tipos passados sejam apenas subclasses de uma superclasse específica. Essa restrição se chama limite de tipo e é implementada com a palavra-chave extends.
1 Restringir a tipos não nulos
Forçar o genérico a aceitar somente tipos não nulos (o limite superior padrão é o tipo nulo Object?):
class Foo<T extends Object> {
// O tipo T passado não pode ser anulável
}
Code language: JavaScript (javascript)
2 Limitar a subclasses de uma superclasse definida
Use extends para informar a classe base, assim você consegue chamar diretamente todos os métodos membros da classe base:
class Foo<T extends SomeBaseClass> {
String toString() => "Instância Foo<$T>";
}
class Extender extends SomeBaseClass {}
Code language: JavaScript (javascript)
Argumentos de tipo válidos: a própria superclasse, qualquer uma de suas subclasses
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
Code language: HTML, XML (xml)
Quando omitir o parâmetro genérico, o limite de tipo é usado por padrão:
var foo = Foo();
print(foo); // Imprime: Instância Foo<SomeBaseClass>
Code language: PHP (php)
Passar um tipo sem parentesco causa erro de compilação estático:
var foo = Foo<Object>(); // Erro de compilação: Object não é subclasse de SomeBaseClass
Code language: JavaScript (javascript)
* Limites de tipo autorreferentes (genéricos F-bound)
Ao restringir genéricos, o limite pode referenciar seu próprio parâmetro de tipo para criar uma autorrestrição, chamado de F-bound.
Exemplo padrão: interface comparável Comparable<T>
abstract interface class Comparable<T> {
int compareTo(T o);
}
// T precisa implementar Comparable<T>, só compara com objetos do mesmo tipo
int compareAndOffset<T extends Comparable<T>>(T t1, T t2) =>
t1.compareTo(t2) + 1;
class A implements Comparable<A> {
@override
int compareTo(A other) {
// Implementar lógica de comparação
return 0;
}
}
int res = compareAndOffset(A(), A());
Code language: PHP (php)
A restrição T extends Comparable<T> significa: T só pode ser comparado com instâncias do mesmo tipo.
* Métodos genéricos
Funções comuns e métodos de classe também suportam parâmetros de tipo genéricos:
T first<T>(List<T> ts) {
// Lógica de validação inicial
T tmp = ts[0];
// Lógica de processamento posterior
return tmp;
}
Code language: PHP (php)
Exemplo completo
T first<T>(List<T> ts) {
if (ts.isEmpty) {
throw ArgumentError("A lista não pode estar vazia");
}
T tmp = ts[0];
return tmp;
}
void main() {
List<String> strList = ["apple", "banana", "orange"];
String firstStr = first(strList);
print(firstStr); //apple
List<int> numList = [10, 20, 30];
int firstNum = first(numList);
print(firstNum);//10
}Code language: Dart (dart)
O parâmetro genérico <T> atua em três locais:
- Tipo de retorno da função
T - Tipo do parâmetro de entrada
List<T> - Tipo de variável local
T tmp