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

Mi primer método con un if / else if me dio missing return statement y me tiré un buen rato mirándolo, porque para mí las dos condiciones cubrían todos los casos: o es mayor o igual que 5, o es menor que 5. ¿Qué más hay? Pues resulta que el compilador no hace esas cuentas, y lo que a ti te parece obvio a él le parece un camino sin salida.

Hoy toca ponerte a prueba con métodos. Son tres ejercicios que suben poco a poco: un conversor para calentar, un método que trabaja con arrays y, para cerrar, un juego en el que varios métodos se llaman entre sí. Antes de las soluciones hay una sección de errores típicos, y en el tercero tendrás que usar sobrecarga de verdad, no solo verla en la teoría.


Métodos en Java ejercicios — Nivel básico

Ejercicio 1 — Conversor de temperaturas

Escribe un programa que pida una temperatura en grados Celsius y la muestre en Fahrenheit y en Kelvin. La conversión de cada escala debe ir en su propio método, y otro método debe comprobar que la temperatura es posible: nada puede estar por debajo del cero absoluto (-273.15 °C).

Salida esperada con 36.6 °C:

=== CONVERSOR DE TEMPERATURAS ===
Temperatura en °C: 36.6

--- Resultado ---
36.6 °C = 97.9 °F
36.6 °C = 309.75 K

Con -300 el programa debe avisar de que esa temperatura no existe y terminar sin convertir nada:

Temperatura en °C: -300
✗ No existe una temperatura por debajo de -273.15 °C

La fórmula es F = C × 9/5 + 32 y K = C + 273.15.

Escribir celsius * (9 / 5) + 32 con los números enteros. En Java, 9 / 5 entre dos enteros es una división entera y da 1, no 1.8, así que tu conversor devolvería celsius + 32 y nadie se daría cuenta hasta comparar con un termómetro. Para que la división sea con decimales, al menos uno de los dos números tiene que ser double: 9.0 / 5.0.

💡 Pistas:

  • Haz tres métodos: esTemperaturaValida (devuelve boolean), celsiusAFahrenheit y celsiusAKelvin (devuelven double)
  • Valida primero, y si no es válida corta el programa con return en main, igual que con las validaciones de control de flujo
  • Los métodos de conversión no imprimen nada: solo calculan y devuelven. Quien imprime es main

Métodos en Java ejercicios — Nivel intermedio

Ejercicio 2 — Estadísticas de la semana

Escribe un programa que pida la temperatura máxima de cada uno de los 7 días de una semana (enteros) y las guarde en un array. Después debe mostrar la temperatura más alta, la más baja, la media y cuántos días estuvieron por encima de la media. Cada cálculo debe ser un método que reciba el array.

Salida esperada con las temperaturas 21, 24, 19, 27, 25, 18 y 22:

=== TEMPERATURAS DE LA SEMANA ===
Día 1: 21
Día 2: 24
Día 3: 19
Día 4: 27
Día 5: 25
Día 6: 18
Día 7: 22

--- Estadísticas ---
Máxima: 27 °C
Mínima: 18 °C
Media: 22.29 °C
Días por encima de la media: 3

Para el máximo y el mínimo, inicializa la variable con datos[0] y empieza el bucle en i = 1. Si la inicializas en 0, un día de invierno con todas las temperaturas bajo cero daría un máximo de 0 que nunca estuvo en tus datos.

Estos métodos reciben el array original, no una copia. Como solo leen sus valores, no pasa nada. Si algún día un método los modifica, el cambio se verá también en main, y verás por qué en el apartado de Python Tutor.

💡 Pistas:

  • Cuatro métodos: maximo, minimo, media y contarMayoresQue, todos public static y con un int[] como parámetro
  • Para la media, ojo con la división: si dividen dos int, pierdes los decimales. Convierte la suma a double antes de dividir con (double) suma
  • contarMayoresQue puede recibir un segundo parámetro double umbral, y así la media se la pasas tú desde main en lugar de recalcularla dentro

Métodos en Java ejercicios — Desafío final

Ejercicio 3 — Adivina el número

Escribe un juego en el que el programa «piensa» un número del 1 al 100 y el jugador tiene 7 intentos para adivinarlo. Tras cada fallo, el programa dice si el número secreto es más alto o más bajo. Al terminar la partida pregunta si quieres jugar otra vez, y al final muestra cuál fue tu mejor partida (la de menos intentos).

