Volver al blog

Ejercicios de Java resueltos para 1º de DAM y DAW: 8 ejercicios tipo examen de Programación

Ejercicios de Java resueltos para 1º de DAM y DAW: 8 ejercicios tipo examen de Programación

Programación es el módulo de primero de DAM y DAW que más gente suspende, y casi siempre se da en Java. El examen práctico se repite mucho de un centro a otro: un menú con do-while y switch, un ejercicio de bucles anidados, uno de arrays, uno de cadenas, una matriz, una clase con encapsulación, una lista de objetos con ArrayList y, a final de curso, algo con ficheros o con HashMap. Si sabes resolver esos ocho tipos de ejercicio sin mirar apuntes, tienes el módulo prácticamente aprobado.

Aquí tienes los ocho resueltos y explicados, en el orden en que suelen aparecer durante el curso. Para cada uno verás el enunciado, la solución, por qué está hecha así y el error que más vemos en clase. Intenta hacerlos tú antes de mirar la solución: leer código resuelto da la sensación de entenderlo, pero lo que se evalúa es escribirlo.

Qué entra en el examen de Programación de primero

El temario oficial cambia poco entre comunidades. Lo que cambia es el orden y cuánto pesa cada parte.

Evaluación Contenidos Ejercicio típico
Primera Variables, tipos, operadores, Scanner, condicionales, bucles Menú con opciones, números primos, adivinar un número
Segunda Métodos, arrays, matrices, String, clases y objetos Estadísticas de un array, palíndromos, sumas de una matriz, clase CuentaBancaria
Tercera Herencia, interfaces, colecciones (ArrayList, HashMap), excepciones, ficheros Gestión de alumnos o productos con una lista, contar palabras de un fichero

En la mayoría de institutos el examen es en el ordenador, con el IDE (Eclipse, NetBeans o IntelliJ) pero sin internet ni apuntes. En algunos es en papel, y entonces cada punto y coma cuenta. Pregunta a tu profesor cuál es tu caso desde el primer día, porque cambia mucho cómo tienes que practicar.

Ejercicio 1: menú con do-while y switch

Enunciado. Haz una calculadora con un menú que se repita hasta que el usuario elija salir. Opciones: 1) sumar, 2) dividir, 0) salir. Si el usuario escribe algo que no es un número, el programa no debe romperse.

import java.util.Scanner;

public class Calculadora {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int opcion;
        do {
            System.out.println("1. Sumar");
            System.out.println("2. Dividir");
            System.out.println("0. Salir");
            opcion = leerEntero(sc, "Opción: ");

            switch (opcion) {
                case 1:
                    int a = leerEntero(sc, "Primer número: ");
                    int b = leerEntero(sc, "Segundo número: ");
                    System.out.println("Resultado: " + (a + b));
                    break;
                case 2:
                    int dividendo = leerEntero(sc, "Dividendo: ");
                    int divisor = leerEntero(sc, "Divisor: ");
                    if (divisor == 0) {
                        System.out.println("No se puede dividir entre 0");
                    } else {
                        System.out.println("Resultado: " + (double) dividendo / divisor);
                    }
                    break;
                case 0:
                    System.out.println("Adiós");
                    break;
                default:
                    System.out.println("Opción no válida");
            }
        } while (opcion != 0);
        sc.close();
    }

    static int leerEntero(Scanner sc, String mensaje) {
        System.out.print(mensaje);
        while (!sc.hasNextInt()) {
            sc.next(); // descarta lo que no es un número
            System.out.print("Eso no es un número. " + mensaje);
        }
        return sc.nextInt();
    }
}

Por qué está hecho así. Se usa do-while y no while porque el menú tiene que mostrarse al menos una vez. La lectura está en un método aparte (leerEntero) para no repetir la validación cinco veces. El (double) delante de dividendo es lo que evita la división entera: sin él, 7 / 2 da 3.

El error que más vemos. Olvidar el break de un case. Java sigue ejecutando el siguiente case (lo que se llama fall-through) y la opción 1 acaba también dividiendo. Si en tu instituto usáis Java 14 o posterior, el switch con flechas (case 1 -> { ... }) evita el problema, pero pregunta si te lo aceptan en el examen.

Ejercicio 2: números primos con bucles anidados

Enunciado. Pide un número N y muestra todos los números primos entre 1 y N, y cuántos hay.

public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    System.out.print("N: ");
    int n = sc.nextInt();

    int contador = 0;
    for (int i = 2; i <= n; i++) {
        if (esPrimo(i)) {
            System.out.print(i + " ");
            contador++;
        }
    }
    System.out.println();
    System.out.println("Hay " + contador + " primos hasta " + n);
}

static boolean esPrimo(int n) {
    if (n < 2) {
        return false;
    }
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            return false;
        }
    }
    return true;
}

Por qué está hecho así. El bucle anidado está escondido dentro de esPrimo: un bucle recorre los candidatos y el otro busca divisores. Separarlo en un método hace que cada parte se pueda probar por separado y que el main se lea como el enunciado. La condición i * i <= n basta, porque si n tiene un divisor mayor que su raíz cuadrada, también tiene uno menor.

El error que más vemos. Usar una variable "bandera" (boolean primo = true) declarada fuera del bucle exterior y no volver a ponerla a true en cada vuelta. El primer número compuesto la pone a false y a partir de ahí ningún número parece primo. Si te piden hacerlo sin métodos, declara la bandera dentro del bucle exterior.

Ejercicio 3: estadísticas de un array

Enunciado. Pide cuántos alumnos hay, lee sus notas en un array y muestra la media, la nota máxima, la mínima y cuántos alumnos están por encima de la media.

public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    System.out.print("¿Cuántos alumnos? ");
    int n = sc.nextInt();
    double[] notas = new double[n];

    for (int i = 0; i < notas.length; i++) {
        System.out.print("Nota del alumno " + (i + 1) + ": ");
        notas[i] = sc.nextDouble();
    }

    double suma = 0;
    double max = notas[0];
    double min = notas[0];
    for (double nota : notas) {
        suma += nota;
        if (nota > max) max = nota;
        if (nota < min) min = nota;
    }
    double media = suma / notas.length;

    int porEncima = 0;
    for (double nota : notas) {
        if (nota > media) porEncima++;
    }

    System.out.printf("Media: %.2f%n", media);
    System.out.println("Máxima: " + max + ", mínima: " + min);
    System.out.println("Por encima de la media: " + porEncima);
}

Por qué está hecho así. Hacen falta dos recorridos: no puedes saber quién está por encima de la media hasta haberla calculado. El máximo y el mínimo empiezan en notas[0], el primer valor real del array.

El error que más vemos. Empezar el máximo en 0 y el mínimo en 0. Con notas el máximo funciona por casualidad, pero el mínimo siempre sale 0. Y con números negativos también falla el máximo. Empieza siempre con el primer elemento. El otro clásico es i <= notas.length, que da ArrayIndexOutOfBoundsException en la última vuelta.

Ojo con nextDouble(): según la configuración regional del ordenador, espera 7,5 y no 7.5. Si te salta InputMismatchException al escribir decimales, es eso.

Ejercicio 4: cadenas, palíndromos y vocales

Enunciado. Pide una frase y di si es un palíndromo (se lee igual al revés, sin tener en cuenta espacios ni mayúsculas) y cuántas vocales tiene.

static boolean esPalindromo(String frase) {
    String limpia = frase.toLowerCase().replace(" ", "");
    int i = 0;
    int j = limpia.length() - 1;
    while (i < j) {
        if (limpia.charAt(i) != limpia.charAt(j)) {
            return false;
        }
        i++;
        j--;
    }
    return true;
}

static int contarVocales(String frase) {
    int contador = 0;
    for (char c : frase.toLowerCase().toCharArray()) {
        if ("aeiouáéíóú".indexOf(c) != -1) {
            contador++;
        }
    }
    return contador;
}

Con "Anita lava la tina", esPalindromo devuelve true y contarVocales, 8.

Por qué está hecho así. Dos índices que avanzan desde los extremos hacia el centro es la forma más clara y no necesita crear una cadena nueva al revés. Para las vocales, indexOf sobre una cadena con todas las vocales evita un if con diez condiciones.

El error que más vemos. Comparar cadenas con ==. nombre == "Ana" compara si son el mismo objeto en memoria, no si tienen el mismo texto, y puede dar false aunque lo parezcan. Para comparar texto se usa siempre equals (o equalsIgnoreCase). Con char sí se usa ==, porque char es un tipo primitivo; por eso charAt(i) != charAt(j) es correcto.

Si te piden invertir la cadena, new StringBuilder(frase).reverse().toString() lo hace en una línea, pero en el examen a veces lo prohíben para que lo hagas con un bucle. Practica las dos formas. Y si te piden hacerlo con recursividad, lo tienes resuelto en la guía de recursividad.

Ejercicio 5: sumas de filas y columnas de una matriz

Enunciado. Dada una matriz de enteros, muestra la suma de cada fila, la suma de cada columna y la posición del valor máximo.

public static void main(String[] args) {
    int[][] m = {
        {3, 8, 1, 5},
        {7, 2, 9, 4},
        {6, 0, 2, 8}
    };

    for (int f = 0; f < m.length; f++) {
        int suma = 0;
        for (int c = 0; c < m[f].length; c++) {
            suma += m[f][c];
        }
        System.out.println("Fila " + f + ": " + suma);
    }

    for (int c = 0; c < m[0].length; c++) {
        int suma = 0;
        for (int f = 0; f < m.length; f++) {
            suma += m[f][c];
        }
        System.out.println("Columna " + c + ": " + suma);
    }

    int filaMax = 0, colMax = 0;
    for (int f = 0; f < m.length; f++) {
        for (int c = 0; c < m[f].length; c++) {
            if (m[f][c] > m[filaMax][colMax]) {
                filaMax = f;
                colMax = c;
            }
        }
    }
    System.out.println("Máximo " + m[filaMax][colMax] + " en (" + filaMax + ", " + colMax + ")");
}

Salida: filas 17, 22 y 16; columnas 16, 10, 12 y 17; máximo 9 en (1, 2).

Por qué está hecho así. Para sumar por filas, el bucle de fuera recorre filas y el de dentro columnas. Para sumar por columnas, al revés. La suma se pone a 0 dentro del bucle de fuera, porque cada fila o columna tiene la suya. Para el máximo se guarda la posición, no el valor, y así se tienen las dos cosas.

El error que más vemos. Confundir m.length (número de filas) con m[0].length (número de columnas). Con matrices cuadradas no se nota, y por eso el examen suele poner una de 3×4.

Ejercicio 6: una clase con encapsulación

Enunciado. Crea una clase CuentaBancaria con IBAN, titular y saldo. Debe permitir ingresar y retirar dinero, sin cantidades negativas ni saldo negativo. Desde fuera no se puede modificar el saldo directamente.

public class CuentaBancaria {

    private final String iban;
    private final String titular;
    private double saldo;

    public CuentaBancaria(String iban, String titular) {
        this.iban = iban;
        this.titular = titular;
        this.saldo = 0;
    }

    public void ingresar(double cantidad) {
        if (cantidad <= 0) {
            throw new IllegalArgumentException("La cantidad debe ser positiva");
        }
        saldo += cantidad;
    }

    public void retirar(double cantidad) {
        if (cantidad <= 0) {
            throw new IllegalArgumentException("La cantidad debe ser positiva");
        }
        if (cantidad > saldo) {
            throw new IllegalStateException("Saldo insuficiente");
        }
        saldo -= cantidad;
    }

    public double getSaldo() { return saldo; }
    public String getIban() { return iban; }
    public String getTitular() { return titular; }

    @Override
    public String toString() {
        return iban + " (" + titular + "): " + saldo + " €";
    }
}

Y para usarla:

CuentaBancaria cuenta = new CuentaBancaria("ES12 3456", "Lucía");
cuenta.ingresar(100);
try {
    cuenta.retirar(150);
} catch (IllegalStateException e) {
    System.out.println("Error: " + e.getMessage());
}
System.out.println(cuenta);

Por qué está hecho así. Los atributos son private y no hay setSaldo: la única forma de cambiar el saldo es pasar por ingresar y retirar, que comprueban las reglas. Eso es la encapsulación, y es lo que el profesor mira. IBAN y titular son final porque no cambian después de crear la cuenta. Las reglas se comprueban con excepciones y no con System.out.println, porque una clase no debe decidir cómo se muestran los errores: eso lo decide quien la usa.

El error que más vemos. Generar automáticamente un getter y un setter para cada atributo. Un setSaldo público deja saltarse todas las reglas y anula la encapsulación. Genera solo los métodos que el enunciado necesita. (En una aplicación real el dinero se guarda con BigDecimal y no con double, pero en el examen de primero se acepta double).

Si la POO todavía te cuesta, tienes la explicación desde cero en programación orientada a objetos explicada con un ejemplo.

Ejercicio 7: lista de objetos con ArrayList

Enunciado. Gestiona una lista de alumnos (nombre y nota). Hay que poder añadir alumnos, buscar uno por nombre, mostrar la lista ordenada de mayor a menor nota y eliminar a todos los suspensos.

public class Alumno {
    private final String nombre;
    private final double nota;

    public Alumno(String nombre, double nota) {
        this.nombre = nombre;
        this.nota = nota;
    }

    public String getNombre() { return nombre; }
    public double getNota() { return nota; }

    @Override
    public String toString() {
        return nombre + ": " + nota;
    }
}
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

public class GestionAlumnos {

    public static void main(String[] args) {
        List<Alumno> alumnos = new ArrayList<>();
        alumnos.add(new Alumno("Marta", 7.5));
        alumnos.add(new Alumno("Pablo", 4.0));
        alumnos.add(new Alumno("Irene", 9.0));
        alumnos.add(new Alumno("Hugo", 3.5));

        Alumno encontrado = buscar(alumnos, "irene");
        System.out.println(encontrado != null ? encontrado : "No existe");

        alumnos.sort(Comparator.comparingDouble(Alumno::getNota).reversed());
        System.out.println(alumnos);

        eliminarSuspensos(alumnos);
        System.out.println(alumnos);
    }

    static Alumno buscar(List<Alumno> alumnos, String nombre) {
        for (Alumno a : alumnos) {
            if (a.getNombre().equalsIgnoreCase(nombre)) {
                return a;
            }
        }
        return null;
    }

    static void eliminarSuspensos(List<Alumno> alumnos) {
        Iterator<Alumno> it = alumnos.iterator();
        while (it.hasNext()) {
            if (it.next().getNota() < 5) {
                it.remove();
            }
        }
    }
}

Salida:

Irene: 9.0
[Irene: 9.0, Marta: 7.5, Pablo: 4.0, Hugo: 3.5]
[Irene: 9.0, Marta: 7.5]

Por qué está hecho así. buscar devuelve el objeto en vez de imprimirlo, para que quien lo llame decida qué hacer con él. Para ordenar se usa un Comparator; si en clase aún no habéis visto referencias a métodos (Alumno::getNota), la alternativa es implementar Comparable<Alumno> en la clase. Para eliminar mientras se recorre se usa un Iterator (en Java 8 o posterior, alumnos.removeIf(a -> a.getNota() < 5) hace lo mismo en una línea).

El error que más vemos. Borrar dentro de un for-each:

for (Alumno a : alumnos) {
    if (a.getNota() < 5) alumnos.remove(a); // ConcurrentModificationException
}

Modificar la lista mientras el for-each la recorre lanza ConcurrentModificationException. Con un for clásico por índice no salta la excepción, pero te saltas el elemento que viene detrás del borrado. Usa Iterator o removeIf.

Ejercicio 8: contar palabras de un fichero con HashMap

Enunciado. Lee el fichero texto.txt y muestra cuántas veces aparece cada palabra, ordenadas alfabéticamente, sin distinguir mayúsculas.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;

public class ContarPalabras {

    public static void main(String[] args) {
        Map<String, Integer> frecuencias = new TreeMap<>();

        try (BufferedReader br = new BufferedReader(new FileReader("texto.txt"))) {
            String linea;
            while ((linea = br.readLine()) != null) {
                for (String palabra : linea.toLowerCase().split("[^\\p{L}]+")) {
                    if (!palabra.isEmpty()) {
                        frecuencias.put(palabra, frecuencias.getOrDefault(palabra, 0) + 1);
                    }
                }
            }
        } catch (IOException e) {
            System.out.println("No se pudo leer el fichero: " + e.getMessage());
            return;
        }

        for (Map.Entry<String, Integer> entrada : frecuencias.entrySet()) {
            System.out.println(entrada.getKey() + ": " + entrada.getValue());
        }
    }
}

Por qué está hecho así. Un Map relaciona cada palabra con su contador. TreeMap las mantiene ordenadas alfabéticamente; con HashMap saldrían en un orden cualquiera. El try (...) con recursos cierra el fichero aunque haya un error, sin necesidad de finally. La expresión [^\\p{L}]+ parte la línea por todo lo que no sea una letra, incluidas comas y puntos, y respeta las tildes y la ñ.

El error que más vemos. Hacer frecuencias.put(palabra, frecuencias.get(palabra) + 1) sin comprobar si la palabra ya existía. La primera vez get devuelve null, y null + 1 lanza NullPointerException. Para eso está getOrDefault. El otro clásico es poner la ruta del fichero mal: FileReader busca la ruta relativa desde la carpeta del proyecto, no desde la carpeta src.

Si en segundo de DAM vas a tener Acceso a Datos, este ejercicio es la base. Lo siguiente (ficheros binarios, serialización, JDBC) lo tienes en la guía de Acceso a Datos.

Cinco preguntas de "¿qué imprime?" que caen siempre

Algunos exámenes, sobre todo los de papel, incluyen fragmentos de código para que digas qué muestran. Hazlas sin ejecutar y comprueba después.

1. División entera.

int a = 7, b = 2;
double r = a / b;
System.out.println(r);

Imprime 3.0. La división se hace entre dos int (da 3) y después se convierte a double. Para obtener 3.5 hay que convertir antes: (double) a / b.

2. Concatenación.

System.out.println(1 + 2 + "3" + 4 + 5);

Imprime 3345. Java evalúa de izquierda a derecha: 1 + 2 es 3; en cuanto aparece una cadena, todo lo que sigue se concatena como texto.

3. Incremento.

int i = 5;
int j = i++ + ++i;
System.out.println(i + " " + j);

Imprime 7 12. i++ devuelve 5 y deja i en 6; ++i sube i a 7 y devuelve 7. 5 + 7 = 12.

4. nextInt y nextLine.

int edad = sc.nextInt();
String nombre = sc.nextLine();

nombre queda vacío sin dejarte escribir. nextInt lee el número pero deja en el búfer el salto de línea de la tecla Intro, y nextLine se lo come. Se arregla con un sc.nextLine() extra justo después del nextInt.

5. Arrays y referencias.

int[] x = {1, 2, 3};
int[] y = x;
y[0] = 99;
System.out.println(x[0]);

Imprime 99. y = x no copia el array: las dos variables apuntan al mismo. Para copiarlo se usa Arrays.copyOf(x, x.length) o x.clone().

Estos y otros fallos los tienes explicados uno a uno en los errores de Java más comunes en primero de DAM y DAW.

Cómo practicar estos ejercicios para que se queden

Hazlos primero sin mirar, aunque te salgan mal. Escribe en castellano los pasos antes de escribir código. Si te atascas más de veinte minutos, mira solo la parte en la que te atascas y sigue tú.

Cambia el enunciado. Cuando te salga un ejercicio, cámbialo: primos entre A y B en vez de entre 1 y N, la media de cada fila de la matriz, ordenar alumnos por nombre si empatan en nota. En el examen no te pondrán el mismo enunciado, te pondrán uno parecido.

Practica en las condiciones del examen. Si es en papel, escribe en papel. Si es en el ordenador sin internet, desactiva el autocompletado avanzado y la IA del IDE durante las prácticas, porque el día del examen no los tendrás.

Aprende a depurar. Poner un punto de interrupción y ejecutar paso a paso te ahorra horas de mirar el código sin ver el fallo. Tienes el método en cómo depurar código paso a paso, y la preparación del día del examen en cómo preparar un examen práctico de programación.

Cuándo pedir ayuda

Vas bien si haces estos ocho ejercicios sin mirar en menos de una hora entre todos y sabes explicar cada línea. Merece la pena pedir ayuda cuando:

  • Entiendes las soluciones al leerlas pero, delante de un enunciado nuevo, no sabes por dónde empezar.
  • Llevas varios días con la misma práctica y no encuentras el error.
  • Los objetos y las listas de la tercera evaluación te han pillado sin tener claros los bucles y los arrays de la primera.
  • Tienes una recuperación o la convocatoria extraordinaria cerca y no sabes qué priorizar.

En nuestras clases de programación para DAM y DAW y en las clases de Java online trabajamos con tus enunciados, tus prácticas y los exámenes de otros años de tu instituto. Escribes tú el código y el profesor te guía hasta que te sale solo. Son clases online uno a uno a 25 € la hora, sin matrícula ni permanencia.

Preguntas frecuentes

¿Qué ejercicios de Java caen en el examen de Programación de DAM?

Casi siempre los mismos tipos: un menú con do-while y switch, bucles anidados (primos, figuras, tablas), recorridos de arrays (media, máximo, búsqueda), manipulación de cadenas, matrices, una clase con encapsulación, una gestión de objetos con ArrayList y, a final de curso, herencia, excepciones, ficheros o HashMap.

¿El examen de Programación de DAM es en papel o en el ordenador?

Depende del instituto y del profesor. Lo más habitual es hacerlo en el ordenador con el IDE pero sin internet ni apuntes, aunque hay centros que lo hacen en papel. Pregúntalo al principio de curso y practica en las mismas condiciones.

¿Cuántos ejercicios tengo que hacer para aprobar Programación?

Más que un número, importa la variedad: si sabes resolver sin ayuda cada tipo de ejercicio de esta guía y alguna variación de cada uno, tienes la base del examen. Como orientación, hacer dos o tres ejercicios nuevos por semana durante el curso funciona mucho mejor que treinta la semana antes.

¿Se puede usar ChatGPT para hacer los ejercicios de Java?

Para que te explique un error o un concepto, sí. Para que te los resuelva, no te sirve: en el examen no lo tendrás y lo que se evalúa es que sepas escribir el código tú. Pídele pistas, no soluciones, y vuelve a hacer el ejercicio sin ayuda al día siguiente.

¿Qué hago si suspendo Programación en primero de DAM o DAW?

Tienes la convocatoria extraordinaria y, en general, cuatro convocatorias por módulo. Si suspendes, repasa por bloques empezando por bucles y arrays, que es donde se apoya todo lo demás, y haz los exámenes de otros años en condiciones reales. Tienes más detalle en cómo aprobar Programación en DAM o DAW.

WhatsApp