Saltar al contenido
metodos.java · devschool

Métodos en Java

Lección 9 de 26 · 11 min de lectura · Actualizado el

En esta lección
  1. Estructura de un método
  2. Llamar a un método
  3. void: métodos que no devuelven nada
  4. Los tipos deben encajar
  5. Sobrecarga
  6. Paso por valor
  7. Varargs: número variable de argumentos
  8. Ámbito de las variables
  9. Documentar con Javadoc
  10. Métodos pequeños y con un solo propósito
  11. ¿Qué significa public static void main?
  12. Errores frecuentes
  13. Resumen

Un método es un bloque de código con nombre que realiza una tarea concreta. Es lo que en otros lenguajes se llama función; en Java se llaman métodos porque siempre viven dentro de una clase. Gracias a ellos escribes una operación una sola vez y la usas donde quieras, divides un programa grande en piezas pequeñas y fáciles de entender, y puedes probar cada pieza por separado. En esta lección aprenderás a crearlos, a pasarles datos, a devolver resultados y a entender qué ocurre con los parámetros por dentro.

Estructura de un método

public static int sumar(int a, int b) {
    return a + b;
}

La primera línea se llama firma o cabecera del método:

  • public static: por ahora, ponlo en todos tus métodos. Más abajo verás qué significa.
  • int: el tipo de retorno, es decir, qué tipo de dato devuelve el método.
  • sumar: el nombre, en camelCase y normalmente con un verbo (calcular, mostrar, esValido).
  • (int a, int b): los parámetros, cada uno con su tipo, separados por comas.
  • return: devuelve el resultado y termina el método.

Llamar a un método

public class Calculadora {

    public static int sumar(int a, int b) {
        return a + b;
    }

    public static void main(String[] args) {
        int resultado = sumar(4, 6);
        System.out.println(resultado);      // 10
        System.out.println(sumar(100, 23)); // 123
        System.out.println(sumar(sumar(1, 2), 3)); // 6
    }
}

Los métodos se escriben dentro de la clase y fuera de main, uno detrás de otro. El orden no importa: main puede llamar a un método escrito debajo.

Los valores que pasas al llamar (4 y 6) se llaman argumentos; las variables que los reciben (a y b), parámetros. Deben coincidir en número, orden y tipo.

void: métodos que no devuelven nada

Si el método solo hace algo (mostrar un mensaje, por ejemplo) y no devuelve ningún valor, su tipo es void:

public static void saludar(String nombre) {
    System.out.println("¡Hola, " + nombre + "!");
}
saludar("Marta"); // ¡Hola, Marta!

Un método void puede usar return; sin valor para terminar antes de tiempo:

public static void mostrarNota(double nota) {
    if (nota < 0 || nota > 10) {
        System.out.println("Nota no válida");
        return; // sale del método
    }
    System.out.println("Tu nota es " + nota);
}

Los tipos deben encajar

Java comprueba que todo cuadre:

public static double calcularMedia(int[] notas) {
    int suma = 0;
    for (int nota : notas) {
        suma += nota;
    }
    return (double) suma / notas.length;
}
  • Si el método dice que devuelve double, todos sus caminos deben terminar en un return con un double. Si en un if te olvidas del return, obtienes el error missing return statement.
  • Si llamas a calcularMedia("hola"), el programa no compila: se esperaba un array de int.

Los métodos que devuelven boolean suelen llamarse es... o tiene... y hacen que las condiciones se lean casi como una frase:

public static boolean esPar(int n) {
    return n % 2 == 0;
}
if (esPar(10)) {
    System.out.println("Par"); // Par
}

Fíjate en que no hace falta escribir if (n % 2 == 0) return true; else return false;: la comparación ya es un boolean.

Sobrecarga

Puedes tener varios métodos con el mismo nombre si reciben parámetros distintos (en número o en tipo). Java elige cuál usar según lo que le pases:

public static int area(int lado) {
    return lado * lado;             // cuadrado
}

public static int area(int base, int altura) {
    return base * altura;           // rectángulo
}

public static double area(double radio) {
    return Math.PI * radio * radio; // círculo
}
System.out.println(area(5));    // 25
System.out.println(area(4, 3)); // 12
System.out.println(area(1.0));  // 3.141592653589793

El tipo de retorno no cuenta para la sobrecarga: dos métodos que solo se diferencian en lo que devuelven no compilan. System.out.println es el ejemplo más famoso de sobrecarga: hay una versión para int, otra para double, otra para String…

