Java

Tutorial Java: Listas

Aprende la interfaz List y ArrayList en Java con ejemplos prácticos y cómo implementar operaciones CRUD para gestionar colecciones eficientemente.

Aprende Java y certifícate

La interfaz List

La interfaz List es uno de los componentes fundamentales del Framework Collections de Java. Esta interfaz define una colección ordenada que permite almacenar elementos duplicados, a diferencia de otras colecciones como los conjuntos (Set).

La principal característica de List es que mantiene un orden de inserción de los elementos y permite acceder a ellos mediante su posición o índice. Esto la hace ideal para situaciones donde necesitamos mantener una secuencia específica de elementos y posiblemente acceder a ellos por su posición.

Características principales de List

  • Ordenada: Los elementos mantienen el orden en que fueron agregados
  • Indexada: Permite acceder a elementos por su posición (índice)
  • Permite duplicados: A diferencia de Set, List puede contener elementos repetidos
  • Permite valores nulos: Puede contener referencias null (aunque no es recomendable)

Métodos principales de la interfaz List

La interfaz List extiende la interfaz Collection y añade métodos específicos para trabajar con colecciones ordenadas:

  • add(E e): Añade un elemento al final de la lista
  • add(int index, E element): Inserta un elemento en la posición especificada
  • get(int index): Obtiene el elemento en la posición indicada
  • set(int index, E element): Reemplaza el elemento en la posición indicada
  • remove(int index): Elimina el elemento en la posición indicada
  • indexOf(Object o): Devuelve la primera posición donde aparece el elemento
  • lastIndexOf(Object o): Devuelve la última posición donde aparece el elemento
  • subList(int fromIndex, int toIndex): Obtiene una sublista entre las posiciones indicadas

Implementaciones de List

Java proporciona varias implementaciones de la interfaz List, cada una con características específicas:

  • ArrayList: Implementación basada en arrays dinámicos. Ofrece acceso rápido por índice.
  • LinkedList: Implementación basada en listas doblemente enlazadas. Eficiente para inserciones y eliminaciones.
  • Vector: Similar a ArrayList pero sincronizada (thread-safe). Considerada legacy.
  • Stack: Extiende Vector e implementa una pila (LIFO). También considerada legacy.

Declaración y uso básico

Para utilizar una List en Java, primero debemos importar el paquete correspondiente:

import java.util.List;
import java.util.ArrayList; // u otra implementación

Luego podemos crear instancias utilizando la interfaz como tipo y una implementación concreta:

// Declaración usando la interfaz como tipo
List<String> nombres = new ArrayList<>();

// Añadir elementos
nombres.add("Ana");
nombres.add("Carlos");
nombres.add("Beatriz");

// Acceder a elementos por índice
String primerNombre = nombres.get(0); // "Ana"

// Insertar en posición específica
nombres.add(1, "Daniel"); // La lista ahora es: Ana, Daniel, Carlos, Beatriz

// Recorrer la lista
for (String nombre : nombres) {
    System.out.println(nombre);
}

// Tamaño de la lista
int cantidad = nombres.size(); // 4

Uso de List con tipos primitivos

Para usar tipos primitivos con List, debemos utilizar sus clases envoltorio (wrapper classes):

// Lista de números enteros
List<Integer> numeros = new ArrayList<>();
numeros.add(10);
numeros.add(20);
numeros.add(30);

// Java realiza autoboxing/unboxing automáticamente
int primerNumero = numeros.get(0); // 10

Operaciones comunes con List

Verificar si un elemento existe

List<String> frutas = new ArrayList<>();
frutas.add("Manzana");
frutas.add("Banana");
frutas.add("Naranja");

boolean contieneManzana = frutas.contains("Manzana"); // true
boolean contienePera = frutas.contains("Pera"); // false

Eliminar elementos

List<String> colores = new ArrayList<>();
colores.add("Rojo");
colores.add("Verde");
colores.add("Azul");
colores.add("Verde"); // Elemento duplicado

// Eliminar por índice
colores.remove(0); // Elimina "Rojo"

// Eliminar por objeto (primera ocurrencia)
colores.remove("Verde"); // Elimina el primer "Verde"

// La lista ahora contiene: [Azul, Verde]

Convertir entre arrays y listas

// De array a lista
String[] arrayPaises = {"España", "Francia", "Italia"};
List<String> listaPaises = Arrays.asList(arrayPaises);

// De lista a array
String[] nuevoArray = listaPaises.toArray(new String[0]);

Iteración sobre una List

Existen varias formas de recorrer una List en Java:

List<Integer> numeros = new ArrayList<>();
numeros.add(10);
numeros.add(20);
numeros.add(30);

// Usando for tradicional con índice
for (int i = 0; i < numeros.size(); i++) {
    System.out.println(numeros.get(i));
}

// Usando for-each (recomendado)
for (Integer numero : numeros) {
    System.out.println(numero);
}

