Programación Funcional: Expresiones Lambda e Interfaces Funcionales
Este artículo ofrece una explicación exhaustiva de la programación funcional, incluyendo expresiones lambda, interfaces funcionales y funciones de orden superior con ejemplos prácticos.
In a Nutshell
La programación funcional se centra en la computación mediante funciones en lugar de cambios de estado. Las expresiones lambda son literales de función en línea que se vinculan a través de interfaces funcionales.
Descripción técnica compacta
Programación Funcional es un paradigma que trata las funciones como elementos principales de construcción. A diferencia de la programación imperativa, evita los cambios de estado.
Expresiones Lambda describen comportamiento anónimo con parámetros, cuerpo y tipo de retorno opcional. El tipo se deduce del contexto destino (Target Type).
Interfaces Funcionales en Java tienen exactamente un método abstracto y permiten funciones de orden superior:
- Predicate:
boolean test(T t)- Evaluar condiciones - Function:
R apply(T t)- Transformaciones - Consumer:
void accept(T t)- Consumir elementos - Supplier:
T get()- Proveer valores
Principios centrales:
- Funciones Puras: Entrada inmutable → salida determinística
- Inmutabilidad: Los datos no cambian
- Transparencia Referencial: Una llamada puede reemplazarse por su resultado
- Funciones de Orden Superior: Funciones como parámetros o valores de retorno
Puntos clave para examen
- Expresiones Lambda: Funciones anónimas con sintaxis compacta
- Interfaces Funcionales: Exactamente un método abstracto
- Funciones de Orden Superior: Funciones como parámetros o valores de retorno
- Funciones Puras: Sin efectos secundarios, determinísticas
- Inmutabilidad: Estructuras de datos invariables
- Streams API: Procesamiento de datos declarativo
- Referencias de Método: Referencia compacta a métodos
- Relevancia IHK: Java moderno, enfoques funcionales
Componentes principales
- Expresiones Lambda:
(x, y) -> x + y - Interfaces Funcionales:
Predicate<T>,Function<T,R> - Funciones Puras: Sin efectos secundarios
- Inmutabilidad: Objetos invariables
- Funciones de Orden Superior:
map(),filter(),reduce() - Streams: Procesamiento de datos secuencial
- Referencias de Método:
String::length - Closures: Acceso a variables externas
Ejemplos prácticos
1. Expresiones Lambda e Interfaces Funcionales en Java
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
public class FunctionalProgrammingDemo {
public static void main(String[] args) {
List<String> nombres = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Lambda con Predicate (Filter)
Predicate<String> masDeChatro = nombre -> nombre.length() > 4;
List<String> nombresLargos = nombres.stream()
.filter(masDeChatro)
.collect(Collectors.toList());
System.out.println("Nombres largos: " + nombresLargos);
// Lambda con Function (Map/Transformación)
Function<String, Integer> longitudNombre = String::length; // Method Reference
List<Integer> longitudes = nombres.stream()
.map(longitudNombre)
.collect(Collectors.toList());
System.out.println("Longitudes: " + longitudes);
// Lambda con Consumer (ForEach)
Consumer<String> impresor = nombre -> System.out.println("Hola " + nombre);
nombres.forEach(impresor);
// Lambda con Supplier (Generar)
Supplier<Double> numeroAleatorio = () -> Math.random();
System.out.println("Número aleatorio: " + numeroAleatorio.get());
// Expresiones Lambda complejas
Predicate<Integer> esPar = n -> n % 2 == 0;
Predicate<Integer> esMayorQueCinco = n -> n > 5;
// Combinar Predicates
Predicate<Integer> esParYMayor = esPar.and(esMayorQueCinco);
List<Integer> numerosFiltrados = numeros.stream()
.filter(esParYMayor)
.collect(Collectors.toList());
System.out.println("Pares y >5: " + numerosFiltrados);
// Función de Orden Superior
Function<Integer, Predicate<Integer>> mayorQue = limite ->
numero -> numero > limite;
Predicate<Integer> mayorQueTres = mayorQue.apply(3);
List<Integer> numerosGrandes = numeros.stream()
.filter(mayorQueTres)
.collect(Collectors.toList());
System.out.println(">3: " + numerosGrandes);
}
}
2. Funciones Puras e Inmutabilidad
// Enfoque Imperativo (con efectos secundarios)
class CalculadoraImperativa {
private int suma = 0;
public void agrega(int valor) {
this.suma += valor; // Efecto secundario: el estado cambia
}
public int getSuma() {
return suma;
}
}
// Enfoque Funcional (Funciones Puras)
class CalculadoraFuncional {
// Función Pura: sin efectos secundarios, determinística
public static int agrega(int a, int b) {
return a + b;
}
// Función Pura con datos inmutables
public static List<Integer> filtraPares(List<Integer> numeros) {
return numeros.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
}
// Función Pura con transformación
public static List<Integer> eleva(List<Integer> numeros) {
return numeros.stream()
.map(n -> n * n)
.collect(Collectors.toList());
}
// Función de Orden Superior
public static List<Integer> procesa(List<Integer> numeros,
Function<Integer, Integer> operacion) {
return numeros.stream()
.map(operacion)
.collect(Collectors.toList());
}
// Función Pura con composición
public static Function<Integer, Integer> multiplicaPor(int factor) {
return numero -> numero * factor;
}
public static Function<Integer, Integer> sumaPor(int valor) {
return numero -> numero + valor;
}
}
// Clase de datos inmutable
public final class Persona {
private final String nombre;
private final int edad;
public Persona(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
// Función Pura para cambios (crea nuevo objeto)
public Persona conNuevaEdad(int nuevaEdad) {
return new Persona(this.nombre, nuevaEdad);
}
public Persona conNuevoNombre(String nuevoNombre) {
return new Persona(nuevoNombre, this.edad);
}
// Getters (sin Setters para inmutabilidad)
public String getNombre() { return nombre; }
public int getEdad() { return edad; }
@Override
public String toString() {
return nombre + " (" + edad + ")";
}
}
// Uso
public class PureFunctionDemo {
public static void main(String[] args) {
// Enfoque Imperativo
CalculadoraImperativa imperativo = new CalculadoraImperativa();
imperativo.agrega(5);
imperativo.agrega(3);
System.out.println("Imperativo: " + imperativo.getSuma()); // 8
// Enfoque Funcional
int resultado1 = CalculadoraFuncional.agrega(5, 3);
int resultado2 = CalculadoraFuncional.agrega(5, 3); // Siempre el mismo resultado
List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> pares = CalculadoraFuncional.filtraPares(numeros);
List<Integer> cuadrados = CalculadoraFuncional.eleva(numeros);
System.out.println("Pares: " + pares);
System.out.println("Cuadrados: " + cuadrados);
// Función de Orden Superior
Function<Integer, Integer> duplica = n -> n * 2;
List<Integer> duplicados = CalculadoraFuncional.procesa(numeros, duplica);
System.out.println("Duplicados: " + duplicados);
// Composición de funciones
Function<Integer, Integer> multiplicar = CalculadoraFuncional.multiplicaPor(2);
Function<Integer, Integer> sumar = CalculadoraFuncional.sumaPor(10);
Function<Integer, Integer> combinada = multiplicar.andThen(sumar);
List<Integer> resultadoCombinado = CalculadoraFuncional.procesa(numeros, combinada);
System.out.println("Combinada (x*2+10): " + resultadoCombinado);
// Inmutabilidad
Persona alice = new Persona("Alice", 25);
Persona aliceMayor = alice.conNuevaEdad(26);
System.out.println("Original: " + alice); // Alice (25)
System.out.println("Modificada: " + aliceMayor); // Alice (26)
}
}
3. Streams API y programación declarativa
import java.util.*;
import java.util.stream.*;
public class StreamAPIDemo {
public static void main(String[] args) {
List<Person> personas = Arrays.asList(
new Person("Alice", 25, "Desarrollo"),
new Person("Bob", 30, "Marketing"),
new Person("Charlie", 35, "Desarrollo"),
new Person("Diana", 28, "Ventas"),
new Person("Eve", 32, "Desarrollo")
);
// Procesamiento declarativo de datos con Streams
// 1. Filtrar y transformar
List<String> nombresDesarrolladores = personas.stream()
.filter(p -> p.getDepartamento().equals("Desarrollo")) // Filtro
.map(Person::getNombre) // Transformar
.sorted() // Ordenar
.collect(Collectors.toList()); // Recopilar
System.out.println("Desarrolladores: " + nombresDesarrolladores);
// 2. Pipeline complejo con múltiples operaciones
Map<String, Double> edadPromedioPorDepartamento = personas.stream()
.collect(Collectors.groupingBy(
Person::getDepartamento,
Collectors.averagingInt(Person::getEdad)
));
System.out.println("Edad promedio: " + edadPromedioPorDepartamento);
// 3. Reduce para agregación
int edadTotal = personas.stream()
.mapToInt(Person::getEdad)
.reduce(0, Integer::sum); // Alternativa: .sum()
System.out.println("Edad total: " + edadTotal);
// 4. Optional para procesamiento seguro
Optional<Person> personaMayorEdad = personas.stream()
.max(Comparator.comparing(Person::getEdad));
personaMayorEdad.ifPresent(p ->
System.out.println("Persona más mayor: " + p.getNombre()));
// 5. Collector personalizado
String todosLosNombres = personas.stream()
.map(Person::getNombre)
.collect(Collectors.joining(", "));
System.out.println("Todos los nombres: " + todosLosNombres);
// 6. Parallel Streams para rendimiento
List<Integer> numerosGrandes = IntStream.range(1, 1_000_000)
.boxed()
.collect(Collectors.toList());
long cantidadPrimos = numerosGrandes.parallelStream()
.filter(StreamAPIDemo::esPrimo)
.count();
System.out.println("Cantidad de primos: " + cantidadPrimos);
}
// Función pura para verificación de número primo
private static boolean esPrimo(int n) {
if (n <= 1) return false;
if (n <= 3) return true;
if (n % 2 == 0 || n % 3 == 0) return false;
for (int i = 5; i * i <= n; i += 6) {
if (n % i == 0 || n % (i + 2) == 0) return false;
}
return true;
}
}
// Clase Person para los ejemplos
class Person {
private final String nombre;
private final int edad;
private final String departamento;
public Person(String nombre, int edad, String departamento) {
this.nombre = nombre;
this.edad = edad;
this.departamento = departamento;
}
public String getNombre() { return nombre; }
public int getEdad() { return edad; }
public String getDepartamento() { return departamento; }
@Override
public String toString() {
return nombre + " (" + edad + ", " + departamento + ")";
}
}
4. Funciones de orden superior y closures
import java.util.function.*;
import java.util.*;
public class HigherOrderFunctionsDemo {
// Función de orden superior: toma una función como parámetro
public static <T, R> List<R> mapea(List<T> lista, Function<T, R> mapper) {
List<R> resultado = new ArrayList<>();
for (T elemento : lista) {
resultado.add(mapper.apply(elemento));
}
return resultado;
}
// Función de orden superior: devuelve una función
public static Function<Integer, Integer> multiplicador(int factor) {
return numero -> numero * factor; // Closure: factor está vinculado
}
// Función de orden superior: devuelve un Predicate
public static Predicate<String> mayorQueLongitud(int minLongitud) {
return texto -> texto.length() > minLongitud;
}
// Función de orden superior con múltiples funciones
public static <T> List<T> procesaCadena(List<T> lista,
List<Function<T, T>> funciones) {
List<T> resultado = new ArrayList<>(lista);
for (Function<T, T> funcion : funciones) {
resultado = mapea(resultado, funcion);
}
return resultado;
}
// Currying (simplificado)
public static Function<Integer, Function<Integer, Integer>> sumaCurried() {
return a -> b -> a + b;
}
// Composición de funciones
public static <T> Function<T, T> compone(Function<T, T> f, Function<T, T> g) {
return x -> f.apply(g.apply(x));
}
public static void main(String[] args) {
List<String> palabras = Arrays.asList("Java", "Python", "JavaScript", "C++");
List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);
// Usar función de orden superior
List<Integer> longitudes = mapea(palabras, String::length);
System.out.println("Longitudes: " + longitudes);
// Devolver una función y usarla
Function<Integer, Integer> duplicar = multiplicador(2);
Function<Integer, Integer> triplicar = multiplicador(3);
List<Integer> duplicados = mapea(numeros, duplicar);
List<Integer> triplicados = mapea(numeros, triplicar);
System.out.println("Duplicados: " + duplicados);
System.out.println("Triplicados: " + triplicados);
// Función de orden superior con Predicate
Predicate<String> mayorQueTres = mayorQueLongitud(3);
List<String> palabrasLargas = palabras.stream()
.filter(mayorQueTres)
.collect(Collectors.toList());
System.out.println("Palabras largas: " + palabrasLargas);
// Cadena de funciones
List<Function<Integer, Integer>> funciones = Arrays.asList(
n -> n * 2, // duplicar
n -> n + 10, // sumar
n -> n / 3 // dividir
);
List<Integer> procesados = procesaCadena(numeros, funciones);
System.out.println("Números procesados: " + procesados);
// Currying
Function<Integer, Function<Integer, Integer>> suma = sumaCurried();
Function<Integer, Integer> sumacinco = suma.apply(5);
int resultado = sumacinco.apply(3); // 5 + 3 = 8
System.out.println("Resultado currying: " + resultado);
// Composición de funciones
Function<Integer, Integer> elevarAlCuadrado = n -> n * n;
Function<Integer, Integer> incrementar = n -> n + 1;
Function<Integer, Integer> cuadradoLuegoIncremento = compone(incrementar, elevarAlCuadrado);
Function<Integer, Integer> incrementoLuegoCuadrado = compone(elevarAlCuadrado, incrementar);
System.out.println("3²+1: " + cuadradoLuegoIncremento.apply(3)); // 10
System.out.println("(3+1)²: " + incrementoLuegoCuadrado.apply(3)); // 16
}
}
5. Programación Funcional en Python
from typing import List, Callable, Optional
from functools import reduce
import operator
# Pure Functions
def addiere(a: int, b: int) -> int:
return a + b
def filtere_gerade(zahlen: List[int]) -> List[int]:
return [n for n in zahlen if n % 2 == 0]
def quadriere(zahlen: List[int]) -> List[int]:
return [n * n for n in zahlen]
# Higher-Order Functions
def verarbeite(zahlen: List[int], operation: Callable[[int], int]) -> List[int]:
return [operation(n) for n in zahlen]
def multiplizierer(faktor: int) -> Callable[[int], int]:
return lambda x: x * faktor
# Function Composition
def komponiere(f: Callable, g: Callable) -> Callable:
return lambda x: f(g(x))
# Currying
def addiere_curried(a: int):
return lambda b: a + b
# Unveränderliche Datenklasse
from dataclasses import dataclass
@dataclass(frozen=True)
class Person:
name: str
alter: int
abteilung: str
def mit_neuem_alter(self, neues_alter: int) -> 'Person':
return Person(self.name, neues_alter, self.abteilung)
# Verwendung
def funktionale_demo():
# Pure Functions
zahlen = [1, 2, 3, 4, 5]
gerade = filtere_gerade(zahlen)
quadrate = quadriere(zahlen)
print(f"Gerade: {gerade}")
print(f"Quadrate: {quadrate}")
# Higher-Order Functions
verdoppeln = multiplizierer(2)
verdreifachen = multiplizierer(3)
verdoppelt = verarbeite(zahlen, verdoppeln)
verdreifacht = verarbeite(zahlen, verdreifachen)
print(f"Verdoppelt: {verdoppelt}")
print(f"Verdreifacht: {verdreifacht}")
# Function Composition
quadrieren = lambda x: x * x
inkrementieren = lambda x: x + 1
quadrieren_dann_inkrementieren = komponiere(inkrementieren, quadrieren)
inkrementieren_dann_quadrieren = komponiere(quadrieren, inkrementieren)
print(f"3²+1: {quadrieren_dann_inkrementieren(3)}") # 10
print(f"(3+1)²: {inkrementieren_dann_quadrieren(3)}") # 16
# Currying
addiere_fuenf = addiere_curried(5)
ergebnis = addiere_fuenf(3) # 8
print(f"Currying Ergebnis: {ergebnis}")
# Reduce für Aggregation
summe = reduce(operator.add, zahlen, 0)
produkt = reduce(operator.mul, zahlen, 1)
print(f"Summe: {summe}")
print(f"Produkt: {produkt}")
# Immutability
alice = Person("Alice", 25, "Entwicklung")
alice_aelter = alice.mit_neuem_alter(26)
print(f"Original: {alice}")
print(f"Verändert: {alice_aelter}")
if __name__ == "__main__":
funktionale_demo()
Comparación de Sintaxis Lambda
Expresiones Lambda en Java
// Diferentes formas de Lambda
Predicate<String> leer = s -> s.isEmpty();
Predicate<String> leer2 = String::isEmpty; // Method Reference
Function<Integer, String> toString = i -> i.toString();
Function<Integer, String> toString2 = Object::toString;
Consumer<String> drucker = s -> System.out.println(s);
Consumer<String> drucker2 = System.out::println;
Supplier<Integer> zufall = () -> (int)(Math.random() * 100);
Expresiones Lambda en Python
# Expresiones Lambda
leer = lambda s: len(s) == 0
verdoppeln = lambda x: x * 2
# Higher-Order Functions con Lambda
zahlen = [1, 2, 3, 4, 5]
verdoppelt = list(map(lambda x: x * 2, zahlen))
gerade = list(filter(lambda x: x % 2 == 0, zahlen))
Expresiones Lambda en JavaScript
// Arrow Functions
const leer = s => s.length === 0;
const verdoppeln = x => x * 2;
// Higher-Order Functions
const zahlen = [1, 2, 3, 4, 5];
const verdoppelt = zahlen.map(x => x * 2);
const gerade = zahlen.filter(x => x % 2 === 0);
Ventajas e Inconvenientes
Ventajas de la Programación Funcional
- Testabilidad: Las funciones puras son sencillas de probar
- Paralelización: La ausencia de efectos secundarios permite procesamiento paralelo seguro
- Reutilización: Las Higher-Order Functions son muy flexibles
- Legibilidad: El código declarativo suele ser más comprensible
- Reducción de errores: Menos cambios de estado significan menos bugs
Inconvenientes
- Curva de aprendizaje: Pensar funcionalmente requiere práctica
- Desempeño: Las abstracciones funcionales pueden introducir overhead
- Memoria: La inmutabilidad puede consumir más memoria
- Debugging: Los stack traces pueden volverse más complejos
Preguntas Frecuentes en Exámenes
-
¿Cuál es la diferencia entre una expresión lambda y una clase anónima? Una expresión lambda es sintaxis más compacta para una Functional Interface, mientras que una clase anónima tiene más boilerplate.
-
Explica qué son las Pure Functions Funciones sin efectos secundarios que siempre devuelven la misma salida para la misma entrada.
-
¿Qué es una Higher-Order Function? Una función que recibe otras funciones como parámetros o las devuelve.
-
¿Por qué es importante la inmutabilidad? Previene cambios de estado inesperados y facilita la paralelización.
Recursos Principales
- https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
- https://docs.oracle.com/javase/tutorial/collections/streams/
- https://www.python.org/doc/essays/list2str.html
Lectura Recomendada
Keine Bücher für Kategorie "programming-languages" gefunden.



