Skip to content
IRC-CodingIRC-Coding
Programación FuncionalLambda ExpressionsFunctional InterfacesHigher Order FunctionsStreamsLenguaje de ProgramaciónProgramación

Programación Funcional: Lambda Expressions e Interfaces

Aprende programación funcional con Lambda expressions, Functional Interfaces, Higher-Order Functions, Pure Functions, Immutability y Streams.

S

schutzgeist

11 min read
Programación Funcional: Lambda Expressions e Interfaces

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

  1. Expresiones Lambda: (x, y) -> x + y
  2. Interfaces Funcionales: Predicate<T>, Function<T,R>
  3. Funciones Puras: Sin efectos secundarios
  4. Inmutabilidad: Objetos invariables
  5. Funciones de Orden Superior: map(), filter(), reduce()
  6. Streams: Procesamiento de datos secuencial
  7. Referencias de Método: String::length
  8. 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

  1. ¿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.

  2. Explica qué son las Pure Functions Funciones sin efectos secundarios que siempre devuelven la misma salida para la misma entrada.

  3. ¿Qué es una Higher-Order Function? Una función que recibe otras funciones como parámetros o las devuelve.

  4. ¿Por qué es importante la inmutabilidad? Previene cambios de estado inesperados y facilita la paralelización.

Recursos Principales

  1. https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
  2. https://docs.oracle.com/javase/tutorial/collections/streams/
  3. https://www.python.org/doc/essays/list2str.html

Lectura Recomendada

Keine Bücher für Kategorie "programming-languages" gefunden.

Volver al blog
Share:

Entradas relacionadas