Skip to content
IRC-CodingIRC-Coding
Java Stream APILambda ExpressionsFunctional InterfacesMap Filter ReduceCollect

Java Stream API: Lambda, Filter, Map, Reduce y Collect

Domina Java Stream API con Lambda Expressions y Functional Interfaces. Aprende Map, Filter, Reduce y Collect con ejemplos prácticos.

S

schutzgeist

14 min read
Java Stream API: Lambda, Filter, Map, Reduce y Collect

Java Stream API: Lambda, Functional Interfaces, Map, Filter, Reduce & Collect

Este artículo es una guía completa sobre la Java Stream API, incluyendo expresiones lambda, Functional Interfaces, map, filter, reduce y collect con ejemplos prácticos.

Resumen rápido

Java Stream API permite procesamiento funcional de datos mediante expresiones lambda. map transforma, filter selecciona, reduce agrega y collect recopila resultados en contenedores.

Descripción técnica concisa

Java Stream API es una API funcional para procesar colecciones de datos. Soporta programación declarativa con expresiones lambda y Functional Interfaces.

Conceptos clave:

Expresiones lambda

  • Sintaxis: (parámetro) -> expresión o (parámetro) -> { sentencias }
  • Inferencia de tipos: El tipo se deduce del contexto
  • Efectivamente final: Las variables deben ser finales o efectivamente finales
  • Referencias a métodos: Notación más compacta para expresiones lambda

Functional Interfaces

- **Predicate<T>**: boolean test(T t) - Prueba
- **Function<T,R>**: R apply(T t) - Transformación
- **Consumer<T>**: void accept(T t) - Consumición
- **Supplier<T>**: T get() - Generación
- **UnaryOperator<T>**: T apply(T t) - Operación unaria
- **BinaryOperator<T>**: T apply(T t1, T t2) - Operación binaria

Operaciones con streams

  • Intermedias: map, filter, sorted, distinct, limit, skip
  • Terminales: forEach, collect, reduce, count, anyMatch, allMatch
  • Cortocircuito: findFirst, findAny, anyMatch, allMatch, noneMatch

Puntos clave para pruebas

  • Stream API: Procesamiento funcional de datos en Java 8+
  • Expresiones lambda: Funciones anónimas con sintaxis compacta
  • Functional Interfaces: Interfaces con un único método abstracto
  • Map: Transformación de elementos
  • Filter: Selección basada en predicados
  • Reduce: Agregación de elementos del stream
  • Collect: Recopilación de resultados en contenedores
  • Relevancia IHK: Java moderno, programación funcional

Componentes principales

  1. Expresiones lambda: Literales de función compactos
  2. Functional Interfaces: Definiciones de función tipificadas
  3. Creación de streams: Colecciones, arrays, E/S, generadores
  4. Operaciones intermedias: Lazy, encadenables
  5. Operaciones terminales: Eager, finalizan el procesamiento del stream
  6. Collectors: Operaciones de recopilación especializadas
  7. Parallel Streams: Procesamiento paralelo de datos
  8. Optional: Contenedor seguro para valores que pueden ser nulos

Ejemplos prácticos

1. Operaciones básicas con streams

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

public class StreamGrundlagen {
    
    public static void main(String[] args) {
        System.out.println("=== Stream API Grundlagen ===");
        
        // Datenquelle
        List<String> namen = Arrays.asList("Alice", "Bob", "Charlie", "Diana", "Eve");
        List<Integer> zahlen = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // Filter Demo
        filterDemo(namen, zahlen);
        
        // Map Demo
        mapDemo(namen, zahlen);
        
        // Reduce Demo
        reduceDemo(zahlen);
        
        // Collect Demo
        collectDemo(namen, zahlen);
        
        // Methodenreferenzen
        methodenreferenzenDemo();
    }
    
    private static void filterDemo(List<String> namen, List<Integer> zahlen) {
        System.out.println("\n--- Filter Demo ---");
        
        // Lambda-Ausdruck für Filter
        List<String> langeNamen = namen.stream()
            .filter(name -> name.length() > 4)
            .collect(Collectors.toList());
        
        System.out.println("Namen mit > 4 Buchstaben: " + langeNamen);
        
        // Mehrere Filter
        List<Integer> gefilterteZahlen = zahlen.stream()
            .filter(zahl -> zahl % 2 == 0)  // Gerade Zahlen
            .filter(zahl -> zahl > 3)       // Größer als 3
            .collect(Collectors.toList());
        
        System.out.println("Gerade Zahlen > 3: " + gefilterteZahlen);
        
        // Komplexes Prädikat
        Predicate<String> komplexesPraedikat = name -> 
            name.startsWith("A") && name.length() <= 5;
        
        List<String> gefilterteNamen = namen.stream()
            .filter(komplexesPraedikat)
            .collect(Collectors.toList());
        
        System.out.println("Namen mit 'A' und ≤5 Buchstaben: " + gefilterteNamen);
    }
    
    private static void mapDemo(List<String> namen, List<Integer> zahlen) {
        System.out.println("\n--- Map Demo ---");
        
        // String zu Integer (Länge)
        List<Integer> namenslaengen = namen.stream()
            .map(name -> name.length())
            .collect(Collectors.toList());
        
        System.out.println("Namenslängen: " + namenslaengen);
        
        // Integer zu String (Quadrate)
        List<String> quadrate = zahlen.stream()
            .map(zahl -> zahl * zahl)
            .map(quad -> "Quadrat: " + quad)
            .collect(Collectors.toList());
        
        System.out.println("Quadrate: " + quadrate);
        
        // FlatMap für verschachtelte Strukturen
        List<List<Integer>> verschachtelt = Arrays.asList(
            Arrays.asList(1, 2, 3),
            Arrays.asList(4, 5),
            Arrays.asList(6, 7, 8, 9)
        );
        
        List<Integer> flach = verschachtelt.stream()
            .flatMap(list -> list.stream())
            .collect(Collectors.toList());
        
        System.out.println("Flachgemacht: " + flach);
        
        // Map mit Objekten
        List<Person> personen = Arrays.asList(
            new Person("Alice", 25),
            new Person("Bob", 30),
            new Person("Charlie", 35)
        );
        
        List<String> personenInfo = personen.stream()
            .map(person -> person.getName() + " (" + person.getAlter() + ")")
            .collect(Collectors.toList());
        
        System.out.println("Personen-Info: " + personenInfo);
    }
    
    private static void reduceDemo(List<Integer> zahlen) {
        System.out.println("\n--- Reduce Demo ---");
        
        // Summe mit Reduce
        Optional<Integer> summe = zahlen.stream()
            .reduce((a, b) -> a + b);
        
        System.out.println("Summe: " + summe.orElse(0));
        
        // Produkt mit Reduce
        Optional<Integer> produkt = zahlen.stream()
            .reduce((a, b) -> a * b);
        
        System.out.println("Produkt: " + produkt.orElse(1));
        
        // Maximum mit Reduce
        Optional<Integer> maximum = zahlen.stream()
            .reduce(Integer::max);
        
        System.out.println("Maximum: " + maximum.orElse(0));
        
        // Reduce mit Identitätswert
        int summeMitIdentitaet = zahlen.stream()
            .reduce(0, Integer::sum);
        
        System.out.println("Summe mit Identität: " + summeMitIdentitaet);
        
        // String-Verkettung
        List<String> woerter = Arrays.asList("Java", "Stream", "API");
        Optional<String> verkettet = woerter.stream()
            .reduce((a, b) -> a + " " + b);
        
        System.out.println("Verkettet: " + verkettet.orElse(""));
    }
    
    private static void collectDemo(List<String> namen, List<Integer> zahlen) {
        System.out.println("\n--- Collect Demo ---");
        
        // To List
        List<String> grossgeschrieben = namen.stream()
            .map(String::toUpperCase)
            .collect(Collectors.toList());
        
        System.out.println("Großgeschrieben: " + grossgeschrieben);
        
        // To Set
        Set<Integer> quadrate = zahlen.stream()
            .map(zahl -> zahl * zahl)
            .collect(Collectors.toSet());
        
        System.out.println("Quadrate als Set: " + quadrate);
        
        // To Map
        Map<String, Integer> namenMap = namen.stream()
            .collect(Collectors.toMap(
                name -> name,           // Key-Mapper
                name -> name.length()   // Value-Mapper
            ));
        
        System.out.println("Namen-Map: " + namenMap);
        
        // Grouping By
        Map<Integer, List<String>> nachLaengeGruppiert = namen.stream()
            .collect(Collectors.groupingBy(String::length));
        
        System.out.println("Nach Länge gruppiert: " + nachLaengeGruppiert);
        
        // Partitioning By
        Map<Boolean, List<Integer>> geradeUngerade = zahlen.stream()
            .collect(Collectors.partitioningBy(zahl -> zahl % 2 == 0));
        
        System.out.println("Partitioniert: " + geradeUngerade);
        
        // Joining
        String namensliste = namen.stream()
            .collect(Collectors.joining(", ", "[", "]"));
        
        System.out.println("Namensliste: " + namensliste);
        
        // Summarizing
        IntSummaryStatistics statistik = zahlen.stream()
            .collect(Collectors.summarizingInt(Integer::intValue));
        
        System.out.println("Statistik: " + statistik);
    }
    
