Una List<T> es una colección de elementos de un tipo específico T organizados en un orden lineal mediante un índice entero que comienza en 0. A diferencia de un Set, donde los elementos son únicos y no tienen una posición fija, en una List la posición es sagrada: si mueves un elemento, cambia su identidad dentro de la secuencia.
Este diseño permite que el acceso a cualquier elemento sea extremadamente rápido si conoces su posición (índice), ya que la máquina virtual puede calcular la dirección de memoria del elemento de forma directa. Sin embargo, esta estructura implica un costo cuando necesitas insertar o eliminar elementos en medio de una lista muy grande, ya que Dart debe desplazar todos los elementos restantes para mantener el orden.
Debes usar listas siempre que el orden de los datos sea fundamental para tu lógica, como una lista de tareas pendientes, un historial de eventos o una cola de mensajes. Si usas una lista para buscar valores únicos de forma recurrente, estarás desperdiciando recursos; en ese caso, un Set sería más eficiente.
Si intentas modificar una lista definida como const (inmutable en tiempo de compilación) o intentas acceder a un índice que no existe (por ejemplo, el índice 5 en una lista de 3 elementos), el programa lanzará un error y se detendrá.
void main() {
// 1. Literales y mutabilidad
// Una lista normal es "growable" (puede crecer o encogerse).
var tareas = ['Comprar leche', 'Lavar coche', 'Reparar bici'];
// Una lista 'const' es inmutable y vive en la sección de solo lectura.
const categorias = ['Hogar', 'Trabajo', 'Ocio'];
// 2. Construcción avanzada
// Generamos una lista de IDs: [101, 102, 103]
var ids = List.generate(3, (i) => 100 + i);
// Creamos una lista de longitud fija (no puedes usar .add())
// Si necesitas que crezca, debes pasar growable: true
var estados = List.filled(3, 'Pendiente');
// 3. Copias y tipos (La importancia de List.of vs List.from)
var listaCopiada = List.of(tareas); // Segura: respeta el tipo List<String>
var listaDinamica = List.from(tareas); // Más permisiva: permite tipos mezclados (evitar si es posible)
// 4. Operaciones de modificación
tareas.add('Hacer ejercicio'); // Añade al final
tareas.insert(1, 'Llamar al médico'); // Inserta en la posición 1 y desplaza el resto
tareas.removeAt(2); // Elimina 'Lavar coche'
tareas.removeWhere((t) => t.contains('reparar')); // Elimina por condición
tareas.removeLast(); // Elimina el último elemento
// 5. Orden y caos
tareas.sort(); // Orden alfabético
tareas.shuffle(); // Desordena la lista aleatoriamente
// 6. Acceso y subsecciones
print('Primera tarea: ${tareas.first}');
print('Última tarea: ${tareas.last}');
print('¿Contiene 'Lavar'? ${tareas.contains('Lavar coche')}');
// getRange devuelve un Iterable (no una lista nueva, es una vista)
var rango = tareas.getRange(0, 2);
// sublist crea una nueva lista con una parte de la original
var subseccion = tareas.sublist(0, 2);
print('Lista final: $tareas');
print('Subsección: $subseccion');
}
Análisis del código
Al observar tareas, fíjate cómo add() y insert() alteran la estructura. Cuando ejecutamos tareas.insert(1, 'Llamar al médico'), Dart no simplemente coloca el elemento, sino que reasigna las posiciones de todos los elementos que estaban en el índice 1 o superiores.
En la parte de construcción, List.generate es tu mejor aliado para inicializar secuencias basadas en un índice, evitando bucles manuales. Es mucho más limpio que crear una lista vacía y hacerle .add() dentro de un for.
Es crucial entender la diferencia que vimos con List.of(tareas) y List.from(tareas). En un entorno de producción, List.of es preferible porque aprovecha el sistema de tipos de Dart para garantizar que la nueva lista sea del mismo tipo que la original. List.from es más flexible porque puede tomar un Iterable<dynamic> y convertirlo, lo que podría introducir errores de tipo si intentas tratar un String como un int más adelante.
Cuando usas tareas.sort(), estás modificando la lista original (mutación). En cambio, si usaras la propiedad .reversed, no estarías ordenando la lista, sino obteniendo un Iterable que te permite ver los elementos en orden inverso sin gastar memoria extra creando una lista nueva, ya que es una “vista” sobre los datos existentes.
El error frecuente
Uno de los errores más comunes es intentar usar un método de modificación en una lista constante:
const lista = [1, 2, 3]; lista.add(4); // Error: UnsupportedError: Cannot add to an unmodifiable list
Si tu intención es que los elementos no cambien, usa const. Si necesitas que la lista pueda crecer, asegúrate de que no sea const.
Otro error sutil ocurre con la propiedad .single. Este método es muy útil para validar que una lista tenga exactamente un elemento:
var lista = [1]; print(lista.single); // Imprime 1 var listaDos = [1, 2]; print(listaDos.single); // Error: StateError: Bad state: Not a single element
Si la lista está vacía o tiene más de un elemento, single lanzará una excepción. Úsalo solo cuando la lógica de tu negocio garantice la existencia de un único elemento.
N° 32