Java

Tutorial Java: Funciones

Java funciones: declaración y uso práctico. Domina la declaración y uso de funciones en Java con ejemplos prácticos y detallados.

Aprende Java y certifícate

Definición de una función y sus distintas partes

En Java, las funciones se implementan como métodos que pertenecen a una clase. Un método es un bloque de código que realiza una tarea específica y puede ser invocado (llamado) cuando sea necesario. Los métodos son fundamentales en la programación Java, ya que permiten organizar el código en unidades lógicas y reutilizables.

Anatomía de un método en Java

Un método en Java se compone de varias partes esenciales que definen su comportamiento, accesibilidad y funcionamiento:

[modificadores] [tipo_retorno] [nombre_método]([parámetros]) [excepciones] {
    // Cuerpo del método
    [sentencias]
    [return valor;]
}

Analicemos cada una de estas partes:

Modificadores de acceso

Los modificadores de acceso determinan desde dónde se puede acceder al método:

  • public: El método es accesible desde cualquier clase.
  • protected: El método es accesible dentro del mismo paquete y subclases.
  • private: El método solo es accesible dentro de la misma clase.
  • Sin modificador (default): El método es accesible solo dentro del mismo paquete.

Además de los modificadores de acceso, existen otros modificadores que afectan el comportamiento:

  • static: El método pertenece a la clase, no a instancias específicas.
  • final: El método no puede ser sobrescrito por subclases.
  • abstract: El método no tiene implementación y debe ser implementado por subclases.
  • synchronized: El método está sincronizado para hilos.

Tipo de retorno

El tipo de retorno especifica qué tipo de dato devuelve el método:

  • Puede ser cualquier tipo de dato primitivo (int, double, boolean, etc.)
  • Puede ser cualquier tipo de referencia (clases, interfaces, arrays)
  • void indica que el método no devuelve ningún valor
public int sumar(int a, int b) {
    return a + b;  // Devuelve un entero
}

public void mostrarMensaje(String mensaje) {
    System.out.println(mensaje);  // No devuelve nada
}

Nombre del método

El nombre del método identifica la función y se utiliza para invocarla. En Java, se siguen estas convenciones:

  • Comienza con una letra minúscula
  • Utiliza camelCase para nombres compuestos
  • Debe ser descriptivo sobre lo que hace el método
  • Generalmente se utilizan verbos

Ejemplos de nombres adecuados:

calcularTotal()
esNumeroPositivo()
obtenerNombreCompleto()

Lista de parámetros

Los parámetros son variables que recibe el método para realizar su tarea. Se definen entre paréntesis después del nombre del método:

public void saludar(String nombre, int edad) {
    System.out.println("Hola " + nombre + ", tienes " + edad + " años.");
}

Si el método no recibe parámetros, se dejan los paréntesis vacíos:

public void mostrarFechaActual() {
    System.out.println(java.time.LocalDate.now());
}

Declaración de excepciones

Se pueden declarar las excepciones que el método puede lanzar usando la palabra clave throws:

public void leerArchivo(String ruta) throws IOException {
    // Código para leer un archivo
}

Cuerpo del método

El cuerpo del método contiene las instrucciones que se ejecutan cuando se llama al método. Se define entre llaves {} y puede incluir:

  • Declaraciones de variables locales
  • Estructuras de control (if, switch, bucles)
  • Llamadas a otros métodos
  • Sentencia return (si el método no es void)
public double calcularAreaCirculo(double radio) {
    // Variable local
    final double PI = 3.14159;
    
    // Cálculo
    double area = PI * radio * radio;
    
    // Retorno del resultado
    return area;
}

Sentencia return

La sentencia return se utiliza para:

  • Devolver un valor del tipo especificado en la declaración del método
  • Finalizar la ejecución del método
public boolean esMayorDeEdad(int edad) {
    if (edad >= 18) {
        return true;  // Termina el método y devuelve true
    }
    return false;  // Termina el método y devuelve false
}

Para métodos void, la sentencia return es opcional y solo se usa para terminar la ejecución:

public void procesarSiPositivo(int numero) {
    if (numero <= 0) {
        return;  // Termina el método si el número no es positivo
    }
    
    // Este código solo se ejecuta si el número es positivo
    System.out.println("Procesando: " + numero);
}

Ejemplos completos de métodos

Veamos algunos ejemplos completos que ilustran diferentes tipos de métodos:

Método simple sin parámetros:

public void mostrarBienvenida() {
    System.out.println("¡Bienvenido a nuestra aplicación!");
}

Método con parámetros y valor de retorno:

public double calcularPromedio(double[] numeros) {
    if (numeros.length == 0) {
        return 0.0;
    }
    
    double suma = 0;
    for (double numero : numeros) {
        suma += numero;
    }
    
    return suma / numeros.length;
}

Método estático de utilidad:

public static String formatearFecha(LocalDate fecha) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
    return fecha.format(formatter);
}

Método con manejo de excepciones:

public int dividir(int dividendo, int divisor) throws ArithmeticException {
    if (divisor == 0) {
        throw new ArithmeticException("No se puede dividir por cero");
    }
    return dividendo / divisor;
}

Buenas prácticas al definir métodos

  • Principio de responsabilidad única: Cada método debe realizar una sola tarea bien definida.
  • Nombres descriptivos: El nombre debe indicar claramente lo que hace el método.
  • Métodos cortos: Se recomienda que los métodos no sean demasiado largos (idealmente menos de 20-30 líneas).
  • Documentación: Utilizar comentarios Javadoc para documentar el propósito, parámetros y valor de retorno.
  • Validación de parámetros: Verificar que los parámetros recibidos sean válidos.
/**
 * Calcula el área de un triángulo usando la fórmula de Herón.
 *
 * @param a Longitud del primer lado
 * @param b Longitud del segundo lado
 * @param c Longitud del tercer lado
 * @return El área del triángulo
 * @throws IllegalArgumentException Si los lados no forman un triángulo válido
 */
public double calcularAreaTriangulo(double a, double b, double c) {
    // Validación de parámetros
    if (a <= 0 || b <= 0 || c <= 0) {
        throw new IllegalArgumentException("Los lados deben ser positivos");
    }
    
    if (a + b <= c || a + c <= b || b + c <= a) {
        throw new IllegalArgumentException("Los lados no forman un triángulo válido");
    }
    
    // Cálculo del semiperímetro
    double s = (a + b + c) / 2;
    
    // Fórmula de Herón
    return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}

