1. Lista de métodos de la Stream
clase
La Stream
clase 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 Stream
objetos.
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 Stream
clase (solo los más básicos):
Métodos | Descripción |
---|---|
|
Crea una secuencia a partir de un conjunto de objetos. |
|
Genera un flujo de acuerdo con la regla especificada |
|
Concatena dos flujos |
|
Filtra los datos, solo pasa los datos que coinciden con la regla especificada |
|
Elimina duplicados. No transmite datos que ya se han encontrado |
|
Ordena los datos |
|
Realiza una acción en cada elemento de la secuencia. |
|
Devuelve una secuencia que se trunca para que no supere el límite especificado |
|
Salta los primeros n elementos |
|
Convierte datos de un tipo a otro |
|
Convierte datos de un tipo a otro |
|
Comprueba si hay al menos un elemento en la transmisión que coincida con la regla especificada |
|
Comprueba si todos los elementos de la secuencia coinciden con la regla especificada |
|
Comprueba si ninguno de los elementos de la secuencia coincide con la regla especificada |
|
Devuelve el primer elemento encontrado que coincide con la regla |
|
Devuelve cualquier elemento en la transmisión que coincida con la regla |
|
Busca el elemento mínimo en el flujo de datos |
|
Devuelve el elemento máximo en el flujo de datos |
|
Devuelve el número de elementos en el flujo de datos |
|
Lee todos los datos de la secuencia y los devuelve como una colección |
2. Operaciones intermedias y terminales por la Stream
clase
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 Stream
clase 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 Stream
interfaz 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.
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();
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 | Sí | No |
Número de métodos en una sola canalización | cualquier | no más de uno |
Produce el resultado final | No | Sí |
Comienza a procesar los datos en la transmisión. | No | Sí |
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 Selector
clase, 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 main
mé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:
|
Pasar de un Stream<Owner> a unStream<Pet> |
|
Retener solo gatos en el flujo de datos |
|
Conservar solo gatos de jengibre en el flujo de datos |
|
Ordenar por edad en orden descendente |
|
Obtener los nombres |
|
Poner el resultado en una lista. |
3. Creando flujos
La Stream
clase 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 T
objeto. 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 |
---|---|
|
Conservar solo números menores de tres |
|
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::println
al 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 n
elementos 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 n
elementos. 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
GO TO FULL VERSION