Si revisas la documentación API del tipo de matriz básico List, descubrirás que su tipo real es List<E>. Los signos de ángulo <...> indican un tipo genérico (tipo parametrizado), es decir, un tipo con parámetros de tipo formales. Por convención, casi todas las variables de tipo se nombran con una sola letra, como E, T, S, K, V.
¿Por qué usar genéricos?
- La función principal de los genéricos es garantizar la seguridad de tipos
- Especificar correctamente los genéricos genera un código de ejecución más optimizado;
- El uso de genéricos reduce drásticamente la repetición de código.
Si quieres que una lista solo almacene cadenas, declárala como List<String>: se trata de una lista de cadenas.
Una vez definida como lista exclusiva de cadenas, insertar valores de otro tipo generará un error.
// Error de comprobación estática de tipos
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error de compilación: 42 no es una cadenaLenguaje del código: 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); // Error de compilación: 42 no es una cadenaLenguaje del código: PHP (php)
Reducir redundancia de código
Supongamos que necesitas una interfaz abstracta para un caché de objetos:
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
Lenguaje del código: JavaScript (javascript)
Más adelante, si requieres un caché que solo guarde cadenas, debes crear una interfaz nueva:
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
Lenguaje del código: Dart (dart)
Luego necesitarás cachés exclusivos para números… tendrás que escribir repetidamente código casi idéntico.
Por ejemplo, terminarías escribiendo algo así:
abstract class IntCache {
int getByKey(String key);
void setByKey(String key, int value);
}Lenguaje del código: Dart (dart)
Los genéricos solucionan este problema de una sola vez: solo debes definir una interfaz universal con parámetros de tipo:
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}Lenguaje del código: Dart (dart)
Aquí T es un tipo marcador de posición, representa cualquier tipo concreto que el desarrollador pase posteriormente.
Ejemplo práctico
// Clase base abstracta de caché genérico
abstract class Cache<T> {
T? getByKey(String key);
void setByKey(String key, T value);
void remove(String key);
}
// Implementación de caché en memoria (basada en 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. Ejemplo de caché de cadenas
final Cache<String> strCache = MemoryCache<String>();
strCache.setByKey("name", "Tom");
print(strCache.getByKey("name")); // Tom
// 2. Caché de enteros int
final Cache<int> intCache = MemoryCache<int>();
intCache.setByKey("age", 26);
print(intCache.getByKey("age")); // 26
// 3. Caché 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
// Prueba de eliminación
userCache.remove("u1");
print(userCache.getByKey("u1")); // null
}
// Clase de entidad personalizada
class User {
final String name;
final int age;
User(this.name, this.age);
}Lenguaje del código: Dart (dart)

Usar literales de colección genéricos
Los literales List, Set y Map admiten genéricos parametrizados:
- List / Set: escribe
<Tipo>antes del corchete abierto - Map: escribe
<TipoClave, TipoValor>antes de la llave abierta
Ejemplos:
// Lista de cadenas
var names = <String>['Seth', 'Kathy', 'Lars'];
// Conjunto de cadenas (elimina duplicados automáticamente)
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
// Diccionario con claves y valores de cadena
var pages = <String, String>{
'index.html': 'Página principal',
'robots.txt': 'Archivo de instrucciones para rastreadores',
'humans.txt': 'Archivo explicativo para desarrolladores humanos',
};
Lenguaje del código: PHP (php)
Ejemplo en ejecución
void main() {
// 1. List<String> Lista de cadenas: ordenada, permite duplicados, acceso por índice
var names = <String>['Seth', 'Kathy', 'Lars', 'Seth'];
print("=== List Lista ===");
print(names); // [Seth, Kathy, Lars, Seth] se permiten duplicados
print(names[0]); // Obtener primer elemento por índice: Seth
names.add("Mike"); // Añadir elemento al final
print("Después de añadir: $names");
// 2. Set<String> Conjunto de cadenas: desordenado, elimina duplicados, sin índices
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars', 'Seth'};
print("\n=== Set Conjunto (eliminación automática de duplicados) ===");
print(uniqueNames); // {Seth, Kathy, Lars} el duplicado Seth se borra solo
print(uniqueNames.contains("Kathy")); // Comprobar existencia: true
uniqueNames.remove("Lars");
print("Después de borrar Lars: $uniqueNames");
// 3. Map<String, String> Diccionario clave-valor: claves únicas, acceso por clave
var pages = <String, String>{
'index.html': 'Página principal',
'robots.txt': 'Archivo de instrucciones para rastreadores',
'humans.txt': 'Archivo explicativo para desarrolladores humanos',
};
print("\n=== Map Diccionario ===");
print(pages);
print(pages['index.html']); // Obtener valor por clave: Página principal
pages["about.html"] = "Página acerca de"; // Añadir par clave-valor
print("Después de añadir about.html: $pages");
// Ejemplos comunes de recorrido
print("\n=== Recorrer List ===");
for (var name in names) {
print("- $name");
}
print("\n=== Recorrer todas las entradas de Map ===");
for (var entry in pages.entries) {
print("Archivo: ${entry.key} → Descripción: ${entry.value}");
}
}Lenguaje del código: Dart (dart)
Usar parámetros genéricos en constructores
Al invocar un constructor, especifica el tipo genérico entre ángulos después del nombre de la clase:
var nameSet = Set<String>.of(names);Lenguaje del código: JavaScript (javascript)
El siguiente código crea un diccionario con claves de tipo int y valores de tipo View:
var views = SplayTreeMap<int, View>();
Lenguaje del código: 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 sin duplicados: $nameSet");
// SplayTreeMap requiere importar dart:collection
var views = SplayTreeMap<int, String>();
views[5] = "Página de configuración";
views[2] = "Página principal";
views[9] = "Mi página";
views[1] = "Pantalla de inicio";
print("\nTreeMap ordenado:");
for (var e in views.entries) {
print("${e.key} : ${e.value}");
}
}Lenguaje del código: PHP (php)
Tipos en tiempo de ejecución de colecciones genéricas
Reificación de tipos (reified)
Los genéricos de Dart son reificados: la información completa del tipo genérico se conserva en tiempo de ejecución.
Puedes comprobar directamente el tipo genérico concreto de una colección durante la ejecución:
void main() {
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // Muestra true
}Lenguaje del código: PHP (php)
Comparación complementaria: Los genéricos de Java usan borrado de tipos, eliminando los parámetros genéricos en tiempo de ejecución. En Java solo se puede comprobar si un objeto es una
List, sin diferenciar entreList<String>yList<int>.
* Restringir tipos de parámetros genéricos
Límite (bound). Si aún no has estudiado clases, basta con entender el concepto general de esta sección.
Al definir un genérico, puedes limitar que los tipos pasados sean subclases de una superclase determinada. Esta restricción se llama límite de tipo y se implementa con la palabra clave extends.
1 Restringir a tipos no anulables
Forzar que el genérico solo acepte tipos no anulables (el límite superior por defecto es el tipo anulable Object?):
class Foo<T extends Object> {
// El tipo T pasado no puede ser anulable
}
Lenguaje del código: JavaScript (javascript)
2 Limitar a subclases de una superclase específica
Usa extends para indicar la clase base; así podrás llamar directamente a todos los métodos miembro de la clase base:
class Foo<T extends SomeBaseClass> {
String toString() => "Instancia Foo<$T>";
}
class Extender extends SomeBaseClass {}
Lenguaje del código: JavaScript (javascript)
Argumentos de tipo válidos: la superclase misma, cualquiera de sus subclases
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
Lenguaje del código: HTML, XML (xml)
Si omites el parámetro genérico, se usa el tipo límite por defecto:
var foo = Foo();
print(foo); // Muestra: Instancia Foo<SomeBaseClass>
Lenguaje del código: PHP (php)
Pasar un tipo sin relación genera un error de compilación estático:
var foo = Foo<Object>(); // Error de compilación: Object no es subclase de SomeBaseClass
Lenguaje del código: JavaScript (javascript)
* Límites de tipo autorreferentes (genéricos con límite F)
Al restringir genéricos, el límite puede hacer referencia a su propio parámetro de tipo para crear una autorrestricción, conocida como límite F (F-bound).
Ejemplo estándar: interfaz comparable Comparable<T>
abstract interface class Comparable<T> {
int compareTo(T o);
}
// T debe implementar Comparable<T>, solo se puede comparar con objetos del mismo 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 comparación
return 0;
}
}
int res = compareAndOffset(A(), A());
Lenguaje del código: PHP (php)
La restricción T extends Comparable<T> significa: T solo se puede comparar con instancias del mismo tipo que él mismo.
* Métodos genéricos
Las funciones normales y los métodos de clase también admiten parámetros de tipo genéricos:
T first<T>(List<T> ts) {
// Lógica de comprobación previa
T tmp = ts[0];
// Lógica de procesamiento posterior
return tmp;
}
Lenguaje del código: PHP (php)
Ejemplo completo
T first<T>(List<T> ts) {
if (ts.isEmpty) {
throw ArgumentError("La lista no puede estar vacía");
}
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
}Lenguaje del código: Dart (dart)
El parámetro genérico <T> actúa en tres lugares:
- Tipo de retorno de la función
T - Tipo del parámetro de entrada
List<T> - Tipo de variable local
T tmp