Saltar al contenido
CodigoDev

10 Ejercicios resueltos de Genéricos y Colecciones en Java

Java Intermedio
Ejercicios resueltos de Genéricos y Colecciones en Java
lightbulb Introducción al ejercicio

Aprende a dominar los genéricos y las colecciones en Java con esta guía práctica. Descubre cómo crear código más seguro, flexible y reutilizable a través de 10 ejercicios resueltos paso a paso, ideales para afianzar tus conocimientos y mejorar tu lógica de programación.

menu_book

Aprendizaje teórico

¿Qué son los Genéricos y las Colecciones en Java?

En Java, las Colecciones son estructuras de datos avanzadas que nos permiten almacenar, organizar y manipular grupos de objetos de forma eficiente. Piensa en ellas como cajas especializadas: algunas mantienen el orden exacto (como List), otras evitan automáticamente los duplicados (Set), y otras guardan los datos en pares de clave-valor (Map).

Por otro lado, los Genéricos (representados comúnmente con símbolos como <T>) son una característica que nos permite especificar exactamente qué tipo de dato guardará esa «caja».

¿Para qué sirven?

Antes de que existieran los genéricos, las colecciones guardaban objetos de tipo Object, lo que provocaba errores sorpresivos al ejecutar el código si mezclábamos datos. Los genéricos aportan seguridad de tipos: el compilador te avisa de inmediato si intentas guardar un número en una lista que creaste para textos.

Ejemplo breve: en lugar de crear una clase para enteros y otra para textos, creas una clase genérica Caja<T>. Al instanciarla, decides su contenido: Caja<String> o Caja<Integer>. Esto hace tu código mucho más reutilizable.

Ejercicio 1: Clase Genérica Simple

help_outline Problema:

Crea una clase genérica llamada Caja que pueda almacenar un objeto de cualquier tipo. La clase debe tener un constructor para inicializar el objeto, un método para guardarlo (modificarlo) y otro para obtenerlo. Pruébala guardando un texto y luego un número.

Pista

Utiliza la convención <T> al definir el nombre de la clase para indicar que trabajará con un tipo de dato genérico.

code Solución explicada:
java
class Caja<T> {
    private T contenido;

    public Caja(T contenido) {
        this.contenido = contenido;
    }

    public T getContenido() {
        return contenido;
    }

    public void setContenido(T contenido) {
        this.contenido = contenido;
    }
}

public class Main {
    public static void main(String[] args) {
        // Creamos una caja para textos
        Caja<String> cajaTexto = new Caja<>("Hola Mundo");
        System.out.println("Caja de texto: " + cajaTexto.getContenido());

        // Creamos una caja para números enteros
        Caja<Integer> cajaNumero = new Caja<>(42);
        System.out.println("Caja de número: " + cajaNumero.getContenido());
    }
}

terminal Resultado esperado:

Caja de texto: Hola Mundo
Caja de número: 42

Ejercicio 2: Método Genérico

help_outline Problema:

Escribe una clase que contenga un método genérico capaz de recibir un arreglo de cualquier tipo (ya sean números, textos, etc.) y lo imprima elemento por elemento en la consola, separados por una coma.

Pista

El parámetro de tipo <T> debe colocarse justo antes del tipo de retorno del método (por ejemplo, antes de void).

code Solución explicada:
java
public class Main {

    // Método genérico
    public static <T> void imprimirArreglo(T[] arreglo) {
        for (int i = 0; i < arreglo.length; i++) {
            System.out.print(arreglo[i]);
            if (i < arreglo.length - 1) {
                System.out.print(", ");
            }
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Integer[] numeros = {1, 2, 3, 4, 5};
        String[] palabras = {"Java", "es", "genial"};

        System.out.print("Arreglo de números: ");
        imprimirArreglo(numeros);

        System.out.print("Arreglo de palabras: ");
        imprimirArreglo(palabras);
    }
}

terminal Resultado esperado:

Arreglo de números: 1, 2, 3, 4, 5
Arreglo de palabras: Java, es, genial

Ejercicio 3: Lista de tareas con ArrayList

help_outline Problema:

Crea un programa que utilice la colección ArrayList para gestionar una lista de tareas pendientes. Debes añadir tres tareas en formato de texto, eliminar la segunda tarea de la lista y mostrar por consola las tareas restantes.

Pista

Utiliza el método add() para agregar elementos y remove(indice) para eliminar, recordando que los índices en Java empiezan en 0.

code Solución explicada:
java
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // Declaramos la lista genérica para Strings
        List<String> tareas = new ArrayList<>();

