7. Manejo de Arreglos y Colecciones
Introducción
Section titled “Introducción”El manejo eficiente de datos es fundamental en cualquier aplicación. Java ofrece diversas estructuras para almacenar y manipular conjuntos de datos, desde los arreglos tradicionales hasta las colecciones del Framework de Colecciones de Java (JCF). En este capítulo, exploraremos estas estructuras, sus características, ventajas y casos de uso.
7.1 Arreglos (simples y multidimensionales)
Section titled “7.1 Arreglos (simples y multidimensionales)”Los arreglos son estructuras de datos que permiten almacenar múltiples valores del mismo tipo bajo un solo nombre de variable. En Java, los arreglos son objetos y tienen una longitud fija que se establece al momento de su creación.
7.1.1 Arreglos unidimensionales
Section titled “7.1.1 Arreglos unidimensionales”Declaración e inicialización
Section titled “Declaración e inicialización”Existen varias formas de declarar e inicializar arreglos en Java:
// Declaración (solo crea la referencia)int[] numeros;
// Asignación (crea el arreglo en memoria)numeros = new int[5]; // Arreglo de 5 enteros inicializados a 0// Declaración y asignación en una sola líneaint[] numeros = new int[5]; // Arreglo de 5 enteros inicializados a 0String[] nombres = new String[3]; // Arreglo de 3 strings inicializados a null// Inicialización con valores específicosint[] numeros = {10, 20, 30, 40, 50}; // Arreglo de 5 enteros con valores asignadosString[] dias = {"Lunes", "Martes", "Miércoles", "Jueves", "Viernes"}; // Arreglo de 5 stringsAcceso y modificación de elementos
Section titled “Acceso y modificación de elementos”Los elementos de un arreglo se acceden mediante índices que comienzan en 0:
int[] numeros = {10, 20, 30, 40, 50};
// Acceso a elementosint primerNumero = numeros[0]; // 10int tercerNumero = numeros[2]; // 30
// Modificación de elementosnumeros[1] = 25; // Cambia el segundo elemento a 25numeros[4] = 55; // Cambia el último elemento a 55
// Error en tiempo de ejecución: ArrayIndexOutOfBoundsException// numeros[5] = 60; // ¡El índice 5 está fuera de los límites del arreglo!Longitud de un arreglo
Section titled “Longitud de un arreglo”La propiedad length devuelve el tamaño de un arreglo:
int[] numeros = {10, 20, 30, 40, 50};int longitud = numeros.length; // 5
// Recorrer un arreglo usando su longitudfor (int i = 0; i < numeros.length; i++) { System.out.println("Elemento " + i + ": " + numeros[i]);}Recorrido de arreglos
Section titled “Recorrido de arreglos”Existen varias formas de recorrer un arreglo en Java:
int[] numeros = {10, 20, 30, 40, 50};
// Usando un bucle for tradicionalfor (int i = 0; i < numeros.length; i++) { System.out.println(numeros[i]);}int[] numeros = {10, 20, 30, 40, 50};
// Usando un bucle for-each (enhanced for)for (int numero : numeros) { System.out.println(numero);}int[] numeros = {10, 20, 30, 40, 50};
// Usando streams (Java 8+)import java.util.Arrays;
Arrays.stream(numeros).forEach(System.out::println);
// O con operaciones más complejasint suma = Arrays.stream(numeros).sum(); // 150int max = Arrays.stream(numeros).max().getAsInt(); // 50Operaciones comunes con arreglos
Section titled “Operaciones comunes con arreglos”La clase java.util.Arrays proporciona métodos útiles para trabajar con arreglos:
import java.util.Arrays;
int[] numeros = {30, 10, 50, 20, 40};
// Ordenar un arregloArrays.sort(numeros);System.out.println(Arrays.toString(numeros)); // [10, 20, 30, 40, 50]
// Buscar un elemento (en un arreglo ordenado)int indice = Arrays.binarySearch(numeros, 30); // 2
// Comparar arreglosint[] otrosNumeros = {10, 20, 30, 40, 50};boolean sonIguales = Arrays.equals(numeros, otrosNumeros); // true
// Llenar un arregloint[] masNumeros = new int[5];Arrays.fill(masNumeros, 7);System.out.println(Arrays.toString(masNumeros)); // [7, 7, 7, 7, 7]
// Copiar un arregloint[] copiaNumeros = Arrays.copyOf(numeros, numeros.length);int[] primerosTres = Arrays.copyOf(numeros, 3); // [10, 20, 30]int[] subArreglo = Arrays.copyOfRange(numeros, 1, 4); // [20, 30, 40]7.1.2 Arreglos multidimensionales
Section titled “7.1.2 Arreglos multidimensionales”Java soporta arreglos multidimensionales, que son arreglos de arreglos. Los más comunes son los arreglos bidimensionales (matrices).
Declaración e inicialización
Section titled “Declaración e inicialización”// Declaraciónint[][] matriz;
// Asignaciónmatriz = new int[3][4]; // Matriz de 3 filas y 4 columnas// Declaración y asignación en una sola líneaint[][] matriz = new int[3][4]; // Matriz de 3 filas y 4 columnas// Inicialización con valores específicosint[][] matriz = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}; // Matriz de 3 filas y 4 columnas con valores asignadosArreglos irregulares (jagged arrays)
Section titled “Arreglos irregulares (jagged arrays)”En Java, cada fila de un arreglo multidimensional puede tener diferente longitud:
// Arreglo irregular (jagged array)int[][] irregular = new int[3][];irregular[0] = new int[2]; // Primera fila con 2 columnasirregular[1] = new int[4]; // Segunda fila con 4 columnasirregular[2] = new int[3]; // Tercera fila con 3 columnas
// O directamenteint[][] irregular2 = { {1, 2}, {3, 4, 5, 6}, {7, 8, 9}};Acceso y modificación de elementos
Section titled “Acceso y modificación de elementos”int[][] matriz = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
// Acceso a elementosint elemento = matriz[1][2]; // 6 (fila 1, columna 2)
// Modificación de elementosmatriz[0][1] = 20; // Cambia el elemento en la fila 0, columna 1 a 20Recorrido de arreglos multidimensionales
Section titled “Recorrido de arreglos multidimensionales”int[][] matriz = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
// Usando bucles for anidadosfor (int i = 0; i < matriz.length; i++) { for (int j = 0; j < matriz[i].length; j++) { System.out.print(matriz[i][j] + " "); } System.out.println(); // Nueva línea después de cada fila}
// Usando bucles for-each anidadosfor (int[] fila : matriz) { for (int elemento : fila) { System.out.print(elemento + " "); } System.out.println();}7.1.3 Limitaciones de los arreglos
Section titled “7.1.3 Limitaciones de los arreglos”Los arreglos en Java tienen varias limitaciones:
- Tamaño fijo: Una vez creado, el tamaño no puede cambiar.
- Tipo homogéneo: Todos los elementos deben ser del mismo tipo.
- Operaciones limitadas: No proporcionan métodos para insertar o eliminar elementos.
- Sin información de tamaño actual: No hay forma de saber cuántos elementos “útiles” contiene un arreglo parcialmente lleno.
7.1.4 Casos de uso para arreglos
Section titled “7.1.4 Casos de uso para arreglos”- Cuando se conoce el tamaño exacto de antemano
- Para estructuras de datos de tamaño fijo (por ejemplo, días de la semana)
- Para operaciones de alto rendimiento donde la simplicidad es crucial
- Para representar datos multidimensionales como matrices o tensores
- Cuando se trabaja con APIs que requieren arreglos
7.2 ArrayList y LinkedList
Section titled “7.2 ArrayList y LinkedList”El Framework de Colecciones de Java proporciona implementaciones dinámicas de listas que superan las limitaciones de los arreglos tradicionales. Las dos implementaciones más comunes son ArrayList y LinkedList.
7.2.1 ArrayList
Section titled “7.2.1 ArrayList”ArrayList es una implementación redimensionable de la interfaz List que utiliza un arreglo dinámico internamente. Es similar a un arreglo tradicional, pero puede crecer o reducirse según sea necesario.
Creación e inicialización
Section titled “Creación e inicialización”import java.util.ArrayList;import java.util.List;
// Creación de ArrayList vacíoArrayList<String> nombres = new ArrayList<>();
// Creación con capacidad inicial (optimización de rendimiento)ArrayList<Integer> numeros = new ArrayList<>(20);
// Usando la interfaz List (recomendado para mejor diseño)List<Double> precios = new ArrayList<>();
// Inicialización con elementosList<String> frutas = new ArrayList<>();frutas.add("Manzana");frutas.add("Banana");frutas.add("Naranja");
// Inicialización usando List.of (Java 9+, crea lista inmutable)List<String> colores = List.of("Rojo", "Verde", "Azul");
// Convertir a ArrayList (para hacerla mutable)List<String> coloresMutables = new ArrayList<>(colores);Operaciones básicas
Section titled “Operaciones básicas”List<String> frutas = new ArrayList<>();
// Añadir elementosfrutas.add("Manzana"); // Añade al finalfrutas.add(1, "Banana"); // Añade en posición específica
// Acceder a elementosString primeraFruta = frutas.get(0); // "Manzana"
// Modificar elementosfrutas.set(1, "Pera"); // Reemplaza "Banana" con "Pera"
// Eliminar elementosfrutas.remove("Manzana"); // Elimina por objetofrutas.remove(0); // Elimina por índice
// Verificar si contiene un elementoboolean contienePera = frutas.contains("Pera");
// Tamañoint tamaño = frutas.size();
// Verificar si está vacíaboolean estaVacia = frutas.isEmpty();
// Limpiar todos los elementosfrutas.clear();Recorrido de ArrayList
Section titled “Recorrido de ArrayList”List<String> frutas = new ArrayList<>();frutas.add("Manzana");frutas.add("Banana");frutas.add("Naranja");
// Usando un bucle for tradicionalfor (int i = 0; i < frutas.size(); i++) { System.out.println(frutas.get(i));}List<String> frutas = new ArrayList<>();frutas.add("Manzana");frutas.add("Banana");frutas.add("Naranja");
// Usando un bucle for-eachfor (String fruta : frutas) { System.out.println(fruta);}import java.util.Iterator;
List<String> frutas = new ArrayList<>();frutas.add("Manzana");frutas.add("Banana");frutas.add("Naranja");
// Usando IteratorIterator<String> iterador = frutas.iterator();while (iterador.hasNext()) { String fruta = iterador.next(); System.out.println(fruta);
// Podemos eliminar elementos durante la iteración if (fruta.equals("Banana")) { iterador.remove(); // Forma segura de eliminar durante la iteración }}List<String> frutas = new ArrayList<>();frutas.add("Manzana");frutas.add("Banana");frutas.add("Naranja");
// Usando Stream APIfrutas.stream().forEach(System.out::println);
// Con operaciones más complejasfrutas.stream() .filter(f -> f.startsWith("M")) .map(String::toUpperCase) .forEach(System.out::println); // MANZANAOperaciones avanzadas
Section titled “Operaciones avanzadas”import java.util.ArrayList;import java.util.Collections;import java.util.List;
List<String> frutas = new ArrayList<>();frutas.add("Manzana");frutas.add("Banana");frutas.add("Naranja");frutas.add("Pera");
// OrdenarCollections.sort(frutas); // ["Banana", "Manzana", "Naranja", "Pera"]
// Ordenar en reversaCollections.reverse(frutas); // ["Pera", "Naranja", "Manzana", "Banana"]
// MezclarCollections.shuffle(frutas); // Orden aleatorio
// Convertir a arrayString[] frutasArray = frutas.toArray(new String[0]);
// Sublista (vista de la lista original)List<String> subLista = frutas.subList(1, 3); // Desde índice 1 (inclusive) hasta 3 (exclusive)
// Buscarint indice = frutas.indexOf("Naranja");int ultimoIndice = frutas.lastIndexOf("Naranja"); // Útil si hay duplicados
// Añadir todos los elementos de otra colecciónList<String> masFrutas = new ArrayList<>();masFrutas.add("Uva");masFrutas.add("Kiwi");frutas.addAll(masFrutas); // Añade al finalfrutas.addAll(1, masFrutas); // Añade desde el índice 17.2.2 LinkedList
Section titled “7.2.2 LinkedList”LinkedList es una implementación de lista doblemente enlazada de las interfaces List y Deque. Cada elemento (nodo) contiene una referencia al elemento anterior y siguiente.
Creación e inicialización
Section titled “Creación e inicialización”import java.util.LinkedList;import java.util.List;import java.util.Deque;
// Creación de LinkedList vacíaLinkedList<String> nombres = new LinkedList<>();
// Como implementación de ListList<Integer> numeros = new LinkedList<>();
// Como implementación de Deque (cola doble)Deque<Double> precios = new LinkedList<>();
// Inicialización con elementosLinkedList<String> frutas = new LinkedList<>();frutas.add("Manzana");frutas.add("Banana");frutas.add("Naranja");Operaciones específicas de LinkedList
Section titled “Operaciones específicas de LinkedList”Además de las operaciones de List, LinkedList proporciona métodos adicionales para operaciones de cola y pila:
LinkedList<String> frutas = new LinkedList<>();frutas.add("Banana");frutas.add("Naranja");
// Operaciones de cola (Queue)frutas.offer("Manzana"); // Añade al final (similar a add)String primera = frutas.poll(); // Obtiene y elimina el primer elementoString cabeza = frutas.peek(); // Obtiene sin eliminar el primer elemento
// Operaciones de pila (Stack)frutas.push("Pera"); // Añade al principioString superior = frutas.pop(); // Obtiene y elimina el primer elemento
// Operaciones de Deque (cola doble)frutas.addFirst("Uva"); // Añade al principiofrutas.addLast("Kiwi"); // Añade al finalString primera2 = frutas.removeFirst(); // Elimina y devuelve el primer elementoString ultima = frutas.removeLast(); // Elimina y devuelve el último elementoString primeraVista = frutas.getFirst(); // Obtiene sin eliminar el primer elementoString ultimaVista = frutas.getLast(); // Obtiene sin eliminar el último elemento7.2.3 ArrayList vs LinkedList
Section titled “7.2.3 ArrayList vs LinkedList”| Característica | ArrayList | LinkedList |
|---|---|---|
| Estructura interna | Arreglo dinámico | Lista doblemente enlazada |
| Acceso aleatorio | O(1) - Constante | O(n) - Lineal |
| Inserción/eliminación al principio/medio | O(n) - Requiere desplazar elementos | O(1) - Solo actualiza referencias |
| Inserción/eliminación al final | O(1) amortizado | O(1) |
| Uso de memoria | Menor (solo almacena elementos) | Mayor (almacena elementos y referencias) |
| Iteración | Más rápida | Más lenta |
| Funcionalidad adicional | Solo operaciones de lista | Operaciones de lista, cola y pila |
¿Cuándo usar cada una?
Section titled “¿Cuándo usar cada una?”import java.util.ArrayList;import java.util.LinkedList;import java.util.List;
// Ejemplo de uso apropiado de ArrayListList<Integer> numeros = new ArrayList<>();// Añadir muchos elementos al final es eficientefor (int i = 0; i < 100000; i++) { numeros.add(i);}// Acceso aleatorio es eficienteint numero = numeros.get(50000); // O(1)
// Ejemplo de uso apropiado de LinkedListLinkedList<String> historial = new LinkedList<>();// Añadir al principio es eficientehistorial.addFirst("Página 3");historial.addFirst("Página 2");historial.addFirst("Página 1");// Implementar navegación adelante/atrásString paginaActual = historial.removeFirst(); // "Página 1"String siguientePagina = historial.removeFirst(); // "Página 2"7.3 HashMap y HashSet
Section titled “7.3 HashMap y HashSet”Las colecciones basadas en hash utilizan tablas hash para almacenar elementos, lo que permite un acceso, inserción y eliminación muy eficientes. Las dos implementaciones más comunes son HashMap y HashSet.
7.3.1 HashMap
Section titled “7.3.1 HashMap”HashMap implementa la interfaz Map y almacena pares clave-valor, donde cada clave es única. Proporciona acceso en tiempo constante O(1) para operaciones básicas.
Creación e inicialización
Section titled “Creación e inicialización”import java.util.HashMap;import java.util.Map;
// Creación de HashMap vacíoHashMap<String, Integer> edades = new HashMap<>();
// Creación con capacidad inicial y factor de cargaHashMap<String, Double> precios = new HashMap<>(100, 0.75f);
// Usando la interfaz Map (recomendado)Map<Integer, String> empleados = new HashMap<>();
// Inicialización con elementosMap<String, String> capitales = new HashMap<>();capitales.put("España", "Madrid");capitales.put("Francia", "París");capitales.put("Italia", "Roma");
// Inicialización usando Map.of (Java 9+, crea mapa inmutable)Map<String, Integer> poblacion = Map.of( "Madrid", 3223000, "Barcelona", 1620000, "Valencia", 791000);
// Convertir a HashMap (para hacerlo mutable)Map<String, Integer> poblacionMutable = new HashMap<>(poblacion);Operaciones básicas
Section titled “Operaciones básicas”Map<String, String> capitales = new HashMap<>();
// Añadir elementoscapitales.put("España", "Madrid");capitales.put("Francia", "París");
// Acceder a elementosString capitalEspaña = capitales.get("España"); // "Madrid"String capitalAlemania = capitales.get("Alemania"); // null
// Acceder con valor por defecto si no existeString capitalPortugal = capitales.getOrDefault("Portugal", "Desconocida"); // "Desconocida"
// Verificar si contiene una clave o valorboolean contieneEspaña = capitales.containsKey("España"); // trueboolean contieneLondres = capitales.containsValue("Londres"); // false
// Modificar elementoscapitales.put("Francia", "París"); // Sobrescribe el valor existente
// Insertar solo si la clave no existecapitales.putIfAbsent("Italia", "Roma"); // Añade Italia->Romacapitales.putIfAbsent("España", "Barcelona"); // No hace nada, España ya existe
// Eliminar elementoscapitales.remove("Francia"); // Elimina Francia->Paríscapitales.remove("Italia", "Milán"); // No elimina nada, el valor no coincide
// Tamañoint tamaño = capitales.size(); // 1
// Verificar si está vacíoboolean estaVacio = capitales.isEmpty(); // false
// Limpiar todos los elementoscapitales.clear(); // Elimina todos los elementosRecorrido de HashMap
Section titled “Recorrido de HashMap”Map<String, String> capitales = new HashMap<>();capitales.put("España", "Madrid");capitales.put("Francia", "París");capitales.put("Italia", "Roma");
// Recorrer las clavesfor (String pais : capitales.keySet()) { System.out.println("País: " + pais);}Map<String, String> capitales = new HashMap<>();capitales.put("España", "Madrid");capitales.put("Francia", "París");capitales.put("Italia", "Roma");
// Recorrer los valoresfor (String capital : capitales.values()) { System.out.println("Capital: " + capital);}Map<String, String> capitales = new HashMap<>();capitales.put("España", "Madrid");capitales.put("Francia", "París");capitales.put("Italia", "Roma");
// Recorrer pares clave-valorfor (Map.Entry<String, String> entrada : capitales.entrySet()) { System.out.println(entrada.getKey() + " -> " + entrada.getValue());}Map<String, String> capitales = new HashMap<>();capitales.put("España", "Madrid");capitales.put("Francia", "París");capitales.put("Italia", "Roma");
// Usando forEach con expresión lambdacapitales.forEach((pais, capital) -> { System.out.println(pais + " -> " + capital);});Operaciones avanzadas
Section titled “Operaciones avanzadas”Map<String, Integer> poblacion = new HashMap<>();poblacion.put("Madrid", 3223000);poblacion.put("Barcelona", 1620000);poblacion.put("Valencia", 791000);
// Computar un valor basado en la clave actualpoblacion.compute("Madrid", (ciudad, habitantes) -> habitantes + 10000);
// Computar un valor solo si la clave existepoblacion.computeIfPresent("Barcelona", (ciudad, habitantes) -> habitantes * 2);
// Computar un valor solo si la clave no existepoblacion.computeIfAbsent("Sevilla", ciudad -> 688000);
// Fusionar valorespoblacion.merge("Valencia", 5000, (valorAntiguo, valorNuevo) -> valorAntiguo + valorNuevo);
// Reemplazar valorespoblacion.replace("Madrid", 3300000);poblacion.replace("Barcelona", 1620000, 1650000); // Solo reemplaza si el valor actual coincide
// Operaciones en masaMap<String, Integer> masCiudades = new HashMap<>();masCiudades.put("Zaragoza", 675000);masCiudades.put("Málaga", 571000);
// Añadir todos los elementos de otro mapapoblacion.putAll(masCiudades);7.3.2 HashSet
Section titled “7.3.2 HashSet”HashSet implementa la interfaz Set y almacena elementos únicos sin duplicados. Utiliza un HashMap internamente para almacenar los elementos como claves (con un objeto ficticio como valor).
Creación e inicialización
Section titled “Creación e inicialización”import java.util.HashSet;import java.util.Set;
// Creación de HashSet vacíoHashSet<String> nombres = new HashSet<>();
// Creación con capacidad inicialHashSet<Integer> numeros = new HashSet<>(100);
// Usando la interfaz Set (recomendado)Set<String> colores = new HashSet<>();
// Inicialización con elementosSet<String> frutas = new HashSet<>();frutas.add("Manzana");frutas.add("Banana");frutas.add("Naranja");
// Inicialización usando Set.of (Java 9+, crea conjunto inmutable)Set<String> dias = Set.of("Lunes", "Martes", "Miércoles", "Jueves", "Viernes");
// Convertir a HashSet (para hacerlo mutable)Set<String> diasMutables = new HashSet<>(dias);
// Inicialización desde otra colecciónimport java.util.Arrays;import java.util.List;
List<String> listaPaises = Arrays.asList("España", "Francia", "Italia", "España");Set<String> paises = new HashSet<>(listaPaises); // Elimina duplicados automáticamenteOperaciones básicas
Section titled “Operaciones básicas”Set<String> colores = new HashSet<>();
// Añadir elementoscolores.add("Rojo"); // Devuelve truecolores.add("Verde");boolean agregado = colores.add("Rojo"); // Devuelve false, ya existe
// Verificar si contiene un elementoboolean contieneAzul = colores.contains("Azul"); // false
// Eliminar elementosboolean eliminado = colores.remove("Verde"); // trueboolean eliminado2 = colores.remove("Amarillo"); // false, no existe
// Tamañoint tamaño = colores.size(); // 1
// Verificar si está vacíoboolean estaVacio = colores.isEmpty(); // false
// Limpiar todos los elementoscolores.clear();Recorrido de HashSet
Section titled “Recorrido de HashSet”Set<String> colores = new HashSet<>();colores.add("Rojo");colores.add("Verde");colores.add("Azul");
// Usando un bucle for-eachfor (String color : colores) { System.out.println(color);}import java.util.Iterator;
Set<String> colores = new HashSet<>();colores.add("Rojo");colores.add("Verde");colores.add("Azul");
// Usando IteratorIterator<String> iterador = colores.iterator();while (iterador.hasNext()) { String color = iterador.next(); System.out.println(color);
// Podemos eliminar elementos durante la iteración if (color.equals("Verde")) { iterador.remove(); }}Set<String> colores = new HashSet<>();colores.add("Rojo");colores.add("Verde");colores.add("Azul");
// Usando Stream APIcolores.stream().forEach(System.out::println);
// Con operaciones más complejascolores.stream() .filter(c -> c.length() > 4) .map(String::toUpperCase) .forEach(System.out::println); // VERDEOperaciones de conjuntos
Section titled “Operaciones de conjuntos”import java.util.HashSet;import java.util.Set;
Set<String> frutas1 = new HashSet<>();frutas1.add("Manzana");frutas1.add("Banana");frutas1.add("Naranja");
Set<String> frutas2 = new HashSet<>();frutas2.add("Naranja");frutas2.add("Pera");frutas2.add("Uva");
// Unión (modificando frutas1)Set<String> union = new HashSet<>(frutas1);union.addAll(frutas2); // [Manzana, Banana, Naranja, Pera, Uva]
// Intersección (elementos comunes)Set<String> interseccion = new HashSet<>(frutas1);interseccion.retainAll(frutas2); // [Naranja]
// Diferencia (elementos en frutas1 pero no en frutas2)Set<String> diferencia = new HashSet<>(frutas1);diferencia.removeAll(frutas2); // [Manzana, Banana]
// Diferencia simétrica (elementos que están en uno u otro conjunto, pero no en ambos)Set<String> difSimetrica = new HashSet<>(frutas1);difSimetrica.addAll(frutas2); // UniónSet<String> temp = new HashSet<>(frutas1);temp.retainAll(frutas2); // InterseccióndifSimetrica.removeAll(temp); // [Manzana, Banana, Pera, Uva]7.3.3 HashMap vs HashSet
Section titled “7.3.3 HashMap vs HashSet”| Característica | HashMap | HashSet |
|---|---|---|
| Interfaz implementada | Map | Set |
| Almacena | Pares clave-valor | Valores únicos |
| Duplicados | No permite claves duplicadas | No permite elementos duplicados |
| Valor nulo | Permite una clave nula y múltiples valores nulos | Permite un elemento nulo |
| Implementación interna | Tabla hash | HashMap (usa HashMap internamente) |
| Uso común | Almacenar asociaciones clave-valor | Almacenar conjuntos de elementos únicos |
7.4 Generics en colecciones
Section titled “7.4 Generics en colecciones”Los genéricos (Generics) permiten crear clases, interfaces y métodos que operan con tipos parametrizados. En el contexto de colecciones, los genéricos proporcionan seguridad de tipos en tiempo de compilación.
7.4.1 Beneficios de los Generics
Section titled “7.4.1 Beneficios de los Generics”- Seguridad de tipos: Detecta errores de tipo en tiempo de compilación en lugar de en tiempo de ejecución
- Eliminación de castings: No es necesario hacer casting al recuperar elementos
- Código más legible: Especifica claramente qué tipos de objetos contiene una colección
- Reutilización de código: Permite escribir algoritmos genéricos que funcionan con diferentes tipos
7.4.2 Uso de Generics con colecciones
Section titled “7.4.2 Uso de Generics con colecciones”// Sin generics (antes de Java 5)List listaAntigua = new ArrayList();listaAntigua.add("Hola");listaAntigua.add(123); // Permitido, pero no seguroString texto = (String) listaAntigua.get(0); // Casting necesario// ClassCastException en tiempo de ejecución si intentamos:// String numero = (String) listaAntigua.get(1);
// Con generics (Java 5+)List<String> listaModerna = new ArrayList<>();listaModerna.add("Hola");// listaModerna.add(123); // Error de compilaciónString texto2 = listaModerna.get(0); // No necesita casting7.4.3 Comodines (Wildcards)
Section titled “7.4.3 Comodines (Wildcards)”Los comodines permiten mayor flexibilidad al trabajar con genéricos.
Comodín desconocido (?)
Section titled “Comodín desconocido (?)”// Lista de tipo desconocidoList<?> listaDesconocida;
// Puede asignarse cualquier tipo de listalistaDesconocida = new ArrayList<String>();listaDesconocida = new ArrayList<Integer>();
// Pero solo se puede leer ObjectObject obj = listaDesconocida.get(0);
// Y no se puede añadir nada (excepto null)// listaDesconocida.add("Hola"); // Error de compilaciónlistaDesconocida.add(null); // PermitidoComodín con límite superior (? extends T)
Section titled “Comodín con límite superior (? extends T)”import java.util.ArrayList;import java.util.List;
// Clase base y subclasesclass Animal {}class Gato extends Animal {}class Perro extends Animal {}
// Método que acepta listas de Animal o cualquier subclase de Animalvoid procesarAnimales(List<? extends Animal> animales) { // Podemos leer elementos como Animal for (Animal animal : animales) { System.out.println(animal); }
// Pero no podemos añadir elementos // animales.add(new Animal()); // Error de compilación // animales.add(new Gato()); // Error de compilación}
// UsoList<Animal> listaAnimales = new ArrayList<>();List<Gato> listaGatos = new ArrayList<>();List<Perro> listaPerros = new ArrayList<>();
procesarAnimales(listaAnimales); // OKprocesarAnimales(listaGatos); // OKprocesarAnimales(listaPerros); // OKComodín con límite inferior (? super T)
Section titled “Comodín con límite inferior (? super T)”import java.util.ArrayList;import java.util.List;
class Animal {}class Gato extends Animal {}class GatoSiames extends Gato {}
// Método que acepta listas de Gato o cualquier superclase de Gatovoid agregarGatos(List<? super Gato> destino) { // Podemos añadir Gatos o subclases de Gato destino.add(new Gato()); destino.add(new GatoSiames());
// Pero no podemos añadir superclases // destino.add(new Animal()); // Error de compilación
// Y solo podemos leer como Object Object obj = destino.get(0); // Solo podemos leer como Object}
// UsoList<Animal> listaAnimales = new ArrayList<>();List<Gato> listaGatos = new ArrayList<>();List<GatoSiames> listaGatosSiameses = new ArrayList<>();
agregarGatos(listaAnimales); // OKagregarGatos(listaGatos); // OK// agregarGatos(listaGatosSiameses); // Error, GatoSiames no es superclase de Gato7.4.4 Principio PECS (Producer Extends, Consumer Super)
Section titled “7.4.4 Principio PECS (Producer Extends, Consumer Super)”Una regla útil para recordar cuándo usar cada tipo de comodín:
- Usa
<? extends T>cuando solo necesites leer de una colección (la colección actúa como productor) - Usa
<? super T>cuando solo necesites escribir en una colección (la colección actúa como consumidor) - Usa
Texacto cuando necesites tanto leer como escribir
import java.util.ArrayList;import java.util.List;
// Ejemplo del principio PECSpublic void copiarElementos(List<? extends Number> origen, List<? super Number> destino) { for (Number numero : origen) { destino.add(numero); }}
// UsoList<Integer> enteros = new ArrayList<>();enteros.add(1);enteros.add(2);
List<Number> numeros = new ArrayList<>();List<Object> objetos = new ArrayList<>();
copiarElementos(enteros, numeros); // OKcopiarElementos(enteros, objetos); // OK// copiarElementos(objetos, numeros); // Error, Object no es subclase de NumberResumen
Section titled “Resumen”En este capítulo, hemos explorado las principales estructuras para almacenar y manipular conjuntos de datos en Java:
-
Arreglos: Estructuras de tamaño fijo que ofrecen acceso directo a elementos mediante índices.
-
ArrayList y LinkedList: Implementaciones dinámicas de la interfaz
Listque permiten almacenar elementos ordenados con diferentes características de rendimiento. -
HashMap y HashSet: Colecciones basadas en tablas hash que ofrecen acceso eficiente a elementos mediante claves o almacenamiento de elementos únicos.
-
Generics: Mecanismo que proporciona seguridad de tipos en tiempo de compilación y mayor flexibilidad al trabajar con colecciones.
La elección de la estructura de datos adecuada depende de los requisitos específicos de la aplicación, como el tipo de acceso a los datos, la frecuencia de modificaciones y las operaciones más comunes que se realizarán.