// Usando Iterator
Iterator<Integer> iterador = numeros.iterator();
while (iterador.hasNext()) {
    System.out.println(iterador.next());
}

Ordenación de listas

La interfaz List proporciona un método sort() para ordenar los elementos:

List<String> nombres = new ArrayList<>();
nombres.add("Zoe");
nombres.add("Ana");
nombres.add("Carlos");

// Ordenar alfabéticamente
Collections.sort(nombres);
// Resultado: [Ana, Carlos, Zoe]

// También podemos usar el método sort de List (Java 8+)
nombres.sort(Comparator.naturalOrder());

Ejemplo práctico: Gestión de tareas

Veamos un ejemplo práctico de cómo usar una List para gestionar una lista de tareas:

import java.util.ArrayList;
import java.util.List;

public class GestorTareas {
    public static void main(String[] args) {
        // Crear lista de tareas
        List<String> tareas = new ArrayList<>();
        
        // Añadir tareas
        tareas.add("Estudiar Java");
        tareas.add("Hacer ejercicio");
        tareas.add("Comprar comida");
        
        // Mostrar todas las tareas
        System.out.println("Lista de tareas:");
        for (int i = 0; i < tareas.size(); i++) {
            System.out.println((i + 1) + ". " + tareas.get(i));
        }
        
        // Marcar una tarea como completada (eliminarla)
        System.out.println("\nCompletando: " + tareas.get(1));
        tareas.remove(1);
        
        // Añadir nueva tarea con prioridad (al inicio)
        tareas.add(0, "Llamar al médico");
        
        // Mostrar lista actualizada
        System.out.println("\nLista actualizada:");
        for (int i = 0; i < tareas.size(); i++) {
            System.out.println((i + 1) + ". " + tareas.get(i));
        }
    }
}

La salida de este programa sería:

Lista de tareas:
1. Estudiar Java
2. Hacer ejercicio
3. Comprar comida

Completando: Hacer ejercicio

Lista actualizada:
1. Llamar al médico
2. Estudiar Java
3. Comprar comida

La interfaz List es una herramienta versátil para manejar colecciones ordenadas en Java. Su capacidad para mantener el orden de inserción y permitir acceso por índice la hace ideal para muchos escenarios de programación donde necesitamos trabajar con secuencias de elementos.

La clase ArrayList

ArrayList es la implementación más utilizada de la interfaz List en Java. Esta clase proporciona una estructura de datos basada en un array dinámico que crece automáticamente según sea necesario, lo que la hace muy práctica para la mayoría de aplicaciones.

Características de ArrayList

  • Acceso rápido: Permite acceder a cualquier elemento en tiempo constante O(1) mediante su índice
  • Redimensionamiento automático: Crece dinámicamente cuando se añaden elementos
  • Optimizada para acceso: Ideal cuando necesitamos acceder frecuentemente a elementos por su posición
  • No sincronizada: No es thread-safe por defecto, lo que la hace más eficiente en aplicaciones de un solo hilo

Creación de un ArrayList

Para utilizar ArrayList, primero debemos importar la clase:

import java.util.ArrayList;

Existen varias formas de crear un ArrayList:

// ArrayList vacío que almacenará cadenas de texto
ArrayList<String> nombres = new ArrayList<>();

// Con capacidad inicial específica (optimización)
ArrayList<Integer> numeros = new ArrayList<>(20);

// A partir de otra colección
ArrayList<String> copiaDeNombres = new ArrayList<>(nombres);

Estructura interna

Internamente, ArrayList utiliza un array para almacenar los elementos. Cuando este array se llena, ArrayList crea automáticamente uno nuevo con mayor capacidad y copia todos los elementos al nuevo array. Este proceso es transparente para el programador.

ArrayList<String> frutas = new ArrayList<>();
// Inicialmente tiene un array interno pequeño

// Al añadir elementos, el array crece según sea necesario
frutas.add("Manzana");
frutas.add("Banana");
frutas.add("Naranja");
// Si el array interno se llena, ArrayList crea uno nuevo más grande

Operaciones básicas

Añadir elementos

ArrayList<String> ciudades = new ArrayList<>();

// Añadir al final (operación más común)
ciudades.add("Madrid");
ciudades.add("Barcelona");

// Añadir en una posición específica
ciudades.add(1, "Valencia"); // Se inserta entre Madrid y Barcelona

Acceder a elementos

ArrayList<String> animales = new ArrayList<>();
animales.add("Perro");
animales.add("Gato");
animales.add("Conejo");

// Acceder por índice
String primerAnimal = animales.get(0); // "Perro"
String ultimoAnimal = animales.get(animales.size() - 1); // "Conejo"

Modificar elementos

ArrayList<Double> precios = new ArrayList<>();
precios.add(19.99);
precios.add(29.99);
precios.add(9.99);

// Modificar un elemento existente
precios.set(1, 24.99); // Cambia 29.99 por 24.99