        // Añadimos elementos
        tareas.add("Aprender Genéricos");
        tareas.add("Hacer las compras");
        tareas.add("Practicar con colecciones");

        System.out.println("Lista original: " + tareas);

        // Eliminamos la segunda tarea (índice 1)
        tareas.remove(1);

        System.out.println("Lista actualizada: " + tareas);
    }
}

terminal Resultado esperado:

Lista original: [Aprender Genéricos, Hacer las compras, Practicar con colecciones]
Lista actualizada: [Aprender Genéricos, Practicar con colecciones]

Ejercicio 4: Eliminar duplicados con HashSet

help_outline Problema:

Imagina que tienes una lista de números enteros donde varios valores se repiten. Utiliza la colección HashSet para crear una nueva estructura que contenga únicamente los números únicos y muéstrala en consola.

Pista

La colección Set en Java no permite elementos duplicados. Puedes pasar la lista original directamente al constructor del HashSet para limpiarla al instante.

code Solución explicada:
java
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        List<Integer> numerosConDuplicados = new ArrayList<>();
        numerosConDuplicados.add(5);
        numerosConDuplicados.add(10);
        numerosConDuplicados.add(5);
        numerosConDuplicados.add(20);
        numerosConDuplicados.add(10);

        System.out.println("Con duplicados: " + numerosConDuplicados);

        // Al pasar la lista al HashSet, los duplicados se eliminan automáticamente
        Set<Integer> numerosUnicos = new HashSet<>(numerosConDuplicados);

        System.out.println("Sin duplicados: " + numerosUnicos);
    }
}

terminal Resultado esperado:

Con duplicados: [5, 10, 5, 20, 10]
Sin duplicados: [20, 5, 10]

Ejercicio 5: Registro de calificaciones con HashMap

help_outline Problema:

Utiliza un HashMap para crear un registro de calificaciones. Guarda el nombre de tres estudiantes como clave (String) y su calificación numérica como valor (Double). Luego, recupera e imprime la calificación de un estudiante en particular.

Pista

Usa el método put(clave, valor) para guardar la información y get(clave) para recuperarla fácilmente mediante el nombre.

code Solución explicada:
java
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        // Map con clave String (nombre) y valor Double (calificación)
        Map<String, Double> calificaciones = new HashMap<>();

        // Guardamos los datos
        calificaciones.put("Ana", 8.5);
        calificaciones.put("Carlos", 9.2);
        calificaciones.put("Lucia", 7.8);

        // Buscamos a un estudiante
        String estudianteABuscar = "Carlos";
        Double nota = calificaciones.get(estudianteABuscar);

        if (nota != null) {
            System.out.println("La calificación de " + estudianteABuscar + " es: " + nota);
        } else {
            System.out.println("Estudiante no encontrado.");
        }
    }
}

terminal Resultado esperado:

La calificación de Carlos es: 9.2

Ejercicio 6: Clase genérica con múltiples tipos

help_outline Problema:

Crea una clase genérica llamada Pareja que acepte dos tipos de datos diferentes (uno para un identificador y otro para el valor). Instancia la clase para guardar el nombre de una persona y su edad, y luego muéstralos.

Pista

Declara la clase utilizando dos letras para los genéricos, por ejemplo class Pareja<K, V>, y define los atributos utilizando esos mismos tipos.

code Solución explicada:
java
class Pareja<K, V> {
    private K clave;
    private V valor;

    public Pareja(K clave, V valor) {
        this.clave = clave;
        this.valor = valor;
    }

    public void mostrarDatos() {
        System.out.println("Clave: " + clave + " | Valor: " + valor);
    }
}

public class Main {
    public static void main(String[] args) {
        // Usamos String para la clave y Integer para el valor
        Pareja<String, Integer> persona = new Pareja<>("Alejandro", 30);
        persona.mostrarDatos();

        // Podemos reusar la clase para otros tipos de datos
        Pareja<Integer, String> codigoPostal = new Pareja<>(1001, "Zona Central");
        codigoPostal.mostrarDatos();
    }
}

terminal Resultado esperado:

