Java 8 lambda filtering based on condition as well as order

  • A+

I was trying to filter a list based on multiple conditions, sorting.

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;         }     } 

Now if I have a list of that, say

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")); 

How would I be able to get a list of the oldest students with a distinct name? In C# this would be quite simple by using System.Linq GroupBy then comparing and then flattening with select, I'm not too sure how I could achieve the same in Java.


Use the toMap collector:

Collection<Student> values =                 .collect(toMap(Student::getName,                         Function.identity(),                         BinaryOperator.maxBy(Comparator.comparingInt(Student::getAge))))                 .values(); 


We're using this overload of toMap:

toMap​(Function<? super T,? extends K> keyMapper,       Function<? super T,? extends U> valueMapper,       BinaryOperator<U> mergeFunction) 
  • Student::getName above is the keyMapper function used to extract the values for the map keys.
  • Function.identity() above is the valueMapper function used to extract the values for the map values where Function.identity() simply returns the elements in the source them selves i.e. the Student objects.
  • BinaryOperator.maxBy(Comparator.comparingInt(Student::getAge)) above is the merge function used to "decide which Student object to return in the case of a key collission i.e. when two given students have the same name" in this case taking the oldest Student .
  • Finally, invoking values() returns us a collection of students.

The equivalent C# code being:

var values = students.GroupBy(s => s.Name, v => v,                           (a, b) => b.OrderByDescending(e => e.Age).Take(1))                       .SelectMany(x => x); 

Explanation (for those unfamiliar with .NET)

We're using this extension method of 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 above is the keySelector function used to extract the value to group by.
  • v => v above is the elementSelector function used to extract the values i.e. the Student objects them selves.
  • b.OrderByDescending(e => e.Age).Take(1) above is the resultSelector which given an IEnumerable<Student> represented as b takes the oldest student.
  • Finally, we apply .SelectMany(x => x); to collapse the resulting IEnumerable<IEnumerable<Student>> into a IEnumerable<Student>.


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: