Métodos en Java — práctica: tres programas que crecen a base de trocitos

Mi primer programa con métodos fue una calculadora en la que puse cada operación dentro del main… y luego, al añadir la resta, copié y pegué el bloque de la suma cambiando el signo. Cuando quise cambiar el formato de los decimales, tuve que tocarlo en cuatro sitios y en uno se me olvidó. Ahí entendí para qué sirven los métodos: no es para que el código sea ‘bonito’, es para tocar las cosas una sola vez.

Con la teoría de métodos ya sabes qué es cada pieza de public static double sumar(...). Ahora toca la parte que de verdad convence: ver un programa que crece sin volverse un lío. En los tres programas de hoy el main se queda corto y limpio, y el trabajo lo hacen métodos pequeños con nombres que se entienden solos. Los tres están compilados y probados, así que la salida que ves es la real.


Métodos en java práctica — Programa 1: Calculadora con métodos

Qué hace: pide dos números, muestra suma, resta y multiplicación, y la división solo si el segundo número no es 0.

Salida esperada con 8.5 y 4:

=== CALCULADORA ===
Primer número: 8.5
Segundo número: 4

8.5 + 4.0 = 12.50
8.5 - 4.0 = 4.50
8.5 * 4.0 = 34.00
8.5 / 4.0 = 2.13

Y con 7 y 0, la última línea pasa a ser ✗ No se puede dividir entre 0.

Cómo lo pensamos:

  1. Cada operación es un método que recibe dos double y devuelve un double. Así el cálculo queda separado de cómo se muestra.
  2. Mostrar el resultado se repite cuatro veces, y ahí es donde en mi anécdota copié y pegué. Lo metemos en un método void privado, porque solo lo usa esta clase.
  3. La división por 0 se comprueba en main, antes de llamar a dividir.
import java.util.Scanner;

public class Calculadora {

    // ---------- Las cuatro operaciones ----------
    // public: son la "cara visible" de la calculadora.
    // static: por ahora, "así se hace" (lo veremos con clases).
    // double + double → double: reciben dos decimales y devuelven uno.

    public static double sumar(double a, double b) {
        return a + b;    // el return envía el resultado a quien llamó al método
    }

    public static double restar(double a, double b) {
        return a - b;
    }

    public static double multiplicar(double a, double b) {
        return a * b;
    }

    public static double dividir(double a, double b) {
        return a / b;    // ojo: con double, dividir entre 0 NO da error, da "Infinity"
    }

    // ---------- Cómo se muestra ----------
    // private: solo se usa dentro de esta clase, es un detalle interno.
    // void: hace algo (imprime) pero no devuelve nada.
    private static void mostrarResultado(String operacion, double resultado) {
        // %.2f = dos decimales; %n = salto de línea. El formato vive AQUÍ, en un solo sitio
        System.out.printf("%s = %.2f%n", operacion, resultado);
    }

    // Sobrecarga: mismo nombre, pero con UN solo parámetro sirve para mostrar avisos.
    // Java elige la versión mirando cuántos argumentos (y de qué tipo) le pasas.
    private static void mostrarResultado(String aviso) {
        System.out.println("✗ " + aviso);
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.println("=== CALCULADORA ===");
        System.out.print("Primer número: ");
        double a = sc.nextDouble();
        System.out.print("Segundo número: ");
        double b = sc.nextDouble();

        System.out.println();
        // Fíjate en lo corto que queda main: solo llama a métodos con nombres claros
        mostrarResultado(a + " + " + b, sumar(a, b));
        mostrarResultado(a + " - " + b, restar(a, b));
        mostrarResultado(a + " * " + b, multiplicar(a, b));

        // Validamos ANTES de llamar a dividir, igual que en control de flujo
        if (b == 0) {
            mostrarResultado("No se puede dividir entre 0");    // versión de 1 parámetro
        } else {
            mostrarResultado(a + " / " + b, dividir(a, b));     // versión de 2 parámetros
        }

        sc.close();
    }
}

