Collections
Apuntes de colecciones en píldoras:
La principal diferencia entre un array y una colección es que las colecciones
son dinámicas, cambian de tamaño (no se pueden almacenar primitivos).
En Java, las colecciones suelen implementarse utilizando interfaces del
framework de colecciones. Este enfoque proporciona un alto nivel de
abstracción y permite cambiar las implementaciones de colecciones
subyacentes sin alterar mucho el código que las utiliza. La interfaz no solo
define un conjunto de métodos, sino que también encapsula el
comportamiento esperado de cualquier clase que la implemente
Set<Cliente> clientes = new HashSet<>();
es una práctica común donde Set es la interfaz y HashSet es una
implementación concreta de esta interfaz. Aquí, Set garantiza que no habrá
elementos duplicados y HashSet proporciona una implementación que usa
una tabla hash para almacenar los elementos, ofreciendo tiempos de
búsqueda, inserción y eliminación constantes en promedio.
List:
ArrayList: implementación basada en un array redimensionable. Es buena
para listas cuyo tamaño cambia dinámicamente y donde las operaciones de
indexación son frecuentes.
List<String> nombres = new ArrayList<>();
LinkedList: implementación basada en una lista enlazada. Es útil cuando
las inserciones y eliminaciones son más comunes que las operaciones de
acceso por índice.
List<String> nombres = new LinkedList<>();
Colas (Queue)
ArrayDeque: una cola de doble terminación que permite agregar y eliminar
elementos desde ambos extremos.
Queue<Integer> cola = new ArrayDeque<>();
PriorityQueue: una cola de prioridad que ordena sus elementos según un
criterio de ordenamiento natural o un Comparator proporcionado.
Queue<Integer> prioridades = new PriorityQueue<>();
Mapas (Map)
HashMap: un mapa basado en tabla hash que no mantiene un orden de sus
claves.
Map<String, Integer> map = new HashMap<>();
TreeMap: un mapa basado en un árbol rojo-negro que ordena las claves de
acuerdo a su orden natural o un Comparator especificado.
Map<String, Integer> map = new TreeMap<>();
LinkedHashMap: similar a HashMap pero mantiene el orden de inserción
de las claves.
Map<String, Integer> map = new LinkedHashMap<>();
Usar interfaces en la declaración (como Set, List, Map) te da la flexibilidad
de cambiar la implementación concreta sin necesidad de cambiar el resto
del código que depende de estas abstracciones. Esto es especialmente útil
para la escritura de código que es robusto, reutilizable y fácil de mantener.
Stream:
En Java, un Stream es una secuencia de elementos soportada por una serie
de operaciones de agregación (como map, filter, sort, etc.), que pueden ser
ejecutadas de manera secuencial o paralela. Los streams fueron
introducidos en Java 8 como parte del paquete [Link]. Son
especialmente útiles para realizar operaciones sobre colecciones de datos
de manera funcional y expresiva, sin alterar los datos originales
(inmutabilidad).
Usos de los Streams en Java
Los streams son útiles para:
1. Realizar operaciones de agregación y transformación: Permiten
ejecutar operaciones complejas sobre los elementos de las
colecciones de manera concisa y clara, como filtrar, mapear, reducir,
encontrar elementos, entre otros.
2. Facilitar la programación funcional: Java incorpora el uso de
expresiones lambda con streams, facilitando el manejo de colecciones
de datos de manera declarativa.
3. Mejorar la legibilidad del código: Reducen la necesidad de usar
bucles explícitos para procesar colecciones, lo que hace que el código
sea más legible y menos propenso a errores.
4. Aprovechar la arquitectura de múltiples núcleos: Los streams
pueden ser procesados en paralelo fácilmente, lo que permite
aprovechar los procesadores multicore para mejorar el rendimiento
de operaciones sobre grandes volúmenes de datos.
En el contexto de los streams en Java, las operaciones de agregación son
aquellas que procesan elementos de un stream para producir un resultado
acumulado o combinado. Estas operaciones son típicamente no-intermedias,
lo que significa que después de que se ejecutan, el stream ya no es
utilizable para futuras operaciones. Las operaciones de agregación son
fundamentales para consolidar datos de una colección en un único
resultado, como sumas, promedios, contar elementos, encontrar máximos y
mínimos, entre otros.
Contar elementos en un stream:
Sumar elementos:
Utilizando el método reduce para sumar todos los elementos de un
stream
Encontrar el máximo o mínimo elemento:
Utilizando max y min con un comparador
Calcular el promedio:
Streams de primitivos como IntStream, DoubleStream y
LongStream proporcionan métodos como average que son útiles
para calcular promedios.
Sumar con un método específico para tipos primitivos:
sum en IntStream, DoubleStream, y LongStream