Eliminar elementos

ArrayList<String> tareas = new ArrayList<>();
tareas.add("Estudiar");
tareas.add("Ejercicio");
tareas.add("Compras");
tareas.add("Ejercicio"); // Elemento duplicado

// Eliminar por índice
tareas.remove(0); // Elimina "Estudiar"

// Eliminar por objeto (primera ocurrencia)
tareas.remove("Ejercicio"); // Elimina el primer "Ejercicio"

// Eliminar todos los elementos
// tareas.clear();

Rendimiento de ArrayList

El rendimiento de ArrayList varía según la operación:

  • Acceso por índice (get/set): O(1) - Tiempo constante
  • Añadir al final (add): O(1) amortizado - Generalmente rápido, excepto cuando necesita redimensionarse
  • Insertar/eliminar en posición específica: O(n) - Costoso para listas grandes, ya que requiere desplazar elementos
  • Buscar elemento (contains/indexOf): O(n) - Búsqueda lineal
ArrayList<Integer> numeros = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
    numeros.add(i);
}

// Acceso por índice - Muy rápido
int numero = numeros.get(500); // O(1)

// Inserción al inicio - Costoso para listas grandes
numeros.add(0, 999); // O(n) - Debe desplazar 1000 elementos

Recorrido de ArrayList

Existen varias formas eficientes de recorrer un ArrayList:

ArrayList<String> lenguajes = new ArrayList<>();
lenguajes.add("Java");
lenguajes.add("Python");
lenguajes.add("JavaScript");

// 1. Bucle for tradicional con índice
for (int i = 0; i < lenguajes.size(); i++) {
    System.out.println(lenguajes.get(i));
}

// 2. Bucle for-each (más limpio y recomendado)
for (String lenguaje : lenguajes) {
    System.out.println(lenguaje);
}

// 3. Usando Iterator
Iterator<String> it = lenguajes.iterator();
while (it.hasNext()) {
    String lenguaje = it.next();
    System.out.println(lenguaje);
}

ArrayList vs Array tradicional

ArrayList ofrece varias ventajas sobre los arrays tradicionales:

  • Tamaño dinámico: No es necesario definir el tamaño al inicio
  • Métodos útiles: Proporciona métodos para añadir, eliminar, buscar, etc.
  • Uso de genéricos: Permite especificar el tipo de datos que contendrá
  • Conversión sencilla: Fácil conversión entre ArrayList y arrays
// Array tradicional
String[] arrayNombres = new String[3];
arrayNombres[0] = "Ana";
arrayNombres[1] = "Juan";
arrayNombres[2] = "María";
// Para añadir más elementos, necesitaríamos crear un nuevo array

// ArrayList equivalente
ArrayList<String> listaNombres = new ArrayList<>();
listaNombres.add("Ana");
listaNombres.add("Juan");
listaNombres.add("María");
listaNombres.add("Pedro"); // Podemos seguir añadiendo sin preocuparnos

Conversión entre ArrayList y arrays

// De ArrayList a array
ArrayList<Integer> listaNumeros = new ArrayList<>();
listaNumeros.add(10);
listaNumeros.add(20);
listaNumeros.add(30);

// Conversión a array
Integer[] arrayNumeros = listaNumeros.toArray(new Integer[0]);

// De array a ArrayList
String[] frutas = {"Manzana", "Pera", "Uva"};
ArrayList<String> listaFrutas = new ArrayList<>(Arrays.asList(frutas));

Ejemplo práctico: Gestión de inventario

Veamos un ejemplo de cómo utilizar ArrayList para gestionar un inventario simple:

import java.util.ArrayList;

public class GestionInventario {
    public static void main(String[] args) {
        // Crear inventario usando ArrayList
        ArrayList<String> inventario = new ArrayList<>();
        
        // Añadir productos al inventario
        inventario.add("Laptop");
        inventario.add("Teléfono");
        inventario.add("Tablet");
        inventario.add("Auriculares");
        
        // Mostrar inventario inicial
        System.out.println("Inventario inicial:");
        mostrarInventario(inventario);
        
        // Verificar si un producto existe
        String productoBuscado = "Tablet";
        if (inventario.contains(productoBuscado)) {
            System.out.println("\nEl producto '" + productoBuscado + "' está en el inventario");
            System.out.println("Posición: " + inventario.indexOf(productoBuscado));
        }
        
        // Actualizar un producto
        int posicionTelefono = inventario.indexOf("Teléfono");
        if (posicionTelefono != -1) {
            inventario.set(posicionTelefono, "Smartphone");
            System.out.println("\nProducto actualizado");
        }
        
        // Eliminar un producto
        boolean eliminado = inventario.remove("Auriculares");
        if (eliminado) {
            System.out.println("Producto 'Auriculares' eliminado");
        }
        
        // Mostrar inventario actualizado
        System.out.println("\nInventario actualizado:");
        mostrarInventario(inventario);
    }
    
