Métodos de clase en Java

📅 Actualizado en febrero 2026 ✍️ Ángel López ⏱️ 18 min de lectura ✓ Nivel principiante-intermedio ★ ★ ★ ★ ★ (5/5)

📐 ¿Qué son los métodos en Java?

Los métodos constituyen la lógica de una clase en Java. Son bloques de código que definen el comportamiento de los objetos: reciben datos, los procesan y, opcionalmente, devuelven un resultado. Cuando un objeto necesita comunicarse con otro, lo hace invocando uno de sus métodos, siguiendo el paradigma de paso de mensajes propio de la programación orientada a objetos.

En términos prácticos, un método es el equivalente a una función en lenguajes procedurales como C, con la diferencia fundamental de que en Java todo método pertenece a una clase. No existen funciones «sueltas» fuera de una clase.

💡
Concepto clave: la invocación de un método se realiza mediante la notación punto: referencia.metodo(argumentos). Para métodos static, se usa el nombre de la clase: NombreClase.metodo(argumentos).

Dominar los métodos es esencial porque permiten descomponer problemas complejos en piezas manejables, reutilizar código, facilitar el testing y mejorar la legibilidad de los programas. Junto con los modificadores de acceso y el encapsulamiento, los métodos son el pilar del diseño orientado a objetos en Java.

🔬 Anatomía de un método: declaración y firma

La declaración de un método en Java consta de dos partes claramente diferenciadas: la cabecera (o declaración) y el cuerpo.

▶️ Estructura general

Java
// Estructura general de un método en Java
[modificadorAcceso] [static] [final] tipoRetorno nombreMetodo([parámetros]) [throws excepciones] {
    // Cuerpo del método: sentencias que se ejecutan
    return valor; // solo si tipoRetorno no es void
}

Cada elemento de la cabecera tiene una función específica:

Elemento Obligatorio Descripción Ejemplo
Modificador de acceso No (default: package-private) Visibilidad del método public, private, protected
static No Pertenece a la clase, no al objeto static
Tipo de retorno Tipo del valor que devuelve (o void) int, String, void
Nombre Identificador en camelCase calcularTotal
Parámetros No (paréntesis vacíos si no tiene) Datos de entrada con tipo y nombre (double precio, int cantidad)
throws No Excepciones que el método puede lanzar throws IOException

🔹 La firma del método

La firma (method signature) es la combinación del nombre del método y su lista de parámetros (tipos y orden). Java usa la firma para distinguir métodos en la sobrecarga. El tipo de retorno no forma parte de la firma.

Java
// Estas dos firmas son DIFERENTES (sobrecarga válida)
public double calcularPrecio(double base)           // firma: calcularPrecio(double)
public double calcularPrecio(double base, int dto)  // firma: calcularPrecio(double, int)

// Esto NO compila: misma firma, distinto retorno
public int calcularPrecio(double base)    // ❌ Error: ya existe calcularPrecio(double)
public double calcularPrecio(double base) // ❌ Conflicto de firma

🔄 Tipos de retorno y la palabra clave return

Todo método debe declarar qué tipo de dato devuelve. Si no devuelve nada, se usa void. La sentencia return finaliza la ejecución del método y entrega el valor al código que lo invocó.

▶️ Método con retorno

Java
public class Calculadora {

    // Método que recibe dos enteros y devuelve su suma
    public int sumar(int a, int b) {
        return a + b; // devuelve un int
    }

    // Método que calcula el IVA de un precio
    public double calcularIVA(double precioBase) {
        double iva = precioBase * 0.21;
        return iva; // devuelve un double
    }
}

▶️ Método void (sin retorno)

Java
public class Impresora {

    // Método void: ejecuta una acción pero no devuelve nada
    public void imprimirSaludo(String nombre) {
        System.out.println("¡Hola, " + nombre + "!");
        // No necesita return (aunque puede usar return; para salir antes)
    }

    // Uso de return en void para salida anticipada
    public void procesarEdad(int edad) {
        if (edad < 0) {
            System.out.println("Edad no válida.");
            return; // sale del método sin devolver nada
        }
        System.out.println("Edad registrada: " + edad);
    }
}
⚠️
Error frecuente: olvidar el return en todos los caminos de ejecución. Si un método declara un tipo de retorno distinto de void, el compilador exige que toda rama posible (if, else, switch) termine con un return.

📦 Parámetros y argumentos: paso por valor

Los parámetros son las variables declaradas en la cabecera del método. Los argumentos son los valores concretos que se pasan al invocarlo. En Java, todo se pasa por valor: se copia el contenido de la variable, no la variable en sí.

🔹 Tipos primitivos: se copia el valor

Java
public class DemoPasoPorValor {

    public static void duplicar(int numero) {
        numero = numero * 2; // modifica la COPIA local
        System.out.println("Dentro del método: " + numero); // 20
    }

    public static void main(String[] args) {
        int valor = 10;
        duplicar(valor);
        System.out.println("Fuera del método: " + valor); // 10 (no cambió)
    }
}

🔹 Objetos: se copia la referencia

Cuando se pasa un objeto, Java copia la referencia (la dirección de memoria), no el objeto. Esto permite modificar el estado interno del objeto, pero no reasignar la referencia original a otro objeto.

Java
public class DemoPasoReferencia {

    public static void cambiarNombre(StringBuilder sb) {
        sb.append(" Modificado"); // ✅ Sí modifica el objeto original
    }

    public static void reasignar(StringBuilder sb) {
        sb = new StringBuilder("Nuevo"); // ❌ Solo cambia la copia local
    }

    public static void main(String[] args) {
        StringBuilder texto = new StringBuilder("Original");

        cambiarNombre(texto);
        System.out.println(texto); // "Original Modificado"

        reasignar(texto);
        System.out.println(texto); // "Original Modificado" (no cambió)
    }
}
💡
Recuerda: Java no tiene paso por referencia. Lo que se copia es la referencia (como copiar la dirección de una casa: dos personas tienen la dirección, pero la casa es una sola). Para profundizar en este mecanismo, consulta la lección sobre el objeto en Java.

🔀 Sobrecarga de métodos (overloading)

La sobrecarga permite definir varios métodos con el mismo nombre dentro de la misma clase, siempre que difieran en su lista de parámetros (cantidad, tipo u orden). Es una forma de polimorfismo en tiempo de compilación.

▶️ Reglas de la sobrecarga

✅ Se puede sobrecargar cambiando el número de parámetros, el tipo de parámetros o el orden de los tipos. ❌ No se puede sobrecargar cambiando solo el tipo de retorno ni solo el nombre de los parámetros.

Java
public class Publicacion {

    private String titulo;
    private String autor;
    private int paginas;

    // Constructor sin parámetros
    public Publicacion() {
        this.titulo = "Sin título";
        this.autor  = "Desconocido";
        this.paginas = 0;
    }

    // Constructor con dos parámetros (sobrecarga)
    public Publicacion(String titulo, String autor) {
        this.titulo = titulo;
        this.autor  = autor;
        this.paginas = 0;
    }

    // Constructor completo (sobrecarga)
    public Publicacion(String titulo, String autor, int paginas) {
        this.titulo = titulo;
        this.autor  = autor;
        this.paginas = paginas;
    }

    // Métodos sobrecargados: mostrar información
    public String describir() {
        return titulo + " de " + autor;
    }

    public String describir(boolean incluirPaginas) {
        String desc = titulo + " de " + autor;
        if (incluirPaginas) {
            desc += " (" + paginas + " págs.)";
        }
        return desc;
    }
}
⚠️
Cuidado con tipos ambiguos: los tipos int, byte y short pueden provocar ambigüedad en la sobrecarga. Si se llama a un método con un literal entero, Java no sabe si debe elegir la versión int, byte o short. Los tipos double y float no presentan este problema porque un decimal literal siempre se interpreta como double salvo que se fuerce con la «f» final.

⚡ Métodos static vs. métodos de instancia

Esta es una de las distinciones más importantes en Java. Determina a quién pertenece el método y cómo se invoca.

Característica Método de instancia Método static
Pertenece a Cada objeto (instancia) La clase en sí
Invocación objeto.metodo() Clase.metodo()
Accede a atributos de instancia ✅ Sí ❌ No
Puede usar this ✅ Sí ❌ No
Puede llamar a métodos de instancia ✅ Sí ❌ No (sin crear un objeto)
Uso típico Lógica que depende del estado del objeto Utilidades, factory methods, main()

▶️ Ejemplo comparativo

Java
public class CuentaBancaria {

    private String titular;
    private double saldo;
    private static int totalCuentas = 0; // atributo static: compartido por todas las instancias

    public CuentaBancaria(String titular, double saldoInicial) {
        this.titular = titular;
        this.saldo = saldoInicial;
        totalCuentas++; // cada nueva cuenta incrementa el contador global
    }

    // ── Método de INSTANCIA: opera sobre el objeto concreto ──
    public void depositar(double cantidad) {
        if (cantidad > 0) {
            this.saldo += cantidad;
            System.out.println(titular + " depositó " + cantidad + "€. Saldo: " + saldo + "€");
        }
    }

    // ── Método de INSTANCIA: necesita acceder a 'this.saldo' ──
    public double getSaldo() {
        return this.saldo;
    }

    // ── Método STATIC: no necesita ningún objeto, opera sobre la clase ──
    public static int getTotalCuentas() {
        return totalCuentas;
    }

    // ── Método STATIC de utilidad ──
    public static double convertirEurAUsd(double euros) {
        return euros * 1.08; // tipo de cambio fijo (ejemplo)
    }

    public static void main(String[] args) {
        System.out.println("Cuentas creadas: " + CuentaBancaria.getTotalCuentas()); // 0

        CuentaBancaria cuenta1 = new CuentaBancaria("Ana García", 1000);
        CuentaBancaria cuenta2 = new CuentaBancaria("Luis Martín", 2500);

        cuenta1.depositar(500); // método de instancia → necesita el objeto
        System.out.println("Cuentas creadas: " + CuentaBancaria.getTotalCuentas()); // 2

        // Método static de utilidad: no necesita objeto
        double usd = CuentaBancaria.convertirEurAUsd(cuenta1.getSaldo());
        System.out.println("Saldo en USD: " + usd);
    }
}
// Salida:
// Cuentas creadas: 0
// Ana García depositó 500.0€. Saldo: 1500.0€
// Cuentas creadas: 2
// Saldo en USD: 1620.0
Buena práctica: si un método no accede a this ni a ningún atributo de instancia, considéralo candidato a static. Esto comunica claramente que su resultado depende solo de sus parámetros, no del estado del objeto. Para más detalle sobre this, consulta la lección sobre this en Java.

🔒 Modificadores de acceso en métodos

Los modificadores de acceso controlan desde dónde se puede invocar un método. Java proporciona cuatro niveles de visibilidad:

Modificador Misma clase Mismo paquete Subclase (otro paquete) Cualquier clase
public
protected
(default / package-private)
private
Principio de mínimo privilegio: declara los métodos con la visibilidad más restrictiva posible. Un método auxiliar interno debe ser private; solo los métodos que forman la API pública de la clase deben ser public. Esto es un pilar del encapsulamiento.

📋 Varargs: número variable de argumentos

Desde Java 5, un método puede aceptar un número variable de argumentos del mismo tipo usando la sintaxis de varargs (tipo... nombre). Internamente, Java lo trata como un array.

Java
public class Estadisticas {

    // Acepta 0, 1, 2... N argumentos double
    public static double promedio(double... valores) {
        if (valores.length == 0) return 0;

        double suma = 0;
        for (double v : valores) {
            suma += v;
        }
        return suma / valores.length;
    }

    public static void main(String[] args) {
        System.out.println(promedio(8.5, 9.0, 7.5));      // 8.333...
        System.out.println(promedio(10, 6));                // 8.0
        System.out.println(promedio());                     // 0.0
    }
}
⚠️
Restricción: solo puede haber un parámetro varargs por método, y debe ser el último parámetro de la lista. Por ejemplo: void metodo(String etiqueta, int... valores) es válido, pero void metodo(int... valores, String etiqueta) no compila.

🔁 Métodos recursivos

Un método recursivo es aquel que se llama a sí mismo para resolver un problema dividiéndolo en subproblemas más pequeños. Toda recursión necesita un caso base (condición de parada) para evitar un bucle infinito que provoque StackOverflowError.

Java
public class Recursion {

    // Factorial: n! = n × (n-1)!   Caso base: 0! = 1
    public static long factorial(int n) {
        if (n < 0) throw new IllegalArgumentException("n no puede ser negativo");
        if (n == 0) return 1;          // caso base
        return n * factorial(n - 1);   // llamada recursiva
    }

    // Fibonacci: f(n) = f(n-1) + f(n-2)   Caso base: f(0)=0, f(1)=1
    public static int fibonacci(int n) {
        if (n <= 0) return 0;  // caso base 1
        if (n == 1) return 1;  // caso base 2
        return fibonacci(n - 1) + fibonacci(n - 2);
    }

    public static void main(String[] args) {
        System.out.println("5! = " + factorial(5));        // 120
        System.out.println("fib(10) = " + fibonacci(10));  // 55
    }
}
💡
Rendimiento: la versión recursiva de Fibonacci tiene complejidad exponencial O(2ⁿ). En producción se prefieren soluciones iterativas o con memoización. Sin embargo, la recursividad es fundamental para entender algoritmos como divide y vencerás y backtracking.

🏢 Ejemplo completo: sistema de gestión de empleados

Este ejemplo combina todos los conceptos: métodos de instancia y static, sobrecarga, paso de parámetros, varargs y retorno de valores en un escenario profesional realista.