Dividir entre 0 con double no falla: Java devuelve Infinity y el programa sigue tan contento, imprimiendo un resultado absurdo. Con int sí lanza una excepción (ArithmeticException), pero con decimales no. Por eso la comprobación b == 0 es tuya, no del lenguaje.

Es habitual preguntarse:

  • ¿Por qué sumar es public y mostrarResultado es private? Porque sumar es algo que otras clases querrían usar, y mostrarResultado es solo cómo se imprime aquí dentro. Si mañana cambias el formato, no rompes a nadie.
  • ¿Por qué el resultado sale 2.13 y no 2.125? Porque %.2f redondea a dos decimales. El número guardado sigue siendo 2.125; solo cambia lo que se muestra.

Métodos en java práctica — Programa 2: Validador de contraseña

Qué hace: pide una contraseña y comprueba cuatro requisitos por separado (8 caracteres o más, una mayúscula, un dígito, sin espacios), mostrando cuáles se cumplen y el veredicto final.

Salida esperada con Clave2026x:

=== VALIDADOR DE CONTRASEÑA ===
Contraseña: Clave2026x

--- Requisitos ---
8 caracteres o más: ✓
Al menos una mayúscula: ✓
Al menos un dígito: ✓
Sin espacios: ✓

Contraseña válida

Con hola mundo, los últimos tres requisitos salen con ✗ y el veredicto es Contraseña no válida.

Cómo lo pensamos:

  1. Cada requisito es un método boolean con una sola responsabilidad. Así cada uno se entiende (y se prueba) por separado.
  2. Un método esValida los combina con &&. Es el único public: los demás son piezas internas.
  3. main solo lee la contraseña y pinta la lista, sin lógica dentro.
import java.util.Scanner;

public class ValidadorPassword {

    // ---------- Piezas pequeñas: cada una comprueba UNA cosa ----------
    // Todas son private: son ladrillos internos, no la API de la clase.
    // Todas devuelven boolean: la respuesta es sí o no.

    private static boolean tieneLongitudMinima(String pw, int minimo) {
        return pw.length() >= minimo;    // la propia comparación ya ES un boolean
    }

    private static boolean tieneMayuscula(String pw) {
        for (int i = 0; i < pw.length(); i++) {
            if (Character.isUpperCase(pw.charAt(i))) {
                return true;    // con UNA mayúscula basta: cortamos ya, sin mirar el resto
            }
        }
        return false;    // si el bucle termina sin encontrar ninguna, es que no hay
    }

    private static boolean tieneDigito(String pw) {
        for (int i = 0; i < pw.length(); i++) {
            if (Character.isDigit(pw.charAt(i))) {
                return true;
            }
        }
        return false;
    }

    private static boolean tieneEspacios(String pw) {
        return pw.contains(" ");    // contains devuelve true si el texto aparece dentro
    }

    // ---------- La pieza que las junta ----------
    // public: es la que usaría cualquier otra parte del programa.
    public static boolean esValida(String pw) {
        // && corta en cuanto una es false: si es corta, ni mira lo demás
        return tieneLongitudMinima(pw, 8)
                && tieneMayuscula(pw)
                && tieneDigito(pw)
                && !tieneEspacios(pw);    // ! porque queremos que NO tenga espacios
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.println("=== VALIDADOR DE CONTRASEÑA ===");
        System.out.print("Contraseña: ");
        String pw = sc.nextLine();    // nextLine porque una contraseña puede llevar espacios

        System.out.println();
        System.out.println("--- Requisitos ---");
        // El operador ternario decide qué símbolo pintar según el boolean que devuelve el método
        System.out.println("8 caracteres o más: " + (tieneLongitudMinima(pw, 8) ? "✓" : "✗"));
        System.out.println("Al menos una mayúscula: " + (tieneMayuscula(pw) ? "✓" : "✗"));
        System.out.println("Al menos un dígito: " + (tieneDigito(pw) ? "✓" : "✗"));
        System.out.println("Sin espacios: " + (!tieneEspacios(pw) ? "✓" : "✗"));

        System.out.println();
        System.out.println(esValida(pw) ? "Contraseña válida" : "Contraseña no válida");

        sc.close();
    }
}

