Uso de Expresiones Lambda en Java: Sintaxis y Aplicaciones

Clase 18 de 39Curso de Programación Funcional con Java SE

Contenido del curso

Functional Programming en Java

Optional y Streams: Datos mas interesantes

Todo junto: Proyecto Job-search

Resumen

Cuando trabajas con programación funcional en Java, llega un momento en el que almacenar cada función en una variable resulta innecesario. Ahí es donde las lambdas cobran protagonismo: son funciones anónimas que existen solo en el fragmento de código donde se usan, sin nombre ni referencia posterior. Comprender su sintaxis y cuándo aplicarla es fundamental para escribir código limpio y expresivo.

¿Qué son las lambdas y por qué usarlas?

Una lambda es una función que no está almacenada en ningún lugar del código [01:10]. Si defines una operación dentro de un forEach, por ejemplo, esa lógica vive únicamente en ese punto. No puedes reutilizarla después porque no tiene nombre ni referencia.

  • Se usan para casos únicos con lógica muy simple.
  • Evitan crear variables o métodos adicionales cuando la operación es breve.
  • Funcionan como el cuerpo de una functional interface.

El ejemplo más directo es recorrer una lista de cursos con forEach y pasar una lambda que imprima cada elemento por pantalla [02:00]. Aunque podrías usar un operador de referencia como System.out::println, la versión lambda deja claro que estás definiendo comportamiento en el acto.

¿Cómo varía la sintaxis según los parámetros?

La sintaxis de las lambdas cambia dependiendo de cuántos parámetros reciba la función y de cuántas líneas tenga el cuerpo.

¿Cómo se escribe una lambda sin argumentos?

Cuando tu functional interface no recibe ningún parámetro, la sintaxis usa paréntesis vacíos seguidos de la flecha [03:30]:

java () -> 42

Esto indica que la lambda no necesita datos de entrada y retorna directamente el valor. Java infiere el retorno sin necesidad de escribir return.

¿Qué sintaxis aplica con un solo parámetro?

Con un Predicate, por ejemplo, la lambda recibe un solo dato. Basta con nombrar el parámetro, agregar la flecha y escribir la expresión [04:40]:

java usarPredicado(text -> text.isEmpty());

No hace falta declarar el tipo de dato ni la palabra return. Los predicados siempre devuelven un booleano, así que Java entiende que text.isEmpty() es el valor de retorno.

¿Y cuando hay dos o más parámetros?

Al trabajar con una BiFunction, necesitas encerrar los parámetros entre paréntesis [05:50]:

java usarBiFunction((x, y) -> x + y);

Esta estructura recibe dos valores y devuelve un resultado directamente. La flecha (->) se forma con un guion seguido del signo mayor que; algunos editores la renderizan como un solo carácter visual, pero en realidad son dos caracteres separados [06:30].

¿Qué pasa cuando el cuerpo necesita varias líneas?

Si tu lambda requiere más de una operación, agregas llaves (braces) para definir un cuerpo más complejo [07:00]:

java usarBiFunction((x, y) -> { System.out.println("x: " + x + ", y: " + y); return x - y; });

  • Al usar llaves, Java necesita la palabra return de forma explícita.
  • En una sola expresión sin llaves, el retorno es implícito.
  • Esta regla aplica para cualquier cantidad de parámetros.

También existe el caso de una función que no recibe parámetros y no retorna nada [08:20]. Para eso puedes crear una functional interface con un método void:

java usarNada(() -> { System.out.println("Hola, alumno"); });

Los paréntesis vacíos indican cero argumentos y las llaves contienen operaciones sin return, porque el tipo de retorno es void.

¿Se pueden declarar tipos explícitos en los parámetros?

Sí. Aunque no es habitual en proyectos del día a día, puedes escribir el tipo directamente [09:40]:

java usarBiFunction((Integer x, Integer y) -> x * y);

Un detalle importante: las functional interfaces operan sobre objetos, no sobre tipos primitivos como int, char o boolean [10:00]. Por eso debes usar Integer en lugar de int. Esta distinción entre tipos primitivos y sus clases envolventes (wrapper classes) es clave para evitar errores de compilación.

Al trabajar con listas, IntelliJ sugiere que el forEach necesita un Consumer de un tipo que extienda de la clase base, indicándolo con la palabra super [10:40]. Esto refleja que puedes generar lambdas que trabajen con subtipos, siempre que la lambda esté definida para el tipo correspondiente.

¿Ya estás usando lambdas en tus proyectos? Comparte en los comentarios qué sintaxis te resulta más natural y en qué situaciones prefieres un operador de referencia.