Para que la salida sea reproducible, el número secreto será siempre 42. Si un intento está fuera de 1-100, se avisa y no cuenta como intento.

Salida esperada de una sesión completa:

=== ADIVINA EL NÚMERO ===
Intento 1/7 (1-100): 150
✗ Debe estar entre 1 y 100
Intento 1/7 (1-100): 50
Más bajo ↓
Intento 2/7 (1-100): 25
Más alto ↑
Intento 3/7 (1-100): 42
✓ ¡Acertaste!
¿Jugar otra vez? (s/n): s

Intento 1/7 (1-100): 42
✓ ¡Acertaste!
¿Jugar otra vez? (s/n): n

Mejor partida: 1 intento

Si no ganas ninguna partida, el programa debe terminar con No ganaste ninguna partida.

El programa debe organizarse en estos métodos:

  • leerEntero en dos versiones (sobrecarga): una que solo lee un entero, y otra que además recibe un mínimo y un máximo y repite hasta que el valor esté en el rango
  • pista, que recibe el intento y el secreto y devuelve el texto de ayuda
  • jugarPartida, que juega una partida entera y devuelve cuántos intentos usó, o -1 si no acertó

Cuando el juego funcione, cambia el 42 por un número aleatorio y tendrás un juego de verdad: int secreto = new Random().nextInt(100) + 1; (con import java.util.Random;). nextInt(100) da un número del 0 al 99, y el + 1 lo pasa a 1-100.

La versión de leerEntero con rango no necesita repetir el código de leer: puede llamar a la versión sencilla. Es una forma muy habitual de usar la sobrecarga, porque cada versión se apoya en la anterior y no duplicas nada.

💡 Pistas:

  • jugarPartida es un for de 1 a maxIntentos: en cada vuelta lee el intento, y si acierta hace return n (ahí sale del método entero). Si el bucle termina sin acertar, devuelve -1
  • El bucle de «jugar otra vez» en main es un do-while, porque la primera partida se juega siempre
  • Para llevar la mejor partida usa una variable mejor que empiece en -1 y se actualice solo cuando usados sea distinto de -1 y, además, mejor esté sin estrenar o usados sea menor
  • Para leer la respuesta s/n usa sc.next() y compara con .equalsIgnoreCase("s"), nunca con ==

¿Qué falla aquí?

Antes de las soluciones, tres errores típicos de esta parte del temario. ¿Ves el problema en cada uno?

Error 1

public static String clasificar(int nota) {
    if (nota >= 5) {
        return "Aprobado";
    } else if (nota < 5) {
        return "Suspenso";
    }
}
// No compila: "missing return statement"

Para ti las dos condiciones cubren todos los casos, pero el compilador no razona con números: solo ve que el último camino posible es un if sin else, y asume que podría no cumplirse ninguna condición, con lo que el método terminaría sin devolver nada. La solución es cerrar con un else o con un return final:

public static String clasificar(int nota) {
    if (nota >= 5) {
        return "Aprobado";
    }
    return "Suspenso";    // ← camino final: siempre devuelve algo
}

Error 2

public class Main {
    public static int doble(int x) {
        return x * 2;
    }
    public int triple(int x) {
        return x * 3;
    }
    public static void main(String[] args) {
        System.out.println(doble(4));     // funciona
        System.out.println(triple(4));    // ← error
    }
}
// "non-static method triple(int) cannot be referenced from a static context"

main es static, y desde un método static no se puede llamar a uno que no lo sea. Ya sabes que, por ahora, static es «así se hace»: aquí ves la consecuencia de olvidarlo. Basta con añadir static a triple. Cuando lleguemos a clases y objetos entenderás por qué existe esa regla.

Error 3

public static int duplicar(int x) {
    return x * 2;
}

public static void main(String[] args) {
    int n = 5;
    duplicar(n);
    System.out.println(n);    // imprime 5, no 10
}

duplicar(n) sí calcula 10, pero ese valor se pierde porque nadie lo guarda. Y n no cambia porque el método recibe una copia del valor, no la variable. Lo que devuelve un método hay que recogerlo:

n = duplicar(n);    // ← ahora n vale 10

Soluciones comentadas

Solución Ejercicio 1

import java.util.Scanner;

public class ConversorTemperaturas {

    // private: es una comprobación interna, solo la usa esta clase.
    // boolean: la respuesta es sí o no.
    private static boolean esTemperaturaValida(double celsius) {
        // -273.15 °C es el cero absoluto: por debajo no existe nada
        return celsius >= -273.15;
    }

    // Recibe grados Celsius, devuelve grados Fahrenheit. No imprime nada: solo calcula
    public static double celsiusAFahrenheit(double celsius) {
        // 9.0 / 5.0 con decimales. Con 9 / 5 sería una división entera y daría 1
        return celsius * 9.0 / 5.0 + 32;
    }

    public static double celsiusAKelvin(double celsius) {
        return celsius + 273.15;
    }

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

        System.out.println("=== CONVERSOR DE TEMPERATURAS ===");
        System.out.print("Temperatura en °C: ");
        double celsius = sc.nextDouble();

        // Validamos ANTES de convertir y cortamos con return, igual que en control de flujo
        if (!esTemperaturaValida(celsius)) {
            System.out.println("✗ No existe una temperatura por debajo de -273.15 °C");
            sc.close();
            return;
        }

        System.out.println();
        System.out.println("--- Resultado ---");
        // main es quien imprime; los métodos solo calculan y devuelven el valor
        System.out.printf("%.1f °C = %.1f °F%n", celsius, celsiusAFahrenheit(celsius));
        System.out.printf("%.1f °C = %.2f K%n", celsius, celsiusAKelvin(celsius));

        sc.close();
    }
}

La decisión clave está en que los métodos de conversión no imprimen. Si hubieran impreso dentro, mañana no podrías reutilizarlos para, por ejemplo, guardar el resultado en una variable o comparar dos temperaturas. Un método que devuelve el valor sirve para cualquier cosa; uno que imprime solo sirve para imprimir.

Solución Ejercicio 2

import java.util.Scanner;

public class EstadisticasArray {

    public static int maximo(int[] datos) {
        int max = datos[0];    // partimos del primer dato real, NO de 0
        for (int i = 1; i < datos.length; i++) {    // empezamos en 1: el 0 ya lo tenemos
            if (datos[i] > max) {
                max = datos[i];
            }
        }
        return max;    // el return va FUERA del bucle: si estuviera dentro, saldría en la primera vuelta
    }

    public static int minimo(int[] datos) {
        int min = datos[0];
        for (int i = 1; i < datos.length; i++) {
            if (datos[i] < min) {
                min = datos[i];
            }
        }
        return min;
    }

    public static double media(int[] datos) {
        int suma = 0;
        for (int i = 0; i < datos.length; i++) {
            suma += datos[i];
        }
        // (double) convierte la suma ANTES de dividir; sin él, 156 / 7 daría 22 en vez de 22.28...
        return (double) suma / datos.length;
    }

    // umbral es double porque le pasaremos la media, que tiene decimales
    public static int contarMayoresQue(int[] datos, double umbral) {
        int contador = 0;
        for (int i = 0; i < datos.length; i++) {
            if (datos[i] > umbral) {
                contador++;
            }
        }
        return contador;
    }

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

        System.out.println("=== TEMPERATURAS DE LA SEMANA ===");
        for (int i = 0; i < temperaturas.length; i++) {
            System.out.print("Día " + (i + 1) + ": ");    // i + 1 para que el usuario vea 1..7, no 0..6
            temperaturas[i] = sc.nextInt();
        }

        // Guardamos la media en una variable: la usamos dos veces y así la calculamos una sola vez
        double media = media(temperaturas);

        System.out.println();
        System.out.println("--- Estadísticas ---");
        System.out.println("Máxima: " + maximo(temperaturas) + " °C");
        System.out.println("Mínima: " + minimo(temperaturas) + " °C");
        System.out.printf("Media: %.2f °C%n", media);
        System.out.println("Días por encima de la media: " + contarMayoresQue(temperaturas, media));

        sc.close();
    }
}

Fíjate en que la variable local media y el método media(...) conviven con el mismo nombre. Java los distingue sin problema: una lleva paréntesis y la otra no. Aun así, en un programa grande conviene que no se llamen igual para no confundirte tú.

