How to sort java streams
During one of the tests, I got a list of items sorted via an api. At least that’s what I thought. From that list, I always extracted the first item because it was always the most recent one that was in the database.
One day, some of the tests started failing. It turned out that the first element was no longer the latest one. Since some of my test cases were based on this latest record, tests failed as a result. The solution is simple. I need to sort that list.
In the test code, that list was not a List but a Stream. It turned out that it was not that simple as I first thought it was.
How to sort a stream
Suppose you have a class of Members and they contain a name and an age. Now I would like to have a stream of Members and I want to sort that stream by age. Since the age is a number, sorting is easy.
Some java code:
import java.util.*;
import java.util.stream.*;
class Test {
static class Member {
private String name;
private int age;
public Member(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name){
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age){
this.age = age;
}
@Override
public String toString(){
return "Member{name='" + name + "', age=" + age + "}";
}
}
public static void main(String [] args) {
List <Member> members = Arrays.asList(
new Member("Z", 40),
new Member("A", 50),
new Member("B", 20)
);
List<Member> sortedMembers = members.stream()
.sorted(Comparator.comparingInt(
Member::getAge))
.collect(Collectors.toList());
sortedMembers.forEach(System.out::println);
}
}
We use the sorted method of the stream. This method has a parameter Comparator.comparingInt. We sort by numbers here. The output of this program is:
Member{name='B', age=20}
Member{name='Z', age=40}
Member{name='A', age=50}
Reversed order
We can also sort in revered order. There is the reversed method for that. See the following example:
List<Member> sortedMembers = members.stream()
.sorted(Comparator.comparingInt(Member::getAge)
.reversed())
.collect(Collectors.toList());
This gives the following result:
Member{name='A', age=50}
Member{name='Z', age=40}
Member{name='B', age=20}
Sort by Strings
Let’s sort the members by name. Then, instead of using comparingInt, you can use the comparing method. Here you can then pass the string field that you want to sort as a string.
List<Member> sortedMembers = members.stream()
.sorted(Comparator.comparing(Member::getName))
.collect(Collectors.toList());
Our result is now that the list is sorted by the name:
Member{name='A', age=50}
Member{name='B', age=20}
Member{name='Z', age=40}
What happens when age is a string?
The API that I used, dit return numbers where I could sort on. But the numbers where not integers. It are strings, like the names. Let’s adjust the test code in our example, so that the age is a string.
class Test {
static class Member {
private String name;
private String age;
public Member(String name, String age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name){
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age){
this.age = age;
}
@Override
public String toString(){
return "Member{name='" + name + "', age=" + age + "}";
}
}
public static void main(String [] args) {
List <Member> members = Arrays.asList(
new Member("Z", "40"),
new Member("A", "50"),
new Member("B", "20")
);
List<Member> sortedMembers = members.stream()
.sorted(Comparator.comparing(Member::getAge))
.collect(Collectors.toList());
sortedMembers.forEach(System.out::println);
}
}
The result looks correct. We get the following ouput:
Member{name='B', age=20}
Member{name='Z', age=40}
Member{name='A', age=50}
But is the result always correct? I add a new member with age 9. Let’s see what happens.
List <Member> members = Arrays.asList(
new Member("Z", "40"),
new Member("A", "50"),
new Member("B", "20"),
new Member("X", "9")
);
Sorting this list returns this output:
Member{name='B', age=20}
Member{name='Z', age=40}
Member{name='A', age=50}
Member{name='X', age=9}
The output is not correct. The age of 9 year should be the first one in the list. If I compared as integer, this did not happen. Maybe I can use comparingInt like the first example. The compiler will cast it to the correct type, no ?
List<Member> sortedMembers = members.stream()
.sorted(Comparator.comparingInt(Member::getAge))
.collect(Collectors.toList());
Compiling this code does not work. The compiler can not cast automatically to the correct type.
error: incompatible types: bad return type in method reference
.sorted(Comparator.comparingInt(Member::getAge))
String cannot be converted to int
I discovered that I can use with a lambda. Let’s try that.
List<Member> sortedMembers = members.stream()
.sorted(Comparator.comparingInt(
(m) -> Integer.parseInt(m.getAge())))
.collect(Collectors.toList());
Yes, this compiles! What is the output of this code? Do we get a sorted stream?
Member{name='X', age=9}
Member{name='B', age=20}
Member{name='Z', age=40}
Member{name='A', age=50}
And it did! The solution to my problem is found. I hope this post can help you as a reader too to sort java streams.
References
- Photo by Snejina Nikolova on Unsplash