streams - java 8 lambdas pdf
Filtrado lambda de Java 8 basado en condición y orden (4)
Estaba tratando de filtrar una lista basada en múltiples condiciones, ordenando.
class Student{
private int Age;
private String className;
private String Name;
public Student(int age, String className, String name) {
Age = age;
this.className = className;
Name = name;
}
public int getAge() {
return Age;
}
public void setAge(int age) {
Age = age;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
}
Ahora si tengo una lista de eso, di
List<Student> students = new ArrayList<>();
students.add(new Student(24, "A", "Smith"));
students.add(new Student(24, "A", "John"));
students.add(new Student(30, "A", "John"));
students.add(new Student(20, "B", "John"));
students.add(new Student(24, "B", "Prince"));
¿Cómo podría obtener una lista de los alumnos más antiguos con un nombre distinto? En C #, esto sería bastante simple usando System.Linq GroupBy luego comparando y luego aplanando con select, no estoy muy seguro de cómo podría lograr lo mismo en Java.
O sin arroyos:
Map<String, Student> map = new HashMap<>();
students.forEach(x -> map.merge(x.getName(), x, (oldV, newV) -> oldV.getAge() > newV.getAge() ? oldV : newV));
Collection<Student> max = map.values();
Si necesitas una agrupación solo ordenada, es bastante simple:
Map<String, List<Student>> collect = students.stream() // stream capabilities
.sorted(Comparator.comparingInt(Student::getAge).reversed()) // sort by age, descending
.collect(Collectors.groupingBy(Student::getName)); // group by name.
Salida en cobro:
- Prince = [Estudiante [Edad = 24, className = B, Name = Prince]],
- Smith = [Estudiante [Edad = 24, nombre de clase = A, Nombre = Smith]]
- Juan = [Estudiante [Edad = 30, nombre de clase = A, Nombre = Juan], Estudiante [Edad = 24, nombre de clase = A, Nombre = Juan], Estudiante [Edad = 20, nombre de clase = B, Nombre = Juan]]
Solo para mezclar y fusionar las otras soluciones, podría alternativamente hacer:
Map<String, Student> nameToStudentMap = new HashMap<>();
Set<Student> finalListOfStudents = students.stream()
.map(x -> nameToStudentMap.merge(x.getName(), x, (a, b) -> a.getAge() > b.getAge() ? a : b))
.collect(Collectors.toSet());
Utilice el colector toMap
:
Collection<Student> values = students.stream()
.collect(toMap(Student::getName,
Function.identity(),
BinaryOperator.maxBy(Comparator.comparingInt(Student::getAge))))
.values();
Explicación
Estamos usando esta sobrecarga de toMap
:
toMap(Function<? super T,? extends K> keyMapper,
Function<? super T,? extends U> valueMapper,
BinaryOperator<U> mergeFunction)
-
Student::getName
arriba es la funciónkeyMapper
utilizada para extraer los valores de las claves del mapa. -
Function.identity()
anterior es la funciónvalueMapper
utilizada para extraer los valores de los valores del mapa dondeFunction.identity()
simplemente devuelve los elementos en la fuente ellos mismos, es decir, los objetos deStudent
. -
BinaryOperator.maxBy(Comparator.comparingInt(Student::getAge))
es la función de combinación que se usa para "decidir qué objeto de Student devolver en el caso de una colisión de claves, es decir, cuando dos alumnos dados tienen el mismo nombre" en este caso tomando elStudent
más antiguo - Finalmente, invocar
values()
nos devuelve una colección de alumnos.
El código C # equivalente es:
var values = students.GroupBy(s => s.Name, v => v,
(a, b) => b.OrderByDescending(e => e.Age).Take(1))
.SelectMany(x => x);
Explicación (para aquellos que no están familiarizados con .NET)
Estamos usando este método de extensión de GroupBy
:
System.Collections.Generic.IEnumerable<TResult> GroupBy<TSource,TKey,TElement,TResult>
(this System.Collections.Generic.IEnumerable<TSource> source,
Func<TSource,TKey> keySelector,
Func<TSource,TElement> elementSelector,
Func<TKey,System.Collections.Generic.IEnumerable<TElement>,TResult> resultSelector);
-
s => s.Name
anterior es la funciónkeySelector
utilizada para extraer el valor para agrupar por. -
v => v
anterior es la funciónelementSelector
utilizada para extraer los valores, es decir, elStudent
opone a ellos mismos. -
b.OrderByDescending(e => e.Age).Take(1)
anterior es elIEnumerable<Student>
resultSelector
que, dado unIEnumerable<Student>
representado comob
toma el alumno más antiguo. - Finalmente, aplicamos
.SelectMany(x => x);
para contraer elIEnumerable<IEnumerable<Student>>
resultante en unIEnumerable<Student>
.