    private static void mostrarInventario(ArrayList<String> inventario) {
        for (int i = 0; i < inventario.size(); i++) {
            System.out.println((i + 1) + ". " + inventario.get(i));
        }
    }
}

La salida de este programa sería:

Inventario inicial:
1. Laptop
2. Teléfono
3. Tablet
4. Auriculares

El producto 'Tablet' está en el inventario
Posición: 2

Producto actualizado
Producto 'Auriculares' eliminado

Inventario actualizado:
1. Laptop
2. Smartphone
3. Tablet

Consideraciones de uso

  • Elección adecuada: ArrayList es ideal cuando necesitamos acceso frecuente por índice y pocas inserciones/eliminaciones en medio de la lista.
  • Capacidad inicial: Si conocemos aproximadamente el número de elementos, podemos optimizar el rendimiento especificando una capacidad inicial.
  • Tipos primitivos: Para almacenar tipos primitivos, debemos usar sus clases envoltorio (Integer, Double, etc.).
  • Sincronización: Si necesitamos thread-safety, podemos usar Collections.synchronizedList(new ArrayList<>()).

Operaciones CRUD

Las operaciones CRUD (Create, Read, Update, Delete) representan las cuatro funciones fundamentales para la persistencia de datos. En el contexto de las listas en Java, estas operaciones nos permiten manipular colecciones de elementos de manera estructurada y eficiente.

¿Qué son las operaciones CRUD?

El acrónimo CRUD se refiere a:

  • Create (Crear): Añadir nuevos elementos a la colección
  • Read (Leer): Consultar o recuperar elementos existentes
  • Update (Actualizar): Modificar elementos existentes
  • Delete (Eliminar): Quitar elementos de la colección

Estas operaciones constituyen la base para cualquier sistema de gestión de datos, desde aplicaciones simples hasta complejos sistemas de bases de datos.

Implementación de CRUD con ArrayList

Vamos a ver cómo implementar cada una de estas operaciones utilizando la clase ArrayList:

Create (Crear)

Para añadir elementos a un ArrayList, disponemos de varios métodos:

ArrayList<String> estudiantes = new ArrayList<>();

// Añadir un elemento al final de la lista
estudiantes.add("María");

// Añadir un elemento en una posición específica
estudiantes.add(0, "Juan"); // Añade "Juan" al principio

// Añadir múltiples elementos de una vez
estudiantes.addAll(Arrays.asList("Pedro", "Ana", "Luis"));

Cuando necesitamos crear registros con estructura más compleja, normalmente utilizamos objetos:

// Definición de la clase Estudiante
class Estudiante {
    private int id;
    private String nombre;
    private int edad;
    
    public Estudiante(int id, String nombre, int edad) {
        this.id = id;
        this.nombre = nombre;
        this.edad = edad;
    }
    
    // Getters y setters
    public int getId() { return id; }
    public String getNombre() { return nombre; }
    public int getEdad() { return edad; }
    public void setNombre(String nombre) { this.nombre = nombre; }
    public void setEdad(int edad) { this.edad = edad; }
    
    @Override
    public String toString() {
        return "Estudiante{id=" + id + ", nombre='" + nombre + "', edad=" + edad + "}";
    }
}

// Uso con ArrayList
ArrayList<Estudiante> listaEstudiantes = new ArrayList<>();
listaEstudiantes.add(new Estudiante(1, "Carlos", 20));
listaEstudiantes.add(new Estudiante(2, "Laura", 22));

Read (Leer)

La operación de lectura nos permite recuperar información de la lista:

ArrayList<String> productos = new ArrayList<>();
productos.add("Laptop");
productos.add("Teléfono");
productos.add("Tablet");

// Leer un elemento específico por su índice
String producto = productos.get(1); // "Teléfono"

// Verificar si un elemento existe
boolean existe = productos.contains("Laptop"); // true

// Encontrar la posición de un elemento
int posicion = productos.indexOf("Tablet"); // 2
int ultimaPosicion = productos.lastIndexOf("Teléfono"); // Útil si hay duplicados

// Obtener el tamaño de la lista
int cantidad = productos.size(); // 3

Para listas de objetos, a menudo necesitamos buscar por algún criterio específico:

// Buscar un estudiante por ID
public Estudiante buscarPorId(ArrayList<Estudiante> lista, int id) {
    for (Estudiante e : lista) {
        if (e.getId() == id) {
            return e;
        }
    }
    return null; // No encontrado
}

// Buscar estudiantes por edad
public ArrayList<Estudiante> buscarPorEdad(ArrayList<Estudiante> lista, int edad) {
    ArrayList<Estudiante> resultado = new ArrayList<>();
    for (Estudiante e : lista) {
        if (e.getEdad() == edad) {
            resultado.add(e);
        }
    }
    return resultado;
}

Update (Actualizar)

Para actualizar elementos en un ArrayList, podemos usar el método set() o modificar directamente los objetos:

ArrayList<String> ciudades = new ArrayList<>();
ciudades.add("Madrid");
ciudades.add("Barcelona");
ciudades.add("Valencia");

// Actualizar un elemento por su índice
ciudades.set(1, "Sevilla"); // Reemplaza "Barcelona" por "Sevilla"

Con objetos, podemos actualizar sus propiedades:

// Actualizar un estudiante por ID
public boolean actualizarEstudiante(ArrayList<Estudiante> lista, int id, 
                                   String nuevoNombre, int nuevaEdad) {
    for (Estudiante e : lista) {
        if (e.getId() == id) {
            e.setNombre(nuevoNombre);
            e.setEdad(nuevaEdad);
            return true; // Actualización exitosa
        }
    }
    return false; // Estudiante no encontrado
}

// Uso
actualizarEstudiante(listaEstudiantes, 1, "Carlos García", 21);

Delete (Eliminar)

Para eliminar elementos de un ArrayList, disponemos de varios métodos:

ArrayList<String> tareas = new ArrayList<>();
tareas.add("Estudiar Java");
tareas.add("Hacer ejercicio");
tareas.add("Leer libro");
tareas.add("Hacer ejercicio"); // Elemento duplicado

// Eliminar por índice
tareas.remove(0); // Elimina "Estudiar Java"

// Eliminar por objeto (primera ocurrencia)
tareas.remove("Hacer ejercicio"); // Elimina la primera ocurrencia

// Eliminar todos los elementos que cumplan una condición
tareas.removeIf(tarea -> tarea.contains("libro"));

// Eliminar todos los elementos
// tareas.clear();

Para listas de objetos, normalmente eliminamos por algún criterio:

// Eliminar un estudiante por ID
public boolean eliminarEstudiante(ArrayList<Estudiante> lista, int id) {
    for (int i = 0; i < lista.size(); i++) {
        if (lista.get(i).getId() == id) {
            lista.remove(i);
            return true; // Eliminación exitosa
        }
    }
    return false; // Estudiante no encontrado
}

// Eliminar estudiantes por edad (elimina todos los que cumplan el criterio)
public int eliminarPorEdad(ArrayList<Estudiante> lista, int edad) {
    int contador = 0;
    Iterator<Estudiante> iterator = lista.iterator();
    while (iterator.hasNext()) {
        Estudiante e = iterator.next();
        if (e.getEdad() == edad) {
            iterator.remove(); // Forma segura de eliminar durante iteración
            contador++;
        }
    }
    return contador; // Número de estudiantes eliminados
}

Ejemplo completo: Sistema de gestión de biblioteca

Veamos un ejemplo práctico que implementa todas las operaciones CRUD para gestionar libros en una biblioteca:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Scanner;

class Libro {
    private int id;
    private String titulo;
    private String autor;
    private boolean prestado;
    
    public Libro(int id, String titulo, String autor) {
        this.id = id;
        this.titulo = titulo;
        this.autor = autor;
        this.prestado = false;
    }
    
    // Getters y setters
    public int getId() { return id; }
    public String getTitulo() { return titulo; }
    public String getAutor() { return autor; }
    public boolean isPrestado() { return prestado; }
    
    public void setTitulo(String titulo) { this.titulo = titulo; }
    public void setAutor(String autor) { this.autor = autor; }
    public void setPrestado(boolean prestado) { this.prestado = prestado; }
    
    @Override
    public String toString() {
        return "Libro #" + id + ": '" + titulo + "' de " + autor + 
               (prestado ? " (Prestado)" : " (Disponible)");
    }
}

public class BibliotecaApp {
    private ArrayList<Libro> catalogo;
    private int siguienteId;
    private Scanner scanner;
    
    public BibliotecaApp() {
        catalogo = new ArrayList<>();
        siguienteId = 1;
        scanner = new Scanner(System.in);
        
        // Añadir algunos libros de ejemplo
        agregarLibro("Don Quijote", "Miguel de Cervantes");
        agregarLibro("Cien años de soledad", "Gabriel García Márquez");
        agregarLibro("El principito", "Antoine de Saint-Exupéry");
    }
    
    // CREATE: Agregar un nuevo libro
    public void agregarLibro(String titulo, String autor) {
        Libro nuevoLibro = new Libro(siguienteId++, titulo, autor);
        catalogo.add(nuevoLibro);
        System.out.println("Libro agregado: " + nuevoLibro);
    }
    
    // READ: Buscar libros
    public void mostrarTodos() {
        if (catalogo.isEmpty()) {
            System.out.println("El catálogo está vacío.");
            return;
        }
        
        System.out.println("\n=== CATÁLOGO DE LIBROS ===");
        for (Libro libro : catalogo) {
            System.out.println(libro);
        }
    }
    
    public Libro buscarPorId(int id) {
        for (Libro libro : catalogo) {
            if (libro.getId() == id) {
                return libro;
            }
        }
        return null;
    }
    
