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.
Tabla de Contendos
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:
- Cada operación es un método que recibe dos
doubley devuelve undouble. Así el cálculo queda separado de cómo se muestra. - Mostrar el resultado se repite cuatro veces, y ahí es donde en mi anécdota copié y pegué. Lo metemos en un método
voidprivado, porque solo lo usa esta clase. - La división por 0 se comprueba en
main, antes de llamar adividir.
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é
sumarespublicymostrarResultadoesprivate? Porquesumares algo que otras clases querrían usar, ymostrarResultadoes 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
%.2fredondea 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:
- Cada requisito es un método
booleancon una sola responsabilidad. Así cada uno se entiende (y se prueba) por separado. - Un método
esValidalos combina con&&. Es el únicopublic: los demás son piezas internas. mainsolo 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
ifgigante? Sí, y sería ilegible. Con métodos,esValidase lee casi como una frase, y si mañana añades un requisito nuevo, escribes un método y una línea. - ¿Por qué
tieneLongitudMinimarecibe 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:
- 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.
- Calcular la media recibe un array y devuelve un número. Clasificar recibe un número y devuelve un texto.
- Imprimir el boletín es un método
voidque 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 cambiasnotas[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 undouble. 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
publicpara lo que otras partes van a usar;privatepara los ladrillos internos- Un método que devuelve algo (
double,boolean,String…) sirve para calcular; unovoidsirve para hacer algo, como imprimir - Un
returnsale del método entero, así que dentro de un bucle no hace faltabreak - Los métodos pueden llamarse entre sí:
imprimirBoletinusa acalcularMediay aclasificar - Un array se puede pasar como parámetro (
double[] notas) y el método recibe el original, no una copia - El
Scannerse crea una vez enmainy se pasa como parámetro; nunca se cierra dentro de un método
¿Te ha servido este artículo? Así puedes apoyar SergioLearns →