verás que dentro de tieneMayuscula hay un return true en mitad de un bucle. Un return sale del método entero, no solo del bucle, así que no hace falta ningún break. Es el patrón «encontré lo que buscaba, ya está» y lo vas a usar constantemente.

Es habitual preguntarse:

  • ¿No sería más corto meterlo todo en un solo if gigante? Sí, y sería ilegible. Con métodos, esValida se lee casi como una frase, y si mañana añades un requisito nuevo, escribes un método y una línea.
  • ¿Por qué tieneLongitudMinima recibe el 8 como parámetro? Para que el número no esté «escondido» dentro del método. Si el mínimo cambia, lo cambias en la llamada, no en el interior.

Métodos en java práctica — Programa 3: Boletín del cuatrimestre

Qué hace: pide la nota de tres asignaturas (validando que estén entre 0 y 10), y muestra un boletín con la clasificación de cada una y la media final.

Salida esperada con las notas 7.5, un 11 (que se rechaza), 9.5 y 6:

=== BOLETÍN DEL CUATRIMESTRE ===
Nota de FP2: 7.5
Nota de IC2: 11
✗ La nota debe estar entre 0 y 10
Nota de IC2: 9.5
Nota de AEDV: 6

--- Boletín ---
FP2     7.50  Notable
IC2     9.50  Sobresaliente
AEDV    6.00  Aprobado
---------------
Media: 7.67 (Notable)

Cómo lo pensamos:

  1. Leer una nota con validación es una tarea completa y se repite tres veces, así que es un método que devuelve la nota buena.
  2. Calcular la media recibe un array y devuelve un número. Clasificar recibe un número y devuelve un texto.
  3. Imprimir el boletín es un método void que usa a los otros. Aquí ves cómo unos métodos llaman a otros.
import java.util.Scanner;

public class Boletin {

    // Recibe el Scanner ya creado (no crea uno nuevo) y el mensaje a mostrar.
    // Devuelve la nota, pero SOLO cuando es válida: el bucle no la deja escapar antes.
    private static double leerNota(Scanner sc, String mensaje) {
        double nota;
        do {
            System.out.print(mensaje);
            nota = sc.nextDouble();
            if (nota < 0 || nota > 10) {
                System.out.println("✗ La nota debe estar entre 0 y 10");
            }
        } while (nota < 0 || nota > 10);    // se repite mientras la nota NO sea válida
        return nota;
    }

    // Un array como parámetro: double[] notas. Devuelve la media como double.
    private static double calcularMedia(double[] notas) {
        double suma = 0;
        for (int i = 0; i < notas.length; i++) {    // length sirve para CUALQUIER tamaño
            suma += notas[i];
        }
        return suma / notas.length;
    }

    // Recibe un número, devuelve un texto. Varios return, uno por camino posible.
    private static String clasificar(double nota) {
        if (nota >= 9) {
            return "Sobresaliente";
        } else if (nota >= 7) {
            return "Notable";
        } else if (nota >= 5) {
            return "Aprobado";
        }
        return "Suspenso";    // si no entró en ningún if, llega aquí: todos los caminos devuelven algo
    }

    // void: pinta el boletín y no devuelve nada. Usa a calcularMedia y a clasificar.
    private static void imprimirBoletin(String[] asignaturas, double[] notas) {
        System.out.println("--- Boletín ---");
        for (int i = 0; i < asignaturas.length; i++) {
            // %-6s = texto alineado a la izquierda en 6 huecos; %5.2f = número en 5 huecos, 2 decimales
            System.out.printf("%-6s %5.2f  %s%n", asignaturas[i], notas[i], clasificar(notas[i]));
        }
        double media = calcularMedia(notas);    // aquí se llama a otro método
        System.out.println("---------------");
        System.out.printf("Media: %.2f (%s)%n", media, clasificar(media));
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);    // UN solo Scanner para todo el programa

        String[] asignaturas = {"FP2", "IC2", "AEDV"};
        double[] notas = new double[asignaturas.length];    // mismo tamaño que las asignaturas

        System.out.println("=== BOLETÍN DEL CUATRIMESTRE ===");
        for (int i = 0; i < asignaturas.length; i++) {
            notas[i] = leerNota(sc, "Nota de " + asignaturas[i] + ": ");
        }

        System.out.println();
        imprimirBoletin(asignaturas, notas);

        sc.close();
    }
}

Crear un new Scanner(System.in) dentro de leerNota, o cerrarlo con sc.close() dentro de un método. Cerrar un Scanner cierra también System.in, y la siguiente lectura falla con NoSuchElementException. Por eso el Scanner se crea una vez en main, se pasa como parámetro y solo se cierra al final, donde se creó.

Si los tres métodos se llaman igual que lo que hacen (leerNota, calcularMedia, clasificar), el main y imprimirBoletin se leen casi como una receta. Cuando un método te obliga a escribir un comentario para explicar qué hace, suele ser señal de que el nombre no es bueno.

Es habitual preguntarse:

  • ¿Puedo pasar un array a un método? Sí, con el tipo delante y los corchetes: double[] notas. Ojo: el método recibe el array original, no una copia, así que si dentro cambias notas[0], cambia también fuera. Lo veremos con calma en el tema de arrays.
  • ¿Por qué clasificar(media) se puede usar con la media si antes se usó con notas sueltas? Porque el método solo pide un double. Da igual de dónde venga: es la gracia de tener un método reutilizable.

Visualízalo con Python Tutor

Selecciona Java en el desplegable y pega esto en pythontutor.com:

public class Demo {

    private static double calcularMedia(double[] notas) {
        double suma = 0;
        for (int i = 0; i < notas.length; i++) {
            suma += notas[i];
        }
        return suma / notas.length;
    }

    private static String clasificar(double nota) {
        if (nota >= 7) {
            return "Notable";
        }
        return "Aprobado";
    }

    public static void main(String[] args) {
        double[] notas = {6.0, 8.0, 9.0};
        double media = calcularMedia(notas);
        String texto = clasificar(media);
        System.out.println(media + " " + texto);
    }
}

Ejecuta paso a paso y fíjate en dos cosas. Primera: cuando main llama a calcularMedia(notas), el array no se copia. El parámetro notas del método apunta al mismo array que el de main, y en Python Tutor lo ves porque las dos flechas terminan en el mismo cuadro. Segunda: cuando el método termina, su marco desaparece con su suma y su i, y solo sobrevive el valor devuelto, que aterriza en la variable media de main. Después clasificar recibe ese valor y abre su propio marco, vacío otra vez.


Resumen

  • Un método = una tarea con nombre. Si un trozo de código se repite, o necesita un comentario para entenderse, es candidato a ser un método
  • public para lo que otras partes van a usar; private para los ladrillos internos
  • Un método que devuelve algo (double, boolean, String…) sirve para calcular; uno void sirve para hacer algo, como imprimir
  • Un return sale del método entero, así que dentro de un bucle no hace falta break
  • Los métodos pueden llamarse entre sí: imprimirBoletin usa a calcularMedia y a clasificar
  • Un array se puede pasar como parámetro (double[] notas) y el método recibe el original, no una copia
  • El Scanner se crea una vez en main y se pasa como parámetro; nunca se cierra dentro de un método

Siguiente paso: ejerciciosMétodos en Java — ejercicios con conversores, estadísticas y un juego de adivinar (y el return que el compilador no veía)

¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

Sigue aprendiendo

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *