Skip to content
IRC-CodingIRC-Coding
Programación FuncionalLambdaFunctional InterfacesHigher-Order FunctionsJava 8Stream APIPure FunctionsImmutabilityLenguaje de programaciónLenguajes de programaciónProgramación

Programación Funcional: Lambda, Functional Interfaces

Aprende programación funcional con Lambda, Functional Interfaces y Higher-Order Functions. Ejemplos en Java 8+ y Python.

S

schutzgeist

18 min read
Programación Funcional: Lambda, Functional Interfaces

Programación Funcional: Lambda, Functional Interfaces y Higher-Order Functions

La programación funcional es un paradigma que trata las funciones como bloques de construcción principales. Promueve datos inmutables y funciones puras para mejorar la testabilidad y la paralelización.

Fundamentos de la Programación Funcional

Principios Clave

  • Pure Functions: Funciones sin efectos secundarios
  • Immutability: Estructuras de datos inmutables
  • First-Class Functions: Funciones como valores
  • Higher-Order Functions: Funciones que aceptan otras funciones como parámetros
  • Function Composition: Combinación de funciones

Funciones Puras vs Impuras

public class FunctionExamples {
    
    // Función pura - sin efectos secundarios
    public static int add(int a, int b) {
        return a + b;
        // Siempre el mismo resultado para las mismas entradas
        // Sin efectos secundarios
    }
    
    // Función impura - con efectos secundarios
    private static int counter = 0;
    
    public static int addToCounter(int value) {
        counter += value; // Efecto secundario: modifica el estado global
        return counter;
        // Diferentes resultados para las mismas entradas
    }
    
    // Función pura para procesamiento de strings
    public static String capitalize(String input) {
        if (input == null) return null;
        return input.toUpperCase();
    }
    
    // Función impura con dependencia externa
    public static String getCurrentTimeFormatted() {
        return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now());
        // Depende de la hora actual
    }
}

Expresiones Lambda en Java

Sintaxis y Conceptos Básicos

public class LambdaBasics {
    
    // Enfoque tradicional con clase anónima
    public static void traditionalApproach() {
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("Traditional approach");
            }
        };
        runnable1.run();
    }
    
    // Expresión lambda
    public static void lambdaApproach() {
        Runnable runnable2 = () -> System.out.println("Lambda approach");
        runnable2.run();
    }
    
    // Lambda con diferentes variantes de sintaxis
    public static void lambdaSyntaxExamples() {
        
        // Sin parámetros
        Runnable noParams = () -> System.out.println("No parameters");
        
        // Un parámetro
        Consumer<String> oneParam = s -> System.out.println("Parameter: " + s);
        
        // Un parámetro con tipo
        Consumer<String> oneParamTyped = (String s) -> System.out.println("Typed: " + s);
        
        // Múltiples parámetros
        BinaryOperator<Integer> twoParams = (a, b) -> a + b;
        
        // Múltiples parámetros con tipos
        BinaryOperator<Integer> twoParamsTyped = (Integer a, Integer b) -> a + b;
        
        // Múltiples líneas
        Predicate<String> multiLine = s -> {
            String trimmed = s.trim();
            return trimmed.length() > 5 && trimmed.startsWith("A");
        };
        
        // Uso
        noParams.run();
        oneParam.accept("Hello");
        System.out.println(twoParams.apply(5, 3));
        System.out.println(multiLine.test("Hello World"));
    }
}

Functional Interfaces

// Functional Interfaces personalizados
@FunctionalInterface
public interface StringProcessor {
    String process(String input);
    
    // Puede tener métodos por defecto
    default String processAndLog(String input) {
        String result = process(input);
        System.out.println("Processed: " + result);
        return result;
    }
    
    // Puede tener métodos estáticos
    static StringProcessor toUpperCase() {
        return String::toUpperCase;
    }
}

@FunctionalInterface
public interface TriFunction<T, U, V, R> {
    R apply(T t, U u, V v);
}

// Uso de Functional Interfaces
public class FunctionalInterfaceExamples {
    
    public static void demonstrateInterfaces() {
        
        // Predicate - Prueba booleana
        Predicate<Integer> isEven = n -> n % 2 == 0;
        Predicate<String> isLong = s -> s.length() > 10;
        
        System.out.println("4 is even: " + isEven.test(4));
        System.out.println("Hello is long: " + isLong.test("Hello"));
        
        // Consumer - Consume valores
        Consumer<String> printer = System.out::println;
        Consumer<List<Integer>> listPrinter = list -> list.forEach(System.out::println);
        
        printer.accept("Hello World");
        listPrinter.accept(Arrays.asList(1, 2, 3, 4, 5));
        
        // Supplier - Proporciona valores
        Supplier<Double> randomSupplier = Math::random;
        Supplier<LocalDateTime> nowSupplier = LocalDateTime::now;
        
        System.out.println("Random: " + randomSupplier.get());
        System.out.println("Now: " + nowSupplier.get());
        
        // Function - Transformación
        Function<String, Integer> stringLength = String::length;
        Function<Integer, String> intToString = Object::toString;
        
        System.out.println("Length of Hello: " + stringLength.apply("Hello"));
        System.out.println("String of 42: " + intToString.apply(42));
        
        // UnaryOperator - Función especial
        UnaryOperator<String> upperCase = String::toUpperCase;
        UnaryOperator<Integer> square = n -> n * n;
        
        System.out.println("Upper: " + upperCase.apply("hello"));
        System.out.println("Square: " + square.apply(5));
        
        // BinaryOperator - Dos parámetros
        BinaryOperator<Integer> add = Integer::sum;
        BinaryOperator<String> concat = String::concat;
        
        System.out.println("Add: " + add.apply(3, 7));
        System.out.println("Concat: " + concat.apply("Hello", " World"));
        
        // Functional Interface personalizado
        StringProcessor reverser = s -> new StringBuilder(s).reverse().toString();
        StringProcessor prefixAdder = s -> "Prefix: " + s;
        
        System.out.println("Reverse: " + reverser.process("Hello"));
        System.out.println("Prefix: " + prefixAdder.process("World"));
        
        // TriFunction personalizado
        TriFunction<Integer, Integer, Integer, Integer> sumThree = (a, b, c) -> a + b + c;
        System.out.println("Sum of 1,2,3: " + sumThree.apply(1, 2, 3));
    }
}

Higher-Order Functions

Funciones como Parámetros

public class HigherOrderFunctions {
    
    // Función que toma una función como parámetro
    public static <T, R> List<R> map(List<T> list, Function<T, R> mapper) {
        List<R> result = new ArrayList<>();
        for (T item : list) {
            result.add(mapper.apply(item));
        }
        return result;
    }
    
    // Función que toma una función como parámetro para filtrar
    public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
        List<T> result = new ArrayList<>();
        for (T item : list) {
            if (predicate.test(item)) {
                result.add(item);
            }
        }
        return result;
    }
    
    // Función que toma una función para reducción
    public static <T> T reduce(List<T> list, T identity, BinaryOperator<T> accumulator) {
        T result = identity;
        for (T item : list) {
            result = accumulator.apply(result, item);
        }
        return result;
    }
    
    // Función que devuelve una función (Closure)
    public static Function<Integer, Integer> createMultiplier(int multiplier) {
        return number -> number * multiplier;
    }
    
    // Función con múltiples funciones como parámetros
    public static <T> List<T> processList(
            List<T> list,
            Predicate<T> filter,
            Function<T, T> mapper,
            Consumer<T> consumer) {
        
        List<T> result = new ArrayList<>();
        for (T item : list) {
            if (filter.test(item)) {
                T processed = mapper.apply(item);
                consumer.accept(processed);
                result.add(processed);
            }
        }
        return result;
    }
    
    // Demostración
    public static void demonstrateHigherOrderFunctions() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // Map: elevar números al cuadrado
        List<Integer> squares = map(numbers, n -> n * n);
        System.out.println("Squares: " + squares);
        
        // Filter: solo números pares
        List<Integer> evens = filter(numbers, n -> n % 2 == 0);
        System.out.println("Evens: " + evens);
        
        // Reduce: calcular suma
        Integer sum = reduce(numbers, 0, Integer::sum);
        System.out.println("Sum: " + sum);
        
        // Closure: crear función multiplicadora
        Function<Integer, Integer> doubler = createMultiplier(2);
        Function<Integer, Integer> tripler = createMultiplier(3);
        
        System.out.println("Double of 5: " + doubler.apply(5));
        System.out.println("Triple of 5: " + tripler.apply(5));
        
        // Procesamiento complejo
        List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
        
        List<String> processed = processList(
            words,
            s -> s.length() > 5,     // Filter: solo palabras largas
            String::toUpperCase,     // Map: convertir a mayúsculas
            System.out::println      // Consumer: imprimir
        );
        
        System.out.println("Processed: " + processed);
    }
}

Composición de Funciones

public class FunctionComposition {
    
    // Métodos auxiliares de Composición de Funciones
    public static <T, U, R> Function<T, R> compose(Function<U, R> f, Function<T, U> g) {
        return x -> f.apply(g.apply(x));
    }
    
    public static <T> Function<T, T> compose(Function<T, T>... functions) {
        return Arrays.stream(functions)
            .reduce(Function.identity(), Function::andThen);
    }
    
    // Ejemplo de composición
    public static void demonstrateComposition() {
        // Funciones individuales
        Function<String, String> addPrefix = s -> "Hello " + s;
        Function<String, String> addSuffix = s -> s + "!";
        Function<String, String> toUpperCase = String::toUpperCase;
        
        // Combinar funciones
        Function<String, String> greet = compose(addSuffix, addPrefix);
        Function<String, String> greetLoud = compose(toUpperCase, greet);
        
        System.out.println(greet.apply("World"));        // Hello World!
        System.out.println(greetLoud.apply("World"));    // HELLO WORLD!
        
        // Con andThen (orden invertido)
        Function<String, String> greetAndThenUpper = addPrefix.andThen(toUpperCase).andThen(addSuffix);
        System.out.println(greetAndThenUpper.apply("World")); // HELLO WORLD!
        
        // Ejemplos matemáticos
        Function<Double, Double> multiplyBy2 = x -> x * 2;
        Function<Double, Double> add3 = x -> x + 3;
        Function<Double, Double> square = x -> x * x;
        
        // (x * 2 + 3)²
        Function<Double, Double> complexOperation = compose(square, compose(add3, multiplyBy2));
        System.out.println("Complex operation (5): " + complexOperation.apply(5.0)); // ((5*2)+3)² = 13² = 169
    }
}

Stream API y Programación Funcional

Procesamiento de Streams

public class StreamFunctionalProgramming {
    
    public static void demonstrateStreamProcessing() {
        List<Person> people = Arrays.asList(
            new Person("Alice", 25, "Engineering"),
            new Person("Bob", 30, "Marketing"),
            new Person("Charlie", 35, "Engineering"),
            new Person("Diana", 28, "HR"),
            new Person("Eve", 32, "Engineering")
        );
        
        // Procesamiento complejo de streams
        List<String> result = people.stream()
            .filter(p -> p.getAge() >= 30)                    // Filtro: edad >= 30
            .filter(p -> p.getDepartment().equals("Engineering")) // Filtro: Ingeniería
            .map(Person::getName)                            // Map: solo nombres
            .map(String::toUpperCase)                        // Map: mayúsculas
            .sorted()                                         // Ordenar
            .collect(Collectors.toList());                    // Recopilar
        
        System.out.println("Filtered and mapped: " + result);
        
        // Reducción con operadores especializados
        double totalAge = people.stream()
            .mapToInt(Person::getAge)
            .sum();
        
        System.out.println("Total age: " + totalAge);
        
        // Agrupación
        Map<String, List<Person>> byDepartment = people.stream()
            .collect(Collectors.groupingBy(Person::getDepartment));
        
        System.out.println("By department: " + byDepartment);
        
        // Particionamiento
        Map<Boolean, List<Person>> byAge = people.stream()
            .collect(Collectors.partitioningBy(p -> p.getAge() >= 30));
        
        System.out.println("By age >= 30: " + byAge);
        
        // Collector personalizado
        Map<String, Double> avgAgeByDept = people.stream()
            .collect(Collectors.groupingBy(
                Person::getDepartment,
                Collectors.averagingInt(Person::getAge)
            ));
        
        System.out.println("Average age by department: " + avgAgeByDept);
    }
    
