Uso de Stream y Colecciones
Api Stream
A partir de Java 8 ha aparecido esta api que facilita el trabajo con colecciones y flujo de datos/trabajo. Con Stream podemos usar todo lo visto con lambda, y de esta manera se exprime todas sus posibilidades.
Podemos obtener el stream de cualquier colección de esta manera:
Collection<String> collection = Arrays.asList("a", "b", "c");
Stream<String> streamOfCollection = collection.stream();
Si se necesita trabajar con Array tenemos estas posibilidades:
Stream<String> streamOfArray = Stream.of("a", "b", "c");
String[] arr = new String[]{"a", "b", "c"};
Stream<String> streamOfArrayFull = Arrays.stream(arr);
Stream tiene muchas posibilidades como por ejemplo con String, Ficheros o generar contenido automático (enlace enlace2)
Uso con colecciones
Usarlo con Maps:
Map<String, Integer> map = new HashMap<>();
map.put("Pepe",5);
map.put("Fran", 8);
map.put("Jose", 7);
List<String> biggerThanSix = map.entrySet().stream()
.filter(e -> 6 < e.getValue())
.map(a -> a.getKey())
.collect(Collectors.toList());
El método estático Stream.of puede ser muy útil para crear streams de datos primitivos o objetos, como en este ejemplo:
Stream<Person> userStream = Stream.of(
new Person("Mahesh", 22),
new Person("Krishn", 20),
new Person("Suresh", 25)
);
userStream.forEach(u -> System.out.println(u.getNombre()));
//Uso con Strings
Stream<String> mystream = Stream.of("AA", "BB", "CC", "DD");
mystream.forEach(e -> System.out.println(e));
Operación forEach
La operación más usada, y que ya hemos usado. Implica un bucle que recorre todos los objetos del Stream. ForEach acepta expresiones lambda.
Operación Map
El uso de Java Stream map es una de las operaciones más comunes cuando trabajamos con un flujo de Streams. El método map nos permite realizar una transformación rápida de los datos y muy directa sobre el flujo original. Es decir “mapea” lo que necesitas de cada objeto de la colección:
Persona p1 = new Persona("pedro", 20, "perez");
Persona p2 = new Persona("juan", 25, "perez");
Persona p3 = new Persona("ana", 30, "perez");
List < Persona > lista = new ArrayList < Persona > ();
lista.add(p1);
lista.add(p2);
lista.add(p3);
lista
.stream()
.map(a -> a.edad )
.forEach(a -> System.out.println(a));;
Es muy frecuente usar la operación .sum con el mapeo. Pruébalo!
Operación filter
Como el propio nombre indica filtra a partir de una condición los objetos que queremos, al más puro estilo “where” en una consulta SQL. Esta operación necesita una interfaz funcional Predicate. Por lógica algo que devuelva booleano. Si queremos rapidez en la instrucción lambda se debe colocar de primero.
Libro l = new Libro("El señor de los anillos", "fantasia", 1100);
Libro l2 = new Libro("El Juego de Ender", "ciencia ficcion", 500);
Libro l3 = new Libro("La fundacion", "ciencia ficcion", 500);
Libro l4 = new Libro("Los pilares de la tierra", "historica", 1200);
List <Libro> lista = Arrays.asList(l, l2, l3, l4);
lista.stream()
.filter(libro -> libro.getPaginas() > 1000)
.map(libro -> libro.getTitulo())
.forEach(System.out::println);
Podemos crear predicados muy complejos y luego usarlos con Stream y filter cuando queramos:
public static boolean buenosLibros(Libro libro) {
Predicate <Libro> p1 = (Libro l) -> l.getCategoria().equals("ciencia ficcion");
Predicate <Libro> p2 = (Libro l) -> l.getCategoria().equals("fantasia");
Predicate <Libro> p3 = (Libro l) -> l.getPaginas() > 1000;
Predicate <Libro> ptotal = p1.or(p2).and(p3);
return ptotal.test(libro);
}
Con filter también es común usar count para obtener el número de elementos que cumple la condición:
cars.stream().filter(x -> x.getColor().equals("White")).count();
Operación Collect
Esta operación se utiliza para obtener una nueva colección a partir de operaciones con Stream:
List<Car> result = cars.stream().filter(x -> x.getColor().equals("White")).collect(Collectors.toList());
El anterior ejemplo obtenemos una nueva lista, pero hay opciones para mapas y conjuntos: Collectors.toSet() o Collectors.toMap.
Operación Sorted
También podemos ordenar un Stream, esta operación puede recibir varios tipos de argumentos:
- Comparator.comparing():
lista.stream()
.sorted(Comparator.comparing( p -> p.getApellidos()))
.forEach(System.out::println);
- CompareTo con un BiPredicate:
List<Employee> employees = empList.stream()
.sorted((e1, e2) -> e1.getName().compareTo(e2.getName()))
.collect(Collectors.toList());
Operación distinct
Genera una lista de objectos descartando los iguales:
List<String> ids = cars.stream()
.filter(x -> x.getColor().equals("White"))
.map(car -> car.getBrand())
.distinct().collect(Collectors.toList());
Si mantenemos los objetos en el stream, la operación distinct usará el método equals de la clase correspondiente, que debemos sobreescribir.
Operación parallel
En el caso que necesitemos mejorar el rendimiento al trabajar con Stream, tenemos la operación parallel que genera varios hilos de ejecución. Tal como vimos con ficheros. Os dejo un enlace para que podáis probarlo: enlace
Las siguientes operaciones es necesario conocer la clase Optional
Operación findFirst
Esta operación nos devolverá el primer elememento del Stream, y los devolverá un objeto de la clase Optional, es decir el primer elemento o sino existe referencia a null.
@Test
public void createStream_whenFindFirstResultIsPresent_thenCorrect() {
List<String> list = Arrays.asList("A", "B", "C", "D");
Optional<String> resultFirst = list.stream().findFirst();
if (resultFirst.isPresent()){System.out.println(resultFirst.get());}
}
Sino no nos importa el orden y nos vale cualquier elemento podéis probar Stream.findAny().
Operación min max
Estas operaciones nos devolverá el mínimo o el máximo de un Stream a partir de recibir un Comparator (Compare, Comparing) de parámetro. Y nos devolverá un Optional como en la operación anterior. Ejemplos sencillos:
List<Integer> list = Arrays.asList(-9, -18, 0, 25, 4);
Integer var = list.stream().min(Integer::compare).get();
List<Integer> list = Arrays.asList(-9, -18, 0, 25, 4);
Optional<Integer> var = list.stream()
.min(Comparator.reverseOrder());
Ejercicio 1
Partiendo de un array de String, usa Stream y min para obtener el string más pequeño del array:
Ejercicio 2
Crea una lista de objetos tipo Persona, una vez lista la colección crea una instrucción lambda que nos devuelva la persona con menos edad.
Ejercicio 3
Genera una interfaz supplier que genera un valor entero (entre 1 y 10) al azar. Prueba Stream.generate para generar 5 numeros (.limit) y los muestre por pantalla.
Ejercicio 4
Partiendo de este pojo:
public class Factura {
private int numero;
private String concepto;
private double importe;
}
Implementa gets, sets, equals, hashCode, constructor por parámetros y toString. Crea una colección donde almacenes objetos factura. Usando lambda obtén una nueva colección filtrada con las facturas de importe mayor a 200. Muestra por pantalla el concepto de la factura. También nos piden una nueva colección con las facturas entre 200 y 300 (investiga el and de Predicate).