    public ArrayList<Libro> buscarPorAutor(String autor) {
        ArrayList<Libro> resultado = new ArrayList<>();
        for (Libro libro : catalogo) {
            if (libro.getAutor().toLowerCase().contains(autor.toLowerCase())) {
                resultado.add(libro);
            }
        }
        return resultado;
    }
    
    // UPDATE: Actualizar información de un libro
    public boolean actualizarLibro(int id, String nuevoTitulo, String nuevoAutor) {
        Libro libro = buscarPorId(id);
        if (libro != null) {
            libro.setTitulo(nuevoTitulo);
            libro.setAutor(nuevoAutor);
            System.out.println("Libro actualizado: " + libro);
            return true;
        }
        System.out.println("Libro no encontrado.");
        return false;
    }
    
    // UPDATE: Cambiar estado de préstamo
    public boolean cambiarEstadoPrestamo(int id) {
        Libro libro = buscarPorId(id);
        if (libro != null) {
            libro.setPrestado(!libro.isPrestado());
            System.out.println("Estado actualizado: " + libro);
            return true;
        }
        System.out.println("Libro no encontrado.");
        return false;
    }
    
    // DELETE: Eliminar un libro
    public boolean eliminarLibro(int id) {
        Iterator<Libro> iterator = catalogo.iterator();
        while (iterator.hasNext()) {
            Libro libro = iterator.next();
            if (libro.getId() == id) {
                iterator.remove();
                System.out.println("Libro eliminado correctamente.");
                return true;
            }
        }
        System.out.println("Libro no encontrado.");
        return false;
    }
    
    // Menú principal
    public void mostrarMenu() {
        int opcion;
        do {
            System.out.println("\n=== SISTEMA DE GESTIÓN DE BIBLIOTECA ===");
            System.out.println("1. Ver todos los libros");
            System.out.println("2. Buscar por ID");
            System.out.println("3. Buscar por autor");
            System.out.println("4. Añadir nuevo libro");
            System.out.println("5. Actualizar libro");
            System.out.println("6. Cambiar estado de préstamo");
            System.out.println("7. Eliminar libro");
            System.out.println("0. Salir");
            System.out.print("Seleccione una opción: ");
            
            opcion = scanner.nextInt();
            scanner.nextLine(); // Consumir el salto de línea
            
            switch (opcion) {
                case 1:
                    mostrarTodos();
                    break;
                case 2:
                    System.out.print("Ingrese ID del libro: ");
                    int id = scanner.nextInt();
                    scanner.nextLine();
                    Libro libro = buscarPorId(id);
                    if (libro != null) {
                        System.out.println("Libro encontrado: " + libro);
                    } else {
                        System.out.println("Libro no encontrado.");
                    }
                    break;
                case 3:
                    System.out.print("Ingrese nombre del autor: ");
                    String autor = scanner.nextLine();
                    ArrayList<Libro> resultados = buscarPorAutor(autor);
                    if (resultados.isEmpty()) {
                        System.out.println("No se encontraron libros de ese autor.");
                    } else {
                        System.out.println("Libros encontrados:");
                        for (Libro l : resultados) {
                            System.out.println(l);
                        }
                    }
                    break;
                case 4:
                    System.out.print("Título del nuevo libro: ");
                    String titulo = scanner.nextLine();
                    System.out.print("Autor del nuevo libro: ");
                    autor = scanner.nextLine();
                    agregarLibro(titulo, autor);
                    break;
                case 5:
                    System.out.print("ID del libro a actualizar: ");
                    id = scanner.nextInt();
                    scanner.nextLine();
                    System.out.print("Nuevo título: ");
                    String nuevoTitulo = scanner.nextLine();
                    System.out.print("Nuevo autor: ");
                    String nuevoAutor = scanner.nextLine();
                    actualizarLibro(id, nuevoTitulo, nuevoAutor);
                    break;
                case 6:
                    System.out.print("ID del libro a cambiar estado: ");
                    id = scanner.nextInt();
                    scanner.nextLine();
                    cambiarEstadoPrestamo(id);
                    break;
                case 7:
                    System.out.print("ID del libro a eliminar: ");
                    id = scanner.nextInt();
                    scanner.nextLine();
                    eliminarLibro(id);
                    break;
                case 0:
                    System.out.println("¡Hasta pronto!");
                    break;
                default:
                    System.out.println("Opción no válida.");
            }
        } while (opcion != 0);
        
        scanner.close();
    }
    
    public static void main(String[] args) {
        BibliotecaApp biblioteca = new BibliotecaApp();
        biblioteca.mostrarMenu();
    }
}

