Aprender Java exige práctica constante. La teoría es imprescindible, pero es al escribir código, cometer errores y depurarlos cuando realmente se consolida el aprendizaje. Esta guía reúne 50 ejercicios de Java resueltos y explicados paso a paso, organizados desde los fundamentos más básicos hasta conceptos avanzados de programación orientada a objetos.
La colección está pensada tanto para estudiantes universitarios que cursan asignaturas de programación como para autodidactas que desean afianzar sus conocimientos. Los ejercicios siguen una progresión cuidadosamente diseñada: si eres capaz de resolver los últimos de cada sección sin consultar la solución, habrás alcanzado un nivel sólido en esa materia.
💡
Consejo pedagógico: Intenta resolver cada ejercicio por tu cuenta antes de consultar la solución. El aprendizaje real se produce cuando luchas con el problema, no cuando lees la respuesta. Dedica al menos 10-15 minutos a cada ejercicio antes de mirar la solución.
📋 Cómo usar esta guía de ejercicios
Los 50 ejercicios están agrupados en 10 categorías temáticas de dificultad creciente. Dentro de cada categoría, los ejercicios también van de menor a mayor complejidad. Al final, un proyecto integrador combina todos los conceptos en una aplicación real.
Categoría
Ejercicios
Nivel
Requisitos previos
Variables y tipos de datos
1 – 5
🟢 Principiante
Ninguno
Condicionales
6 – 10
🟢 Principiante
Variables
Bucles
11 – 15
🟢 Principiante
Condicionales
Arrays y matrices
16 – 20
🟡 Intermedio
Bucles
Cadenas de texto
21 – 25
🟡 Intermedio
Arrays
Métodos y funciones
26 – 30
🟡 Intermedio
Strings
POO: clases y objetos
31 – 35
🟡 Intermedio
Métodos
POO avanzada
36 – 40
🔴 Avanzado
Clases y objetos
Excepciones
41 – 45
🔴 Avanzado
POO
Colecciones
46 – 50
🔴 Avanzado
Excepciones
✅
Buena práctica: Crea un proyecto en tu IDE para cada sección. Así tendrás tu propio repositorio personal de ejercicios resueltos al que recurrir como referencia rápida.
🔢 Ejercicios de variables y tipos de datos
Las variables son el pilar fundamental de cualquier programa. En Java, cada variable tiene un tipo de dato que determina qué valores puede almacenar y qué operaciones se pueden realizar con ella. Estos ejercicios cubren declaración, inicialización, conversión de tipos y uso de las clases matemáticas de Java.
📝 Ejercicio 1: Intercambio de variables sin variable auxiliar
Declara dos variables enteras con valores 10 y 25. Intercambia sus valores sin usar una tercera variable y muestra el resultado por consola.
Java
public classIntercambioVariables {
public static voidmain(String[] args) {
int a = 10;
int b = 25;
System.out.println("Antes: a=" + a + ", b=" + b);
a = a + b; // a = 35
b = a - b; // b = 10 (valor original de a)
a = a - b; // a = 25 (valor original de b)System.out.println("Después: a=" + a + ", b=" + b);
}
}
Salida:Antes: a=10, b=25 → Después: a=25, b=10
Explicación: Sumamos ambos en a, restamos para recuperar cada valor original. También se puede resolver con XOR: a ^= b; b ^= a; a ^= b;. Ambas técnicas evitan la variable temporal.
📝 Ejercicio 2: Conversión de temperatura
Convierte una temperatura de 36.6 grados Celsius a Fahrenheit (F = C × 9/5 + 32) y a Kelvin (K = C + 273.15). Muestra los resultados formateados.
Explicación: Usamos double para decimales. Es crucial escribir 9.0 / 5.0 y no 9 / 5, porque la división entre enteros en Java produce un entero (1), generando un resultado incorrecto.
📝 Ejercicio 3: Desglose de segundos en horas, minutos y segundos
Dado un total de 86523 segundos, desglósalo en horas, minutos y segundos restantes. Formato de salida: HH:MM:SS.
Java
public classDesgloseSegundos {
public static voidmain(String[] args) {
int total = 86523;
int horas = total / 3600;
int minutos = (total % 3600) / 60;
int segundos = total % 60;
System.out.printf("%02d:%02d:%02d%n", horas, minutos, segundos);
}
}
Salida:24:02:03
Explicación: El operador / entre enteros da el cociente y % da el resto. %02d formatea con cero a la izquierda si el dígito es menor que 10.
📝 Ejercicio 4: Área y perímetro de un círculo
Calcula el área y el perímetro de un círculo de radio 7.5 usando Math.PI. Muestra los resultados con 4 decimales.
Java
public classCirculo {
public static voidmain(String[] args) {
double radio = 7.5;
double area = Math.PI * Math.pow(radio, 2);
double perimetro = 2 * Math.PI * radio;
System.out.printf("Radio: %.1f%n", radio);
System.out.printf("Área: %.4f%n", area);
System.out.printf("Perímetro: %.4f%n", perimetro);
}
}
Salida:Área: 176.7146 | Perímetro: 47.1239
Explicación:Math.PI ofrece π con máxima precisión double. Math.pow(radio, 2) eleva al cuadrado; también podría escribirse radio * radio, que es más eficiente.
📝 Ejercicio 5: Casting y promoción de tipos
Convierte un int con valor 257 a byte, y un double con valor 9.99 a int. Muestra los resultados y explica por qué cambian los valores.
Explicación:byte almacena -128 a 127; al convertir 257 se produce overflow (257 % 256 = 1). La conversión double → int trunca la parte decimal sin redondear. Ambos son narrowing casting, requieren cast explícito y pueden perder información.
🔀 Ejercicios de condicionales (if, switch)
Las estructuras condicionales permiten tomar decisiones en el programa. Java ofrece if-else para condiciones generales y switch para seleccionar entre múltiples opciones discretas.
📝 Ejercicio 6: Clasificador de notas
Escribe un programa que reciba una nota (0-10) y muestre la calificación: Suspenso (0-4), Aprobado (5-6), Notable (7-8), Sobresaliente (9), Matrícula de Honor (10).
Java
public classClasificadorNotas {
public static voidmain(String[] args) {
double nota = 7.5;
String calificacion;
if (nota < 0 || nota > 10) {
calificacion = "Nota no válida";
} else if (nota < 5) {
calificacion = "Suspenso";
} else if (nota < 7) {
calificacion = "Aprobado";
} else if (nota < 9) {
calificacion = "Notable";
} else if (nota < 10) {
calificacion = "Sobresaliente";
} else {
calificacion = "Matrícula de Honor";
}
System.out.println("Nota: " + nota + " → " + calificacion);
}
}
Explicación: El orden importa: al usar < en cada escalón, las condiciones se evalúan de menor a mayor. Si llegamos a un else if, sabemos que las condiciones anteriores eran falsas.
📝 Ejercicio 7: Calculadora con switch
Implementa una calculadora que reciba dos números y un operador (+, -, *, /) y ejecute la operación. Controla la división por cero.
Java
public classCalculadora {
public static voidmain(String[] args) {
double a = 15.0, b = 4.0;
char op = '/';
double resultado;
switch (op) {
case'+': resultado = a + b; break;
case'-': resultado = a - b; break;
case'*': resultado = a * b; break;
case'/':
if (b == 0) { System.out.println("Error: división por cero"); return; }
resultado = a / b; break;
default:
System.out.println("Operador no válido"); return;
}
System.out.printf("%.1f %c %.1f = %.2f%n", a, op, b, resultado);
}
}
Salida:15.0 / 4.0 = 3.75
Explicación:switch evalúa el operador. El break es obligatorio para evitar el fall-through. El default captura operadores no reconocidos.
📝 Ejercicio 8: Año bisiesto
Determina si un año es bisiesto. Regla: divisible por 4, excepto si es divisible por 100, salvo que también sea divisible por 400.
Java
public classAnioBisiesto {
public static voidmain(String[] args) {
int anio = 2024;
boolean bisiesto = (anio % 4 == 0 && anio % 100 != 0)
|| (anio % 400 == 0);
System.out.println(anio + (bisiesto ? " SÍ es bisiesto" : " NO es bisiesto"));
}
}
Explicación: La condición compuesta expresa las tres reglas en una línea. Ejemplos: 2000 (sí, %400), 1900 (no, %100 pero no %400), 2024 (sí, %4 y no %100).
📝 Ejercicio 9: Clasificador de triángulos
Dados tres lados, determina si forman un triángulo válido y clasifícalo en equilátero, isósceles o escaleno.
Java
public classClasificadorTriangulos {
public static voidmain(String[] args) {
double a = 5, b = 5, c = 8;
if (a + b <= c || a + c <= b || b + c <= a) {
System.out.println("No forman un triángulo válido"); return;
}
if (a == b && b == c) System.out.println("Equilátero");
else if (a == b || b == c || a == c) System.out.println("Isósceles");
elseSystem.out.println("Escaleno");
}
}
Explicación: Primero la desigualdad triangular (suma de dos lados > tercero). Luego clasificamos: 3 iguales = equilátero, 2 iguales = isósceles, todos distintos = escaleno.
📝 Ejercicio 10: Número mayor de tres con operador ternario
Dados tres números enteros, encuentra el mayor usando exclusivamente operadores ternarios anidados (sin if-else ni Math.max).
Java
public classMayorDeTres {
public static voidmain(String[] args) {
int a = 42, b = 87, c = 65;
int mayor = (a > b) ? (a > c ? a : c) : (b > c ? b : c);
System.out.println("Mayor de (" + a + "," + b + "," + c + ") = " + mayor);
}
}
Salida:Mayor de (42,87,65) = 87
Explicación: El ternario anidado primero compara a con b; el ganador se compara con c. Es compacto pero menos legible que Math.max(a, Math.max(b, c)) — úsalo con moderación.
🔄 Ejercicios de bucles (for, while, do-while)
Los bucles permiten repetir bloques de código. Java ofrece for (iteraciones conocidas), while (condición evaluada antes) y do-while (al menos una ejecución garantizada).
📝 Ejercicio 11: Serie de Fibonacci
Genera los primeros 20 números de Fibonacci (0, 1, 1, 2, 3, 5, 8, 13...).
Java
public classFibonacci {
public static voidmain(String[] args) {
int n = 20;
long anterior = 0, actual = 1;
for (int i = 0; i < n; i++) {
System.out.print(anterior + (i < n - 1 ? ", " : "\n"));
long sig = anterior + actual;
anterior = actual;
actual = sig;
}
}
}
Explicación: Usamos long porque Fibonacci crece rápidamente y desbordaría un int a partir del término 47. Enfoque iterativo O(n), mucho más eficiente que la versión recursiva O(2^n).
📝 Ejercicio 12: Números primos hasta 100
Muestra todos los números primos entre 2 y 100. Optimiza probando divisores solo hasta la raíz cuadrada.
Java
public classNumerosPrimos {
public static voidmain(String[] args) {
int limite = 100, cont = 0;
for (int num = 2; num <= limite; num++) {
boolean primo = true;
for (int d = 2; d <= Math.sqrt(num); d++) {
if (num % d == 0) { primo = false; break; }
}
if (primo) {
System.out.printf("%4d", num);
if (++cont % 10 == 0) System.out.println();
}
}
System.out.println("\nTotal: " + cont + " primos");
}
}
Explicación: Solo probamos divisores hasta √n: si un número tiene un divisor mayor que su raíz, necesariamente tiene otro menor. El break optimiza al encontrar el primer divisor.
📝 Ejercicio 13: Pirámide de asteriscos
Dibuja una pirámide centrada de 7 filas con asteriscos.
Java
public classPiramide {
public static voidmain(String[] args) {
int filas = 7;
for (int i = 1; i <= filas; i++) {
System.out.print(" ".repeat(filas - i)); // espaciosSystem.out.println("*".repeat(2 * i - 1)); // asteriscos
}
}
}
Explicación: En la fila i hay filas - i espacios y 2*i - 1 asteriscos. String.repeat() (Java 11+) simplifica enormemente la generación de patrones repetitivos.
📝 Ejercicio 14: Máximo Común Divisor (algoritmo de Euclides)
Implementa el algoritmo de Euclides con while para calcular el MCD de 48 y 18.
Java
public classMcdEuclides {
public static voidmain(String[] args) {
int a = 48, b = 18;
int origA = a, origB = b;
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
System.out.println("MCD(" + origA + ", " + origB + ") = " + a);
}
}
Salida:MCD(48, 18) = 6
Explicación: Euclides reemplaza el mayor por el resto de la división hasta que el resto sea 0. Proceso: 48%18=12 → 18%12=6 → 12%6=0. Es uno de los algoritmos más antiguos (300 a.C.).
📝 Ejercicio 15: Adivinar un número (do-while)
Simula un juego donde el usuario debe adivinar un número aleatorio entre 1 y 100. Da pistas ("más alto" / "más bajo") hasta acertar. Usa do-while.
Java
import java.util.Scanner;
import java.util.Random;
public classAdivinaNumero {
public static voidmain(String[] args) {
int secreto = newRandom().nextInt(100) + 1;
Scanner sc = newScanner(System.in);
int intento, intentos = 0;
System.out.println("Adivina el número (1-100):");
do {
System.out.print("Tu intento: ");
intento = sc.nextInt();
intentos++;
if (intento < secreto) System.out.println("↑ Más alto");
else if (intento > secreto) System.out.println("↓ Más bajo");
} while (intento != secreto);
System.out.println("¡Correcto! Lo adivinaste en " + intentos + " intentos.");
sc.close();
}
}
Explicación:do-while garantiza al menos una lectura. Random.nextInt(100) + 1 genera un número entre 1 y 100. El bucle continúa hasta que el intento coincida con el número secreto.
📊 Ejercicios de arrays y matrices
Los arrays almacenan múltiples valores del mismo tipo en posiciones consecutivas de memoria. En Java tienen tamaño fijo. Son la base para entender colecciones más avanzadas.
📝 Ejercicio 16: Estadísticas de un array
Calcula mínimo, máximo, suma, media y cantidad de pares de un array de enteros.
Java
public classEstadisticasArray {
public static voidmain(String[] args) {
int[] datos = {23, 8, 45, 12, 67, 34, 91, 56, 3, 78};
int min = datos[0], max = datos[0], suma = 0, pares = 0;
for (int v : datos) {
if (v < min) min = v;
if (v > max) max = v;
suma += v;
if (v % 2 == 0) pares++;
}
System.out.printf("Min=%d Max=%d Suma=%d Media=%.2f Pares=%d%n",
min, max, suma, (double) suma / datos.length, pares);
}
}
Explicación: Inicializamos min/max con el primer elemento (nunca con 0). El for-each recorre todo el array. Una sola pasada O(n) calcula todas las estadísticas.
Ordena un array de enteros de menor a mayor con el algoritmo Bubble Sort. Incluye la optimización de parada anticipada.
Java
import java.util.Arrays;
public classBubbleSort {
public static voidmain(String[] args) {
int[] arr = {64, 34, 25, 12, 22, 11, 90};
System.out.println("Antes: " + Arrays.toString(arr));
for (int i = 0; i < arr.length - 1; i++) {
boolean swap = false;
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = tmp;
swap = true;
}
}
if (!swap) break;
}
System.out.println("Después: " + Arrays.toString(arr));
}
}
Explicación: Compara pares adyacentes e intercambia si están desordenados. La bandera swap permite terminar anticipadamente si ya está ordenado. Complejidad: O(n²) peor caso, O(n) mejor caso.
📝 Ejercicio 18: Búsqueda binaria
Implementa búsqueda binaria en un array ordenado. Busca el valor 34 en {2, 5, 8, 12, 16, 23, 34, 45, 56, 72, 91}.
Java
public classBusquedaBinaria {
static intbuscar(int[] arr, int objetivo) {
int izq = 0, der = arr.length - 1;
while (izq <= der) {
int mid = izq + (der - izq) / 2;
if (arr[mid] == objetivo) return mid;
else if (arr[mid] < objetivo) izq = mid + 1;
else der = mid - 1;
}
return -1;
}
public static voidmain(String[] args) {
int[] datos = {2, 5, 8, 12, 16, 23, 34, 45, 56, 72, 91};
System.out.println("34 encontrado en posición: " + buscar(datos, 34));
}
}
Salida:34 encontrado en posición: 6
Explicación: Divide el espacio de búsqueda a la mitad en cada iteración. Usamos izq + (der - izq) / 2 para evitar overflow. Complejidad: O(log n).
📝 Ejercicio 19: Matriz traspuesta
Calcula la traspuesta de una matriz 3×3 (intercambiar filas por columnas).
Java
public classMatrizTraspuesta {
public static voidmain(String[] args) {
int[][] m = {{1,2,3}, {4,5,6}, {7,8,9}};
int[][] t = new int[3][3];
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
t[j][i] = m[i][j];
System.out.println("Traspuesta:");
for (int[] fila : t)
System.out.println(java.util.Arrays.toString(fila));
}
}
Explicación: El elemento [i][j] pasa a [j][i]. Creamos una nueva matriz para no modificar la original.
📝 Ejercicio 20: Eliminar duplicados de un array
Dado {4, 2, 7, 2, 4, 8, 7, 1}, genera un nuevo array sin duplicados manteniendo el orden original.
Java
import java.util.*;
public classEliminarDuplicados {
public static voidmain(String[] args) {
int[] original = {4, 2, 7, 2, 4, 8, 7, 1};
LinkedHashSet<Integer> set = newLinkedHashSet<>();
for (int n : original) set.add(n);
int[] sinDuplicados = set.stream().mapToInt(Integer::intValue).toArray();
System.out.println("Original: " + Arrays.toString(original));
System.out.println("Sin dup.: " + Arrays.toString(sinDuplicados));
}
}
Salida:Sin dup.: [4, 2, 7, 8, 1]
Explicación:LinkedHashSet descarta duplicados y mantiene el orden de inserción. El stream convierte de vuelta a int[]. Sin colecciones, habría que recorrer con un bucle anidado O(n²).
🔤 Ejercicios de cadenas de texto (String)
La clase String en Java es inmutable: cada operación crea un nuevo objeto. Dominar sus métodos es fundamental para el procesamiento de texto, validaciones y transformaciones de datos.
📝 Ejercicio 21: Detector de palíndromos
Determina si una cadena es palíndromo ignorando mayúsculas, espacios y tildes. Prueba con "Anita lava la tina".
Java
import java.text.Normalizer;
public classPalindromo {
static booleanesPalindromo(String texto) {
String limpio = Normalizer.normalize(texto, Normalizer.Form.NFD)
.replaceAll("[\\p{M}]", "")
.replaceAll("[^a-zA-Z0-9]", "")
.toLowerCase();
int i = 0, j = limpio.length() - 1;
while (i < j) {
if (limpio.charAt(i++) != limpio.charAt(j--)) returnfalse;
}
returntrue;
}
public static voidmain(String[] args) {
String[] pruebas = {"Anita lava la tina", "Dábale arroz a la zorra el abad", "Hola"};
for (String t : pruebas)
System.out.printf("\"%s\" → %s%n", t, esPalindromo(t) ? "SÍ" : "NO");
}
}
Explicación:Normalizer descompone acentos (á → a + marcas), luego eliminamos las marcas. Dos punteros convergen comparando caracteres — más eficiente que invertir todo el String.
📝 Ejercicio 22: Contar palabras y vocales
Cuenta vocales, consonantes, dígitos y palabras en "Java 21 es genial".
Java
public classContadorTexto {
public static voidmain(String[] args) {
String texto = "Java 21 es genial";
int vocales = 0, consonantes = 0, digitos = 0;
for (char c : texto.toLowerCase().toCharArray()) {
if ("aeiouáéíóú".indexOf(c) != -1) vocales++;
else if (Character.isLetter(c)) consonantes++;
else if (Character.isDigit(c)) digitos++;
}
int palabras = texto.trim().split("\\s+").length;
System.out.printf("Vocales=%d Consonantes=%d Dígitos=%d Palabras=%d%n",
vocales, consonantes, digitos, palabras);
}
}
Explicación:indexOf busca en un string de vocales. split("\\s+") divide por uno o más espacios para contar palabras correctamente.
📝 Ejercicio 23: Cifrado César
Cifra "HOLA MUNDO" desplazando cada letra 3 posiciones. Descifra con desplazamiento 23 (26-3).
Comprime "aaabbbccddddee" a "a3b3c2d4e2". Si la compresión no reduce el tamaño, devuelve la original.
Java
public classCompresionRLE {
staticStringcomprimir(String s) {
StringBuilder sb = newStringBuilder();
int i = 0;
while (i < s.length()) {
char c = s.charAt(i);
int count = 0;
while (i < s.length() && s.charAt(i) == c) { count++; i++; }
sb.append(c).append(count);
}
return sb.length() < s.length() ? sb.toString() : s;
}
public static voidmain(String[] args) {
System.out.println(comprimir("aaabbbccddddee")); // a3b3c2d4e2System.out.println(comprimir("abcde")); // abcde (no comprime)
}
}
Explicación: Recorremos la cadena contando caracteres consecutivos iguales. Si la versión comprimida no es más corta, devolvemos la original. Este algoritmo es la base de formatos de compresión como BMP y TIFF.
⚙️ Ejercicios de métodos y funciones
Los métodos permiten dividir un programa en bloques reutilizables. Un buen diseño sigue el principio de responsabilidad única: cada método hace una sola cosa y la hace bien.
📝 Ejercicio 26: Método recursivo — Factorial
Implementa el cálculo del factorial de forma recursiva e iterativa. Compara ambos enfoques.
Java
public classFactorial {
static longfactorialRec(int n) {
if (n <= 1) return1;
return n * factorialRec(n - 1);
}
static longfactorialIter(int n) {
long r = 1;
for (int i = 2; i <= n; i++) r *= i;
return r;
}
public static voidmain(String[] args) {
for (int n : new int[]{5, 10, 20})
System.out.printf("%d! = %d%n", n, factorialIter(n));
}
}
Explicación: La versión recursiva es elegante pero consume pila. La iterativa es más eficiente. Con long, el máximo factorial preciso es 20!.
📝 Ejercicio 27: Sobrecarga de métodos — Calculadora de áreas
Crea un método calcularArea sobrecargado para círculo (radio), rectángulo (base, altura) y triángulo (base, altura, bandera).
Explicación: La sobrecarga permite varios métodos con el mismo nombre pero distintos parámetros. Java selecciona el correcto según los tipos de los argumentos en tiempo de compilación.
📝 Ejercicio 28: Varargs — Suma flexible
Crea un método que sume cualquier cantidad de números usando int... numeros.
Java
public classSumaFlexible {
static intsumar(int... numeros) {
int total = 0;
for (int n : numeros) total += n;
return total;
}
public static voidmain(String[] args) {
System.out.println(sumar(1, 2)); // 3System.out.println(sumar(1, 2, 3, 4, 5)); // 15System.out.println(sumar()); // 0
}
}
Explicación:int... numeros recibe cualquier cantidad de argumentos como array. Solo puede haber un vararg y debe ser el último parámetro.
Crea un método genérico que encuentre el máximo de tres valores de cualquier tipo Comparable.
Java
public classMaximoGenerico {
static <TextendsComparable<T>> Tmaximo(T a, T b, T c) {
T max = a;
if (b.compareTo(max) > 0) max = b;
if (c.compareTo(max) > 0) max = c;
return max;
}
public static voidmain(String[] args) {
System.out.println(maximo(3, 7, 5));
System.out.println(maximo("alfa", "zeta", "beta"));
}
}
Explicación:<T extends Comparable<T>> restringe T a tipos comparables. Un solo método funciona con Integer, String, Double, etc.
📝 Ejercicio 30: Paso de arrays a métodos
Crea un método que reciba un array desordenado, lo ordene internamente y devuelva la posición de un valor objetivo.
Java
import java.util.Arrays;
public classOrdenarYBuscar {
static intbuscarOrdenado(int[] arr, int objetivo) {
Arrays.sort(arr);
returnArrays.binarySearch(arr, objetivo);
}
public static voidmain(String[] args) {
int[] datos = {45, 12, 78, 34, 23, 56};
System.out.println("34 en posición: " + buscarOrdenado(datos, 34));
System.out.println("Ordenado: " + Arrays.toString(datos));
}
}
Explicación: Al pasar un array a un método, se pasa la referencia: el array original queda modificado. Arrays.sort() usa TimSort O(n log n).
🏗️ Ejercicios de POO: clases y objetos
La Programación Orientada a Objetos organiza el código en clases que modelan entidades del mundo real. Cada objeto tiene estado (atributos) y comportamiento (métodos). Los cuatro pilares son: encapsulamiento, herencia, polimorfismo y abstracción.
📝 Ejercicio 31: Clase CuentaBancaria
Modela una cuenta bancaria con titular, saldo, métodos depositar y retirar (sin saldo negativo).
Explicación: Atributos private (encapsulamiento), validación en métodos, toString() para representación legible. retirar devuelve boolean indicando si tuvo éxito.
📝 Ejercicio 32: Clase Estudiante con Comparable
Crea una clase Estudiante con nombre, nota media y compareTo para ordenar por nota descendente.
Explicación:Comparable define el orden natural. Invertimos parámetros en compareTo para orden descendente. Collections.sort() usa este método automáticamente.
📝 Ejercicio 33: Enum con comportamiento — DiaSemana
Crea un enum DiaSemana con método que indique si es laborable y cuántas horas de trabajo tiene.
Java
public enumDiaSemana {
LUNES(8), MARTES(8), MIERCOLES(8), JUEVES(8), VIERNES(7),
SABADO(0), DOMINGO(0);
private final int horas;
DiaSemana(int h) { horas = h; }
public booleanesLaborable() { return horas > 0; }
public static voidmain(String[] args) {
for (DiaSemana d : DiaSemana.values())
System.out.printf("%-10s %s (%dh)%n", d,
d.esLaborable() ? "Laborable" : "Festivo", d.horas);
}
}
Explicación: Los enum en Java pueden tener atributos, constructores y métodos. Son más potentes que simples constantes numéricas.
📝 Ejercicio 34: Clase inmutable — Punto2D
Crea una clase inmutable Punto2D con distanciaA(), trasladar() (devuelve nuevo punto) y equals/hashCode.
Java
import java.util.Objects;
public final classPunto2D {
private final double x, y;
publicPunto2D(double x, double y) { this.x = x; this.y = y; }
public doubledistanciaA(Punto2D otro) {
returnMath.sqrt(Math.pow(otro.x - x, 2) + Math.pow(otro.y - y, 2));
}
publicPunto2Dtrasladar(double dx, double dy) {
return newPunto2D(x + dx, y + dy);
}
@Overridepublic booleanequals(Object o) {
if (!(o instanceofPunto2D p)) returnfalse;
returnDouble.compare(x, p.x) == 0 && Double.compare(y, p.y) == 0;
}
@Overridepublic inthashCode() { returnObjects.hash(x, y); }
@OverridepublicStringtoString() { return"(" + x + ", " + y + ")"; }
public static voidmain(String[] args) {
Punto2D a = newPunto2D(3, 4);
Punto2D b = a.trasladar(2, -1);
System.out.printf("Distancia %s → %s = %.2f%n", a, b, a.distanciaA(b));
}
}
Explicación: Clase inmutable: final, atributos private final, sin setters. trasladar() devuelve nuevo objeto. equals y hashCode son imprescindibles para colecciones.
📝 Ejercicio 35: Composición — Clase Dirección dentro de Persona
Modela una relación de composición: Persona tiene una Direccion. Demuestra que si se destruye Persona, Dirección deja de tener sentido.
Explicación: En composición, el objeto contenido se crea dentro del contenedor y no existe independientemente. La Direccion se crea en el constructor de Persona, no se recibe desde fuera.
🧬 Ejercicios de POO avanzada: herencia y polimorfismo
La herencia permite crear clases derivadas que heredan atributos y métodos de una clase base. El polimorfismo permite que una referencia de tipo padre apunte a objetos de tipos hijos, ejecutando el método adecuado en tiempo de ejecución.
📝 Ejercicio 36: Jerarquía de Figuras geométricas
Crea una clase abstracta Figura con método abstracto calcularArea(). Implementa Circulo, Rectangulo y Triangulo. Usa polimorfismo para calcular el área total de un array de figuras.
Explicación:Figura es abstracta: no se puede instanciar, pero define el contrato. Cada subclase implementa calcularArea(). El bucle for-each llama al método correcto según el tipo real del objeto (late binding).
📝 Ejercicio 37: Interfaz Imprimible
Crea una interfaz Imprimible con método imprimir(). Impleméntala en Factura y Informe. Demuestra polimorfismo con la interfaz.
Explicación: Las interfaces definen un contrato. default permite métodos con implementación por defecto (Java 8+). El método enviarAImpresora acepta cualquier Imprimible — polimorfismo puro.
📝 Ejercicio 38: Patrón Template Method — Procesador de pedidos
Implementa el patrón Template Method: una clase abstracta ProcesadorPedido define el flujo (validar → calcular → enviar), y las subclases personalizan cada paso.
Java
abstract classProcesadorPedido {
public final voidprocesar() { // Template methodvalidar(); calcularTotal(); enviar();
System.out.println("Pedido procesado ✓\n");
}
abstract voidvalidar();
abstract voidcalcularTotal();
abstract voidenviar();
}
classPedidoOnlineextendsProcesadorPedido {
voidvalidar() { System.out.println("Validando tarjeta de crédito..."); }
voidcalcularTotal() { System.out.println("Calculando total + envío..."); }
voidenviar() { System.out.println("Enviando confirmación por email..."); }
}
classPedidoTiendaextendsProcesadorPedido {
voidvalidar() { System.out.println("Verificando stock en tienda..."); }
voidcalcularTotal() { System.out.println("Calculando total con IVA..."); }
voidenviar() { System.out.println("Imprimiendo ticket..."); }
}
public classTestPedidos {
public static voidmain(String[] args) {
newPedidoOnline().procesar();
newPedidoTienda().procesar();
}
}
Explicación: El método procesar() es final (no se puede sobreescribir) y define la secuencia. Las subclases solo personalizan los pasos individuales. Es un patrón de diseño GoF muy utilizado en frameworks.
📝 Ejercicio 39: Clase sellada (sealed) — Medios de pago
Usa sealed (Java 17+) para restringir qué clases pueden extender MedioPago.
Explicación:sealed restringe las subclases permitidas. record (Java 16+) genera automáticamente constructor, getters, equals, hashCode y toString. Combinados, son ideales para modelar tipos cerrados.
📝 Ejercicio 40: Clase interna y expresión lambda
Ordena una lista de empleados por salario usando: a) clase anónima, b) lambda, c) referencia de método. Compara las tres sintaxis.
Explicación: Las lambdas (Java 8+) simplifican enormemente la ordenación. Comparator.comparingDouble con reversed() es la forma más idiomática en Java moderno.
🛡️ Ejercicios de excepciones
Las excepciones permiten manejar errores de forma elegante sin corromper el flujo del programa. Java distingue entre excepciones checked (obligatorio manejar) y unchecked (opcionales, heredan de RuntimeException).
📝 Ejercicio 41: Try-catch-finally básico
Escribe un programa que intente dividir dos números leídos como String, manejando NumberFormatException y ArithmeticException.
Java
public classDivisionSegura {
static intdividir(String sA, String sB) {
try {
int a = Integer.parseInt(sA);
int b = Integer.parseInt(sB);
return a / b;
} catch (NumberFormatException e) {
System.out.println("Error: entrada no numérica → " + e.getMessage());
} catch (ArithmeticException e) {
System.out.println("Error: división por cero");
} finally {
System.out.println("Bloque finally ejecutado siempre");
}
return0;
}
public static voidmain(String[] args) {
System.out.println(dividir("10", "3"));
System.out.println(dividir("10", "0"));
System.out.println(dividir("abc", "5"));
}
}
Explicación:finally se ejecuta siempre, haya o no excepción. Es ideal para liberar recursos. Los catch se ordenan de más específico a más general.
📝 Ejercicio 42: Excepción personalizada
Crea SaldoInsuficienteException y úsala en una cuenta bancaria que lance la excepción al intentar retirar más del saldo disponible.
Explicación: Las excepciones personalizadas que extienden Exception son checked: el compilador obliga a manejarlas. Pueden tener atributos adicionales (deficit) para dar contexto al error.
📝 Ejercicio 43: Try-with-resources
Lee un archivo de texto usando try-with-resources para garantizar el cierre automático del recurso.
Java
import java.io.*;
import java.nio.file.*;
public classLeerArchivo {
public static voidmain(String[] args) {
Path ruta = Path.of("datos.txt");
try (BufferedReader br = Files.newBufferedReader(ruta)) {
String linea;
int num = 1;
while ((linea = br.readLine()) != null) {
System.out.printf("%3d: %s%n", num++, linea);
}
} catch (NoSuchFileException e) {
System.out.println("Archivo no encontrado: " + ruta);
} catch (IOException e) {
System.out.println("Error de lectura: " + e.getMessage());
}
// br se cierra automáticamente aquí
}
}
Explicación:try-with-resources (Java 7+) cierra automáticamente cualquier recurso que implemente AutoCloseable. Es la forma correcta de manejar archivos, conexiones y streams.
📝 Ejercicio 44: Multi-catch y cadena de excepciones
Demuestra multi-catch (catch (A | B e)) y cómo encadenar excepciones con el constructor de causa.
Explicación: Multi-catch (Java 7+) maneja varias excepciones en un bloque. La cadena de excepciones (new RuntimeException(msg, causa)) preserva la traza de errores original — crucial para debugging.
📝 Ejercicio 45: Validador con Optional
Usa Optional para manejar valores potencialmente nulos de forma segura, evitando NullPointerException.
Java
import java.util.*;
public classOptionalDemo {
staticOptional<String> buscarUsuario(int id) {
Map<Integer, String> db = Map.of(1, "Ana", 2, "Pedro", 3, "Laura");
returnOptional.ofNullable(db.get(id));
}
public static voidmain(String[] args) {
// Forma segura — sin NullPointerExceptionString nombre = buscarUsuario(1).orElse("Desconocido");
System.out.println("Usuario 1: " + nombre);
buscarUsuario(99).ifPresentOrElse(
n -> System.out.println("Encontrado: " + n),
() -> System.out.println("Usuario 99 no existe")
);
}
}
Explicación:Optional (Java 8+) obliga a considerar el caso nulo. orElse provee un valor por defecto, ifPresentOrElse maneja ambos casos. Nunca uses Optional.get() sin verificar antes.
📦 Ejercicios de colecciones (ArrayList, HashMap)
Las colecciones son estructuras de datos dinámicas que superan las limitaciones de los arrays. El framework de colecciones de Java incluye List, Set, Map y Queue, cada una con múltiples implementaciones optimizadas para diferentes casos de uso.
📝 Ejercicio 46: Gestión de inventario con HashMap
Crea un sistema de inventario usando HashMap<String, Integer> con operaciones de añadir stock, vender y mostrar productos con bajo stock.
Java
import java.util.*;
public classInventario {
privateMap<String, Integer> stock = newHashMap<>();
voidanadir(String producto, int cantidad) {
stock.merge(producto, cantidad, Integer::sum);
}
booleanvender(String producto, int cantidad) {
int actual = stock.getOrDefault(producto, 0);
if (actual < cantidad) returnfalse;
stock.put(producto, actual - cantidad);
returntrue;
}
voidalertaBajoStock(int umbral) {
stock.entrySet().stream()
.filter(e -> e.getValue() <= umbral)
.sorted(Map.Entry.comparingByValue())
.forEach(e -> System.out.printf("⚠️ %s: %d uds%n", e.getKey(), e.getValue()));
}
public static voidmain(String[] args) {
Inventario inv = newInventario();
inv.anadir("Teclado", 50); inv.anadir("Ratón", 5); inv.anadir("Monitor", 3);
inv.vender("Teclado", 48);
inv.alertaBajoStock(5);
}
}
Explicación:merge incrementa si la clave existe. getOrDefault evita NullPointerException. Streams filtran y ordenan sin modificar el mapa original.
📝 Ejercicio 47: Agrupar con Collectors.groupingBy
Dada una lista de palabras, agrúpalas por su longitud usando Streams.
Explicación:Collectors.groupingBy crea un Map donde la clave es el criterio de agrupación. Es equivalente a un GROUP BY de SQL pero en memoria.
📝 Ejercicio 48: Cola de prioridad — Sistema de urgencias
Simula un sistema de urgencias hospitalarias con PriorityQueue donde los pacientes se atienden por gravedad.
Java
import java.util.*;
public classUrgencias {
recordPaciente(String nombre, int gravedad) {} // 1=leve, 5=críticopublic static voidmain(String[] args) {
PriorityQueue<Paciente> cola = newPriorityQueue<>(
Comparator.comparingInt(Paciente::gravedad).reversed());
cola.add(newPaciente("María", 2));
cola.add(newPaciente("Pedro", 5));
cola.add(newPaciente("Ana", 3));
cola.add(newPaciente("Luis", 1));
System.out.println("Orden de atención:");
while (!cola.isEmpty()) {
Paciente p = cola.poll();
System.out.printf(" [Gravedad %d] %s%n", p.gravedad(), p.nombre());
}
}
}
Explicación:PriorityQueue ordena elementos según el Comparator. Con reversed() atendemos primero a los más graves. poll() extrae y elimina el elemento de mayor prioridad.
📝 Ejercicio 49: Stream pipeline completo — Análisis de ventas
Procesa una lista de ventas: filtra por importe mínimo, agrupa por categoría, calcula el total por categoría y ordena descendentemente.
Explicación: Pipeline completo: filter → collect(groupingBy + summingDouble) → sort → forEach. Los Streams permiten procesar datos declarativamente, como consultas SQL.
📝 Ejercicio 50: Caché LRU con LinkedHashMap
Implementa una caché LRU (Least Recently Used) de tamaño fijo usando LinkedHashMap.
Java
import java.util.*;
public classCacheLRU<K, V> extendsLinkedHashMap<K, V> {
private final int capacidad;
publicCacheLRU(int capacidad) {
super(capacidad, 0.75f, true); // true = access orderthis.capacidad = capacidad;
}
@Overrideprotected booleanremoveEldestEntry(Map.Entry<K, V> eldest) {
returnsize() > capacidad;
}
public static voidmain(String[] args) {
CacheLRU<String, Integer> cache = newCacheLRU<>(3);
cache.put("A", 1); cache.put("B", 2); cache.put("C", 3);
System.out.println(cache); // {A=1, B=2, C=3}
cache.get("A"); // A se vuelve reciente
cache.put("D", 4); // B es eliminado (más antiguo)System.out.println(cache); // {C=3, A=1, D=4}
}
}
Explicación:LinkedHashMap con accessOrder=true reordena las entradas al acceder. Al sobrepasar la capacidad, removeEldestEntry elimina la menos usada. Es la implementación clásica de caché LRU usada en bases de datos y servidores web.
🎓 Proyecto integrador: sistema de gestión de biblioteca
Este proyecto final combina todos los conceptos practicados en los 50 ejercicios anteriores: clases, herencia, interfaces, colecciones, excepciones, Streams y Optional. Modela un sistema de gestión de biblioteca con libros, socios y préstamos.
💡
Estructura del proyecto: El sistema se compone de 5 clases: Libro (entidad con ISBN, título, autor y estado), Socio (con límite de préstamos), Prestamo (asocia libro-socio con fechas), LibroNoDisponibleException (excepción personalizada) y Biblioteca (gestiona todo el sistema con colecciones).
🏆 Proyecto completo: Biblioteca
Implementa un sistema que permita: registrar libros y socios, realizar préstamos (máximo 3 por socio), devolver libros, buscar libros por autor y generar estadísticas.
Conceptos integrados: encapsulamiento (atributos privados), enum (EstadoLibro), record (Socio, Prestamo), HashMap y ArrayList (gestión de datos), Streams y Collectors (búsqueda y estadísticas), Optional (manejo seguro de nulos), excepciones checked personalizadas (LibroNoDisponibleException) y java.time (fechas).
⚠️
Siguiente paso: Para un sistema real, añadirías persistencia (base de datos), interfaz gráfica o web, validaciones más estrictas y tests unitarios con JUnit. Este ejercicio es la base sobre la que construir una aplicación profesional.
❓ Preguntas frecuentes sobre 50 ejercicios de Java resueltos paso a paso
Las dudas más comunes respondidas de forma clara y directa.
Esta guía incluye 50 ejercicios de Java organizados en 10 categorías temáticas, desde nivel principiante hasta avanzado, más un proyecto integrador completo que combina todos los conceptos.
No. Los ejercicios están organizados de forma progresiva. Los primeros apartados (variables, condicionales, bucles) son aptos para principiantes absolutos. Los últimos (colecciones, POO avanzada) requieren conocimientos intermedios.
Todos los ejercicios son compatibles con Java 8 o superior. Algunos ejercicios de colecciones usan características de Java 11+. Recomendamos Java 17 LTS o Java 21 LTS para la mejor experiencia.
Puedes usar cualquier IDE: IntelliJ IDEA Community (gratuito y muy completo), Eclipse, NetBeans o VS Code con la extensión Java Extension Pack. Para principiantes, IntelliJ IDEA Community es la opción más recomendada.
Sí. Los ejercicios cubren el temario estándar de asignaturas de Programación I y II de grados universitarios: tipos de datos, estructuras de control, arrays, métodos, POO, herencia, polimorfismo, excepciones y colecciones.
Recomendamos seguir el orden propuesto: primero leer el enunciado, intentar resolver el ejercicio por tu cuenta durante al menos 10-15 minutos, y solo después consultar la solución. Es fundamental escribir el código tú mismo, no limitarse a leerlo.
Sí. Muchos ejercicios de arrays, strings, métodos y colecciones son similares a los que se plantean en entrevistas técnicas para puestos junior y de nivel medio. El proyecto integrador demuestra capacidad de diseño orientado a objetos.
★★★★★
Valora este artículo
¿Útil?
💬 Foro de discusión
¿Tienes dudas sobre 50 ejercicios de Java resueltos paso a paso? Comparte tu pregunta con la comunidad.
¿Tienes cuenta?o comenta como invitado ↓
Iniciar sesión
🔑 Recuperar contraseña
Introduce el email con el que te registraste. Te enviaremos un enlace para crear una nueva contraseña.
Crear cuenta
Solo necesitas nombre, email y contraseña. Sin verificación por email.
Todavía no hay mensajes. ¡Sé el primero en participar!
🍪 Esta web utiliza cookies
Usamos cookies propias para mejorar tu experiencia de navegación y analizar
el uso del sitio. No compartimos datos con terceros ni usamos cookies de
publicidad. Puedes aceptar todas, aceptar solo las necesarias o configurar
tus preferencias.
Política de privacidad
Imprescindibles para el funcionamiento del sitio: preferencias de interfaz,
gestión de sesiones y este mismo aviso de cookies. No recogen datos
identificativos.
Nos permiten entender cómo navegas por el contenido para mejorar la
experiencia de aprendizaje. Utilizan identificadores anónimos (UUID) sin
vinculación a datos personales. Retención máxima: 6 meses.
¿Cómo valorarías tu experiencia aprendiendo en esta sección?