Funciones con parámetros: cero parámetros, un parámetro, varios parámetros

Los parámetros permiten que los métodos reciban datos externos para realizar sus operaciones.

Métodos sin parámetros

Los métodos sin parámetros (o con cero parámetros) se definen con paréntesis vacíos y no reciben ningún dato externo para realizar su tarea. Estos métodos suelen:

  • Realizar operaciones basadas únicamente en el estado interno del objeto
  • Devolver información constante o calculada internamente
  • Ejecutar acciones que no requieren datos de entrada
public class Reloj {
    private int hora;
    private int minuto;
    
    // Constructor que inicializa con la hora actual
    public Reloj() {
        LocalTime ahora = LocalTime.now();
        this.hora = ahora.getHour();
        this.minuto = ahora.getMinute();
    }
    
    // Método sin parámetros que devuelve la hora formateada
    public String obtenerHoraActual() {
        return String.format("%02d:%02d", hora, minuto);
    }
    
    // Método sin parámetros que incrementa un minuto
    public void avanzarMinuto() {
        minuto++;
        if (minuto >= 60) {
            minuto = 0;
            hora = (hora + 1) % 24;
        }
    }
}

Los métodos sin parámetros también son comunes para implementar getters que devuelven el valor de atributos:

public class Producto {
    private String nombre;
    private double precio;
    
    // Constructor
    public Producto(String nombre, double precio) {
        this.nombre = nombre;
        this.precio = precio;
    }
    
    // Getters sin parámetros
    public String getNombre() {
        return nombre;
    }
    
    public double getPrecio() {
        return precio;
    }
}

Métodos con un parámetro

Los métodos con un solo parámetro reciben un único dato para realizar su operación. Estos métodos son ideales cuando:

  • Se necesita realizar una operación sobre un único valor
  • Se quiere modificar un atributo del objeto con un nuevo valor
  • Se busca validar o transformar un dato específico
public class Calculadora {
    // Método con un parámetro que calcula el cuadrado
    public int calcularCuadrado(int numero) {
        return numero * numero;
    }
    
    // Método con un parámetro que verifica si es primo
    public boolean esPrimo(int numero) {
        if (numero <= 1) {
            return false;
        }
        
        for (int i = 2; i <= Math.sqrt(numero); i++) {
            if (numero % i == 0) {
                return false;
            }
        }
        
        return true;
    }
}

Los setters son ejemplos típicos de métodos con un parámetro:

public class Usuario {
    private String nombre;
    private String email;
    
    // Setters con un parámetro
    public void setNombre(String nombre) {
        this.nombre = nombre;
    }
    
    public void setEmail(String email) {
        // Validación simple antes de asignar
        if (email != null && email.contains("@")) {
            this.email = email;
        } else {
            throw new IllegalArgumentException("Email inválido");
        }
    }
}

Métodos con varios parámetros

Los métodos con varios parámetros permiten operaciones más complejas que requieren varios datos de entrada. Estos métodos son útiles cuando:

  • Se necesitan varios valores para realizar un cálculo
  • Se quieren modificar múltiples atributos a la vez
  • Se busca comparar o combinar diferentes valores
public class GestorReservas {
    // Método con tres parámetros
    public boolean verificarDisponibilidad(String habitacion, LocalDate fechaInicio, LocalDate fechaFin) {
        // Verificar si la habitación está disponible en el rango de fechas
        if (fechaInicio.isAfter(fechaFin)) {
            throw new IllegalArgumentException("La fecha de inicio debe ser anterior a la fecha fin");
        }
        
        // Lógica para verificar disponibilidad
        return consultarBaseDeDatos(habitacion, fechaInicio, fechaFin);
    }
    
    // Método auxiliar (simulado)
    private boolean consultarBaseDeDatos(String habitacion, LocalDate inicio, LocalDate fin) {
        // Simulación de consulta
        return Math.random() > 0.3; // 70% de probabilidad de disponibilidad
    }
    
    // Método con cuatro parámetros
    public String generarCodigoReserva(String nombreCliente, String habitacion, 
                                      LocalDate fechaInicio, int duracion) {
        String iniciales = obtenerIniciales(nombreCliente);
        String fechaStr = fechaInicio.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        
        return iniciales + "-" + habitacion + "-" + fechaStr + "-" + duracion;
    }
    
    private String obtenerIniciales(String nombre) {
        // Lógica para obtener iniciales
        return nombre.substring(0, Math.min(2, nombre.length())).toUpperCase();
    }
}

Orden y agrupación de parámetros

Cuando se trabaja con múltiples parámetros, es importante considerar:

  • Orden lógico: Colocar los parámetros en un orden intuitivo
  • Agrupación conceptual: Parámetros relacionados deben ir juntos
  • Consistencia: Mantener el mismo orden en métodos relacionados
// Orden lógico: primero el origen, luego el destino
public double calcularDistancia(Punto origen, Punto destino) {
    return Math.sqrt(Math.pow(destino.x - origen.x, 2) + 
                    Math.pow(destino.y - origen.y, 2));
}

// Consistencia en el orden de parámetros
public void dibujarLinea(Punto inicio, Punto fin, Color color, float grosor) {
    // Código para dibujar
}

public void dibujarRectangulo(Punto esquinaSuperior, Punto esquinaInferior, 
                             Color color, float grosor) {
    // Código para dibujar
}

Parámetros con valores por defecto

Java no soporta directamente parámetros con valores por defecto como otros lenguajes, pero se pueden implementar mediante:

  • Sobrecarga de métodos: Definir múltiples versiones del mismo método con diferentes parámetros
  • Patrón Builder: Para casos con muchos parámetros opcionales

Ejemplo de sobrecarga de métodos:

public class Notificador {
    // Versión completa con todos los parámetros
    public void enviarMensaje(String destinatario, String asunto, 
                             String cuerpo, boolean alta_prioridad) {
        // Lógica para enviar mensaje
        System.out.println("Enviando a: " + destinatario);
        System.out.println("Asunto: " + asunto);
        System.out.println("Cuerpo: " + cuerpo);
        System.out.println("Prioridad alta: " + alta_prioridad);
    }
    
    // Versión con valor por defecto para prioridad (false)
    public void enviarMensaje(String destinatario, String asunto, String cuerpo) {
        enviarMensaje(destinatario, asunto, cuerpo, false);
    }
    
    // Versión con valores por defecto para asunto y prioridad
    public void enviarMensaje(String destinatario, String cuerpo) {
        enviarMensaje(destinatario, "Sin asunto", cuerpo, false);
    }
}

Validación de parámetros

Es una buena práctica validar los parámetros recibidos para garantizar que cumplen con los requisitos esperados:

public class GestorArchivos {
    public void guardarDatos(String ruta, byte[] datos) {
        // Validación de parámetros
        if (ruta == null || ruta.isEmpty()) {
            throw new IllegalArgumentException("La ruta no puede estar vacía");
        }
        
        if (datos == null || datos.length == 0) {
            throw new IllegalArgumentException("No hay datos para guardar");
        }
        
        // Lógica para guardar los datos
        try {
            Files.write(Path.of(ruta), datos);
        } catch (IOException e) {
            throw new RuntimeException("Error al guardar el archivo", e);
        }
    }
}

Parámetros inmutables

Para evitar efectos secundarios inesperados, se recomienda tratar los parámetros como inmutables:

public class ProcesadorTexto {
    // Incorrecto: modifica el parámetro
    public void procesarListaIncorrecto(List<String> palabras) {
        palabras.removeIf(palabra -> palabra.length() < 3);
        // Esto modifica la lista original
    }
    
    // Correcto: no modifica el parámetro original
    public List<String> procesarListaCorrecto(List<String> palabras) {
        // Crear una nueva lista para no modificar la original
        List<String> resultado = new ArrayList<>(palabras);
        resultado.removeIf(palabra -> palabra.length() < 3);
        return resultado;
    }
}

Parámetros de tipo primitivo vs. referencia

Es importante entender la diferencia entre pasar parámetros de tipo primitivo y de tipo referencia:

  • Los tipos primitivos (int, double, boolean, etc.) se pasan por valor
  • Los tipos de referencia (objetos, arrays) se pasan por referencia al valor del objeto
public class DemostradorParametros {
    public void demostrarPasoPorValor() {
        int numero = 10;
        System.out.println("Antes de llamar al método: " + numero);
        
        modificarPrimitivo(numero);
        System.out.println("Después de llamar al método: " + numero);
        // Imprime 10, el valor original no cambia
    }
    
    private void modificarPrimitivo(int valor) {
        valor = valor * 2;  // Esta modificación no afecta a la variable original
    }
    
    public void demostrarPasoPorReferencia() {
        List<String> frutas = new ArrayList<>();
        frutas.add("Manzana");
        System.out.println("Antes de llamar al método: " + frutas);
        
        modificarLista(frutas);
        System.out.println("Después de llamar al método: " + frutas);
        // La lista original se modifica
    }
    
    private void modificarLista(List<String> lista) {
        lista.add("Banana");  // Esta modificación afecta a la lista original
    }
}

Consideraciones de rendimiento

Cuando se trabaja con métodos que reciben muchos parámetros o parámetros de gran tamaño, es importante considerar:

  • Número de parámetros: Demasiados parámetros pueden indicar que el método hace demasiadas cosas
  • Objetos grandes: Pasar objetos grandes por referencia es más eficiente que copiarlos
  • Inmutabilidad: Los objetos inmutables son seguros para pasar por referencia
// Demasiados parámetros - podría refactorizarse
public void configurarConexion(String servidor, int puerto, String usuario, 
                              String contraseña, boolean ssl, int timeout, 
                              String protocolo, Map<String, String> opciones) {
    // Implementación
}

// Mejor enfoque: usar un objeto de configuración
public void configurarConexion(ConfiguracionConexion config) {
    // Implementación usando config.getServidor(), config.getPuerto(), etc.
}

// Clase para encapsular los parámetros
public class ConfiguracionConexion {
    private String servidor;
    private int puerto;
    // Resto de atributos, getters y setters
}

Ejemplos prácticos de uso de parámetros

Ejemplo 1: Calculadora con diferentes tipos de parámetros

public class CalculadoraAvanzada {
    // Sin parámetros - devuelve un valor constante
    public double obtenerPI() {
        return Math.PI;
    }
    
    // Un parámetro primitivo
    public double calcularRaizCuadrada(double numero) {
        if (numero < 0) {
            throw new IllegalArgumentException("No se puede calcular la raíz de un número negativo");
        }
        return Math.sqrt(numero);
    }
    
    // Dos parámetros primitivos
    public double elevarAPotencia(double base, double exponente) {
        return Math.pow(base, exponente);
    }
    
    // Un parámetro de tipo array
    public double calcularPromedio(double[] numeros) {
        if (numeros == null || numeros.length == 0) {
            throw new IllegalArgumentException("El array no puede estar vacío");
        }
        
        double suma = 0;
        for (double num : numeros) {
            suma += num;
        }
        
        return suma / numeros.length;
    }
    
    // Un parámetro de tipo objeto
    public double calcularPerimetro(Rectangulo rectangulo) {
        return 2 * (rectangulo.getAncho() + rectangulo.getAlto());
    }
}

// Clase auxiliar
class Rectangulo {
    private double ancho;
    private double alto;
    
    public Rectangulo(double ancho, double alto) {
        this.ancho = ancho;
        this.alto = alto;
    }
    
    public double getAncho() { return ancho; }
    public double getAlto() { return alto; }
}

Ejemplo 2: Procesador de texto con diferentes parámetros

public class ProcesadorTexto {
    // Sin parámetros
    public String generarTextoAleatorio() {
        String[] palabras = {"Java", "es", "un", "lenguaje", "de", "programación", "versátil"};
        StringBuilder resultado = new StringBuilder();
        
        for (int i = 0; i < 5; i++) {
            int indice = (int) (Math.random() * palabras.length);
            resultado.append(palabras[indice]).append(" ");
        }
        
        return resultado.toString().trim();
    }
    
    // Un parámetro String
    public int contarPalabras(String texto) {
        if (texto == null || texto.isEmpty()) {
            return 0;
        }
        
        return texto.split("\\s+").length;
    }
    
    // Dos parámetros: String y boolean
    public String formatearTexto(String texto, boolean mayusculas) {
        if (texto == null) {
            return "";
        }
        
        return mayusculas ? texto.toUpperCase() : texto.toLowerCase();
    }
    
    // Tres parámetros: String, String, int
    public String reemplazarYLimitar(String texto, String buscar, int longitudMaxima) {
        if (texto == null) {
            return "";
        }
        
        String resultado = texto.replace(buscar, "***");
        
        if (resultado.length() > longitudMaxima) {
            return resultado.substring(0, longitudMaxima) + "...";
        }
        
        return resultado;
    }
}

