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
- Pure Functions: Sin efectos secundarios, deterministas
- Immutability: Estructuras de datos inmutables
- Higher-Order Functions: Funciones como parámetros o valores de retorno
- Function Composition: Combinación de funciones
- Lazy Evaluation: Evaluación retrasada
- Monads: Optional, Either, Stream
Ejercicios típicos en exámenes
- Implementa una función pura
- Explica la composición de funciones
- Compara enfoques imperativos vs funcionales
- Implementa una función de orden superior
- 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
- Programación funcional: expresiones Lambda - Expresiones Lambda e interfaces funcionales
- Programación funcional: Lambda con Streams - Stream API y operaciones funcionales