Solución Ejercicio 3

import java.util.Scanner;

public class AdivinaNumero {

    // ---------- Sobrecarga: dos versiones de leerEntero ----------

    // Versión 1: lee un entero, el que sea
    private static int leerEntero(Scanner sc, String mensaje) {
        System.out.print(mensaje);
        return sc.nextInt();
    }

    // Versión 2: lo repite hasta que esté entre min y max.
    // No duplica código de lectura: llama a la versión 1
    private static int leerEntero(Scanner sc, String mensaje, int min, int max) {
        int valor = leerEntero(sc, mensaje);
        while (valor < min || valor > max) {
            System.out.println("✗ Debe estar entre " + min + " y " + max);
            valor = leerEntero(sc, mensaje);
        }
        return valor;
    }

    // Devuelve el texto de ayuda según si el intento se quedó corto o se pasó
    private static String pista(int intento, int secreto) {
        if (intento < secreto) {
            return "Más alto ↑";
        }
        return "Más bajo ↓";    // si llegamos aquí, el intento es mayor (el igual se trata antes)
    }

    // Juega UNA partida. Devuelve los intentos usados si acierta, o -1 si se le acaban
    private static int jugarPartida(Scanner sc, int secreto, int maxIntentos) {
        for (int n = 1; n <= maxIntentos; n++) {
            // Un intento fuera de rango no llega aquí: leerEntero lo repite y no consume la vuelta
            int intento = leerEntero(sc, "Intento " + n + "/" + maxIntentos + " (1-100): ", 1, 100);

            if (intento == secreto) {
                System.out.println("✓ ¡Acertaste!");
                return n;    // sale del método entero, con el número de intentos usados
            }
            System.out.println(pista(intento, secreto));
        }
        // Si el for termina, es que se agotaron los intentos sin acertar
        System.out.println("✗ Sin intentos. El número era " + secreto);
        return -1;
    }

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

        final int SECRETO = 42;       // final = no se puede modificar; en mayúsculas por convención
        final int MAX_INTENTOS = 7;
        int mejor = -1;               // -1 = "todavía no he ganado ninguna"
        String otra;

        System.out.println("=== ADIVINA EL NÚMERO ===");
        do {
            int usados = jugarPartida(sc, SECRETO, MAX_INTENTOS);

            // Actualizamos la mejor partida solo si ganó (usados != -1) y es la primera o mejora la anterior
            if (usados != -1 && (mejor == -1 || usados < mejor)) {
                mejor = usados;
            }

            System.out.print("¿Jugar otra vez? (s/n): ");
            otra = sc.next();    // next() lee una palabra suelta, suficiente para "s" o "n"
            System.out.println();
        } while (otra.equalsIgnoreCase("s"));

        if (mejor == -1) {
            System.out.println("No ganaste ninguna partida");
        } else {
            // Ternario para singular/plural: "1 intento" pero "3 intentos"
            System.out.println("Mejor partida: " + mejor + (mejor == 1 ? " intento" : " intentos"));
        }

        sc.close();
    }
}

Mira cómo se reparten el trabajo. main solo coordina: lanza partidas y lleva la cuenta de la mejor. jugarPartida conoce las reglas del juego, pero no sabe cómo se lee un número ni qué pista se da, porque eso lo delega en leerEntero y en pista. Si mañana quieres cambiar el texto de las pistas, tocas un método de cuatro líneas y no tienes que buscar nada dentro del bucle.


Visualízalo con Python Tutor

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

public class Demo {

    public static void intentarCambiar(int x, int[] arr) {
        x = 99;        // cambia la COPIA de x
        arr[0] = 99;   // cambia el array ORIGINAL
    }

    public static void main(String[] args) {
        int n = 1;
        int[] datos = {1, 2, 3};

        intentarCambiar(n, datos);

        System.out.println(n);         // ¿1 o 99?
        System.out.println(datos[0]);  // ¿1 o 99?
    }
}

Ejecuta paso a paso. Cuando se abre el marco de intentarCambiar, verás que x es una variable nueva con su propio 1, independiente de n, y por eso al poner x = 99 el n de main no se entera. En cambio arr no contiene el array: contiene una flecha hacia el mismo array que datos. Las dos flechas terminan en el mismo cuadro, así que cuando haces arr[0] = 99 estás cambiando el original, y datos[0] vale 99 al volver. Es la explicación visual de las dos cosas que han salido hoy: por qué duplicar(n); sin guardar el resultado no cambia nada (Error 3), y por qué los métodos de estadísticas del Ejercicio 2 podrían alterar tu array si no tuvieras cuidado.