    private static void methodenreferenzenDemo() {
        System.out.println("\n--- Methodenreferenzen Demo ---");
        
        List<String> namen = Arrays.asList("alice", "bob", "charlie");
        
        // Statische Methodenreferenz
        List<String> grossgeschrieben = namen.stream()
            .map(String::toUpperCase)
            .collect(Collectors.toList());
        
        System.out.println("Statische Referenz: " + grossgeschrieben);
        
        // Instanzmethodenreferenz
        List<Integer> laengen = namen.stream()
            .map(String::length)
            .collect(Collectors.toList());
        
        System.out.println("Instanz-Referenz: " + laengen);
        
        // Konstruktorreferenz
        List<Person> personen = namen.stream()
            .map(name -> new Person(name, 20 + name.length()))
            .collect(Collectors.toList());
        
        System.out.println("Konstruktor-Referenz: " + 
                          personen.stream()
                                 .map(Person::getName)
                                 .collect(Collectors.toList()));
    }
    
    // Hilfsklasse
    static class Person {
        private String name;
        private int alter;
        
        public Person(String name, int alter) {
            this.name = name;
            this.alter = alter;
        }
        
        public String getName() { return name; }
        public int getAlter() { return alter; }
    }
}

2. Operaciones avanzadas con Streams

import java.util.*;
import java.util.stream.*;
import java.util.function.*;

public class FortgeschritteneStreams {
    
    public static void main(String[] args) {
        System.out.println("=== Operaciones avanzadas con Streams ===");
        
        // Datos para las demostraciones
        List<Student> studenten = Arrays.asList(
            new Student("Alice", "Informatik", 85, 3),
            new Student("Bob", "Mathematik", 92, 2),
            new Student("Charlie", "Informatik", 78, 4),
            new Student("Diana", "Physik", 88, 1),
            new Student("Eve", "Informatik", 95, 2),
            new Student("Frank", "Mathematik", 73, 3)
        );
        
        // Ordenamiento
        sortierungDemo(studenten);
        
        // Limit y Skip
        limitSkipDemo(studenten);
        
        // Distinct
        distinctDemo();
        
        // Operaciones Match
        matchDemo(studenten);
        
        // Operaciones Find
        findDemo(studenten);
        
        // Manejo de Optional
        optionalDemo(studenten);
        
        // Parallel Streams
        parallelStreamDemo(studenten);
    }
    
    private static void sortierungDemo(List<Student> studenten) {
        System.out.println("\n--- Demo de Ordenamiento ---");
        
        // Ordenar por nota
        List<Student> nachNote = studenten.stream()
            .sorted(Comparator.comparing(Student::getNote))
            .collect(Collectors.toList());
        
        System.out.println("Por nota ascendente:");
        nachNote.forEach(s -> System.out.println("  " + s.getName() + ": " + s.getNote()));
        
        // Ordenar por nota descendente
        List<Student> nachNoteAbsteigend = studenten.stream()
            .sorted(Comparator.comparing(Student::getNote).reversed())
            .collect(Collectors.toList());
        
        System.out.println("\nPor nota descendente:");
        nachNoteAbsteigend.forEach(s -> System.out.println("  " + s.getName() + ": " + s.getNote()));
        
        // Ordenamiento por múltiples criterios
        List<Student> mehrkriterium = studenten.stream()
            .sorted(Comparator
                .comparing(Student::getFach)
                .thenComparing(Student::getNote)
                .thenComparing(Student::getName))
            .collect(Collectors.toList());
        
        System.out.println("\nPor materia, nota y nombre:");
        mehrkriterium.forEach(s -> System.out.println("  " + s.getFach() + " - " + 
                                                      s.getName() + ": " + s.getNote()));
    }
    