Clave: Alejandro | Valor: 30
Clave: 1001 | Valor: Zona Central

Ejercicio 7: Restricciones de tipo (Bounded Types)

help_outline Problema:

Crea una clase genérica llamada Calculadora que tenga una restricción: solo debe aceptar números (clases que deriven de Number). Implementa un método que reciba un valor genérico y devuelva su doble exacto convertido a tipo double.

Pista

Utiliza la palabra clave extends en la declaración del genérico (<T extends Number>). Esto te permitirá usar el método doubleValue() que comparten todos los números en Java.

code Solución explicada:
java
// Restringimos T para que solo acepte subclases de Number
class Calculadora<T extends Number> {
    private T numero;

    public Calculadora(T numero) {
        this.numero = numero;
    }

    public double obtenerDoble() {
        // Al saber que es un Number, podemos usar doubleValue()
        return numero.doubleValue() * 2;
    }
}

public class Main {
    public static void main(String[] args) {
        Calculadora<Integer> calcEntero = new Calculadora<>(15);
        System.out.println("Doble de 15 (Entero): " + calcEntero.obtenerDoble());

        Calculadora<Double> calcDecimal = new Calculadora<>(10.5);
        System.out.println("Doble de 10.5 (Decimal): " + calcDecimal.obtenerDoble());
        
        // Calculadora<String> error = new Calculadora<>("Hola"); // Esto daría error de compilación
    }
}

terminal Resultado esperado:

Doble de 15 (Entero): 30.0
Doble de 10.5 (Decimal): 21.0

Ejercicio 8: Ordenar objetos con Comparable

help_outline Problema:

Crea una clase Jugador que tenga un nombre y un puntaje. Haz que esta clase implemente la interfaz genérica Comparable<Jugador> para que los jugadores puedan ordenarse de mayor a menor puntaje usando Collections.sort().

Pista

Sobrescribe el método compareTo(Jugador otro). Para ordenar de mayor a menor (descendente), compara el puntaje del otro jugador con el tuyo.

code Solución explicada:
java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class Jugador implements Comparable<Jugador> {
    private String nombre;
    private int puntaje;

    public Jugador(String nombre, int puntaje) {
        this.nombre = nombre;
        this.puntaje = puntaje;
    }

    @Override
    public int compareTo(Jugador otro) {
        // Orden descendente (mayor a menor)
        return Integer.compare(otro.puntaje, this.puntaje);
    }

    @Override
    public String toString() {
        return nombre + " (" + puntaje + " pts)";
    }
}

public class Main {
    public static void main(String[] args) {
        List<Jugador> jugadores = new ArrayList<>();
        jugadores.add(new Jugador("Mario", 1500));
        jugadores.add(new Jugador("Luigi", 2100));
        jugadores.add(new Jugador("Peach", 1800));

        System.out.println("Sin ordenar: " + jugadores);

        // Collections.sort() utiliza el método compareTo internamente
        Collections.sort(jugadores);

        System.out.println("Ordenados: " + jugadores);
    }
}

terminal Resultado esperado:

Sin ordenar: [Mario (1500 pts), Luigi (2100 pts), Peach (1800 pts)]
Ordenados: [Luigi (2100 pts), Peach (1800 pts), Mario (1500 pts)]

Ejercicio 9: Comodines (Wildcards) con colecciones

help_outline Problema:

Escribe un método estático que reciba una lista, pero con una condición: la lista debe poder contener cualquier tipo numérico (Double, Integer, Float). El método debe sumar todos los elementos y devolver el total.

Pista

En los parámetros del método, utiliza el comodín o wildcard ? combinado con extends Number, así: List<? extends Number>.

code Solución explicada:
java
import java.util.Arrays;
import java.util.List;

public class Main {

    // Método que acepta una lista de cualquier tipo que herede de Number
    public static double sumarLista(List<? extends Number> lista) {
        double suma = 0.0;
        for (Number num : lista) {
            suma += num.doubleValue();
        }
        return suma;
    }

    public static void main(String[] args) {
        List<Integer> enteros = Arrays.asList(1, 2, 3, 4);
        List<Double> decimales = Arrays.asList(1.5, 2.5, 3.0);

        System.out.println("Suma de enteros: " + sumarLista(enteros));
        System.out.println("Suma de decimales: " + sumarLista(decimales));
    }
}

terminal Resultado esperado:

