1. Lista de métodos de la Streamclase

La Streamclase se creó para facilitar la construcción de cadenas de flujos de datos. Para lograr esto, la Stream<T>clase tiene métodos que devuelven nuevos Streamobjetos.

Cada uno de estos flujos de datos realiza una acción simple, pero si los combina en cadenas y agrega funciones lambda interesantes , entonces tiene un mecanismo poderoso para generar el resultado que desea. Pronto lo verás por ti mismo.

Estos son los métodos de la Streamclase (solo los más básicos):

Métodos Descripción
Stream<T> of()
Crea una secuencia a partir de un conjunto de objetos.
Stream<T> generate()
Genera un flujo de acuerdo con la regla especificada
Stream<T> concat()
Concatena dos flujos
Stream<T> filter()
Filtra los datos, solo pasa los datos que coinciden con la regla especificada
Stream<T> distinct()
Elimina duplicados. No transmite datos que ya se han encontrado
Stream<T> sorted()
Ordena los datos
Stream<T> peek()
Realiza una acción en cada elemento de la secuencia.
Stream<T> limit(n)
Devuelve una secuencia que se trunca para que no supere el límite especificado
Stream<T> skip(n)
Salta los primeros n elementos
Stream<R> map()
Convierte datos de un tipo a otro
Stream<R> flatMap()
Convierte datos de un tipo a otro
boolean anyMatch()
Comprueba si hay al menos un elemento en la transmisión que coincida con la regla especificada
boolean allMatch()
Comprueba si todos los elementos de la secuencia coinciden con la regla especificada
boolean noneMatch()
Comprueba si ninguno de los elementos de la secuencia coincide con la regla especificada
Optional<T> findFirst()
Devuelve el primer elemento encontrado que coincide con la regla
Optional<T> findAny()
Devuelve cualquier elemento en la transmisión que coincida con la regla
Optional<T> min()
Busca el elemento mínimo en el flujo de datos
Optional<T> max()
Devuelve el elemento máximo en el flujo de datos
long count()
Devuelve el número de elementos en el flujo de datos
R collect()
Lee todos los datos de la secuencia y los devuelve como una colección

2. Operaciones intermedias y terminales por la Streamclase

Como puede ver, no todos los métodos de la tabla anterior devuelven un Stream. Esto está relacionado con el hecho de que los métodos de la Streamclase se pueden dividir en métodos intermedios (también conocidos como no terminales ) y métodos terminales .

métodos intermedios

Los métodos intermedios devuelven un objeto que implementa la Streaminterfaz y se pueden encadenar.

métodos terminales

Los métodos de terminal devuelven un valor que no es un Stream.

Canalización de llamada de método

Por lo tanto, puede crear una canalización de transmisión que consta de cualquier cantidad de métodos intermedios y una sola llamada de método de terminal al final. Este enfoque le permite implementar una lógica bastante compleja, al tiempo que aumenta la legibilidad del código.

Canalización de llamada de método

Los datos dentro de un flujo de datos no cambian en absoluto. Una cadena de métodos intermedios es una forma ingeniosa (declarativa) de especificar una canalización de procesamiento de datos que se ejecutará después de llamar al método terminal.

En otras palabras, si no se llama al método terminal, los datos en el flujo de datos no se procesan de ninguna manera. Solo después de llamar al método de terminal, los datos comienzan a procesarse de acuerdo con las reglas especificadas en la canalización de flujo.

stream()
  .intemediateOperation1()
  .intemediateOperation2()
  ...
  .intemediateOperationN()
  .terminalOperation();
Aspecto general de una tubería.

Comparación de métodos intermedios y terminales:

intermedio Terminal
Tipo de retorno Stream No unStream
Se puede combinar con múltiples métodos del mismo tipo para formar una canalización No
Número de métodos en una sola canalización cualquier no más de uno
Produce el resultado final No
Comienza a procesar los datos en la transmisión. No

Veamos un ejemplo.

Supongamos que tenemos un club para amantes de los animales. Mañana el club celebra el Día del Gato Pelirrojo. El club tiene dueños de mascotas, cada uno de los cuales tiene una lista de mascotas. No se limitan a los gatos.

Tarea: debe identificar todos los nombres de todos los gatos pelirrojos para crear tarjetas de felicitación personalizadas para ellos para las "vacaciones profesionales" de mañana. Las tarjetas de felicitación deben ordenarse por la edad del gato, de mayor a menor.