Resumen

  • Un método que devuelve un valor sirve para calcular; si imprime dentro, solo sirve para imprimir. Si dudas, que devuelva y que imprima quien llama
  • Todos los caminos de un método con tipo de retorno tienen que terminar en un return: el compilador no hace cuentas, solo comprueba que no quede ningún camino sin salida
  • Los tipos primitivos (int, double…) se pasan por valor: el método trabaja con una copia. Si quieres el resultado, recógelo con n = metodo(n)
  • Los arrays se pasan como una flecha al original: un método que los modifique cambia los datos de quien lo llamó
  • La sobrecarga te deja usar el mismo nombre con distintos parámetros, y una versión puede apoyarse en otra para no repetir código
  • Una división entre dos int es entera: para tener decimales, al menos uno tiene que ser double
  • Desde un método static solo puedes llamar a otros static, por ahora

Chuletario — métodos en Java

// ============================================
// CHULETARIO — Métodos en Java
// Sergio Learns · sergiolearns.com
// ============================================

// ANATOMÍA DE UN MÉTODO
public static double calcularMedia(int a, int b) {
//  │      │      │       │          └── parámetros: tipo + nombre, uno a uno
//  │      │      │       └── nombre del método
//  │      │      └── tipo que devuelve (void si no devuelve nada)
//  │      └── static: por ahora, "así se hace" (se explica con clases)
//  └── visibilidad: public (desde fuera) o private (solo esta clase)
    return (a + b) / 2.0;
}

// DEVOLVER UN VALOR — todos los caminos deben terminar en return
public static String clasificar(int nota) {
    if (nota >= 5) {
        return "Aprobado";
    }
    return "Suspenso";           // camino final: siempre devuelve algo
}

// MÉTODO VOID — hace algo, no devuelve nada
public static void saludar(String nombre) {
    System.out.println("Hola, " + nombre);
}

// RECOGER LO QUE DEVUELVE UN MÉTODO — si no, se pierde
n = duplicar(n);                 // ← correcto
duplicar(n);                     // ← el resultado se descarta

// PASO POR VALOR (tipos primitivos) — el método recibe una COPIA
static void cambiar(int x) { x = 99; }    // no afecta a la variable original

// ARRAYS COMO PARÁMETRO — el método recibe el ORIGINAL
static void cambiar(int[] a) { a[0] = 99; }    // SÍ afecta al array de quien llama
static double media(int[] datos) { ... }       // solo lee: no hay problema

// SOBRECARGA — mismo nombre, distintos parámetros
static int sumar(int a, int b)           { return a + b; }
static double sumar(double a, double b)  { return a + b; }
static int sumar(int a, int b, int c)    { return a + b + c; }

// UNA VERSIÓN APOYADA EN OTRA — sin duplicar código
static int leer(Scanner sc)                { return sc.nextInt(); }
static int leer(Scanner sc, int min, int max) {
    int v = leer(sc);                      // reutiliza la sencilla
    while (v < min || v > max) { v = leer(sc); }
    return v;
}

// RETURN EN MITAD DE UN BUCLE — sale del método entero (no hace falta break)
static boolean contieneDigito(String s) {
    for (int i = 0; i < s.length(); i++) {
        if (Character.isDigit(s.charAt(i))) {
            return true;
        }
    }
    return false;                // si el bucle acaba, no había ninguno
}

// DIVISIÓN ENTERA vs CON DECIMALES
9 / 5          // 1   (int entre int)
9.0 / 5        // 1.8 (basta con que uno sea double)
(double) suma / n    // convierte ANTES de dividir

// SCANNER — UNO solo en main, pasado como parámetro; nunca cerrarlo dentro de un método
static int leerNota(Scanner sc, String mensaje) { ... }

// ERRORES QUE VAS A VER
// "missing return statement"           → algún camino no devuelve nada
// "non-static ... from a static context" → falta static en el método
Sigue aprendiendoTodos los artículos de Java

¿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 *