    // Clase Person para ejemplos
    public static class Person {
        private String name;
        private int age;
        private String department;
        
        public Person(String name, int age, String department) {
            this.name = name;
            this.age = age;
            this.department = department;
        }
        
        // Getters
        public String getName() { return name; }
        public int getAge() { return age; }
        public String getDepartment() { return department; }
        
        @Override
        public String toString() {
            return String.format("%s (%d, %s)", name, age, department);
        }
    }
}

Evaluación Perezosa con Streams

public class LazyEvaluation {
    
    public static void demonstrateLazyEvaluation() {
        // Stream infinito, posible gracias a evaluación perezosa
        Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 1);
        
        // Solo se calculan los primeros 10 elementos
        List<Integer> firstTen = infiniteStream
            .limit(10)
            .collect(Collectors.toList());
        
        System.out.println("First 10: " + firstTen);
        
        // Números de Fibonacci con evaluación perezosa
        Stream<Long> fibonacci = Stream.generate(new FibonacciSupplier());
        
        List<Long> firstFibonacci = fibonacci
            .limit(10)
            .collect(Collectors.toList());
        
        System.out.println("First 10 Fibonacci: " + firstFibonacci);
        
        // Filter y map perezosos
        List<Integer> processed = Stream.iterate(1, n -> n + 1)
            .filter(LazyEvaluation::isPrime)      // Solo números primos
            .map(n -> n * n)                      // Elevar al cuadrado
            .limit(5)                             // Solo los primeros 5
            .collect(Collectors.toList());
        
        System.out.println("First 5 prime squares: " + processed);
    }
    
    private static boolean isPrime(int n) {
        if (n <= 1) return false;
        if (n == 2) return true;
        if (n % 2 == 0) return false;
        
        for (int i = 3; i * i <= n; i += 2) {
            if (n % i == 0) return false;
        }
        return true;
    }
    
    // Supplier de Fibonacci
    private static class FibonacciSupplier implements Supplier<Long> {
        private long a = 0;
        private long b = 1;
        
        @Override
        public Long get() {
            long result = a;
            long next = a + b;
            a = b;
            b = next;
            return result;
        }
    }
}

Programación Funcional en Python

Lambda y Funciones de Orden Superior

from functools import reduce, partial
from typing import List, Callable, Any

# Expresiones lambda
def lambda_examples():
    # Funciones lambda simples
    square = lambda x: x ** 2
    add = lambda x, y: x + y
    is_even = lambda x: x % 2 == 0
    
    print(f"Square of 5: {square(5)}")
    print(f"Add 3 + 7: {add(3, 7)}")
    print(f"Is 4 even: {is_even(4)}")
    
    # Lambda con funciones integradas
    numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
    # Map con lambda
    squares = list(map(lambda x: x ** 2, numbers))
    print(f"Squares: {squares}")
    
    # Filter con lambda
    evens = list(filter(lambda x: x % 2 == 0, numbers))
    print(f"Evens: {evens}")
    
    # Reduce con lambda
    sum_all = reduce(lambda x, y: x + y, numbers)
    print(f"Sum: {sum_all}")
    
    # Ordenar con lambda
    words = ["apple", "banana", "cherry", "date"]
    sorted_by_length = sorted(words, key=lambda x: len(x))
    print(f"Sorted by length: {sorted_by_length}")

