7.3. Colecciones - RFGProfesor/Programacion GitHub Wiki
Tiempo de lectura: 1 hora.
Última revisión: 06/2025.
A menudo es necesario guardar información sin conocer de antemano el espacio que va a ocupar en la memoria.
En estos casos, los arrays no son una buena solución; en su lugar, se recurre a estructuras dinámicas de datos. Ahora bien, ¿de dónde proceden estas estructuras dinámicas?
El origen está en la interfaz Collection, que define una serie de funcionalidades comunes a todas las colecciones; ya sean de tipo listas, conjuntos o colas.
Cabe destacar que los diferentes tipos de listas, conjuntos y colas, no implementan de forma directa la interfaz Collection, sino que lo hacen a través de otra más específica.
codegym.cc
Hay colecciones de diferentes tipos adaptadas a distintos fines más o menos específicos. Los diferentes tipos se definen a través de interfaces específicas que implementan Collection:
- Listas
Se utilizan para maneja sucesiones de datos que puden estar repetidos y cuyo orden puede ser relevante. Las listas implementan la interfaz List, que a su vez implementa la interfaz Collection.
- Conjuntos
En este caso el orden de los datos no es relevante, lo que importa es la pertenencia al conjunto; por lo que las repeticiones, no tienen sentido. Los conjuntos implementan la interfaz Set, que a su vez implementa la interfaz Collection.
- Colas
Se utilizan para manejar estructuras de datos con orden de procesamiento, comúnmente en el estilo FIFO (First In, First Out), donde el primer elemento que se inserta es el primero en salir. Las colas implementan la interfaz Queue.
La interfaz Collection provee funcionalidades básicas que son heredades por las demás interfaces:
Hay que tener en cuenta que además de estas funcionalidades básicas, cada interfaz específica añade nuevo métodos.
Un ArrayList es una implementación de la interfaz List en Java, especialmente eficiente para operaciones de lectura.
La tipología de dato indicada al declarar la lista, se mantiene fija durante su tiempo de vida.
Declaración
🟢EJEMPLO
import java.util.ArrayList;
ArrayList<String> lista = new ArrayList<>();
Agregar
🟢EJEMPLO
lista.add("Juan");
lista.add("Ana");
Acceder a elementos
🟢EJEMPLO
System.out.println(lista.get(0)); // Imprime: Juan
Eliminar elementos
🟢EJEMPLO
lista.remove("Ana"); // Elimina el elemento por su valor
lista.remove(0); // Elimina el elemento por su índice
Tamaño Dinámico
Internamente, el ArrayList usa un array. Cuando este se llena, se crea un nuevo array con mayor capacidad (generalmente el doble) y los elementos se copian al nuevo array.
Capacidad vs Tamaño
- Capacidad: El espacio reservado internamente.
- Tamaño: La cantidad actual de elementos.
Se puede establecer una capacidad inicial para optimizar rendimiento:
🟢EJEMPLO
ArrayList<Integer> lista = new ArrayList<>(50); // Capacidad inicial 50
Este funcionamiento hace que ArrayList sea recomendable para listas que cambian poco y se leen mucho. Si se va a trabajar con una lista que cambia mucho, sobre todo cuando se producen muchas inserciones y borrados en medio de la lista, se recomienda utilizar la lista LinkedList, que también implementa la interfaz List. El uso es similar al del ArrayList.
🟢EJEMPLO
import java.util.ArrayList;
public class ArrayListEjemplo {
public static void main(String[] args) {
ArrayList<String> lista = new ArrayList<>();
// Agregar elementos
lista.add("Manzana");
lista.add("Banana");
lista.add("Cereza");
// Mostrar elementos
System.out.println("Lista: " + lista);
// Acceder a un elemento
System.out.println("Elemento en índice 1: " + lista.get(1)); // Banana
// Modificar un elemento
lista.set(1, "Durazno");
System.out.println("Lista después de modificar: " + lista);
// Eliminar un elemento
lista.remove("Manzana");
System.out.println("Lista después de eliminar: " + lista);
// Verificar si contiene un elemento
System.out.println("¿Contiene 'Cereza'? " + lista.contains("Cereza"));
// Limpiar la lista
lista.clear();
System.out.println("¿La lista está vacía? " + lista.isEmpty());
}
}
/*Salida:
Lista: [Manzana, Banana, Cereza]
Elemento en índice 1: Banana
Lista después de modificar: [Manzana, Durazno, Cereza]
Lista después de eliminar: [Durazno, Cereza]
¿Contiene 'Cereza'? true
¿La lista está vacía? true*/
Con bucle for
🟢EJEMPLO
for (int i = 0; i < lista.size(); i++) {
System.out.println(lista.get(i));
}
Con bucle for-each
🟢EJEMPLO
for (String elemento : lista) {
System.out.println(elemento);
}
Usando un iterador
🟢EJEMPLO
import java.util.Iterator;
Iterator<String> iterador = lista.iterator();
while (iterador.hasNext()) {
System.out.println(iterador.next());
}
De array a arraylist
🟢EJEMPLO
import java.util.Arrays;
import java.util.ArrayList;
String[] array = {"A", "B", "C"};
ArrayList<String> lista = new ArrayList<>(Arrays.asList(array));
De arraylist a array
🟢EJEMPLO
String[] array = lista.toArray(new String[0]);
Para los ArrayLists, Java proporciona las utilidades de la clase de utilidades Collections:
- Collections.sort(): Ordena en orden natural (alfabético o ascendente).
- Collections.reverseOrder(): Invierte el orden natural.
🟢EJEMPLO
Ordenación ascendente.
import java.util.ArrayList;
import java.util.Collections;
public class OrdenarArrayList {
public static void main(String[] args) {
ArrayList<Integer> numeros = new ArrayList<>();
numeros.add(5);
numeros.add(1);
numeros.add(4);
numeros.add(3);
numeros.add(2);
// Ordenar en orden ascendente
Collections.sort(numeros);
// Mostrar el ArrayList ordenado
System.out.println("ArrayList ordenado: " + numeros);
}
}
🟢EJEMPLO
Ordenación descendente.
import java.util.ArrayList;
import java.util.Collections;
public class OrdenarArrayList {
public static void main(String[] args) {
ArrayList<Integer> numeros = new ArrayList<>();
numeros.add(5);
numeros.add(1);
numeros.add(4);
numeros.add(3);
numeros.add(2);
// Ordenar en orden descendente
Collections.sort(numeros, Collections.reverseOrder());
// Mostrar el ArrayList ordenado
System.out.println("ArrayList ordenado: " + numeros);
}
}
🟢EJEMPLO
Ordenación por un campo.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
class Persona {
private String nombre;
private int edad;
public Persona(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
public String getNombre() {
return nombre;
}
@Override
public String toString() {
return "Persona{nombre='" + nombre + "', edad=" + edad + '}';
}
}
public class OrdenarArrayList {
public static void main(String[] args) {
ArrayList<Persona> personas = new ArrayList<>();
personas.add(new Persona("Carlos", 25));
personas.add(new Persona("Ana", 30));
personas.add(new Persona("Luis", 20));
// Ordenar por nombre alfabéticamente
Collections.sort(personas, Comparator.comparing(Persona::getNombre));
// Mostrar la lista ordenada
System.out.println("ArrayList ordenado por nombre:");
for (Persona persona : personas) {
System.out.println(persona);
}
}
}
Un Set es una colección que no permite elementos duplicados. Es decir, si intentas agregar dos veces el mismo elemento, el segundo se ignora automáticamente.
Piensa en un Set como una "bolsa de cosas únicas" con las siguientes características:
- No permite duplicados.
- No garantiza un orden específico de los elementos (a menos que uses una clase que lo controle).
- Se usa cuando lo más importante es ver si un elemento está presente o no, sin importar cuántas veces ni en qué orden.
TreeSet
- Mantiene los elementos ordenados.
- Usa una estructura de árbol internamente.
- No permite duplicados.
- Útil si necesitas los elementos ordenados automáticamente, pero es algo más lento que los demás tipos de conjuntos. Implementa la intefaz SortedSet que a su vez implementa Set, gracias a esto los elementos están ordenados.
🟢EJEMPLO
import java.util.TreeSet;
public class Main {
public static void main(String[] args) {
TreeSet<String> nombres = new TreeSet<>();
nombres.add("Carlos");
nombres.add("Ana");
nombres.add("Luis");
nombres.add("Ana"); // duplicado, no se agrega
System.out.println(nombres); // [Ana, Carlos, Luis]
}
}
Los nombres aparecen ordenados alfabéticamente.
LinkedinHashSet
- Mantiene el orden de inserción.
- No permite duplicados.
- Más rápido que TreeSet, pero no ordena los elementos: solo los recuerda en el orden en que llegaron.
🟢EJEMPLO
import java.util.LinkedHashSet;
public class Main {
public static void main(String[] args) {
LinkedHashSet<String> frutas = new LinkedHashSet<>();
frutas.add("Manzana");
frutas.add("Banana");
frutas.add("Pera");
frutas.add("Banana"); // duplicado, no se agrega
System.out.println(frutas); // [Manzana, Banana, Pera]
}
}
HashSet
Almacena elementos sin permitir duplicados y no garantiza ningún orden específico.
🟢EJEMPLO
import java.util.HashSet;
public class Main {
public static void main(String[] args) {
HashSet<String> colores = new HashSet<>();
colores.add("Rojo");
colores.add("Verde");
colores.add("Azul");
colores.add("Rojo"); // duplicado, se ignora
System.out.println(colores); // El orden puede variar
}
}
El orden no es el mismo que el de inserción, y puede cambiar en cada ejecución.
La interfaz Qeue representa una cola, una colección que almacena elementos antes de su procesado. Provee métodos para insertar, remover y examinar. El comportamiento depende de la implementación.
- En general, aunque no necesariamente, las colas ordenan los elementos en una manera FIFO (first in, first out). Aunque en algunos casos se ordena de forma LIFO (last in, first out).
- Los elementos se insertan por el final con los métodos offer o add.
- Los elementos se extraen y consultan por el inicio, con los métodos poll o remove.
- Se puede consultar el primer elemento con peed o element.
Hay tres implementaciones: LinkedList, PriorityQueue y ArrayDeque.
🟢EJEMPLO
import java.util.Queue;
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
Queue<String> cola = new LinkedList<>();
cola.offer("Juan");
cola.offer("Ana");
cola.offer("Pedro");
System.out.println("Primer en la cola: " + cola.peek()); // Juan
// Atendemos a alguien (eliminamos del frente)
System.out.println("Atendiendo a: " + cola.poll()); // Juan
System.out.println("Ahora en el frente: " + cola.peek()); // Ana
}
}