Suma de enteros: 10.0
Suma de decimales: 7.0

Ejercicio 10: Integración de Genéricos y Colecciones

help_outline Problema:

Crea un mini sistema de inventario. Define una clase genérica Producto<T> (donde T representa el tipo de código del producto, que puede ser alfanumérico o numérico). Luego, utiliza un HashMap para guardar el inventario, asociando el código del producto como clave y el objeto Producto como valor. Por último, imprime el inventario.

Pista

El Map utilizará la misma declaración de tipo que el identificador del producto. Si T es un String, el Map se definirá como Map<String, Producto<String>>.

code Solución explicada:
java
import java.util.HashMap;
import java.util.Map;

// Clase producto genérica para soportar distintos formatos de códigos
class Producto<T> {
    private T codigo;
    private String nombre;
    private double precio;

    public Producto(T codigo, String nombre, double precio) {
        this.codigo = codigo;
        this.nombre = nombre;
        this.precio = precio;
    }

    public T getCodigo() { 
        return codigo; 
    }

    @Override
    public String toString() {
        return nombre + " - $" + precio;
    }
}

public class Main {
    public static void main(String[] args) {
        // Inventario donde el código es de tipo String
        Map<String, Producto<String>> inventario = new HashMap<>();

        Producto<String> prod1 = new Producto<>("A-101", "Teclado Mecánico", 45.99);
        Producto<String> prod2 = new Producto<>("B-202", "Ratón Inalámbrico", 25.50);

        inventario.put(prod1.getCodigo(), prod1);
        inventario.put(prod2.getCodigo(), prod2);

        // Imprimir todo el inventario iterando sobre el Map
        System.out.println("--- Inventario de la Tienda ---");
        for (Map.Entry<String, Producto<String>> entrada : inventario.entrySet()) {
            System.out.println("Código: " + entrada.getKey() + " | Detalles: " + entrada.getValue());
        }
    }
}

terminal Resultado esperado:

--- Inventario de la Tienda ---
Código: A-101 | Detalles: Teclado Mecánico - $45.99
Código: B-202 | Detalles: Ratón Inalámbrico - $25.50
help

Preguntas frecuentes

¿Cuál es la diferencia entre ArrayList y LinkedList?

Ambas implementan la interfaz List. ArrayList usa un arreglo dinámico, siendo más rápido para buscar elementos. LinkedList enlaza los datos como una cadena, por lo que es más eficiente al insertar o eliminar elementos en medio de la lista.

¿Por qué usar genéricos en lugar de Object?

Los genéricos proporcionan seguridad de tipos en tiempo de compilación. Si usas Object, puedes guardar cualquier cosa, pero al recuperar los datos debes hacer conversiones manuales (casteos), lo que suele causar errores inesperados al ejecutar el programa.

¿Qué diferencia hay entre List, Set y Map?

List guarda elementos en orden y permite duplicados. Set no mantiene un orden específico y prohíbe terminantemente los elementos repetidos. Map funciona diferente, ya que almacena la información relacionándola siempre mediante pares de clave y valor.

task_alt

Conclusión

Dominar las colecciones y los genéricos marca la diferencia entre un código rígido y uno verdaderamente profesional. Como hemos visto en estos ejercicios, estas herramientas te permiten crear estructuras de datos adaptables, seguras y altamente reutilizables.

Te animo a seguir experimentando: cambia los tipos de datos, combina diferentes colecciones o crea tus propias clases genéricas. La práctica constante es la mejor forma de afianzar estos conceptos en tu mente. ¡Sigue programando y elevando la calidad de tus proyectos en Java!

rocket_launch

Sigue aprendiendo

Guía profunda sobre el funcionamiento de los genéricos, explicada por los creadores del lenguaje.

Aprende a fondo la jerarquía completa de colecciones y sus interfaces principales.

A
Autor del artículo

Alejandro Nes

calendar_today update Actualizado

Desarrollador de Software y creador de contenido educativo

hardware

Herramientas recomendadas para practicar

Java

Java es un lenguaje de programación orientado a objetos, tipado y multiplataforma, respaldado por la JVM. Su principio de «escribe una vez, ejecuta en cualquier lugar» lo hace útil para sistemas empresariales, microservicios en la nube, escritorio y Android.

Otros ejercicios prácticos recomendados

Ver todos arrow_forward