Sort users based on initials with Java Stream API. If initials repeat, sort based on user's age. Both of them are sorted in descending order

Viewed 99

I am trying to sort users based on their initials, and when initials are repeated, say 'Alex Fay' and 'Alba Finn', I have to sort them based on their age in descending order.

This is my current code:

List<SortedUser> sortedUserList = list.stream()
        .sorted((a, b) -> {
             int first = a.getName().charAt(0) + a.getLastName().charAt(0);
             int second = b.getName().charAt(0) + b.getLastName().charAt(0);
             if(first > second) {
                  return -1;
             } if(first < second) {
                   return 1;
             }

             return b.getAge() - a.getAge();
           }).collect(Collectors.toList()); 

The problem with this code is that when I have initials 'AR' and 'EN', their sums are the same (147) - therefore first == second, and they appear one after the other in incorrect order. I also tried to compare the first name initial and the last name initial separately, but I still couldn't get it to work.

What can I change in order to sort them the way I want it?

Sample input:

(name=Alexandre, lastName=Wuckert, age=66),
(name=Allan, lastName=Wehner, age=84),
(name=Bradley, lastName=Thompson, age=78),
(name=Bernice, lastName=Schoen, age=63)

Sample output:

(name=Bradley, lastName=Thompson, age=78),
(name=Bernice, lastName=Schoen, age=63),
(name=Allan, lastName=Wehner, age=84),
(name=Alexandre, lastName=Wuckert, age=66)
2 Answers

Adding characters to sort on is unreliable since many pairs can sum to the same value. So you should build a String of initials rather than add them. Then use the following in your sort method.

String first = a.getName().charAt(0) + "" + a.getLastName().charAt(0);
String second = b.getName().charAt(0) +"" + b.getLastName().charAt(0);
int result = first.compareTo(second);
    
return result > 0 ? -1 : result < 0 ? 1 : b.getAge-a.getAge();

The above will sort in descending order based on the initials. If result == 0 then the ages will be used (as you did before).

Note: you could also do second.compareTo(first) and then either swap 1 and -1 or swap the inequality signs. Which ever makes more sense to you.

Here are some other alternatives for your consideration. I used a record to hold the User data.

record User(String getName, String getLastName, int getAge) {
}

List<User> list =
        new ArrayList<>(List.of(new User("Alexandre", "Wuckert", 66),
                new User("Allan", "Wehner", 84),
                new User("Bradley", "Thompson", 78),
                new User("Bernice", "Schoen", 63)));
  • stream the list of users.
  • You can create a String comparator and then apply it to the sort method
Comparator<User> compUsers = Comparator
        .comparing((User u) -> u.getName().charAt(0) + ""
                + u.getLastName().charAt(0))
        .thenComparing(User::getAge).reversed();
  • first compare the initials
  • if the initials are equal, then compare on the user's age.
  • reversed() reverses are previous comparators to change from ascending (default) to descending.

List<User> sortedUsers = list.stream().sorted(compUsers).toList();

sortedUsers.forEach(System.out::println);

prints

User[getName=Bradley, getLastName=Thompson, getAge=78]
User[getName=Bernice, getLastName=Schoen, getAge=63]
User[getName=Allan, getLastName=Wehner, getAge=84]
User[getName=Alexandre, getLastName=Wuckert, getAge=66]

Note that you could also just sort the list in place and not use streams as long as the list is mutable. Here the previously defined Comparator is used.

list.sort(compUsers);

Note: It is also possible to put the Comparator code directly into the sort method. But defining it separately lends itself to reuse and can make the stream construct less cluttered.

To make the code more readable and less error-prone, use Java 8 static methods of the Comparator interface instead of placing a chain of conditions into a lambda expression.

Here's another way of implementing a comparator that will compare users based on the first letter of the first name, then by the first letter of the last name and finally by age in descending order:

Comparator<SortedUser> byFirstLettersAndAgeReversed =
    Comparator.comparingInt((SortedUser user) -> user.getFirstName().charAt(0))
        .thenComparingInt(user -> user.getLastName().charAt(0))
        .thenComparingInt(SortedUser::getAge).reversed();


 List<SortedUser> sortedUserList = list.stream()
    .sorted(byFirstLettersAndAgeReversed)
    .collect(Collectors.toList());

You can define a couple of comparators as public static fields inside the user class and reuse them, when you need to sort or compare these objects in a specific way.

Related