    private static void limitSkipDemo(List<Student> studenten) {
        System.out.println("\n--- Demo de Limit y Skip ---");
        
        // Primeros 3 estudiantes
        List<Student> ersteDrei = studenten.stream()
            .limit(3)
            .collect(Collectors.toList());
        
        System.out.println("Primeros 3 estudiantes:");
        ersteDrei.forEach(s -> System.out.println("  " + s.getName()));
        
        // Omitir los primeros 2
        List<Student> nachUeberspringen = studenten.stream()
            .skip(2)
            .collect(Collectors.toList());
        
        System.out.println("\nDespués de omitir los primeros 2:");
        nachUeberspringen.forEach(s -> System.out.println("  " + s.getName()));
        
        // Paginación (página 2, 2 elementos por página)
        int seite = 2;
        int groesse = 2;
        List<Student> paginiert = studenten.stream()
            .skip((seite - 1) * groesse)
            .limit(groesse)
            .collect(Collectors.toList());
        
        System.out.println("\nPágina " + seite + " (tamaño " + groesse + "):");
        paginiert.forEach(s -> System.out.println("  " + s.getName()));
    }
    
    private static void distinctDemo() {
        System.out.println("\n--- Demo de Distinct ---");
        
        List<Integer> zahlenMitDuplikaten = Arrays.asList(1, 2, 2, 3, 4, 4, 4, 5, 1);
        
        List<Integer> eindeutigeZahlen = zahlenMitDuplikaten.stream()
            .distinct()
            .collect(Collectors.toList());
        
        System.out.println("Con duplicados: " + zahlenMitDuplikaten);
        System.out.println("Únicos: " + eindeutigeZahlen);
        
        // Distinct con objetos
        List<String> faecher = Arrays.asList("Informatik", "Mathematik", "Informatik", 
                                           "Physik", "Mathematik", "Informatik");
        
        List<String> eindeutigeFaecher = faecher.stream()
            .distinct()
            .collect(Collectors.toList());
        
        System.out.println("\nMaterias con duplicados: " + faecher);
        System.out.println("Materias únicas: " + eindeutigeFaecher);
    }
    
    private static void matchDemo(List<Student> studenten) {
        System.out.println("\n--- Demo de Match ---");
        
        // All Match - todos cumplen la condición
        boolean alleBestanden = studenten.stream()
            .allMatch(s -> s.getNote() >= 50);
        
        System.out.println("Todos aprobaron: " + alleBestanden);
        
        boolean alleInformatik = studenten.stream()
            .allMatch(s -> s.getFach().equals("Informatik"));
        
        System.out.println("Todos Informatik: " + alleInformatik);
        
        // Any Match - al menos uno cumple la condición
        boolean jemandInformatik = studenten.stream()
            .anyMatch(s -> s.getFach().equals("Informatik"));
        
        System.out.println("Alguien estudia Informatik: " + jemandInformatik);
        
        boolean jemandSehrGut = studenten.stream()
            .anyMatch(s -> s.getNote() >= 90);
        
        System.out.println("Alguien muy bien: " + jemandSehrGut);
        
        // None Match - ninguno cumple la condición
        boolean niemandDurchgefallen = studenten.stream()
            .noneMatch(s -> s.getNote() < 50);
        
        System.out.println("Nadie reprobó: " + niemandDurchgefallen);
    }
    
    private static void findDemo(List<Student> studenten) {
        System.out.println("\n--- Demo de Find ---");
        
        // Find First - primer elemento
        Optional<Student> erster = studenten.stream()
            .findFirst();
        
        erster.ifPresent(s -> System.out.println("Primer estudiante: " + s.getName()));
        
        // Find Any - cualquier elemento (especialmente útil en Parallel Streams)
        Optional<Student> irgendeinInformatiker = studenten.stream()
            .filter(s -> s.getFach().equals("Informatik"))
            .findAny();
        
        irgendeinInformatiker.ifPresent(s -> 
            System.out.println("Un estudiante de Informatik: " + s.getName()));
        
        // Find con predicado complejo
        Optional<Student> besterMathematiker = studenten.stream()
            .filter(s -> s.getFach().equals("Mathematik"))
            .max(Comparator.comparing(Student::getNote));
        
        besterMathematiker.ifPresent(s -> 
            System.out.println("Mejor estudiante de Mathematik: " + s.getName() + " (" + s.getNote() + ")"));
    }
    
    private static void optionalDemo(List<Student> studenten) {
        System.out.println("\n--- Demo de Manejo de Optional ---");
        
        // Optional con Map
        Optional<String> ersterName = studenten.stream()
            .findFirst()
            .map(Student::getName);
        
        ersterName.ifPresent(name -> System.out.println("Primer nombre: " + name));
        
        // Optional con Filter
        Optional<Student> besterStudent = studenten.stream()
            .max(Comparator.comparing(Student::getNote));
        
        String besterName = besterStudent
            .filter(s -> s.getNote() >= 90)
            .map(Student::getName)
            .orElse("Ninguno con 90+ puntos");
        
        System.out.println("Mejor estudiante (90+): " + besterName);
        
        // Optional Chaining
        Optional<String> fachDesBesten = studenten.stream()
            .max(Comparator.comparing(Student::getNote))
            .flatMap(s -> Optional.ofNullable(s.getFach()))
            .map(String::toUpperCase);
        
        fachDesBesten.ifPresent(fach -> 
            System.out.println("Materia del mejor: " + fach));
        
        // Optional con Supplier
        String defaultValue = studenten.stream()
            .filter(s -> s.getName().equals("NichtExistent"))
            .findFirst()
            .map(Student::getName)
            .orElseGet(() -> "Estudiante-Predeterminado");
        
        System.out.println("Valor por defecto: " + defaultValue);
    }
    
    private static void parallelStreamDemo(List<Student> studenten) {
        System.out.println("\n--- Demo de Parallel Stream ---");
        
        // Procesamiento paralelo
        long startZeit = System.currentTimeMillis();
        
        List<String> namenParallel = studenten.parallelStream()
            .filter(s -> s.getNote() > 80)
            .map(Student::getName)
            .sorted()
            .collect(Collectors.toList());
        
        long endZeit = System.currentTimeMillis();
        
        System.out.println("Resultado paralelo: " + namenParallel);
        System.out.println("Tiempo paralelo: " + (endZeit - startZeit) + "ms");
        
        // Comparación con procesamiento secuencial
        startZeit = System.currentTimeMillis();
        
        List<String> namenSequentiell = studenten.stream()
            .filter(s -> s.getNote() > 80)
            .map(Student::getName)
            .sorted()
            .collect(Collectors.toList());
        
        endZeit = System.currentTimeMillis();
        
        System.out.println("\nResultado secuencial: " + namenSequentiell);
        System.out.println("Tiempo secuencial: " + (endZeit - startZeit) + "ms");
        
        // Información de threads en Parallel Stream
        System.out.println("\nInformación de threads en Parallel Stream:");
        studenten.parallelStream()
            .forEach(s -> System.out.println(s.getName() + " en " + 
                                             Thread.currentThread().getName()));
    }
    
    // Clase Student
    static class Student {
        private String name;
        private String fach;
        private int note;
        private int semester;
        
        public Student(String name, String fach, int note, int semester) {
            this.name = name;
            this.fach = fach;
            this.note = note;
            this.semester = semester;
        }
        
        public String getName() { return name; }
        public String getFach() { return fach; }
        public int getNote() { return note; }
        public int getSemester() { return semester; }
    }
}

3. Collectors especializados y operaciones personalizadas

import java.util.*;
import java.util.stream.*;
import java.util.function.*;

public class CollectorsEspecializados {
    
    public static void main(String[] args) {
        System.out.println("=== Demo de Collectors Especializados ===");
        
        // Datos de prueba
        List<Producto> productos = Arrays.asList(
            new Producto("Laptop", "Electrónica", 999.99, 5),
            new Producto("Ratón", "Electrónica", 29.99, 15),
            new Producto("Teclado", "Electrónica", 79.99, 8),
            new Producto("Libro", "Libros", 19.99, 20),
            new Producto("Bolígrafo", "Oficina", 2.99, 50),
            new Producto("Papel", "Oficina", 9.99, 30)
        );
        
        // Agrupación con agregación
        agrupacionConAgregacion(productos);
        
        // Agrupación multinivel
        agrupacionMultinivel(productos);
        
        // Collector personalizado
        demoCollectorPersonalizado();
        
        // Collectors descendentes
        demoCollectorsDescendentes(productos);
        
        // Streams primitivos
        demoStreamsPrivativos();
    }
    
    private static void agrupacionConAgregacion(List<Producto> productos) {
        System.out.println("\n--- Agrupación con Agregación ---");
        
        // Agrupación por categoría con estadísticas
        Map<String, DoubleSummaryStatistics> estadisticasPrecios = productos.stream()
            .collect(Collectors.groupingBy(
                Producto::getCategoria,
                Collectors.summarizingDouble(Producto::getPrecio)
            ));
        
        estadisticasPrecios.forEach((categoria, estadistica) -> {
            System.out.println(categoria + ":");
            System.out.println("  Promedio: " + estadistica.getAverage());
            System.out.println("  Mínimo: " + estadistica.getMin());
            System.out.println("  Máximo: " + estadistica.getMax());
            System.out.println("  Suma: " + estadistica.getSum());
        });
        
        // Agrupación con mapeo
        Map<String, Set<String>> categoriasNombres = productos.stream()
            .collect(Collectors.groupingBy(
                Producto::getCategoria,
                Collectors.mapping(Producto::getNombre, Collectors.toSet())
            ));
        
        System.out.println("\nCategorías con nombres de productos:");
        categoriasNombres.forEach((categoria, nombres) -> 
            System.out.println(categoria + ": " + nombres));
        
        // Agrupación con filtro
        Map<String, List<Producto>> productosCaros = productos.stream()
            .collect(Collectors.groupingBy(
                Producto::getCategoria,
                Collectors.filtering(p -> p.getPrecio() > 50, Collectors.toList())
            ));
        
        System.out.println("\nProductos caros (>50€):");
        productosCaros.forEach((categoria, listaProductos) -> {
            if (!listaProductos.isEmpty()) {
                System.out.println(categoria + ": " + 
                    listaProductos.stream().map(Producto::getNombre).collect(Collectors.toList()));
            }
        });
    }
    
    private static void agrupacionMultinivel(List<Producto> productos) {
        System.out.println("\n--- Agrupación Multinivel ---");
        
        // Agrupar productos por categorías de precio
        Map<String, Map<String, List<Producto>>> multinivel = productos.stream()
            .collect(Collectors.groupingBy(
                p -> p.getPrecio() < 50 ? "Económico" : "Premium",
                Collectors.groupingBy(Producto::getCategoria)
            ));
        
        System.out.println("Agrupación multinivel:");
        multinivel.forEach((categoriaPrecios, mapaCategoria) -> {
            System.out.println(categoriaPrecios + ":");
            mapaCategoria.forEach((categoria, listaProductos) -> {
                System.out.println("  " + categoria + ": " + 
                    listaProductos.stream().map(Producto::getNombre).collect(Collectors.toList()));
            });
        });
    }
    
    private static void demoCollectorPersonalizado() {
        System.out.println("\n--- Demo de Collector Personalizado ---");
        
        List<String> palabras = Arrays.asList("Java", "Stream", "API", "Functional", "Programming");
        
        // Collector personalizado para concatenación de strings con separador y prefijo/sufijo
        Collector<String, StringBuilder, String> collectorStringPersonalizado = Collector.of(
            StringBuilder::new,                    // Supplier
            (builder, str) -> {                     // Accumulator
                if (builder.length() > 0) {
                    builder.append(" | ");
                }
                builder.append(str.toUpperCase());
            },
            StringBuilder::append,                  // Combiner
            StringBuilder::toString,                // Finisher
            Characteristics.IDENTITY_FINISH
        );
        
        String resultado = palabras.stream().collect(collectorStringPersonalizado);
        System.out.println("Resultado del collector personalizado: " + resultado);
        
        // Collector personalizado para estadísticas
        Collector<Integer, int[], Double> collectorPromedio = Collector.of(
            () -> new int[2],                       // [sum, count]
            (acc, num) -> {
                acc[0] += num;                       // sum
                acc[1]++;                           // count
            },
            (acc1, acc2) -> {
                acc1[0] += acc2[0];
                acc1[1] += acc2[1];
                return acc1;
            },
            acc -> acc[1] == 0 ? 0 : (double) acc[0] / acc[1]  // average
        );
        
        List<Integer> numeros = Arrays.asList(10, 20, 30, 40, 50);
        double promedio = numeros.stream().collect(collectorPromedio);
        System.out.println("Promedio personalizado: " + promedio);
    }
    
    private static void demoCollectorsDescendentes(List<Producto> productos) {
        System.out.println("\n--- Demo de Collectors Descendentes ---");
        
        // GroupingBy con Counting
        Map<String, Long> cantidadPorCategoria = productos.stream()
            .collect(Collectors.groupingBy(
                Producto::getCategoria,
                Collectors.counting()
            ));
        
        System.out.println("Cantidad por categoría:");
        cantidadPorCategoria.forEach((categoria, cantidad) -> 
            System.out.println(categoria + ": " + cantidad));
        
        // GroupingBy con Summing
        Map<String, Integer> inventarioPorCategoria = productos.stream()
            .collect(Collectors.groupingBy(
                Producto::getCategoria,
                Collectors.summingInt(Producto::getInventario)
            ));
        
        System.out.println("\nInventario por categoría:");
        inventarioPorCategoria.forEach((categoria, cantidad) -> 
            System.out.println(categoria + ": " + cantidad));
        
        // GroupingBy con maxBy
        Map<String, Optional<Producto>> productoMasCaroPorCategoria = productos.stream()
            .collect(Collectors.groupingBy(
                Producto::getCategoria,
                Collectors.maxBy(Comparator.comparing(Producto::getPrecio))
            ));
        
        System.out.println("\nProducto más caro por categoría:");
        productoMasCaroPorCategoria.forEach((categoria, opcional) -> 
            opcional.ifPresent(p -> System.out.println(categoria + ": " + p.getNombre())));
        
        // CollectingAndThen para resultados postprocesados
        Map<String, String> infoCategoria = productos.stream()
            .collect(Collectors.groupingBy(
                Producto::getCategoria,
                Collectors.collectingAndThen(
                    Collectors.toList(),
                    lista -> lista.size() + " productos, " +
                             String.format("%.2f€", 
                                 lista.stream().mapToDouble(Producto::getPrecio).average().orElse(0))
                )
            ));
        
        System.out.println("\nInfo de categoría:");
        infoCategoria.forEach((categoria, info) -> System.out.println(categoria + ": " + info));
    }
    
    private static void demoStreamsPrivativos() {
        System.out.println("\n--- Demo de Streams Primitivos ---");
        
        // IntStream
        IntStream numeros = IntStream.range(1, 10);
        
        int suma = numeros.sum();
        System.out.println("Suma 1-9: " + suma);
        
        // Con boxed para convertir a stream de objetos
        List<Integer> listaNumeros = IntStream.rangeClosed(1, 5)
            .boxed()
            .collect(Collectors.toList());
        
        System.out.println("Números como List: " + listaNumeros);
        
        // DoubleStream con cálculos
        double[] precios = {19.99, 29.99, 99.99, 149.99};
        
        DoubleSummaryStatistics estadisticasPrecios = Arrays.stream(precios)
            .summaryStatistics();
        
        System.out.println("\nEstadísticas de precios:");
        System.out.println("  Cantidad: " + estadisticasPrecios.getCount());
        System.out.println("  Suma: " + estadisticasPrecios.getSum());
        System.out.println("  Promedio: " + estadisticasPrecios.getAverage());
        System.out.println("  Mín: " + estadisticasPrecios.getMin());
        System.out.println("  Máx: " + estadisticasPrecios.getMax());
        
        // LongStream para números grandes
        long factorial = LongStream.rangeClosed(1, 10)
            .reduce(1, (a, b) -> a * b);
        
        System.out.println("\n10! = " + factorial);
        
        // Stream primitivo con filtro
        long numerosParas = IntStream.rangeClosed(1, 20)
            .filter(n -> n % 2 == 0)
            .count();
        
        System.out.println("Números pares 1-20: " + numerosParas);
        
        // MapToObj para transformación
        List<String> numerosComoStrings = IntStream.rangeClosed(1, 5)
            .mapToObj(n -> "Número " + n)
            .collect(Collectors.toList());
        
        System.out.println("Números como strings: " + numerosComoStrings);
    }
    
    // Clase Producto
    static class Producto {
        private String nombre;
        private String categoria;
        private double precio;
        private int inventario;
        
        public Producto(String nombre, String categoria, double precio, int inventario) {
            this.nombre = nombre;
            this.categoria = categoria;
            this.precio = precio;
            this.inventario = inventario;
        }
        
        public String getNombre() { return nombre; }
        public String getCategoria() { return categoria; }
        public double getPrecio() { return precio; }
        public int getInventario() { return inventario; }
    }
}

Vista General de Interfaces Funcionales

InterfaceMétodoDescripciónEjemplo
Predicate<T>boolean test(T t)Prueba una condicións -> s.length() > 5
Function<T,R>R apply(T t)Transforma valoress -> s.toUpperCase()
Consumer<T>void accept(T t)Consume valoresSystem.out::println
Supplier<T>T get()Proporciona valores() -> new Random()
UnaryOperator<T>T apply(T t)Operación unariax -> x * x
BinaryOperator<T>T apply(T t1, T t2)Operación binaria(a, b) -> a + b

Vista General de Operaciones en Streams

Operaciones Intermedias (Lazy)

// Filter
stream.filter(x -> x > 0)

// Map
stream.map(x -> x * 2)

// FlatMap
stream.flatMap(list -> list.stream())

// Sorted
stream.sorted()
stream.sorted(Comparator.reverseOrder())

// Distinct
stream.distinct()

// Limit/Skip
stream.limit(10)
stream.skip(5)

// Peek (para depuración)
stream.peek(System.out::println)

Operaciones Terminales (Eager)

// ForEach
stream.forEach(System.out::println)

// Collect
stream.collect(Collectors.toList())

// Reduce
stream.reduce((a, b) -> a + b)

// Count
stream.count()

// Min/Max
stream.min(Comparator.naturalOrder())
stream.max(Comparator.reverseOrder())

// Match
stream.anyMatch(x -> x > 0)
stream.allMatch(x -> x > 0)
stream.noneMatch(x -> x > 0)

// Find
stream.findFirst()
stream.findAny()

Tipos de Referencias a Métodos

Referencia a Método Estático

// Lambda: s -> Integer.parseInt(s)
// Referencia: Integer::parseInt
list.stream().map(Integer::parseInt)

Referencia a Método de Instancia

// Lambda: s -> s.toUpperCase()
// Referencia: String::toUpperCase
list.stream().map(String::toUpperCase)

Referencia a Constructor

// Lambda: name -> new Person(name)
// Referencia: Person::new
list.stream().map(Person::new)

Consideraciones de Rendimiento

Cuándo Usar Streams

  • Procesamiento complejo de datos: Operaciones de filtrado, transformación y reducción
  • Legibilidad: Código declarativo en lugar de bucles imperativos
  • Paralelización: Cambio sencillo a procesamiento paralelo
  • Programación funcional: Estructuras de datos inmutables

Cuándo Evitar Streams

  • Operaciones simples: Los bucles tradicionales suelen ser más rápidos
  • Código crítico en rendimiento: El overhead de los streams puede ser relevante
  • Arrays primitivos: Las operaciones especializadas funcionan mejor
  • Colecciones muy pequeñas: El overhead supera los beneficios

Ventajas e Inconvenientes

Ventajas de Stream API

  • Legibilidad: Sintaxis declarativa y expresiva
  • Composición: Operaciones encadenables con facilidad
  • Paralelización: Cambio sencillo a procesamiento paralelo
  • Funcional: Soporte para programación funcional
  • Lazy Evaluation: Procesamiento eficiente

Inconvenientes

  • Rendimiento: Overhead en operaciones simples
  • Depuración: Más difícil que bucles imperativos
  • Curva de aprendizaje: Conceptos y sintaxis nuevos
  • Memoria: Las colecciones intermedias pueden consumir memoria

Preguntas Frecuentes en Exámenes

  1. ¿Cuál es la diferencia entre operaciones intermedias y terminales? Las intermedias son lazy y devuelven un stream, mientras que las terminales son eager y finalizan el procesamiento.

  2. ¡Explica las expresiones lambda! Son funciones anónimas con sintaxis compacta: (parámetro) -> expresión o (parámetro) -> { sentencias }.

  3. ¿Cuándo usas referencias a métodos? Como alternativa más corta a expresiones lambda cuando ya existe un método que se ajusta perfectamente.

  4. ¿Cuál es la ventaja de los streams paralelos? Procesamiento automático en paralelo en sistemas multi-núcleo para mejor rendimiento.

Fuentes Principales

  1. https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
  2. https://docs.oracle.com/javase/tutorial/collections/streams/
  3. https://www.baeldung.com/java-8-streams
Volver al blog
Share:

Entradas relacionadas