Paso por valor y paso por referencia

En Java, la forma en que se pasan los argumentos a los métodos afecta directamente al comportamiento de los programas.

Fundamentos del paso de parámetros en Java

Java utiliza exclusivamente el mecanismo de paso por valor para todos sus parámetros. Sin embargo, esto puede resultar confuso porque el comportamiento varía dependiendo de si trabajamos con tipos primitivos o tipos de referencia:

  • Para tipos primitivos: se pasa una copia del valor
  • Para tipos de referencia: se pasa una copia de la referencia (no el objeto en sí)

Esta distinción es crucial y explica por qué a veces parece que Java utiliza paso por referencia cuando en realidad no es así.

Paso por valor con tipos primitivos

Cuando se pasa un tipo primitivo (int, double, boolean, etc.) a un método, Java crea una copia del valor y la pasa al método. Cualquier modificación que se realice a este parámetro dentro del método no afectará a la variable original.

public class DemostradorPrimitivos {
    public static void main(String[] args) {
        int numero = 10;
        System.out.println("Antes de llamar al método: " + numero);
        
        modificarNumero(numero);
        
        System.out.println("Después de llamar al método: " + numero);
        // Imprime 10 - el valor original no cambia
    }
    
    public static void modificarNumero(int valor) {
        valor = valor * 2;  // Esta modificación solo afecta a la copia local
        System.out.println("Dentro del método: " + valor);  // Imprime 20
    }
}

En este ejemplo, aunque dentro del método modificarNumero() el valor se duplica, la variable original numero en el método main() permanece inalterada. Esto demuestra el paso por valor: el método recibe una copia independiente del valor original.

Paso por valor con tipos de referencia

Aquí es donde surge la confusión. Cuando se pasa un objeto (un tipo de referencia como String, ArrayList, clases personalizadas, etc.), Java pasa por valor una copia de la referencia al objeto, no el objeto en sí.

Esto significa que:

  • El método recibe una copia de la referencia que apunta al mismo objeto
  • Se pueden modificar las propiedades del objeto a través de esta referencia
  • No se puede hacer que la referencia apunte a un objeto diferente (desde la perspectiva del llamador)

Veamos un ejemplo:

public class DemostradorReferencias {
    public static void main(String[] args) {
        // Creamos un ArrayList
        ArrayList<String> frutas = new ArrayList<>();
        frutas.add("Manzana");
        System.out.println("Antes de llamar al método: " + frutas);
        
        // Llamamos al método que modifica el contenido
        modificarContenido(frutas);
        System.out.println("Después de modificar contenido: " + frutas);
        // Imprime [Manzana, Plátano] - el contenido SÍ cambia
        
        // Llamamos al método que intenta cambiar la referencia
        reemplazarReferencia(frutas);
        System.out.println("Después de reemplazar referencia: " + frutas);
        // Imprime [Manzana, Plátano] - la referencia NO cambia
    }
    
    public static void modificarContenido(ArrayList<String> lista) {
        lista.add("Plátano");  // Modifica el objeto al que apunta la referencia
    }
    
    public static void reemplazarReferencia(ArrayList<String> lista) {
        // Creamos un nuevo ArrayList y hacemos que la referencia local apunte a él
        lista = new ArrayList<>();
        lista.add("Naranja");
        lista.add("Pera");
        // Estos cambios no afectan a la referencia original en main()
    }
}

Este ejemplo ilustra la diferencia clave:

  • En modificarContenido(), se modifica el objeto al que apunta la referencia, y estos cambios son visibles fuera del método.
  • En reemplazarReferencia(), se intenta hacer que la referencia apunte a un nuevo objeto, pero este cambio solo afecta a la copia local de la referencia, no a la referencia original en main().

Casos especiales y consideraciones

El caso de String

String en Java es inmutable, lo que significa que no se puede modificar una vez creado. Esto puede llevar a confusión:

public static void main(String[] args) {
    String nombre = "Juan";
    modificarString(nombre);
    System.out.println(nombre);  // Imprime "Juan", no "Juan Pérez"
}

public static void modificarString(String texto) {
    texto = texto + " Pérez";  // Crea un nuevo objeto String
}

Aunque String es un tipo de referencia, su inmutabilidad hace que se comporte de manera similar a los tipos primitivos en este contexto.

Arrays como caso especial

Los arrays en Java son objetos, por lo que se pasan por valor la referencia:

public static void main(String[] args) {
    int[] numeros = {1, 2, 3};
    
    modificarContenidoArray(numeros);
    System.out.println(Arrays.toString(numeros));  // Imprime [10, 2, 3]
    
    reemplazarArray(numeros);
    System.out.println(Arrays.toString(numeros));  // Sigue imprimiendo [10, 2, 3]
}

public static void modificarContenidoArray(int[] array) {
    array[0] = 10;  // Modifica el contenido del array
}

public static void reemplazarArray(int[] array) {
    array = new int[]{4, 5, 6};  // Solo cambia la referencia local
}

Objetos inmutables

Java tiene varios tipos de referencia inmutables además de String, como Integer, Double, BigDecimal, etc. Estos objetos, una vez creados, no pueden cambiar su estado interno:

public static void main(String[] args) {
    Integer numero = 10;
    modificarInteger(numero);
    System.out.println(numero);  // Imprime 10, no 20
}

public static void modificarInteger(Integer valor) {
    valor = valor + 10;  // Crea un nuevo objeto Integer
}

Simulando paso por referencia en Java

Aunque Java no tiene paso por referencia verdadero, se pueden simular algunos de sus efectos:

Usando objetos contenedores

Se puede crear una clase simple que contenga el valor que queremos modificar:

public class Contenedor<T> {
    private T valor;
    
    public Contenedor(T valor) {
        this.valor = valor;
    }
    
    public T getValor() {
        return valor;
    }
    
    public void setValor(T valor) {
        this.valor = valor;
    }
}

public static void main(String[] args) {
    Contenedor<Integer> contador = new Contenedor<>(10);
    duplicarValor(contador);
    System.out.println(contador.getValor());  // Imprime 20
}

public static void duplicarValor(Contenedor<Integer> cont) {
    cont.setValor(cont.getValor() * 2);
}

Usando arrays de un solo elemento

Otra técnica común es usar un array de un solo elemento:

public static void main(String[] args) {
    int[] contador = {10};
    duplicarValor(contador);
    System.out.println(contador[0]);  // Imprime 20
}

public static void duplicarValor(int[] cont) {
    cont[0] = cont[0] * 2;
}

Usando objetos mutables

Para tipos más complejos, se pueden usar clases propias con métodos que modifiquen su estado:

public class Contador {
    private int valor;
    
    public Contador(int valorInicial) {
        this.valor = valorInicial;
    }
    
    public int getValor() {
        return valor;
    }
    
    public void incrementar() {
        valor++;
    }
    
    public void duplicar() {
        valor *= 2;
    }
}

public static void main(String[] args) {
    Contador c = new Contador(10);
    procesarContador(c);
    System.out.println(c.getValor());  // Imprime 21
}

public static void procesarContador(Contador contador) {
    contador.duplicar();
    contador.incrementar();
}

Implicaciones y buenas prácticas

Rendimiento

El paso por valor puede tener implicaciones de rendimiento:

  • Para tipos primitivos: la copia es generalmente rápida ya que son valores pequeños
  • Para tipos de referencia: solo se copia la referencia (un valor pequeño), no el objeto completo

Para objetos muy grandes, no hay que preocuparse por el costo de copiar todo el objeto, ya que solo se copia la referencia.

Inmutabilidad y efectos secundarios

Entender el paso por valor/referencia es crucial para gestionar los efectos secundarios en nuestro código:

// Enfoque con efectos secundarios
public void procesarDatos(List<Cliente> clientes) {
    clientes.removeIf(cliente -> cliente.getSaldo() < 0);
    // La lista original se modifica
}

// Enfoque sin efectos secundarios (más predecible)
public List<Cliente> filtrarClientesConSaldoPositivo(List<Cliente> clientes) {
    List<Cliente> resultado = new ArrayList<>(clientes);
    resultado.removeIf(cliente -> cliente.getSaldo() < 0);
    return resultado;
    // La lista original no se modifica
}

El segundo enfoque es generalmente preferible porque:

  • Es más predecible y fácil de razonar sobre su comportamiento
  • Facilita las pruebas unitarias
  • Reduce los errores causados por modificaciones inesperadas

Documentación clara

Es importante documentar claramente cuando un método modifica sus parámetros:

/**
 * Ordena la lista proporcionada en orden ascendente.
 * 
 * @param lista La lista a ordenar (será modificada por este método)
 */
public void ordenarLista(List<Integer> lista) {
    Collections.sort(lista);
}

Ejemplos prácticos de uso

Ejemplo 1: Procesamiento de datos financieros

public class ProcesadorFinanciero {
    /**
     * Calcula el total de una factura añadiendo impuestos.
     * Este método no modifica el objeto original.
     */
    public Factura calcularTotalConImpuestos(Factura factura, double tasaImpuesto) {
        // Creamos una nueva factura para no modificar la original
        Factura resultado = new Factura(factura);
        
        double subtotal = factura.getSubtotal();
        double impuestos = subtotal * tasaImpuesto;
        
        resultado.setImpuestos(impuestos);
        resultado.setTotal(subtotal + impuestos);
        
        return resultado;
    }
    
    /**
     * Aplica un descuento a todos los ítems de la factura.
     * Este método modifica el objeto factura original.
     */
    public void aplicarDescuento(Factura factura, double porcentajeDescuento) {
        for (Item item : factura.getItems()) {
            double precioConDescuento = item.getPrecio() * (1 - porcentajeDescuento/100);
            item.setPrecio(precioConDescuento);
        }
        
        // Recalculamos el subtotal
        double nuevoSubtotal = factura.getItems().stream()
                              .mapToDouble(item -> item.getPrecio() * item.getCantidad())
                              .sum();
        
        factura.setSubtotal(nuevoSubtotal);
    }
}

class Factura {
    private List<Item> items;
    private double subtotal;
    private double impuestos;
    private double total;
    
    // Constructor de copia
    public Factura(Factura original) {
        this.items = new ArrayList<>(original.items);
        this.subtotal = original.subtotal;
        this.impuestos = original.impuestos;
        this.total = original.total;
    }
    
    // Resto de la implementación...
}

class Item {
    private String nombre;
    private double precio;
    private int cantidad;
    
    // Getters y setters...
}

Ejemplo 2: Manipulación de imágenes

public class ProcesadorImagen {
    /**
     * Aplica un filtro de escala de grises a la imagen.
     * Este método modifica la imagen original.
     */
    public void aplicarFiltroGrises(int[][] pixeles) {
        int altura = pixeles.length;
        int anchura = pixeles[0].length;
        
        for (int y = 0; y < altura; y++) {
            for (int x = 0; x < anchura; x++) {
                int rgb = pixeles[y][x];
                
                int r = (rgb >> 16) & 0xFF;
                int g = (rgb >> 8) & 0xFF;
                int b = rgb & 0xFF;
                
                // Fórmula para convertir a escala de grises
                int gris = (int)(0.299 * r + 0.587 * g + 0.114 * b);
                
                // Crear nuevo valor RGB con el mismo valor para R, G y B
                int nuevoRgb = (gris << 16) | (gris << 8) | gris;
                
                pixeles[y][x] = nuevoRgb;
            }
        }
    }
    
    /**
     * Crea una versión redimensionada de la imagen original.
     * Este método no modifica la imagen original.
     */
    public int[][] redimensionar(int[][] imagenOriginal, int nuevaAnchura, int nuevaAltura) {
        int[][] nuevaImagen = new int[nuevaAltura][nuevaAnchura];
        
        int alturaOriginal = imagenOriginal.length;
        int anchuraOriginal = imagenOriginal[0].length;
        
        for (int y = 0; y < nuevaAltura; y++) {
            for (int x = 0; x < nuevaAnchura; x++) {
                // Cálculo simple de correspondencia de píxeles (interpolación más básica)
                int xOriginal = x * anchuraOriginal / nuevaAnchura;
                int yOriginal = y * alturaOriginal / nuevaAltura;
                
                nuevaImagen[y][x] = imagenOriginal[yOriginal][xOriginal];
            }
        }
        
        return nuevaImagen;
    }
}