# Funciones de orden superior
def higher_order_functions():
    # Función que recibe una función como parámetro
    def apply_operation(numbers: List[int], operation: Callable[[int], int]) -> List[int]:
        return [operation(num) for num in numbers]
    
    # Función que retorna una función
    def create_multiplier(factor: int) -> Callable[[int], int]:
        return lambda x: x * factor
    
    # Uso
    numbers = [1, 2, 3, 4, 5]
    
    squares = apply_operation(numbers, lambda x: x ** 2)
    cubes = apply_operation(numbers, lambda x: x ** 3)
    
    print(f"Squares: {squares}")
    print(f"Cubes: {cubes}")
    
    # Closure
    doubler = create_multiplier(2)
    tripler = create_multiplier(3)
    
    print(f"Double of 5: {doubler(5)}")
    print(f"Triple of 5: {tripler(5)}")
    
    # Composición de funciones
    def compose(f: Callable, g: Callable) -> Callable:
        return lambda x: f(g(x))
    
    add_one = lambda x: x + 1
    multiply_by_two = lambda x: x * 2
    
    add_then_multiply = compose(multiply_by_two, add_one)
    multiply_then_add = compose(add_one, multiply_by_two)
    
    print(f"Add then multiply (5): {add_then_multiply(5)}")  # (5 + 1) * 2 = 12
    print(f"Multiply then add (5): {multiply_then_add(5)}")  # (5 * 2) + 1 = 11

# Funciones parciales
def partial_functions():
    def multiply(x: int, y: int) -> int:
        return x * y
    
    def power(base: int, exponent: int) -> int:
        return base ** exponent
    
    # Aplicación parcial
    double = partial(multiply, 2)
    triple = partial(multiply, 3)
    
    square = partial(power, 2)
    cube = partial(power, 3)
    
    print(f"Double of 5: {double(5)}")
    print(f"Triple of 5: {triple(5)}")
    print(f"Square of 5: {square(5)}")
    print(f"Cube of 5: {cube(5)}")

# List Comprehensions (alternativa funcional)
def list_comprehensions():
    numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
    # Enfoque tradicional con map/filter
    squares_even = list(filter(lambda x: x % 2 == 0, map(lambda x: x ** 2, numbers)))
    
    # Con list comprehension
    squares_even_comp = [x ** 2 for x in numbers if x % 2 == 0]
    
    print(f"Squares of evens (map/filter): {squares_even}")
    print(f"Squares of evens (comprehension): {squares_even_comp}")
    
    # Comprehensions anidadas
    matrix = [[i * j for j in range(1, 4)] for i in range(1, 4)]
    print(f"Multiplication table: {matrix}")
    
    # Dictionary comprehension
    word_lengths = {word: len(word) for word in ["apple", "banana", "cherry"]}
    print(f"Word lengths: {word_lengths}")

# Decoradores (funciones de orden superior)
def decorators():
    def timing_decorator(func):
        import time
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            end = time.time()
            print(f"{func.__name__} took {end - start:.4f} seconds")
            return result
        return wrapper
    
    def memoization_decorator(func):
        cache = {}
        def wrapper(*args):
            if args in cache:
                return cache[args]
            result = func(*args)
            cache[args] = result
            return result
        return wrapper
    
    @timing_decorator
    @memoization_decorator
    def fibonacci(n: int) -> int:
        if n <= 1:
            return n
        return fibonacci(n - 1) + fibonacci(n - 2)
    
    print(f"Fibonacci(10): {fibonacci(10)}")
    print(f"Fibonacci(15): {fibonacci(15)}")  # Más rápido gracias a memoization

# Ejecutar todos los ejemplos
if __name__ == "__main__":
    print("=== Lambda Examples ===")
    lambda_examples()
    
    print("\n=== Higher-Order Functions ===")
    higher_order_functions()
    
    print("\n=== Partial Functions ===")
    partial_functions()
    
    print("\n=== List Comprehensions ===")
    list_comprehensions()
    
    print("\n=== Decorators ===")
    decorators()

Inmutabilidad y Funciones Puras

Estructuras de Datos Inmutables

public class ImmutableDataStructures {
    
    // Clase Person inmutable
    public static final class Person {
        private final String name;
        private final int age;
        private final List<String> hobbies;
        
        public Person(String name, int age, List<String> hobbies) {
            this.name = Objects.requireNonNull(name);
            this.age = age;
            this.hobbies = List.copyOf(hobbies); // Copia defensiva
        }
        
        // Getters - sin setters
        public String getName() { return name; }
        public int getAge() { return age; }
        public List<String> getHobbies() { return hobbies; }
        
        // Los métodos generan nuevas instancias
        public Person withAge(int newAge) {
            return new Person(this.name, newAge, this.hobbies);
        }
        
        public Person withName(String newName) {
            return new Person(newName, this.age, this.hobbies);
        }
        
        public Person addHobby(String hobby) {
            List<String> newHobbies = new ArrayList<>(this.hobbies);
            newHobbies.add(hobby);
            return new Person(this.name, this.age, newHobbies);
        }
        
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Person person = (Person) o;
            return age == person.age && 
                   Objects.equals(name, person.name) && 
                   Objects.equals(hobbies, person.hobbies);
        }
        
        @Override
        public int hashCode() {
            return Objects.hash(name, age, hobbies);
        }
        
        @Override
        public String toString() {
            return String.format("Person{name='%s', age=%d, hobbies=%s}", name, age, hobbies);
        }
    }
    
    // Operaciones funcionales sobre datos inmutables
    public static void demonstrateImmutability() {
        List<Person> people = Arrays.asList(
            new Person("Alice", 25, Arrays.asList("reading", "swimming")),
            new Person("Bob", 30, Arrays.asList("gaming", "cooking")),
            new Person("Charlie", 35, Arrays.asList("music", "travel"))
        );
        
        // La lista original permanece sin cambios
        List<Person> olderPeople = people.stream()
            .map(person -> person.withAge(person.getAge() + 1))
            .collect(Collectors.toList());
        
        System.out.println("Original: " + people);
        System.out.println("Aged by 1: " + olderPeople);
        
        // Transformación funcional
        List<String> hobbies = people.stream()
            .flatMap(person -> person.getHobbies().stream())
            .distinct()
            .sorted()
            .collect(Collectors.toList());
        
        System.out.println("All hobbies: " + hobbies);
    }
}

Ejemplos de Funciones Puras

public class PureFunctions {
    
    // Función pura sin efectos secundarios
    public static int calculateArea(int width, int height) {
        if (width < 0 || height < 0) {
            throw new IllegalArgumentException("Dimensions must be positive");
        }
        return width * height;
    }
    
    // Función pura para procesamiento de strings
    public static String formatFullName(String firstName, String lastName) {
        if (firstName == null || lastName == null) {
            return "";
        }
        return String.format("%s %s", 
            firstName.trim().toUpperCase(), 
            lastName.trim().toUpperCase()
        );
    }
    
    // Función pura para procesamiento de listas
    public static List<Integer> filterAndSquare(List<Integer> numbers, Predicate<Integer> predicate) {
        return numbers.stream()
            .filter(predicate)
            .map(n -> n * n)
            .collect(Collectors.toList());
    }
    
    // Función impura para comparación
    private static int counter = 0;
    
    public static int incrementCounter() {
        return counter++; // Efecto secundario
    }
    
    // Demostración
    public static void demonstratePureFunctions() {
        // Función pura - siempre el mismo resultado
        int area1 = calculateArea(5, 3);
        int area2 = calculateArea(5, 3);
        System.out.println("Areas are equal: " + (area1 == area2)); // true
        
        // Función pura con strings
        String fullName1 = formatFullName("John", "Doe");
        String fullName2 = formatFullName("John", "Doe");
        System.out.println("Names are equal: " + fullName1.equals(fullName2)); // true
        
        // Función pura con listas
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        List<Integer> evenSquares = filterAndSquare(numbers, n -> n % 2 == 0);
        System.out.println("Even squares: " + evenSquares);
        
        // Función impura - resultados diferentes
        int count1 = incrementCounter();
        int count2 = incrementCounter();
        System.out.println("Counts are different: " + (count1 != count2)); // true
    }
}

Mónadas y Conceptos Funcionales

Mónada Optional

public class MonadExamples {
    
    // Optional para operaciones seguras con null
    public static void demonstrateOptional() {
        Optional<String> optional = Optional.of("Hello World");
        
        // Transformación con map
        Optional<Integer> length = optional.map(String::length);
        System.out.println("Length: " + length.orElse(0));
        
        // Filter
        Optional<String> filtered = optional.filter(s -> s.length() > 5);
        System.out.println("Filtered: " + filtered.orElse("Too short"));
        
        // FlatMap para optionals anidados
        Optional<String> upperCase = optional.flatMap(s -> 
            s.length() > 5 ? Optional.of(s.toUpperCase()) : Optional.empty()
        );
        System.out.println("Upper case: " + upperCase.orElse("Not available"));
        
        // Cadena segura contra null
        String result = Optional.ofNullable(getUser())
            .map(User::getAddress)
            .map(Address::getCity)
            .orElse("Unknown");
        
        System.out.println("City: " + result);
    }
    
    // Mónada Either (implementación simplificada)
    public static class Either<L, R> {
        private final L left;
        private final R right;
        
        private Either(L left, R right) {
            this.left = left;
            this.right = right;
        }
        
        public static <L, R> Either<L, R> left(L value) {
            return new Either<>(value, null);
        }
        
        public static <L, R> Either<L, R> right(R value) {
            return new Either<>(null, value);
        }
        
        public boolean isLeft() { return left != null; }
        public boolean isRight() { return right != null; }
        
        public L getLeft() { return left; }
        public R getRight() { return right; }
        
        public <T> Either<L, T> map(Function<R, T> mapper) {
            if (isRight()) {
                return Either.right(mapper.apply(right));
            }
            return Either.left(left);
        }
        
        public <T> Either<L, T> flatMap(Function<R, Either<L, T>> mapper) {
            if (isRight()) {
                return mapper.apply(right);
            }
            return Either.left(left);
        }
    }
    
    // Uso de Either
    public static Either<String, Integer> parseNumber(String input) {
        try {
            return Either.right(Integer.parseInt(input));
        } catch (NumberFormatException e) {
            return Either.left("Invalid number: " + input);
        }
    }
    
    public static void demonstrateEither() {
        Either<String, Integer> result1 = parseNumber("123");
        Either<String, Integer> result2 = parseNumber("abc");
        
        result1.map(n -> n * 2)
               .map(Object::toString)
               .ifRight(System.out::println)
               .ifLeft(System.err::println);
        
        result2.map(n -> n * 2)
               .map(Object::toString)
               .ifRight(System.out::println)
               .ifLeft(System.err::println);
    }
    
    // Métodos auxiliares para Either
    private static <T> void ifRight(Either<String, T> either, Consumer<T> consumer) {
        if (either.isRight()) {
            consumer.accept(either.getRight());
        }
    }
    
    private static <T> void ifLeft(Either<String, T> either, Consumer<String> consumer) {
        if (either.isLeft()) {
            consumer.accept(either.getLeft());
        }
    }
    
    // Clases dummy para los ejemplos
    private static User getUser() {
        return new User("John", new Address("123 Main St", "New York"));
    }
    
    private static class User {
        private String name;
        private Address address;
        
        public User(String name, Address address) {
            this.name = name;
            this.address = address;
        }
        
        public Address getAddress() { return address; }
    }
    
    private static class Address {
        private String street;
        private String city;
        
        public Address(String street, String city) {
            this.street = street;
            this.city = city;
        }
        
        public String getCity() { return city; }
    }
}

Rendimiento y buenas prácticas

Rendimiento en programación funcional

public class FunctionalPerformance {
    
    // Bucle tradicional
    public static int traditionalSum(List<Integer> numbers) {
        int sum = 0;
        for (int num : numbers) {
            sum += num;
        }
        return sum;
    }
    
    // Variante funcional con Stream
    public static int functionalSum(List<Integer> numbers) {
        return numbers.stream().reduce(0, Integer::sum);
    }
    
    // Parallel Stream para grandes volúmenes de datos
    public static int parallelSum(List<Integer> numbers) {
        return numbers.parallelStream().reduce(0, Integer::sum);
    }
    
    // Comparación de rendimiento
    public static void performanceComparison() {
        List<Integer> numbers = IntStream.range(0, 1_000_000)
            .boxed()
            .collect(Collectors.toList());
        
        // Calentamiento
        traditionalSum(numbers);
        functionalSum(numbers);
        parallelSum(numbers);
        
        // Benchmark
        long start = System.nanoTime();
        int result1 = traditionalSum(numbers);
        long traditionalTime = System.nanoTime() - start;
        
        start = System.nanoTime();
        int result2 = functionalSum(numbers);
        long functionalTime = System.nanoTime() - start;
        
        start = System.nanoTime();
        int result3 = parallelSum(numbers);
        long parallelTime = System.nanoTime() - start;
        
        System.out.println("Results equal: " + (result1 == result2 && result2 == result3));
        System.out.println("Traditional: " + (traditionalTime / 1_000_000) + " ms");
        System.out.println("Functional: " + (functionalTime / 1_000_000) + " ms");
        System.out.println("Parallel: " + (parallelTime / 1_000_000) + " ms");
    }
    
    // Buenas prácticas
    public static void bestPractices() {
        List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
        
        // 1. Method References en lugar de Lambda cuando sea posible
        List<Integer> lengths1 = words.stream()
            .map(s -> s.length())           // Lambda
            .collect(Collectors.toList());
        
        List<Integer> lengths2 = words.stream()
            .map(String::length)            // Method Reference
            .collect(Collectors.toList());
        
        // 2. Primitive Streams para mejor rendimiento
        int sum = words.stream()
            .mapToInt(String::length)       // IntStream en lugar de Stream<Integer>
            .sum();
        
        // 3. Aprovechar la evaluación perezosa
        Optional<String> firstLong = words.stream()
            .filter(s -> s.length() > 5)
            .findFirst();                    // Se detiene tras el primer resultado
        
        // 4. Preferir operaciones inmutables
        List<String> processed = words.stream()
            .map(String::toUpperCase)
            .filter(s -> s.startsWith("A"))
            .collect(Collectors.toList());  // Nueva lista en lugar de modificar
        
        System.out.println("Best practices completed");
    }
}

Conceptos relevantes para exámenes

Conceptos funcionales importantes

  1. Pure Functions: Sin efectos secundarios, deterministas
  2. Immutability: Estructuras de datos inmutables
  3. Higher-Order Functions: Funciones como parámetros o valores de retorno
  4. Function Composition: Combinación de funciones
  5. Lazy Evaluation: Evaluación retrasada
  6. Monads: Optional, Either, Stream

Ejercicios típicos en exámenes

  1. Implementa una función pura
  2. Explica la composición de funciones
  3. Compara enfoques imperativos vs funcionales
  4. Implementa una función de orden superior
  5. Describe las ventajas de la programación funcional

Resumen

La programación funcional ofrece numerosas ventajas:

  • Testabilidad: Las funciones puras son fáciles de probar
  • Paralelizabilidad: La ausencia de efectos secundarios facilita la programación concurrente
  • Mantenibilidad: Los datos inmutables reducen errores
  • Legibilidad: El código declarativo suele ser más claro

La combinación de programación orientada a objetos y programación funcional permite construir arquitecturas de software robustas, escalables y mantenibles.

Recomendación de libros

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

Más artículos sobre programación funcional

La programación funcional es un paradigma importante en el desarrollo moderno de software. Los siguientes artículos te ayudarán a dominar todos los aspectos de la programación funcional.

Fundamentos y conceptos

Volver al blog
Share:

Entradas relacionadas