Paso por valor

Esta es una de las ideas más importantes de la lección, y fuente de muchas dudas. En Java, los argumentos siempre se pasan por valor: el método recibe una copia de lo que hay en la variable. Lo que cambia es qué hay dentro de esa variable.

Con primitivos: el original no cambia

public class PasoPorValor {

    public static void duplicar(int numero) {
        numero = numero * 2;
        System.out.println("Dentro: " + numero); // Dentro: 20
    }

    public static void main(String[] args) {
        int puntos = 10;
        duplicar(puntos);
        System.out.println("Fuera: " + puntos);  // Fuera: 10
    }
}

numero es una copia de puntos. Cambiar la copia no afecta al original. Si quieres que el cambio llegue fuera, devuelve el valor y guárdalo:

public static int duplicar(int numero) {
    return numero * 2;
}
int puntos = 10;
puntos = duplicar(puntos);
System.out.println(puntos); // 20

Con arrays y objetos: se copia la referencia

Como viste en variables, una variable de tipo array u objeto no guarda los datos, sino la dirección donde están. Al pasarla a un método se copia esa dirección, así que el parámetro apunta al mismo array. Si el método modifica su contenido, el cambio se ve fuera:

import java.util.Arrays;

public class SubirNotas {

    public static void subirUnPunto(int[] notas) {
        for (int i = 0; i < notas.length; i++) {
            notas[i] = Math.min(notas[i] + 1, 10);
        }
    }

    public static void main(String[] args) {
        int[] clase = {4, 7, 10};
        subirUnPunto(clase);
        System.out.println(Arrays.toString(clase)); // [5, 8, 10]
    }
}

Pero si dentro del método asignas un array nuevo al parámetro, solo cambias la copia de la dirección; el original sigue intacto:

public static void reiniciar(int[] notas) {
    notas = new int[] {0, 0, 0}; // el parámetro apunta a otro array
}
int[] clase = {4, 7, 10};
reiniciar(clase);
System.out.println(Arrays.toString(clase)); // [4, 7, 10]
Qué pasasQué recibe el método¿Puede cambiar el original?
Primitivo (int, double…)Copia del valorNo
Array u objetoCopia de la referenciaSu contenido, sí; la variable original, no
StringCopia de la referenciaNo, porque los String son inmutables

Varargs: número variable de argumentos

A veces no sabes cuántos valores recibirá un método. Con ... detrás del tipo, el método acepta cero o más argumentos, que dentro se tratan como un array:

public static int sumarTodos(int... numeros) {
    int total = 0;
    for (int n : numeros) {
        total += n;
    }
    return total;
}
System.out.println(sumarTodos());            // 0
System.out.println(sumarTodos(5));           // 5
System.out.println(sumarTodos(1, 2, 3, 4));  // 10
System.out.println(sumarTodos(new int[] {10, 20})); // 30

Reglas: solo puede haber un parámetro varargs y debe ser el último (mostrar(String titulo, int... valores)). String.format y printf usan varargs; por eso admiten tantos valores como marcadores.

Ámbito de las variables

El ámbito de una variable es la zona del código donde existe. En Java la regla es sencilla: una variable existe desde que se declara hasta la llave } que cierra su bloque.

public class Ambito {

    public static int calcularTotal(int unidades) {
        double precio = 2.5;             // solo existe en calcularTotal
        int total = (int) (unidades * precio);
        return total;
    }

    public static void main(String[] args) {
        int total = calcularTotal(4);    // otra variable distinta, de main
        System.out.println(total);       // 10

        for (int i = 0; i < 3; i++) {
            int doble = i * 2;           // solo existe dentro del for
        }
        // System.out.println(i);      // no compila: i ya no existe
        // System.out.println(precio); // no compila: es de otro método
    }
}
  • Los parámetros y las variables declaradas dentro de un método son locales: nacen al llamarlo y desaparecen al terminar.
  • Dos métodos pueden tener variables con el mismo nombre sin que se mezclen.
  • Un método no puede ver las variables locales de otro. Si necesita un dato, pásaselo como parámetro.

Esto es bueno: cada método es una caja cerrada que solo se comunica por sus parámetros y su return. Las variables que pertenecen a toda la clase (atributos) las verás en clases y objetos.

Documentar con Javadoc

Un comentario Javadoc (/** ... */) justo encima de un método explica qué hace, qué recibe y qué devuelve. Los IDE lo muestran al pasar el ratón por encima de cualquier llamada, y la herramienta javadoc genera con él páginas HTML de documentación:

/**
 * Calcula el precio final aplicando un descuento.
 *
 * @param precio    precio original en euros; debe ser positivo
 * @param descuento porcentaje de descuento, de 0 a 100
 * @return el precio con el descuento aplicado
 */
public static double aplicarDescuento(double precio, int descuento) {
    return precio - precio * descuento / 100.0;
}
System.out.println(aplicarDescuento(80, 25)); // 60.0

Las etiquetas más usadas son @param (una por parámetro), @return y @throws (qué excepción puede lanzar).

Métodos pequeños y con un solo propósito

Un buen método hace una sola cosa y su nombre dice exactamente cuál. Compara un main que lo hace todo con otro que delega:

public class Tienda {

    public static void main(String[] args) {
        double[] carrito = {12.5, 3.99, 20.0};
        double subtotal = sumar(carrito);
        double total = aplicarIva(subtotal);
        mostrarTicket(subtotal, total);
    }

    static double sumar(double[] precios) {
        double suma = 0;
        for (double p : precios) {
            suma += p;
        }
        return suma;
    }

    static double aplicarIva(double importe) {
        return importe * 1.21;
    }

    static void mostrarTicket(double subtotal, double total) {
        System.out.printf("Subtotal: %.2f%n", subtotal);
        System.out.printf("Total con IVA: %.2f%n", total);
    }
}
Subtotal: 36.49
Total con IVA: 44.15

main se lee como un índice de lo que hace el programa. Algunas pautas:

  • Si no encuentras un nombre claro para un método, probablemente hace demasiadas cosas.
  • Si un método pasa de 20-30 líneas, busca partes que puedas extraer.
  • Evita que un método calcule y muestre: el que calcula y devuelve un valor se puede reutilizar y probar; el que imprime, no.

¿Qué significa public static void main?

Ahora ya puedes leerlo entero:

  • public: el método se puede usar desde fuera de la clase. La JVM necesita acceder a él para arrancar el programa. Los modificadores de acceso se explican en paquetes.
  • static: pertenece a la clase y no a un objeto concreto, así que se puede ejecutar sin crear objetos (lo verás en encapsulación, static y final).
  • void: no devuelve nada.
  • main: el nombre que la JVM busca para empezar.
  • String[] args: un array con los argumentos que se pasan al ejecutar el programa desde la terminal (java Programa uno dos).

Errores frecuentes

  • Escribir un método dentro de main: no compila; van al mismo nivel, dentro de la clase.
  • Olvidar el return en algún camino de un método que no es void: missing return statement.
  • Ignorar el valor devuelto: duplicar(puntos); no cambia puntos si no guardas el resultado.
  • Esperar que un método cambie un primitivo que recibe como parámetro.
  • Argumentos en otro orden o tipo: aplicarDescuento(25, 80) compila, pero calcula otra cosa.

Resumen

  • Un método tiene tipo de retorno, nombre, parámetros y cuerpo; return devuelve el valor.
  • void indica que no devuelve nada.
  • La sobrecarga permite varios métodos con el mismo nombre y distintos parámetros.
  • Java pasa siempre por valor: con primitivos se copia el valor; con arrays y objetos, la referencia, así que su contenido sí puede cambiar.
  • tipo... nombre (varargs) acepta un número variable de argumentos.
  • Las variables locales solo existen dentro de su bloque.
  • Documenta con Javadoc y escribe métodos pequeños con un solo propósito.

Un método también puede llamarse a sí mismo. Esa técnica, muy potente, es la recursividad.

Pon a prueba lo que has aprendido

[Java] ¿Qué significa void en un método?
public static void saludar() {
  System.out.println("Hola");
}

[Java] ¿Qué imprime este código?
static void cambiar(int[] datos, int numero) {
    datos[0] = 99;
    numero = 99;
}

// en main:
int[] v = {1, 2};
int n = 1;
cambiar(v, n);
System.out.println(v[0] + " " + n);

[Java] Con estos dos métodos sobrecargados, ¿qué imprime el código?
static void mostrar(int x) { System.out.print("int "); }
static void mostrar(double x) { System.out.print("double "); }

// en main:
mostrar(3);
mostrar(3.0);
mostrar('A');

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.