Introducci贸n a las secuencias de Java 8

    Introducci贸n

    El tema principal de este art铆culo son los temas de procesamiento de datos avanzados que utilizan una nueva funcionalidad agregada a Java 8: la API Stream y la API Collector.

    Para aprovechar al m谩ximo este art铆culo, ya debe estar familiarizado con las principales API de Java, la Object y String clases y la API de colecci贸n.

    API de transmisi贸n

    los java.util.stream El paquete consta de clases, interfaces y muchos tipos para permitir operaciones de estilo funcional sobre elementos. Java 8 introduce un concepto de Stream que permite al programador procesar datos de forma descriptiva y confiar en una arquitectura de m煤ltiples n煤cleos sin la necesidad de escribir ning煤n c贸digo especial.

    驴Qu茅 es un Stream?

    UN Stream representa una secuencia de objetos derivados de una fuente, sobre la cual se pueden realizar operaciones agregadas.

    Desde un punto de vista puramente t茅cnico, una secuencia es una interfaz escrita, una secuencia de T. Esto significa que una secuencia se puede definir para cualquier tipo de objeto, una secuencia de n煤meros, una secuencia de caracteres, una secuencia de personas o incluso un arroyo de una ciudad.

    Desde el punto de vista del desarrollador, es un concepto nuevo que puede parecer una colecci贸n, pero de hecho es muy diferente de una colecci贸n.

    Hay algunas definiciones clave que debemos analizar para comprender esta noci贸n de Stream y por qu茅 se diferencia de una colecci贸n:

    Una secuencia no contiene datos

    El concepto err贸neo m谩s com煤n que me gustar铆a abordar primero: una corriente no contener cualquier dato. Es muy importante tenerlo en cuenta y comprenderlo.

    No hay datos en una secuencia, sin embargo, hay datos almacenados en una colecci贸n.

    UN Collection es una estructura que contiene sus datos. Un Stream est谩 ah铆 para procesar los datos y extraerlos de la fuente dada, o moverlos a un destino. La fuente puede ser una colecci贸n, aunque tambi茅n puede ser una matriz o un recurso de E / S. La transmisi贸n se conectar谩 a la fuente, consumir谩 los datos y procesar谩 los elementos que contiene de alguna manera.

    Una transmisi贸n no deber铆a modificar la fuente

    Una secuencia no debe modificar la fuente de los datos que procesa. En realidad, esto no lo hace cumplir el compilador de la JVM en s铆, por lo que es simplemente un contrato. Si voy a construir mi propia implementaci贸n de una secuencia, no debo modificar la fuente de los datos que estoy procesando. Aunque est谩 perfectamente bien modificar los datos en la transmisi贸n.

    驴Por qu茅 es as铆? Porque si queremos procesar estos datos en paralelo, los vamos a distribuir entre todos los n煤cleos de nuestros procesadores y no queremos tener ning煤n tipo de problemas de visibilidad o sincronizaci贸n que puedan dar lugar a malos rendimientos o errores. Evitar este tipo de interferencia significa que no debemos modificar la fuente de los datos mientras los procesamos.

    Una fuente puede ser ilimitada

    Probablemente el punto m谩s poderoso de estos tres. Significa que el flujo en s铆 mismo puede procesar tantos datos como queramos. Ilimitado no significa que una fuente deba ser infinita. De hecho, una fuente puede ser finita, pero es posible que no tengamos acceso a los elementos contenidos en esa fuente.

    Suponga que la fuente es un archivo de texto simple. Un archivo de texto tiene un tama帽o conocido incluso si es muy grande. Suponga tambi茅n que los elementos de esa fuente son, de hecho, las l铆neas de este archivo de texto.

    Ahora, es posible que sepamos el tama帽o exacto de este archivo de texto, pero si no lo abrimos y revisamos manualmente el contenido, nunca sabremos cu谩ntas l铆neas tiene. Esto es lo que significa ilimitado: es posible que no siempre sepamos de antemano la cantidad de elementos que una secuencia procesar谩 desde la fuente.

    Esas son las tres definiciones de una corriente. De modo que podemos ver en esas tres definiciones que un flujo realmente no tiene nada que ver con una colecci贸n. Una colecci贸n contiene sus datos. Una colecci贸n puede modificar los datos que contiene. Y, por supuesto, una colecci贸n contiene una cantidad conocida y finita de datos.

    Caracter铆sticas de la corriente

    • Secuencia de elementos: las secuencias proporcionan un conjunto de elementos de un tipo particular de manera secuencial. La secuencia obtiene un elemento a pedido y nunca almacena un elemento.
    • Fuente: las secuencias toman una colecci贸n, una matriz o recursos de E / S como fuente para sus datos.
    • Operaciones agregadas: las transmisiones admiten operaciones agregadas como forEach, filtro, mapa, ordenado, coincidencia y otras.
    • Anulaci贸n: la mayor铆a de las operaciones sobre una secuencia devuelven una secuencia, lo que significa que sus resultados se pueden encadenar. La funci贸n de estas operaciones es tomar datos de entrada, procesarlos y devolver la salida de destino. los collect() El m茅todo es una operaci贸n de terminal que generalmente est谩 presente al final de las operaciones para indicar el final del procesamiento de la secuencia.
    • Iteraciones automatizadas: las operaciones de transmisi贸n llevan a cabo iteraciones internamente sobre la fuente de los elementos, a diferencia de las colecciones donde se requiere una iteraci贸n expl铆cita.

    Crear una secuencia

    Podemos generar un flujo con la ayuda de algunos m茅todos:

    corriente()

    los stream() El m茅todo devuelve la secuencia secuencial con una colecci贸n como fuente. Puede utilizar cualquier colecci贸n de objetos como fuente:

    private List<String> list = new Arrays.asList("Scott", "David", "Josh");
    list.stream();
    
    paraleloStream ()

    los parallelStream() El m茅todo devuelve una secuencia paralela con una colecci贸n como fuente:

    private List<String> list = new Arrays.asList("Scott", "David", "Josh");
    list.parallelStream().forEach(element -> method(element));
    

    Lo que pasa con los flujos paralelos es que al ejecutar una operaci贸n de este tipo, el tiempo de ejecuci贸n de Java segrega el flujo en m煤ltiples subflujos. Ejecuta las operaciones agregadas y combina el resultado. En nuestro caso, llama al method con cada elemento de la corriente en paralelo.

    Sin embargo, esto puede ser un arma de doble filo, ya que ejecutar operaciones pesadas de esta manera podr铆a bloquear otros flujos paralelos ya que bloquea los subprocesos en el grupo.

    Corriente de()

    La est谩tica of() El m茅todo se puede utilizar para crear una secuencia a partir de una matriz de objetos u objetos individuales:

    Stream.of(new Employee("David"), new Employee("Scott"), new Employee("Josh"));
    
    Stream.builder ()

    Y por 煤ltimo, puede utilizar la est谩tica .builder() m茅todo para crear una secuencia de objetos:

    Stream.builder<String> streamBuilder = Stream.builder();
    
    streamBuilder.accept("David");
    streamBuilder.accept("Scott");
    streamBuilder.accept("Josh");
    
    Stream<String> stream = streamBuilder.build();
    

    Llamando al .build() , empaquetamos los objetos aceptados en un Stream normal.

    Filtrar con una corriente

    public class FilterExample {
        public static void main(String[] args) {
        List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Orange");
    
        // Traditional approach
        for (String fruit : fruits) {
            if (!fruit.equals("Orange")) {
                System.out.println(fruit + " ");
            }
        }
    
        // Stream approach
        fruits.stream() 
                .filter(fruit -> !fruit.equals("Orange"))
                .forEach(fruit -> System.out.println(fruit));
        }
    }
    

    Un enfoque tradicional para filtrar una sola fruta ser铆a con un bucle cl谩sico para cada uno.

    El segundo enfoque utiliza una secuencia para filtrar los elementos de la secuencia que coinciden con el predicado dado, en una nueva secuencia que es devuelta por el m茅todo.

    Adem谩s, este enfoque utiliza una forEach() m茅todo, que realiza una acci贸n para cada elemento del flujo devuelto. Puede reemplazar esto con algo llamado referencia de m茅todo. En Java 8, una referencia de m茅todo es la sintaxis abreviada de una expresi贸n lambda que ejecuta solo un m茅todo.

    La sintaxis de referencia del m茅todo es simple e incluso puede reemplazar la expresi贸n lambda anterior .filter(fruit -> !fruit.equals("Orange")) con eso:

    Object::method;
    

    Actualicemos el ejemplo y usemos referencias de m茅todos y veamos c贸mo se ve:

    public class FilterExample {
        public static void main(String[] args) {
        List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Orange");
    
        fruits.stream()
                .filter(FilterExample::isNotOrange)
                .forEach(System.out::println);
        }
        
        private static boolean isNotOrange(String fruit) {
            return !fruit.equals("Orange");
        }
    }
    

    Las transmisiones son m谩s f谩ciles y mejores de usar con expresiones Lambda y este ejemplo destaca lo simple y limpia que se ve la sintaxis en comparaci贸n con el enfoque tradicional.

    Mapeo con una corriente

    Un enfoque tradicional ser铆a iterar a trav茅s de una lista con un bucle for mejorado:

    List<String> models = Arrays.asList("BMW", "Audi", "Peugeot", "Fiat");
    
    System.out.print("Imperative style: " + "n");
    
    for (String car : models) {
        if (!car.equals("Fiat")) {
            Car model = new Car(car);
            System.out.println(model);
        }
    }
    

    Por otro lado, un enfoque m谩s moderno es utilizar un Stream para mapear:

    List<String> models = Arrays.asList("BMW", "Audi", "Peugeot", "Fiat");
            
    System.out.print("Functional style: " + "n");
    
    models.stream()
            .filter(model -> !model.equals("Fiat"))
    //      .map(Car::new)                 // Method reference approach
    //      .map(model -> new Car(model))  // Lambda approach
            .forEach(System.out::println);
    

    Para ilustrar el mapeo, considere esta clase:

    private String name;
        
    public Car(String model) {
        this.name = model;
    }
    
    // getters and setters
    
    @Override
    public String toString() {
        return "name="" + name + """;
    }
    

    Es importante se帽alar que el models lista es una lista de cadenas, no una lista de Car. los .map() El m茅todo espera un objeto de tipo T y devuelve un objeto de tipo R.

    Estamos convirtiendo String en un tipo de autom贸vil, esencialmente.

    Si ejecuta este c贸digo, el estilo imperativo y el estilo funcional deber铆an devolver lo mismo.

    Recolectar con una corriente

    A veces, querr谩 convertir una secuencia en una colecci贸n o mapa. Utilizando la clase de utilidad Collectors y las funcionalidades que ofrece:

    List<String> models = Arrays.asList("BMW", "Audi", "Peugeot", "Fiat");
    
    List<Car> carList = models.stream()
            .filter(model -> !model.equals("Fiat"))
            .map(Car::new)
            .collect(Collectors.toList());
    

    Coincidencia con una corriente

    Una tarea cl谩sica es categorizar objetos de acuerdo con ciertos criterios. Podemos hacer esto al hacer coincidir la informaci贸n necesaria con la informaci贸n del objeto y verificar si eso es lo que necesitamos:

    List<Car> models = Arrays.asList(new Car("BMW", 2011), new Car("Audi", 2018), new Car("Peugeot", 2015));
    
    boolean all = models.stream().allMatch(model -> model.getYear() > 2010);
    System.out.println("Are all of the models newer than 2010: " + all);
    
    boolean any = models.stream().anyMatch(model -> model.getYear() > 2016);
    System.out.println("Are there any models newer than 2016: " + any);
    
    boolean none = models.stream().noneMatch(model -> model.getYear() < 2010);
    System.out.println("Is there a car older than 2010: " + none);
    
    • allMatch() – Devoluciones true si todos los elementos de esta secuencia coinciden con el predicado proporcionado.
    • anyMatch() – Devoluciones true si alg煤n elemento de esta secuencia coincide con el predicado proporcionado.
    • noneMatch() – Devoluciones true si ning煤n elemento de esta secuencia coincide con el predicado proporcionado.

    En el ejemplo de c贸digo anterior, todos los predicados dados se satisfacen y todos devolver谩n true.

    Conclusi贸n

    La mayor铆a de las personas utilizan Java 8. Aunque no todo el mundo utiliza Streams. El hecho de que representen un enfoque m谩s nuevo de la programaci贸n y representen un toque con la programaci贸n de estilo funcional junto con las expresiones lambda para Java, no significa necesariamente que sea un enfoque mejor. Simplemente ofrecen una nueva forma de hacer las cosas. Depende de los propios desarrolladores decidir si confiar en la programaci贸n de estilo funcional o imperativo. Con un nivel suficiente de ejercicio, la combinaci贸n de ambos principios puede ayudarlo a mejorar su software.

    Como siempre, le recomendamos que consulte el documentaci贸n oficial para informacion adicional.

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *