Artículo

Programación funcional en Java: guía completa

Aprende programación funcional en Java con lambdas, streams, Optional y referencias a métodos. Guía con ejemplos de código listos para ejecutar en CodeJa.

Programación funcional en Java: guía completa

La programación funcional en Java se apoya en lambdas, streams, Optional y referencias a métodos para escribir código declarativo y menos propenso a errores. Los streams procesan colecciones con operaciones como filter, map y reduce sin modificar los datos originales. Java la soporta desde la versión 8.

Programación funcional en Java: guía completa

Última actualización: agosto 2026

Contenido Rápido

  • Datos inmutables y funciones sin efectos secundarios
  • Lambdas: funciones anónimas que se pasan como argumentos
  • Streams: procesa colecciones con filter, map y reduce
  • Optional elimina los chequeos defensivos de null
  • Referencias a métodos: lambdas aún más cortas
  • Java lo soporta nativamente desde la versión 8

La programación funcional no es un lenguaje nuevo ni una librería: es una forma de pensar sobre cómo se transforman los datos. En vez de escribir pasos que modifican variables una a una, describes qué quieres obtener y el código hace el trabajo. Java adoptó el paradigma en la versión 8 con lambdas, el API Stream y Optional.

Para un programador acostumbrado al estilo imperativo, el cambio puede parecer brusco. Vale la pena hacerlo: el código funcional es más corto, más legible y más fácil de testear, porque cada función devuelve un resultado sin tocar estado compartido. Esta guía recorre los cuatro pilares con ejemplos que puedes ejecutar.

Qué es la programación funcional en Java

Un programa imperativo describe cómo hacer las cosas: crea una variable, recorre la lista, comprueba una condición, actualiza el valor. Un programa funcional describe qué quiere obtener y compone funciones para lograrlo. Se apoya en tres reglas: datos inmutables, funciones sin efectos secundarios y funciones que se pasan como argumentos.

En la práctica, la programación funcional en Java no es un todo o nada: convive con el resto del lenguaje. Puedes usar streams en un método y bucles tradicionales en otro sin conflicto. Lo importante es saber cuándo cada herramienta aporta más.

Lambdas: las funciones como valores

Una lambda es una función anónima que puedes guardar en una variable o pasar como argumento. Se escribe con la sintaxis (parámetros) -> cuerpo y su tipo lo define una interfaz funcional, una interfaz con un único método abstracto. Las tienes explicadas en interfaces funcionales en Java 8 y en lambdas en Java 8.

El ejemplo clásico es BiFunction, que recibe dos argumentos y devuelve un resultado:

import java.util.function.BiFunction;

public class CalculadoraFuncional {

    public static void main(String[] args) {
        BiFunction<Integer, Integer, Integer> sumar = (a, b) -> a + b;
        BiFunction<Integer, Integer, Integer> multiplicar = (a, b) -> a * b;

        System.out.println("Suma: " + sumar.apply(4, 6));
        System.out.println("Producto: " + multiplicar.apply(4, 6));
    }
}

Prueba este código en el Java Playground de CodeJa: compila y ejecuta Java directamente en el navegador, sin instalar nada.

Fíjate en que la lambda (a, b) -> a + b no declara tipos: el compilador los infiere de la interfaz funcional. Tampoco hay estado mutable ni efectos secundarios, así que la misma entrada produce siempre el mismo resultado. El paquete java.util.function define las interfaces funcionales estándar, como Function, Predicate y Consumer; su referencia oficial está en el javadoc de java.util.function.

El API Stream: procesar colecciones sin tocar los datos

El API Stream es el corazón de la programación funcional en Java. Un stream es una secuencia de elementos procesada con operaciones encadenadas: filter selecciona, map transforma y reduce combina. Nunca modifica la colección original: cada operación devuelve un nuevo stream y la fuente queda intacta. Para la base, lee qué es un Java Stream y streams en Java 8.

import java.util.List;
import java.util.stream.Collectors;

public class EjemploStream {

    public static void main(String[] args) {
        List<String> nombres = List.of("Ana", "Bruno", "Carla", "David", "Elena");

        List<String> resultado = nombres.stream()
                .filter(nombre -> nombre.length() > 4)
                .map(String::toUpperCase)
                .sorted()
                .collect(Collectors.toList());

        System.out.println(resultado);
    }
}

El pipeline se lee como una frase: coge la lista, deja los nombres con más de cuatro letras, pásalos a mayúsculas, ordénalos y recógelos. Frente al bucle con lista auxiliar, el código declarativo expresa la intención directamente. Tenemos guías de filter y Predicates, de map con streams y de cómo convertir bucles for a streams.

Optional: eliminar los chequeos de null

El null es una de las mayores fuentes de errores en Java, y el estilo funcional lo aborda con Optional, un contenedor que puede estar vacío o contener un valor. En vez de comprobar con un if, encadenas operaciones y decides qué ocurre cuando no hay valor al final. La guía completa está en Optional en Java 8.

import java.util.Optional;

public class EjemploOptional {

    public static void main(String[] args) {
        Optional<String> correo = Optional.ofNullable(null);

        String resultado = correo
                .map(String::trim)
                .filter(valor -> valor.contains("@"))
                .orElse("sin-correo");

        System.out.println(resultado);
    }
}

El ejemplo simula un correo que no existe: map y filter se saltan los pasos cuando el Optional está vacío, y orElse devuelve el valor por defecto. El resultado es código sin un solo if de null. Optional no sustituye todos los chequeos del proyecto, pero la posibilidad de que no haya valor queda explícita en el tipo de retorno.

Referencias a métodos: lambdas aún más cortas

Una referencia a método es una lambda más compacta: en vez de escribir (s) -> s.toUpperCase(), escribes String::toUpperCase. El código gana legibilidad cuando ya existe una función que hace lo que necesitas. Están explicadas en referencias a métodos en Java.

import java.util.List;

public class EjemploReduce {

    public static void main(String[] args) {
        List<Integer> precios = List.of(1200, 800, 2400, 1500);

        int total = precios.stream()
                .reduce(0, Integer::sum);

        int mayor = precios.stream()
                .reduce(Integer.MIN_VALUE, Math::max);

        System.out.println("Total: " + total);
        System.out.println("Mayor: " + mayor);
    }
}

Aquí reduce combina todos los elementos en un único valor: con Integer::sum acumulas la suma y con Math::max te quedas con el mayor, sin un solo bucle. Es la operación de plegado que da nombre al paradigma y la más difícil de interiorizar, porque sustituye al acumulador que aprendimos con bucles.

Tabla de comparación: programación imperativa vs funcional

CaracterísticaProgramación imperativaProgramación funcional
Paradigma de datosMutableInmutable
EstiloBucles y asignacionesStreams y funciones
TestingEstado compartidoFunciones puras
Riesgo típicoEfectos secundariosCurva de aprendizaje
Uso típicoAlgoritmos de bajo nivelProcesamiento de colecciones

La tabla resume la diferencia de mentalidad: no es qué hace cada estilo, sino cómo organiza los datos y el flujo. La programación funcional no es superior en todos los casos, pero en el procesamiento de colecciones gana en claridad y seguridad.

Cuándo usar programación funcional en Java

El estilo funcional brilla en el procesamiento de colecciones y en pipelines de datos. También es la base de la concurrencia moderna: las funciones puras se ejecutan en paralelo sin riesgos porque no comparten estado, una ventaja que aprovechan java.util.concurrent y los hilos en Java. Para evitar fugas de memoria, revisa ThreadLocal en Java.

Hay casos donde el bucle sigue siendo mejor: cuando necesitas romper la iteración a mitad, trabajas con índices concretos o el rendimiento extremo importa más que la legibilidad. Y cuando tengas que combinar varias fuentes de datos, tienes nuestra guía para fusionar streams en Java.

La programación funcional separa al programador junior del senior, y por eso forma parte del curso avanzado de Java de CodeJa y de la ruta de junior a senior. Practica: escribe tus propios pipelines, refactoriza bucles antiguos y prueba cada ejemplo en el Java Playground.

Preguntas Frecuentes

¿Qué es la programación funcional en Java?

Es un paradigma que trata las funciones como valores y evita el estado mutable. En Java se apoya en lambdas, el API Stream y Optional. Está disponible de forma nativa desde la versión 8.

¿Java es un lenguaje funcional?

No exactamente: Java es multi-paradigma, con la orientación a objetos como base. Soporta programación funcional desde Java 8, pero no impone el paradigma. Puedes combinar ambos estilos en el mismo proyecto.

¿Qué es una lambda en Java?

Una lambda es una función anónima que se pasa como argumento o se guarda en una variable. Su sintaxis es (parámetros) -> cuerpo y su tipo lo define una interfaz funcional. Es la pieza que permite tratar las funciones como ciudadanos de primera clase.

¿Cuál es la diferencia entre map y filter en streams?

filter selecciona los elementos que cumplen una condición y devuelve solo los que la superan. map transforma cada elemento y devuelve el resultado de aplicar la función a todos. Por eso filter puede reducir el número de elementos y map lo mantiene, cambiando su valor.

¿Qué es una referencia a método?

Es una forma abreviada de escribir una lambda cuando ya existe un método que hace lo que necesitas. En vez de (s) -> s.toUpperCase() escribes String::toUpperCase. El compilador la trata como una lambda equivalente.

Artículos Relacionados

programacion funcional lambdas streams java 8 funciones puras

Newsletter Semanal de Java

Cada viernes recibe lo más nuevo del ecosistema Java: frameworks, herramientas y mejores prácticas.

🔒 Sin spam. Cancela cuando quieras.

Compartir artículo