Java
public class Empleado {

    // ── Atributos de instancia ──
    private String nombre;
    private String departamento;
    private double salarioBase;
    private int antiguedad; // años

    // ── Atributo static: compartido por todos los empleados ──
    private static int totalEmpleados = 0;
    private static final double IRPF_BASE = 0.15;

    // ── Constructor (sobrecarga) ──
    public Empleado(String nombre, String departamento, double salarioBase) {
        this(nombre, departamento, salarioBase, 0);
    }

    public Empleado(String nombre, String departamento, double salarioBase, int antiguedad) {
        this.nombre = nombre;
        this.departamento = departamento;
        this.salarioBase = salarioBase;
        this.antiguedad = antiguedad;
        totalEmpleados++;
    }

    // ── Método de instancia: calcula salario neto ──
    public double calcularSalarioNeto() {
        double bruto = calcularSalarioBruto();
        double irpf = calcularIRPF(bruto);
        return bruto - irpf;
    }

    // ── Método de instancia: salario bruto con bonus por antigüedad ──
    public double calcularSalarioBruto() {
        double bonusAntiguedad = salarioBase * (antiguedad * 0.02); // 2% por año
        return salarioBase + bonusAntiguedad;
    }

    // ── Método de instancia sobrecargado ──
    public double calcularSalarioBruto(double bonusExtra) {
        return calcularSalarioBruto() + bonusExtra;
    }

    // ── Método private auxiliar ──
    private double calcularIRPF(double bruto) {
        double tipo = IRPF_BASE;
        if (bruto > 40000) tipo = 0.24;
        else if (bruto > 25000) tipo = 0.19;
        return bruto * tipo;
    }

    // ── Método static: no depende de ningún objeto ──
    public static int getTotalEmpleados() {
        return totalEmpleados;
    }

    // ── Método static con varargs: calcula nómina total del equipo ──
    public static double calcularNominaTotal(Empleado... empleados) {
        double total = 0;
        for (Empleado emp : empleados) {
            total += emp.calcularSalarioNeto();
        }
        return total;
    }

    // ── Método toString: representación textual ──
    @Override
    public String toString() {
        return String.format("%s (%s) - Bruto: %.2f€ - Neto: %.2f€",
                nombre, departamento, calcularSalarioBruto(), calcularSalarioNeto());
    }

    // ── Main: demostración ──
    public static void main(String[] args) {
        Empleado ana  = new Empleado("Ana García", "Desarrollo", 32000, 5);
        Empleado luis = new Empleado("Luis Martín", "Marketing", 28000, 2);
        Empleado marta = new Empleado("Marta López", "Desarrollo", 45000);

        System.out.println("=== Plantilla: " + Empleado.getTotalEmpleados() + " empleados ===");
        System.out.println(ana);
        System.out.println(luis);
        System.out.println(marta);

        // Salario bruto con bonus extra
        System.out.printf("%nAna con bonus navideño: %.2f€%n",
                ana.calcularSalarioBruto(2000));

        // Nómina total usando varargs
        double nomina = Empleado.calcularNominaTotal(ana, luis, marta);
        System.out.printf("Nómina total del equipo: %.2f€%n", nomina);
    }
}
// Salida:
// === Plantilla: 3 empleados ===
// Ana García (Desarrollo) - Bruto: 35200.00€ - Neto: 28512.00€
// Luis Martín (Marketing) - Bruto: 29120.00€ - Neto: 23587.20€
// Marta López (Desarrollo) - Bruto: 45000.00€ - Neto: 34200.00€
//
// Ana con bonus navideño: 37200.00€
// Nómina total del equipo: 86299.20€

🚫 Errores frecuentes al trabajar con métodos

❌ Error 1: llamar a un método de instancia sin crear un objeto

Java
public class ErrorInstancia {
    public void saludar() {
        System.out.println("Hola");
    }

    public static void main(String[] args) {
        // ❌ Error: non-static method saludar() cannot be referenced from a static context
        saludar();

        // ✅ Correcto: crear un objeto primero
        ErrorInstancia obj = new ErrorInstancia();
        obj.saludar();
    }
}

❌ Error 2: no devolver un valor en todos los caminos de ejecución

Java
// ❌ Error de compilación: missing return statement
public String clasificar(int nota) {
    if (nota >= 5) {
        return "Aprobado";
    }
    // ¿Y si nota < 5? Falta el return para ese camino
}

// ✅ Corrección
public String clasificar(int nota) {
    if (nota >= 5) {
        return "Aprobado";
    }
    return "Suspenso"; // cubre todos los caminos
}

