Un Map<K, V> es una colección que almacena elementos en pares de clave-valor, donde cada clave es única para identificar su valor correspondiente. A diferencia de una List, no accedes a los datos mediante un índice numérico, sino a través de su clave. Internamente, la implementación por defecto en Dart es LinkedHashMap [disponible desde Dart 1.0], lo que garantiza que, además de una eficiencia de búsqueda cercana a la constante (O(1)), el mapa preserve el orden de inserción de los elementos. Usarás un Map siempre que necesites indexar información mediante un identificador único (como un correo electrónico o un ID de producto) en lugar de una posición secuencial. Si intentas acceder a una clave que no existe mediante el operador [], el mapa no lanzará un error, sino que devolverá null; esto es fundamental entenderlo porque el tipo de retorno de esa operación es siempre V? (el valor es nullable). Si intentas forzar el acceso a un valor inexistente sin la debida validación, tu programa fallará en tiempo de ejecución.
void main() {
// Definición inicial mediante un literal de Map
final configBase = {
'api_url': 'https://api.service.com',
'timeout': 30,
'retries': 3,
};
// Creamos una copia usando Map.of para asegurar que trabajamos sobre una nueva instancia
final config = Map.of(configBase);
// Usamos Map.fromIterable para transformar una lista en un mapa
final words = ['dart', 'is', 'fast'];
final wordLengths = Map.fromIterable(
words,
key: (item) => item,
value: (item) => item.length,
);
// Combinamos dos listas independientes con Map.fromIterables
final keys = ['id', 'name'];
final values = [101, 'Server_Alpha'];
final identityMap = Map.fromIterables(keys, values);
// Manipulación segura de datos
// putIfAbsent solo inserta el valor si la clave no existe actualmente
config.putIfAbsent('environment', () => 'production');
// update permite transformar el valor actual.
// Si 'retries' existe, le sumamos 1; si no, inicializamos en 0.
config.update('retries', (val) => val + 1, ifAbsent: () => 0);
// Acceso con nulabilidad: el operador [] devuelve V?
// Usamos el operador null-coalescing para garantizar un valor por defecto
final maxRetries = config['retries'] ?? 0;
// Transformación mediante map: devolvemos un nuevo Map con las claves en mayúsculas
final activeConfig = config.map((key, value) {
return MapEntry(key.toUpperCase(), value);
});
print('Configuración final: $activeConfig');
print('Longitudes de palabras: $wordLengths');
print('Identidad del servidor: $identityMap');
print('Retries calculados: $maxRetries');
// Iteración eficiente sobre MapEntry (clave y valor juntos)
print('\nDetalle de la configuración activa:');
activeConfig.forEach((key, value) {
print('-> $key: $value');
});
}
En el código anterior, observa cómo configBase establece la estructura inicial. Al usar Map.of(configBase), estamos creando una copia superficial (shallow copy); esto es vital para evitar efectos secundarios si modificamos config más adelante.
Cuando trabajamos con wordLengths mediante Map.fromIterable, estamos proyectando cada elemento de la lista a una clave y un valor de forma inmediata. Es una operación limpia, pero ten cuidado: si la función key genera claves duplicadas, la última clave procesada sobrescribirá a las anteriores. En el caso de identityMap, Map.fromIterables es la forma más directa de unir dos colecciones, siempre que ambas tengan la misma longitud, de lo contrario, lanzará una excepción.
Fíjate en la lógica de config.update. A diferencia de config['retries'] = val, que simplemente sobrescribe, update es una operación atómica que te permite leer el valor previo para calcular el nuevo, lo cual es fundamental para contadores o acumuladores. Por otro lado, al usar config.map, no estamos modificando config en el lugar, sino generando un nuevo Map donde cada elemento es un MapEntry. Esto es una práctica mucho más segura y funcional que iterar y modificar un mapa mientras lo recorres.
El uso de config['retries'] ?? 0 es el patrón estándar para manejar la nulabilidad que mencionamos antes. Como config['retries'] devuelve un int?, el operador ?? nos protege de errores si la clave llegara a desaparecer inesperadamente.
El error frecuente
Un error muy común es el uso del operador de aserción nula ! cuando accedes a una clave de la que no estás seguro.
void main() {
final scores = {'alice': 10, 'bob': 15};
// ERROR: Esto fallará en tiempo de ejecución porque 'charlie' no existe
// y el operador ! fuerza la conversión de null a int.
final charlieScore = scores['charlie']!;
print(charlieScore);
}
En lugar de usar !, utiliza siempre ?? para proveer un valor por defecto o usa un bloque if (map.containsKey(key)) para validar la existencia de la clave antes de operar con su contenido.
N° 35