Primero, proporcionamos algunas clases para ayudar a resolver esta tarea:

public enum Color {
   WHITE,
   BLACK,
   DARK_GREY,
   LIGHT_GREY,
   FOXY,
   GREEN,
   YELLOW,
   BLUE,
   MAGENTA
}
public abstract class Animal {
   private String name;
   private Color color;
   private int age;

   public Animal(String name, Color color, int age) {
      this.name = name;
      this.color = color;
      this.age = age;
   }

   public String getName() {
      return name;
   }

   public Color getColor() {
      return color;
   }

   public int getAge() {
      return age;
   }
}
public class Cat extends Animal {
   public Cat(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Dog extends Animal {
   public Dog(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Parrot extends Animal {
   public Parrot(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Pig extends Animal {
   public Pig(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Snake extends Animal {
   public Snake(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Owner {
   private String name;
   private List<Animal> pets = new ArrayList<>();

   public Owner(String name) {
      this.name = name;
   }

   public List<Animal> getPets() {
      return pets;
   }
}

Ahora veamos la Selectorclase, donde la selección se realizará de acuerdo con los criterios especificados:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Selector {
   private static List<Owner> owners;

   private static void initData() {
      final Owner owner1 = new Owner("Ronan Turner");
      owner1.getPets().addAll(List.of(
            new Cat("Baron", Color.BLACK, 3),
            new Cat("Sultan", Color.DARK_GREY, 4),
            new Dog("Elsa", Color.WHITE, 0)
      ));

      final Owner owner2 = new Owner("Scarlet Murray");
      owner2.getPets().addAll(List.of(
            new Cat("Ginger", Color.FOXY, 7),
            new Cat("Oscar", Color.FOXY, 5),
            new Parrot("Admiral", Color.BLUE, 3)
      ));

      final Owner owner3 = new Owner("Felicity Mason");
      owner3.getPets().addAll(List.of(
            new Dog("Arnold", Color.FOXY, 3),
            new Pig("Vacuum Cleaner", Color.LIGHT_GREY, 8)
      ));

      final Owner owner4 = new Owner("Mitchell Stone");
      owner4.getPets().addAll(List.of(
            new Snake("Mr. Boa", Color.DARK_GREY, 2)
      ));

      final Owner owner5 = new Owner("Jonathan Snyder");
      owner5.getPets().addAll(List.of(
            new Cat("Fisher", Color.BLACK, 16),
            new Cat("Zorro", Color.FOXY, 14),
            new Cat("Margo", Color.WHITE, 3),
            new Cat("Brawler", Color.DARK_GREY, 1)
      ));

      owners = List.of(owner1, owner2, owner3, owner4, owner5);
   }
}

Lo que queda es agregar código al mainmétodo. Actualmente, primero llamamos al initData()método, que completa la lista de dueños de mascotas en el club. Luego seleccionamos los nombres de los gatos pelirrojos ordenados por su edad en orden descendente.

Primero, veamos el código que no usa flujos para resolver esta tarea:

public static void main(String[] args) {
   initData();

   List<String> findNames = new ArrayList<>();
   List<Cat> findCats = new ArrayList<>();
   for (Owner owner : owners) {
      for (Animal pet : owner.getPets()) {
         if (Cat.class.equals(pet.getClass()) && Color.FOXY == pet.getColor()) {
            findCats.add((Cat) pet);
         }
      }
   }

   Collections.sort(findCats, new Comparator<Cat>() {
      public int compare(Cat o1, Cat o2) {
         return o2.getAge() - o1.getAge();
      }
   });

   for (Cat cat : findCats) {
      findNames.add(cat.getName());
   }

   findNames.forEach(System.out::println);
}

Ahora veamos una alternativa:

public static void main(String[] args) {
   initData();

   final List<String> findNames = owners.stream()
           .flatMap(owner -> owner.getPets().stream())
           .filter(pet -> Cat.class.equals(pet.getClass()))
           .filter(cat -> Color.FOXY == cat.getColor())
           .sorted((o1, o2) -> o2.getAge() - o1.getAge())
           .map(Animal::getName)
           .collect(Collectors.toList());

   findNames.forEach(System.out::println);
}

Como puede ver, el código es mucho más compacto. Además, cada línea de la tubería de flujo es una sola acción, por lo que se pueden leer como oraciones en inglés:

.flatMap(owner -> owner.getPets().stream())
Pasar de un Stream<Owner>a unStream<Pet>
.filter(pet -> Cat.class.equals(pet.getClass()))
Retener solo gatos en el flujo de datos
.filter(cat -> Color.FOXY == cat.getColor())
Conservar solo gatos de jengibre en el flujo de datos
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
Ordenar por edad en orden descendente
.map(Animal::getName)
Obtener los nombres
.collect(Collectors.toList())
Poner el resultado en una lista.

3. Creando flujos

La Streamclase tiene tres métodos que aún no hemos cubierto. El propósito de estos tres métodos es crear nuevos hilos.

Stream<T>.of(T obj)método

El of()método crea una secuencia que consta de un solo elemento. Esto suele ser necesario cuando, por ejemplo, una función toma un Stream<T>objeto como argumento, pero solo tiene un Tobjeto. Luego, puede usar el of()método fácil y simplemente para obtener una transmisión que consta de un solo elemento .

Ejemplo:

Stream<Integer> stream = Stream.of(1);

Stream<T> Stream.of(T obj1, T obj2, T obj3, ...)método

El of()método crea una secuencia que consta de elementos pasados . Se permite cualquier número de elementos. Ejemplo:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

Stream<T> Stream.generate(Supplier<T> obj)método

El generate()método le permite establecer una regla que se utilizará para generar el siguiente elemento de la secuencia cuando se solicite. Por ejemplo, puede dar un número aleatorio cada vez.

Ejemplo:

Stream<Double> s = Stream.generate(Math::random);

Stream<T> Stream.concat(Stream<T> a, Stream<T> b)método

El concat()método concatena los dos flujos pasados ​​en uno . Cuando se leen los datos , se leen primero del primer flujo y luego del segundo. Ejemplo:

Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> stream2 = Stream.of(10, 11, 12, 13, 14);
Stream<Integer> result = Stream.concat(stream1, stream2);

4. Filtrado de datos

Otros 6 métodos crean nuevos flujos de datos, lo que le permite combinar flujos en cadenas (o canalizaciones) de complejidad variable.

Stream<T> filter(Predicate<T>)método

Este método devuelve un nuevo flujo de datos que filtra el flujo de datos de origen de acuerdo con la regla aprobada . El método debe invocarse en un objeto cuyo tipo sea Stream<T>.

Puede especificar la regla de filtrado mediante una función lambda , que luego el compilador convertirá en un Predicate<T>objeto.

Ejemplos:

Corrientes encadenadas Explicación
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> stream2 = stream.filter(x -> (x < 3));

Conservar solo números menores de tres
Stream<Integer> stream = Stream.of(1, -2, 3, -4, 5);
Stream<Integer> stream2 = stream.filter(x -> (x > 0));

Conservar solo los números mayores que cero

Stream<T> sorted(Comparator<T>)método

Este método devuelve un nuevo flujo de datos que ordena los datos en el flujo de origen . Pasa un comparador , que establece las reglas para comparar dos elementos del flujo de datos.

Stream<T> distinct()método

Este método devuelve un nuevo flujo de datos que contiene solo los elementos únicos en el flujo de datos de origen . Todos los datos duplicados se descartan. Ejemplo:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2, 3, 4);
Stream<Integer> stream2 = stream.distinct(); // 1, 2, 3, 4, 5

Stream<T> peek(Consumer<T>)método

Este método devuelve un nuevo flujo de datos , aunque los datos que contiene son los mismos que los del flujo de origen. Pero cuando se solicita el siguiente elemento de la secuencia, se llama a la función que pasó al método.peek()

Si pasa la función System.out::printlnal peek()método, todos los objetos se mostrarán cuando pasen por la secuencia.

Stream<T> limit(int n)método

Este método devuelve un nuevo flujo de datos que contiene solo los primeros nelementos del flujo de datos de origen . Todos los demás datos se descartan. Ejemplo:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2, 3, 4);
Stream<Integer> stream2 = stream.limit(3); // 1, 2, 3

Stream<T> skip(int n)método

Este método devuelve un nuevo flujo de datos que contiene todos los mismos elementos que el flujo de origen , pero omite (ignora) los primeros nelementos. Ejemplo:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2, 3, 4);
Stream<Integer> stream2 = stream.skip(3); // 4, 5, 2, 2, 2, 3, 4