Uso de la API Stream en Java

Datos de prueba:

public class DatosEmpleado {
    
    public static List<Empleado> obtenerEmpleados(){
        List<Empleado> lista = new ArrayList<>();
        
        lista.add(new Empleado(1001, "Ana García", 34, 6000.38));
        lista.add(new Empleado(1002, "Carlos López", 28, 9876.12));
        lista.add(new Empleado(1003, "María Rodríguez", 33, 3000.82));
        lista.add(new Empleado(1004, "Pedro Martínez", 26, 7657.37));
        lista.add(new Empleado(1005, "Laura Sánchez", 65, 5555.32));
        lista.add(new Empleado(1006, "David Fernández", 42, 9500.43));
        lista.add(new Empleado(1007, "Elena Pérez", 26, 4333.32));
        lista.add(new Empleado(1008, "Javier Gómez", 35, 2500.32));
        
        return lista;
    }
    
}

Tres etapas principales

1. Creación del Stream

En Java 8, las interfaces de colección proporcionan dos métodos para generar streams:

  • stream() - Crea un stream secuencial
  • parallelStream() - Crea un stream paralelo
  • Método estático stream() de Arrays
  • Método of() de Stream

2. Operaciones intermedias del Stream

Múltiples operaciones intermedias pueden encadenarse para formar una pipeline.

2.1. Filtrado y segmentación
2.2. Transformación
2.3. Ordenación

3. Operaciones terminales del Stream

Las operaciones terminales producen resultados a partir de la pipeline. El resultado puede ser cualquier valor que no sea un stream, como List, Integer, o incluso void.

Una vez que se ejecuta una operación terminal, el stream no puede reutilizarse.

  • count() retorna el número total de elementos
  • max(Comparator c) retorna el valor máximo
  • min(Comparator c) retorna el valor mínimo
  • forEach(Consumer c) iteración interna (a diferencia de las colecciones que requieren iteración externa)

Reducción y recolección

  • reduce(T iden, BinaryOperator b) combina repetidamente los elementos del stream para producir un valor. Retorna T
  • collect(Collector c) transforma el stream a otra forma. Recibe una implementación de Collectro para realizar operaciones de resumen
  1. Ejemplo uno
/**
 * 1. Stream se enfoca en operaciones con datos, interactuando con la CPU
 *    Las colecciones se enfocan en almacenamiento de datos, interactuando con la memoria
 *
 * 2. Características principales:
 * ① Stream no almacena elementos por sí mismo
 * ② Stream no modifica el objeto fuente. En su lugar, retorna un nuevo Stream con el resultado
 * ③ Las operaciones de Stream son de ejecución diferida. Se ejecutan solo cuando se necesita el resultado
 *
 * 3. Flujo de ejecución de Stream:
 * ① Instanciación del Stream
 * ② Serie de operaciones intermedias (filtrado, transformación, ...)
 * ③ Operación terminal
 *
 * 4. Explicación:
 * 4.1 Una cadena de operaciones intermedias procesa los datos de la fuente
 * 4.2 Al ejecutar la operación terminal, se ejecuta la cadena de operaciones y se produce el resultado
 */
public class PruebaStreamAPI {

    //Método 1: Creación mediante colección
    @Test
    public void prueba1(){
        List<Empleado> empleados = DatosEmpleado.obtenerEmpleados();

        // Stream secuencial
        Stream<Empleado> stream = empleados.stream();

        // Stream paralelo
        Stream<Empleado> streamParalelo = empleados.parallelStream();
    }

    //Método 2: Creación mediante array
    @Test
    public void prueba2(){
        int[] numeros = {1,2,3,4,5,6};
        IntStream streamNumeros = Arrays.stream(numeros);

        Empleado emp1 = new Empleado(1001,"Juan");
        Empleado emp2 = new Empleado(1002,"María");
        Empleado[] empleadosArray = {emp1, emp2};
        Stream<Empleado> streamEmpleados = Arrays.stream(empleadosArray);
    }

    //Método 3: Creación mediante Stream.of()
    @Test
    public void prueba3(){
        Stream<Integer> stream = Stream.of(10, 20, 30, 40, 50);
    }

    //Método 4: Creación de stream infinito
    @Test
    public void prueba4(){
        // Iteración - primeros 10 números pares
        Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);

        // Generación - 10 números aleatorios
        Stream.generate(Math::random).limit(10).forEach(System.out::println);
    }
}

/**
 * Prueba de operaciones intermedias del Stream
 */
public class PruebaStreamOperacionesIntermedias {

    //1-Filtrado y segmentación
    @Test
    public void pruebaFiltrado(){
        List<Empleado> empleados = DatosEmpleado.obtenerEmpleados();
        
        // Filtrado por salario
        empleados.stream()
                .filter(emp -> emp.getSalario() > 7000)
                .forEach(System.out::println);

        System.out.println();
        // Limitar resultados
        empleados.stream().limit(3).forEach(System.out::println);
        
        System.out.println();
        // Saltar elementos
        empleados.stream().skip(2).forEach(System.out::println);

        System.out.println();
        // Eliminar duplicados
        empleados.add(new Empleado(1010,"Carlos Ruiz",40,8000));
        empleados.add(new Empleado(1010,"Carlos Ruiz",40,8000));
        
        empleados.stream().distinct().forEach(System.out::println);
    }

    //Transformación
    @Test
    public void pruebaTransformacion(){
        List<String> textos = Arrays.asList("java", "stream", "api", "ejemplo");
        
        // Transformar a mayúsculas
        textos.stream()
              .map(String::toUpperCase)
              .forEach(System.out::println);

        // Nombres de empleados con longitud mayor a 5
        List<Empleado> empleados = DatosEmpleado.obtenerEmpleados();
        empleados.stream()
                .map(Empleado::getNombre)
                .filter(nombre -> nombre.length() > 5)
                .forEach(System.out::println);

        System.out.println();
        // Transformación plana
        Stream<Character> caracteres = textos.stream()
                                           .flatMap(this::convertirACaracteres);
        caracteres.forEach(System.out::println);
    }

    // Convertir string a stream de caracteres
    private Stream<Character> convertirACaracteres(String texto){
        List<Character> listaCaracteres = new ArrayList<>();
        for(char c : texto.toCharArray()){
            listaCaracteres.add(c);
        }
        return listaCaracteres.stream();
    }

    //3-Ordenación
    @Test
    public void pruebaOrdenacion(){
        // Ordenación natural
        List<Integer> numeros = Arrays.asList(15, 8, 23, 4, 42, 11);
        numeros.stream().sorted().forEach(System.out::println);

        // Ordenación personalizada
        List<Empleado> empleados = DatosEmpleado.obtenerEmpleados();
        empleados.stream().sorted((e1, e2) -> {
            int comparacionEdad = Integer.compare(e1.getEdad(), e2.getEdad());
            if(comparacionEdad != 0){
                return comparacionEdad;
            } else {
                return Double.compare(e1.getSalario(), e2.getSalario());
            }
        }).forEach(System.out::println);
    }
}

/**
 * Prueba de operaciones terminales del Stream
 */
public class PruebaStreamOperacionesTerminales {

    //1-Coincidencia y búsqueda
    @Test
    public void pruebaCoincidencia(){
        List<Empleado> empleados = DatosEmpleado.obtenerEmpleados();

        // Verificar si todos los empleados son mayores de 18
        boolean todosMayores = empleados.stream()
                                      .allMatch(emp -> emp.getEdad() > 18);
        System.out.println("Todos mayores de 18: " + todosMayores);

        // Verificar si algún empleado gana más de 9000
        boolean algunoAltoSalario = empleados.stream()
                                           .anyMatch(emp -> emp.getSalario() > 9000);
        System.out.println("Algún salario alto: " + algunoAltoSalario);

        // Primer empleado
        Optional<Empleado> primerEmpleado = empleados.stream().findFirst();
        System.out.println("Primer empleado: " + primerEmpleado);
    }

    @Test
    public void pruebaBusqueda(){
        List<Empleado> empleados = DatosEmpleado.obtenerEmpleados();
        
        // Contar empleados con salario mayor a 5000
        long cantidad = empleados.stream()
                               .filter(emp -> emp.getSalario() > 5000)
                               .count();
        System.out.println("Empleados con salario > 5000: " + cantidad);

        // Salario máximo
        Optional<Double> salarioMaximo = empleados.stream()
                                                .map(Empleado::getSalario)
                                                .max(Double::compare);
        System.out.println("Salario máximo: " + salarioMaximo);

        // Empleado con salario mínimo
        Optional<Empleado> empleadoMinSalario = empleados.stream()
                                                       .min((e1, e2) -> 
                                                       Double.compare(e1.getSalario(), e2.getSalario()));
        System.out.println("Empleado mínimo salario: " + empleadoMinSalario);
    }

    //2-Reducción
    @Test
    public void pruebaReduccion(){
        // Suma de números
        List<Integer> numeros = Arrays.asList(5, 10, 15, 20, 25);
        Integer suma = numeros.stream().reduce(0, Integer::sum);
        System.out.println("Suma: " + suma);

        // Suma total de salarios
        List<Empleado> empleados = DatosEmpleado.obtenerEmpleados();
        Optional<Double> totalSalarios = empleados.stream()
                                                .map(Empleado::getSalario)
                                                .reduce((s1, s2) -> s1 + s2);
        System.out.println("Total salarios: " + totalSalarios.get());
    }

    //3-Recolección
    @Test
    public void pruebaRecoleccion(){
        List<Empleado> empleados = DatosEmpleado.obtenerEmpleados();
        
        // Empleados con salario > 6000 como Lista
        List<Empleado> empleadosAltosSalarios = empleados.stream()
                                                       .filter(emp -> emp.getSalario() > 6000)
                                                       .collect(Collectors.toList());
        empleadosAltosSalarios.forEach(System.out::println);

        System.out.println();
        // Como Set
        Set<Empleado> conjuntoEmpleados = empleados.stream()
                                                 .filter(emp -> emp.getSalario() > 6000)
                                                 .collect(Collectors.toSet());
        conjuntoEmpleados.forEach(System.out::println);
    }
}

Etiquetas: java Stream API programación funcional Operaciones Intermedias Operaciones Terminales

Publicado el 9-6 19:58