Consideraciones importantes para operaciones CRUD

  • Identificadores únicos: Para sistemas CRUD efectivos, es recomendable asignar identificadores únicos a cada elemento, como hicimos con el campo id en los ejemplos.

  • Validación de datos: Antes de realizar operaciones de creación o actualización, es importante validar los datos para mantener la integridad de la colección.

  • Manejo de errores: Implementa un manejo adecuado de situaciones como elementos no encontrados o datos inválidos.

  • Iteración segura: Cuando elimines elementos durante una iteración, usa Iterator.remove() en lugar de ArrayList.remove() para evitar la excepción ConcurrentModificationException.

  • Rendimiento: Para colecciones grandes, considera el impacto en rendimiento de las operaciones. Por ejemplo, buscar elementos por criterios puede ser costoso en tiempo de ejecución.

  • Persistencia: En aplicaciones reales, normalmente querrás guardar los datos en algún medio persistente (archivos, bases de datos) en lugar de mantenerlos solo en memoria.

Patrones comunes en implementaciones CRUD

  • Patrón DAO (Data Access Object): Separa la lógica de acceso a datos de la lógica de negocio.
// Ejemplo simplificado de patrón DAO
interface LibroDAO {
    void crear(Libro libro);
    Libro leerPorId(int id);
    List<Libro> leerTodos();
    boolean actualizar(Libro libro);
    boolean eliminar(int id);
}

class LibroDAOImpl implements LibroDAO {
    private ArrayList<Libro> libros = new ArrayList<>();
    
    @Override
    public void crear(Libro libro) {
        libros.add(libro);
    }
    
    @Override
    public Libro leerPorId(int id) {
        for (Libro libro : libros) {
            if (libro.getId() == id) {
                return libro;
            }
        }
        return null;
    }
    
    // Implementación de los demás métodos...
}
  • Patrón Repositorio: Similar al DAO pero más orientado al dominio y menos a la persistencia.

  • Patrón Servicio: Encapsula la lógica de negocio que opera sobre los datos.

class BibliotecaServicio {
    private LibroDAO libroDAO;
    
    public BibliotecaServicio(LibroDAO libroDAO) {
        this.libroDAO = libroDAO;
    }
    
    public void prestarLibro(int id) {
        Libro libro = libroDAO.leerPorId(id);
        if (libro != null && !libro.isPrestado()) {
            libro.setPrestado(true);
            libroDAO.actualizar(libro);
        } else {
            throw new RuntimeException("El libro no está disponible");
        }
    }
    
    // Más métodos de negocio...
}

Las operaciones CRUD constituyen la base fundamental para trabajar con colecciones de datos en Java. Dominar estas operaciones te permitirá desarrollar aplicaciones robustas que gestionen información de manera eficiente y estructurada.

Aprende Java online

Otros ejercicios de programación de Java

Evalúa tus conocimientos de esta lección Listas con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Streams: match

Test

Gestión de errores y excepciones

Código

CRUD en Java de modelo Customer sobre un ArrayList

Proyecto

Clases abstractas

Test

Listas

Código

Métodos de la clase String

Código

Streams: reduce()

Test

API java.nio 2

Puzzle

Polimorfismo

Código

Pattern Matching

Código

Streams: flatMap()

Test

Llamada y sobrecarga de funciones

Puzzle

Métodos referenciados

Test

Métodos de la clase String

Código

Representación de Fecha

Puzzle

Operadores lógicos

Test

Inferencia de tipos con var

Código

Tipos de datos

Código

Estructuras de iteración

Puzzle

Streams: forEach()

Test

Objetos

Puzzle

Funciones lambda

Test

Uso de Scanner

Puzzle

Tipos de variables

Puzzle

Streams: collect()

Puzzle

Operadores aritméticos

Puzzle

Arrays y matrices

Código

Clases y objetos

Código

Interfaz funcional Consumer

Test

CRUD en Java de modelo Customer sobre un HashMap

Proyecto

Interfaces

Código

Enumeraciones Enums

Código

API Optional

Test

Interfaz funcional Function

Test

Encapsulación

Test

Interfaces

Código

Uso de API Optional

Puzzle

Representación de Hora

Test

Herencia básica

Test

Clases y objetos

Código

Interfaz funcional Supplier

Puzzle

HashMap

Puzzle

Sobrecarga de métodos

Test

Polimorfismo de tiempo de ejecución

Puzzle

OOP en Java

Proyecto

Sobrecarga de métodos

Código

CRUD de productos en Java

Proyecto

Clases sealed

Código

Creación de Streams

Test

Records

Código

Encapsulación

Código

Streams: min max

Puzzle

Herencia

Código

Métodos avanzados de la clase String

Puzzle

Funciones

Código

Polimorfismo de tiempo de compilación

Test

Reto sintaxis Java

Proyecto

Conjuntos

Código

Estructuras de control

Código

Recursión

Código

Excepciones

Puzzle

Herencia avanzada

Puzzle

Estructuras de selección

Test

Uso de interfaces

Test

Operadores

Código

Variables

Código

HashSet

Test

Objeto Scanner

Test

Streams: filter()

Puzzle

Operaciones de Streams

Puzzle

Interfaz funcional Predicate

Puzzle

Streams: sorted()

Test

Configuración de entorno

Test

Uso de variables

Test

Clases

Test

Streams: distinct()

Puzzle

Streams: count()

Test

ArrayList

Test

Mapas

Código

Datos de referencia

Test

Interfaces funcionales

Puzzle

Métodos básicos de la clase String

Test

Tipos de datos

Código

Clases abstractas

Código

Instalación

Test

Funciones

Código

Excepciones

Código

Estructuras de control

Código

Herencia de clases

Código

La clase Scanner

Código

Generics

Código

Streams: map()

Puzzle

Funciones y encapsulamiento

Test

Todas las lecciones de Java

Accede a todas las lecciones de Java y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Instalación De Java

Introducción Y Entorno

Configuración De Entorno Java

Introducción Y Entorno

Tipos De Datos

Sintaxis

Variables

Sintaxis

Operadores

Sintaxis

Estructuras De Control

Sintaxis

Funciones

Sintaxis

Recursión

Sintaxis

Arrays Y Matrices

Sintaxis

Excepciones

Programación Orientada A Objetos

Clases Y Objetos

Programación Orientada A Objetos

Encapsulación

Programación Orientada A Objetos

Herencia

Programación Orientada A Objetos

Clases Abstractas

Programación Orientada A Objetos

Interfaces

Programación Orientada A Objetos

Sobrecarga De Métodos

Programación Orientada A Objetos

Polimorfismo

Programación Orientada A Objetos

La Clase Scanner

Programación Orientada A Objetos

Métodos De La Clase String

Programación Orientada A Objetos

Excepciones

Programación Orientada A Objetos

Records

Programación Orientada A Objetos

Pattern Matching

Programación Orientada A Objetos

Inferencia De Tipos Con Var

Programación Orientada A Objetos

Enumeraciones Enums

Programación Orientada A Objetos

Generics

Programación Orientada A Objetos

Clases Sealed

Programación Orientada A Objetos

Listas

Framework Collections

Conjuntos

Framework Collections

Mapas

Framework Collections

Funciones Lambda

Programación Funcional

Interfaz Funcional Consumer

Programación Funcional

Interfaz Funcional Predicate

Programación Funcional

Interfaz Funcional Supplier

Programación Funcional

Interfaz Funcional Function

Programación Funcional

Métodos Referenciados

Programación Funcional

Creación De Streams

Programación Funcional

Operaciones Intermedias Con Streams: Map()

Programación Funcional

Operaciones Intermedias Con Streams: Filter()

Programación Funcional

Operaciones Intermedias Con Streams: Distinct()

Programación Funcional

Operaciones Finales Con Streams: Collect()

Programación Funcional

Operaciones Finales Con Streams: Min Max

Programación Funcional

Operaciones Intermedias Con Streams: Flatmap()

Programación Funcional

Operaciones Intermedias Con Streams: Sorted()

Programación Funcional

Operaciones Finales Con Streams: Reduce()

Programación Funcional

Operaciones Finales Con Streams: Foreach()

Programación Funcional

Operaciones Finales Con Streams: Count()

Programación Funcional

Operaciones Finales Con Streams: Match

Programación Funcional

Api Optional

Programación Funcional

Transformación

Programación Funcional

Reducción Y Acumulación

Programación Funcional

Mapeo

Programación Funcional

Streams Paralelos

Programación Funcional

Agrupación Y Partición

Programación Funcional

Filtrado Y Búsqueda

Programación Funcional

Api Java.nio 2

Entrada Y Salida Io

Fundamentos De Io

Entrada Y Salida Io

Leer Y Escribir Archivos

Entrada Y Salida Io

Httpclient Moderno

Entrada Y Salida Io

Clases De Nio2

Entrada Y Salida Io

Api Java.time

Api Java.time

Localtime

Api Java.time

Localdatetime

Api Java.time

Localdate

Api Java.time

Executorservice

Concurrencia

Virtual Threads (Project Loom)

Concurrencia

Future Y Completablefuture

Concurrencia

Spring Framework

Frameworks Para Java

Micronaut

Frameworks Para Java

Maven

Frameworks Para Java

Gradle

Frameworks Para Java

Lombok Para Java

Frameworks Para Java

Quarkus

Frameworks Para Java

Ecosistema Jakarta Ee De Java

Frameworks Para Java

Introducción A Junit 5

Testing

Accede GRATIS a Java y certifícate

Certificados de superación de Java

Supera todos los ejercicios de programación del curso de Java y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender la interfaz List y sus características principales.
  • Conocer las implementaciones comunes de List, especialmente ArrayList.
  • Aprender a crear, modificar, acceder y eliminar elementos en listas.
  • Entender el rendimiento y uso adecuado de ArrayList.
  • Implementar operaciones CRUD con listas y objetos en Java.