❌ Error 3: confundir sobrecarga con sobreescritura

Java
// SOBRECARGA (overloading): misma clase, distinta firma
class Calculadora {
    int sumar(int a, int b) { return a + b; }
    double sumar(double a, double b) { return a + b; } // ✅ Sobrecarga
}

// SOBREESCRITURA (overriding): clase hija redefine método del padre
class Animal {
    void hablar() { System.out.println("..."); }
}
class Perro extends Animal {
    @Override
    void hablar() { System.out.println("¡Guau!"); } // ✅ Sobreescritura
}

La sobrecarga se resuelve en tiempo de compilación según los tipos de los argumentos. La sobreescritura se resuelve en tiempo de ejecución según el tipo real del objeto (polimorfismo). Es fundamental no confundir ambos conceptos. Consulta también la lección sobre herencia en Java para profundizar en la sobreescritura.

❌ Error 4: abusar de métodos static cuando se necesita estado

Java
// ❌ Antipatrón: todo static, perdiendo los beneficios de la POO
class GestorUsuarios {
    static String nombre;    // ¡Compartido por toda la aplicación!
    static int edad;

    static void mostrar() {
        System.out.println(nombre + " (" + edad + ")");
    }
}

// ✅ Correcto: cada usuario es un objeto con su propio estado
class Usuario {
    private String nombre;
    private int edad;

    public Usuario(String nombre, int edad) {
        this.nombre = nombre;
        this.edad = edad;
    }

    public void mostrar() {
        System.out.println(nombre + " (" + edad + ")");
    }
}

✏️ Ejercicios prácticos

Ejercicio 1: ¿Qué imprime este código?

Analiza la siguiente clase y determina la salida por consola. Presta atención a la sobrecarga y al paso por valor.

Java
public class Quiz {
    public static int procesar(int x) {
        x = x + 10;
        return x;
    }

    public static int procesar(int x, int y) {
        return x * y;
    }

    public static void main(String[] args) {
        int a = 5;
        int b = procesar(a);
        int c = procesar(a, 3);
        System.out.println(a + " " + b + " " + c);
    }
}

Ejercicio 2: Clase Conversor con métodos sobrecargados

Crea una clase Conversor con los siguientes métodos, todos llamados convertir:

1) Recibe grados Celsius (double) y devuelve Fahrenheit: F = C × 9/5 + 32.

2) Recibe kilómetros (double) y una cadena de unidad destino ("millas" o "metros") y devuelve la conversión.

3) Recibe un array de temperaturas en Celsius y devuelve un array de Fahrenheit.

Ejercicio 3: Diseña una clase Biblioteca con métodos especializados

Diseña una clase Biblioteca que gestione una colección de libros (usa un ArrayList<String>). Implementa los siguientes métodos:

1) agregarLibro(String titulo) → añade un libro si no existe ya (devuelve boolean).

2) agregarLibro(String... titulos) → añade varios libros usando varargs (devuelve el número de libros añadidos con éxito).

3) buscar(String fragmento) → devuelve un ArrayList de títulos que contengan el fragmento (búsqueda insensible a mayúsculas).

4) static contarPalabras(String titulo) → método estático que cuenta las palabras de un título.

❓ Preguntas frecuentes sobre Métodos de clase en Java

Las dudas más comunes respondidas de forma clara y directa.

Un método static pertenece a la clase y se invoca con NombreClase.metodo(), sin necesidad de crear un objeto. Un método de instancia pertenece a cada objeto y requiere crear una instancia con new antes de poder invocarlo. Los métodos static no pueden acceder a atributos de instancia ni usar this.
La sobrecarga (overloading) permite definir varios métodos con el mismo nombre pero diferente lista de parámetros (cantidad, tipo u orden). El compilador determina cuál ejecutar según los argumentos de la llamada. No se puede sobrecargar cambiando solo el tipo de retorno.
Java siempre pasa los argumentos por valor. Para tipos primitivos se copia el valor; para objetos se copia la referencia (no el objeto). Esto significa que un método puede modificar el estado interno de un objeto recibido, pero no puede hacer que la variable original apunte a otro objeto.
Directamente no: un método solo puede devolver un valor con return. Sin embargo, se pueden devolver múltiples valores encapsulándolos en un objeto, en un array o, desde Java 16, en un record. También se puede usar un Map o una lista como tipo de retorno.
Se recomienda usar métodos static para operaciones que no dependen del estado de un objeto concreto: funciones de utilidad (Math.sqrt(), Integer.parseInt()), métodos de fábrica (factory methods) y el método main(). Si el método necesita acceder a atributos de instancia, debe ser de instancia.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre Métodos de clase en Java? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!