Resumen de conceptos clave

  • Java utiliza exclusivamente paso por valor para todos los tipos de datos.
  • Para tipos primitivos, se pasa una copia del valor, y las modificaciones dentro del método no afectan a la variable original.
  • Para tipos de referencia, se pasa una copia de la referencia (no el objeto), lo que permite modificar el objeto pero no cambiar a qué objeto apunta la referencia original.
  • Los objetos inmutables como String no pueden ser modificados, por lo que se comportan de manera similar a los tipos primitivos.
  • Se pueden simular algunos aspectos del paso por referencia usando objetos contenedores, arrays de un elemento o clases mutables.
  • Es una buena práctica evitar modificar los parámetros de entrada cuando sea posible, para crear código más predecible y fácil de mantener.

Varargs

Los varargs (argumentos variables) son una característica de Java que permite a los métodos aceptar un número variable de argumentos del mismo tipo. Esta funcionalidad, introducida en Java 5, simplifica el código al eliminar la necesidad de crear múltiples versiones sobrecargadas de un método o pasar arrays explícitamente.

Sintaxis y declaración

Para declarar un método que acepta varargs, se utiliza la sintaxis con tres puntos (...) después del tipo de dato:

public void metodo(TipoDato... parametro) {
    // Implementación
}

Dentro del método, el parámetro varargs se trata como un array del tipo especificado:

public void imprimirNumeros(int... numeros) {
    for (int numero : numeros) {
        System.out.print(numero + " ");
    }
    System.out.println();
}

Este método puede ser invocado con cualquier número de argumentos enteros:

imprimirNumeros();              // No imprime nada
imprimirNumeros(5);             // Imprime: 5
imprimirNumeros(1, 2, 3);       // Imprime: 1 2 3
imprimirNumeros(7, 8, 9, 10);   // Imprime: 7 8 9 10

Reglas y restricciones

Al trabajar con varargs, es importante conocer estas reglas:

  • Un método puede tener solo un parámetro varargs.
  • El parámetro varargs debe ser el último en la lista de parámetros.
  • Técnicamente, se puede pasar un array en lugar de argumentos individuales.
// Correcto: varargs como único parámetro
public void metodo1(String... textos) { }

// Correcto: varargs como último parámetro
public void metodo2(int id, String nombre, double... valores) { }

// Incorrecto: varargs no es el último parámetro
public void metodo3(String... textos, int numero) { }

// Incorrecto: múltiples parámetros varargs
public void metodo4(int... numeros, String... textos) { }

Uso con otros tipos de parámetros

Los varargs se pueden combinar con parámetros regulares, siempre que el parámetro varargs sea el último:

public void registrarUsuario(String nombre, int edad, String... intereses) {
    System.out.println("Usuario: " + nombre + ", Edad: " + edad);
    System.out.println("Intereses: " + String.join(", ", intereses));
}

Este método se puede invocar de varias formas:

registrarUsuario("Ana", 28);  // Sin intereses
registrarUsuario("Carlos", 35, "Música");  // Un interés
registrarUsuario("Elena", 42, "Deportes", "Viajes", "Fotografía");  // Varios intereses

Varargs y sobrecarga de métodos

Cuando se trabaja con sobrecarga de métodos y varargs, pueden surgir ambigüedades. Java selecciona el método más específico:

public class DemoVarargs {
    // Método con un parámetro específico
    public void mostrar(String texto) {
        System.out.println("Método con String: " + texto);
    }
    
    // Método con varargs
    public void mostrar(String... textos) {
        System.out.println("Método con varargs: " + Arrays.toString(textos));
    }
    
    public static void main(String[] args) {
        DemoVarargs demo = new DemoVarargs();
        
        demo.mostrar("Hola");  // Llama al método con un parámetro String
        demo.mostrar("Hola", "Mundo");  // Llama al método con varargs
        demo.mostrar();  // Llama al método con varargs (array vacío)
    }
}

En este ejemplo, cuando se llama a mostrar("Hola"), Java elige el método más específico, que es el que toma un único String, no el método varargs.

Paso de arrays a métodos varargs

Se puede pasar un array directamente a un método varargs:

public void sumarNumeros(int... numeros) {
    int suma = 0;
    for (int num : numeros) {
        suma += num;
    }
    System.out.println("Suma: " + suma);
}

// Uso con argumentos individuales
sumarNumeros(1, 2, 3);  // Suma: 6

// Uso con un array
int[] miArray = {4, 5, 6};
sumarNumeros(miArray);  // Suma: 15

Sin embargo, hay que tener cuidado con las ambigüedades cuando se mezclan arrays y varargs en sobrecarga de métodos:

// Este método toma un array como parámetro
public void procesar(int[] numeros) {
    System.out.println("Método con array");
}

// Este método usa varargs
public void procesar(int... numeros) {
    System.out.println("Método con varargs");
}

// ¡Error de compilación! Ambigüedad entre los dos métodos

En este caso, ambos métodos tienen la misma firma desde la perspectiva del compilador, lo que causa un error.

Casos de uso prácticos

Métodos de utilidad para concatenación

public class StringUtils {
    public static String concatenar(String delimitador, String... textos) {
        if (textos.length == 0) {
            return "";
        }
        
        StringBuilder resultado = new StringBuilder(textos[0]);
        
        for (int i = 1; i < textos.length; i++) {
            resultado.append(delimitador).append(textos[i]);
        }
        
        return resultado.toString();
    }
}

// Uso
String resultado = StringUtils.concatenar(", ", "Java", "Python", "C++");
System.out.println(resultado);  // Java, Python, C++

Operaciones matemáticas flexibles

public class CalculadoraFlexible {
    public static int sumar(int... numeros) {
        int resultado = 0;
        for (int num : numeros) {
            resultado += num;
        }
        return resultado;
    }
    
    public static int maximo(int... numeros) {
        if (numeros.length == 0) {
            throw new IllegalArgumentException("No se proporcionaron números");
        }
        
        int max = numeros[0];
        for (int i = 1; i < numeros.length; i++) {
            if (numeros[i] > max) {
                max = numeros[i];
            }
        }
        
        return max;
    }
}

// Uso
int suma = CalculadoraFlexible.sumar(5, 10, 15, 20);  // 50
int maximo = CalculadoraFlexible.maximo(8, 3, 12, 7);  // 12

Registro y depuración

public class Logger {
    public enum Nivel { INFO, WARNING, ERROR }
    
    public static void log(Nivel nivel, String mensaje, Object... detalles) {
        StringBuilder sb = new StringBuilder();
        sb.append("[").append(nivel).append("] ");
        sb.append(mensaje);
        
        if (detalles.length > 0) {
            sb.append(" - Detalles: ");
            for (int i = 0; i < detalles.length; i++) {
                if (i > 0) {
                    sb.append(", ");
                }
                sb.append(detalles[i]);
            }
        }
        
        System.out.println(sb.toString());
    }
}

// Uso
Logger.log(Logger.Nivel.INFO, "Aplicación iniciada");
Logger.log(Logger.Nivel.WARNING, "Espacio en disco bajo", "Disponible: 15%", "Ruta: /var/logs");
Logger.log(Logger.Nivel.ERROR, "Conexión fallida", "Servidor: db.ejemplo.com", "Puerto: 5432", "Timeout: 30s");

Implementación de formato personalizado

public class Formateador {
    public static String formatear(String formato, Object... args) {
        // Versión simplificada de String.format()
        StringBuilder resultado = new StringBuilder();
        int argIndex = 0;
        
        for (int i = 0; i < formato.length(); i++) {
            char c = formato.charAt(i);
            
            if (c == '%' && i + 1 < formato.length() && formato.charAt(i + 1) == 's') {
                if (argIndex < args.length) {
                    resultado.append(args[argIndex]);
                    argIndex++;
                } else {
                    resultado.append("%s");  // No hay suficientes argumentos
                }
                i++;  // Saltar el siguiente carácter ('s')
            } else {
                resultado.append(c);
            }
        }
        
        return resultado.toString();
    }
}

// Uso
String mensaje = Formateador.formatear("Hola %s, tienes %s mensajes nuevos.", "María", 5);
System.out.println(mensaje);  // Hola María, tienes 5 mensajes nuevos.

Consideraciones de rendimiento

Aunque los varargs son convenientes, hay algunas consideraciones de rendimiento a tener en cuenta:

  • Creación de arrays: Cada llamada a un método varargs crea un nuevo array, lo que implica una pequeña sobrecarga.
  • Métodos críticos: Para métodos que se llaman con mucha frecuencia en bucles ajustados, considerar versiones sobrecargadas específicas para los casos más comunes.
// Versión con varargs (conveniente pero menos eficiente para casos simples)
public int sumar(int... numeros) {
    int suma = 0;
    for (int n : numeros) suma += n;
    return suma;
}

// Versiones sobrecargadas para casos comunes (más eficientes)
public int sumar(int a, int b) {
    return a + b;
}

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

// La versión varargs se usa para 4 o más argumentos

Varargs con genéricos

Al combinar varargs con genéricos, se debe tener cuidado con las advertencias de seguridad de tipos:

// Genera una advertencia de "unsafe" debido a la combinación de varargs y genéricos
public <T> List<T> crearLista(T... elementos) {
    return Arrays.asList(elementos);
}

Para suprimir esta advertencia, se puede usar la anotación @SafeVarargs:

@SafeVarargs
public final <T> List<T> crearLista(T... elementos) {
    return Arrays.asList(elementos);
}

Esta anotación solo se puede aplicar a métodos que:

  • No pueden ser sobrescritos (métodos private, static, final o constructores)
  • No modifican el array varargs

Ejemplos avanzados

Implementación de un constructor de consultas SQL

public class ConsultaSQL {
    private String tabla;
    private List<String> condiciones = new ArrayList<>();
    
    public ConsultaSQL(String tabla) {
        this.tabla = tabla;
    }
    
    public ConsultaSQL donde(String condicion, Object... parametros) {
        String condicionFormateada = condicion;
        
        for (Object param : parametros) {
            // Reemplazar el primer ? con el valor del parámetro
            String valorSeguro = param instanceof String ? 
                "'" + ((String) param).replace("'", "''") + "'" : 
                String.valueOf(param);
                
            condicionFormateada = condicionFormateada.replaceFirst("\\?", valorSeguro);
        }
        
        condiciones.add(condicionFormateada);
        return this;
    }
    
    public String construir() {
        StringBuilder sql = new StringBuilder("SELECT * FROM ");
        sql.append(tabla);
        
        if (!condiciones.isEmpty()) {
            sql.append(" WHERE ");
            sql.append(String.join(" AND ", condiciones));
        }
        
        return sql.toString();
    }
}

// Uso
ConsultaSQL consulta = new ConsultaSQL("usuarios")
    .donde("edad > ?", 18)
    .donde("ciudad = ?", "Madrid")
    .donde("activo = ?", true);

String sql = consulta.construir();
System.out.println(sql);
// SELECT * FROM usuarios WHERE edad > 18 AND ciudad = 'Madrid' AND activo = true

Implementación de un sistema de eventos

public class SistemaEventos {
    // Interfaz funcional para manejar eventos
    @FunctionalInterface
    public interface ManejadorEvento {
        void manejar(String evento, Object... datos);
    }
    
    private Map<String, List<ManejadorEvento>> manejadores = new HashMap<>();
    
    // Registrar un manejador para un tipo de evento
    public void registrar(String tipoEvento, ManejadorEvento manejador) {
        manejadores.computeIfAbsent(tipoEvento, k -> new ArrayList<>())
                  .add(manejador);
    }
    
    // Disparar un evento con datos variables
    public void disparar(String tipoEvento, Object... datos) {
        List<ManejadorEvento> eventosManejadores = manejadores.get(tipoEvento);
        
        if (eventosManejadores != null) {
            for (ManejadorEvento manejador : eventosManejadores) {
                manejador.manejar(tipoEvento, datos);
            }
        }
    }
}

// Uso
SistemaEventos eventos = new SistemaEventos();

// Registrar manejadores
eventos.registrar("login", (evento, datos) -> {
    System.out.println("Usuario " + datos[0] + " ha iniciado sesión desde " + datos[1]);
});

eventos.registrar("compra", (evento, datos) -> {
    System.out.println("Nueva compra: Producto=" + datos[0] + ", Cantidad=" + datos[1] + 
                      ", Total=" + datos[2]);
});

// Disparar eventos
eventos.disparar("login", "usuario123", "192.168.1.5");
eventos.disparar("compra", "Laptop", 1, 1299.99);

Buenas prácticas con varargs

  • Usar varargs para simplificar APIs: Cuando un método puede aceptar un número variable de argumentos del mismo tipo, los varargs hacen que la API sea más limpia.
  • Validar el contenido: Aunque se permita un número variable de argumentos, a menudo se necesita validar que haya al menos uno o que cumplan ciertas condiciones.
public double calcularPromedio(double... numeros) {
    if (numeros.length == 0) {
        throw new IllegalArgumentException("Se requiere al menos un número");
    }
    
    double suma = 0;
    for (double num : numeros) {
        suma += num;
    }
    
    return suma / numeros.length;
}
  • Documentar: Indicar en la documentación que el método acepta un número variable de argumentos y cualquier restricción aplicable.
/**
* Encuentra el valor máximo entre los números proporcionados.
* 
* @param numeros Uno o más números entre los que buscar el máximo
* @return El valor máximo encontrado
* @throws IllegalArgumentException Si no se proporciona ningún número
*/
public static int maximo(int... numeros) {
    // Implementación
}
  • Considerar versiones sobrecargadas para casos comunes: Para métodos de uso frecuente, proporcionar versiones específicas para 1, 2 o 3 argumentos puede mejorar el rendimiento.
  • Evitar modificar el array varargs: Aunque técnicamente es posible, modificar el array varargs puede llevar a comportamientos inesperados si el llamador reutiliza el array.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende Java online

Ejercicios de esta lección Funciones

Evalúa tus conocimientos de esta lección Funciones con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Clases abstractas

Test

Streams: reduce()

Test

Streams: flatMap()

Test

Llamada y sobrecarga de funciones

Puzzle

Métodos referenciados

Test

Métodos de la clase String

Código

Representación de Fecha

Puzzle

Operadores lógicos

Test

Tipos de datos

Código

Estructuras de iteración

Puzzle

Streams: forEach()

Test

Objetos

Puzzle

Funciones lambda

Test

Uso de Scanner

Puzzle

CRUD en Java de modelo Customer sobre un ArrayList

Proyecto

Tipos de variables

Puzzle

Streams: collect()

Puzzle

Operadores aritméticos

Puzzle

Interfaz funcional Consumer

Test

API java.nio 2

Puzzle

API Optional

Test

Interfaz funcional Function

Test

Encapsulación

Test

Interfaces

Código

Uso de API Optional

Puzzle

Representación de Hora

Test

Herencia básica

Test

Clases y objetos

Código

Interfaz funcional Supplier

Puzzle

HashMap

Puzzle

Sobrecarga de métodos

Test

Polimorfismo de tiempo de ejecución

Puzzle

OOP en Java

Proyecto

Creación de Streams

Test

Streams: min max

Puzzle

Métodos avanzados de la clase String

Puzzle

Polimorfismo de tiempo de compilación

Test

Excepciones

Puzzle

Herencia avanzada

Puzzle

Estructuras de selección

Test

Uso de interfaces

Test

HashSet

Test

Objeto Scanner

Test

Streams: filter()

Puzzle

Operaciones de Streams

Puzzle

Interfaz funcional Predicate

Puzzle

Streams: sorted()

Test

Configuración de entorno

Test

CRUD en Java de modelo Customer sobre un HashMap

Proyecto

Uso de variables

Test

Clases

Test

Streams: distinct()

Puzzle

Streams: count()

Test

ArrayList

Test

Datos de referencia

Test

Interfaces funcionales

Puzzle

Métodos básicos de la clase String

Test

Instalación

Test

Funciones

Código

Estructuras de control

Código

Herencia de clases

Código

Streams: map()

Puzzle

Funciones y encapsulamiento

Test

Streams: match

Test

Gestión de errores y excepciones

Código

Datos primitivos

Puzzle

Todas las lecciones de Java

Accede a todas las lecciones de Java y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Instalación De Java

Introducción Y Entorno

Configuración De Entorno Java

Introducción Y Entorno

Ecosistema Jakarta Ee De Java

Introducción Y Entorno

Tipos De Datos

Sintaxis

Variables

Sintaxis

Operadores

Sintaxis

Estructuras De Control

Sintaxis

Funciones

Sintaxis

Excepciones

Programación Orientada A Objetos

Clases Y Objetos

Programación Orientada A Objetos

Encapsulación

Programación Orientada A Objetos

Herencia

Programación Orientada A Objetos

Clases Abstractas

Programación Orientada A Objetos

Interfaces

Programación Orientada A Objetos

Sobrecarga De Métodos

Programación Orientada A Objetos

Polimorfismo

Programación Orientada A Objetos

La Clase Scanner

Programación Orientada A Objetos

Métodos De La Clase String

Programación Orientada A Objetos

Listas

Framework Collections

Conjuntos

Framework Collections

Mapas

Framework Collections

Funciones Lambda

Programación Funcional

Interfaz Funcional Consumer

Programación Funcional

Interfaz Funcional Predicate

Programación Funcional

Interfaz Funcional Supplier

Programación Funcional

Interfaz Funcional Function

Programación Funcional

Métodos Referenciados

Programación Funcional

Creación De Streams

Programación Funcional

Operaciones Intermedias Con Streams: Map()

Programación Funcional

Operaciones Intermedias Con Streams: Filter()

Programación Funcional

Operaciones Intermedias Con Streams: Distinct()

Programación Funcional

Operaciones Finales Con Streams: Collect()

Programación Funcional

Operaciones Finales Con Streams: Min Max

Programación Funcional

Operaciones Intermedias Con Streams: Flatmap()

Programación Funcional

Operaciones Intermedias Con Streams: Sorted()

Programación Funcional

Operaciones Finales Con Streams: Reduce()

Programación Funcional

Operaciones Finales Con Streams: Foreach()

Programación Funcional

Operaciones Finales Con Streams: Count()

Programación Funcional

Operaciones Finales Con Streams: Match

Programación Funcional

Api Optional

Programación Funcional

Api Java.nio 2

Entrada Y Salida (Io)

Api Java.time

Api Java.time

Accede GRATIS a Java y certifícate

Certificados de superación de Java

Supera todos los ejercicios de programación del curso de Java y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  • Identificar las partes componentes de un método en Java: modificadores, tipo de retorno, nombre del método, parámetros, excepciones, y cuerpo del método
  • Comprender cómo los modificadores de acceso influyen en la visibilidad de los métodos desde distintas clases y paquetes
  • Diferenciar entre tipos de retorno y cuándo usar el tipo void
  • Aprender cómo definir y usar los parámetros en los métodos
  • Implementar el manejo de excepciones en las definiciones de métodos
  • Aplicar correctamente la sentencia return para finalizar un método y devolver un valor
  • Escribir ejemplos prácticos que implementen la buena práctica de diseño de métodos según el principio de responsabilidad única