Composing comparators

Let's consider the same Melon class and List of Melon from the preceding section.

Now, let's sort this List of Melon by weight using Comparator.comparing():

Comparator<Melon> byWeight = Comparator.comparing(Melon::getWeight);

// Horned(1600g), Hemi(1600g), Gac(2000g), Apollo(3000g), Gac(3000g)
List<Melon> sortedMelons = melons.stream()
.sorted(byWeight)
.collect(Collectors.toList());

We can sort the list by type as well:

Comparator<Melon> byType = Comparator.comparing(Melon::getType);

// Apollo(3000g), Gac(2000g), Gac(3000g), Hemi(1600g), Horned(1600g)
List<Melon> sortedMelons = melons.stream()
.sorted(byType)
.collect(Collectors.toList());

To reverse the sorting order, simply call reversed():

Comparator<Melon> byWeight 
= Comparator.comparing(Melon::getWeight).reversed();

So far, so good!

Now, let's assume that we want to sort the list by weight and type. In other words, when two melons have the same weight (for example, Horned (1600g), Hemi(1600g)) they should be sorted by type (for example, Hemi(1600g), Horned(1600g)). A naive approach will look as follows:

// Apollo(3000g), Gac(2000g), Gac(3000g), Hemi(1600g), Horned(1600g)
List<Melon> sortedMelons = melons.stream()
.sorted(byWeight)
.sorted(byType)
.collect(Collectors.toList());

Obviously, the result is not what we expected. This is happening because the comparators have not been applied to the same list. The byWeight comparator is applied to the original list, while the byType comparator is applied to the output of byWeight. Basically, byType cancels the effect of byWeight.

The solution comes from the Comparator.thenComparing() method. This method allows us to chain comparators:

Comparator<Melon> byWeightAndType 
= Comparator.comparing(Melon::getWeight)
.thenComparing(Melon::getType);

// Hemi(1600g), Horned(1600g), Gac(2000g), Apollo(3000g), Gac(3000g)
List<Melon> sortedMelons = melons.stream()
.sorted(byWeightAndType)
.collect(Collectors.toList());

This flavor of thenComparing() takes a Function as an argument. This Function is used to extract the Comparable sort key. The returned Comparator is applied only when the previous Comparator has found two equal objects.

Another flavor of thenComparing() gets a Comparator:

Comparator<Melon> byWeightAndType = Comparator.comparing(Melon::getWeight)
.thenComparing(Comparator.comparing(Melon::getType));

Finally, let's consider the following List of Melon:

List<Melon> melons = Arrays.asList(new Melon("Gac", 2000),
new Melon("Horned", 1600), new Melon("Apollo", 3000),
new Melon("Gac", 3000), new Melon("hemi", 1600));

We intentionally added a mistake to the last Melon. Its type is lowercase this time. If we apply the byWeightAndType comparator, then the output will be as follows:

Horned(1600g), hemi(1600g), ...

Being a lexicographic-order comparator, byWeightAndType will place Horned before hemi. So, it will be useful to sort by type in a case-insensitive manner. An elegant solution to this problem will rely on another flavor of thenComparing() , which allows us to pass a Function and Comparator as arguments. The Function that is passed extracts the Comparable sort key, and the given Comparator is used to compare this sort key:

Comparator<Melon> byWeightAndType = Comparator.comparing(Melon::getWeight)
.thenComparing(Melon::getType, String.CASE_INSENSITIVE_ORDER);

This time, the result will be as follows (we are back on track):

hemi(1600g), Horned(1600g),...
For int, long, and double, we have comparingInt(), comparingLong(), comparingDouble(), thenComparingInt()thenComparingLong(), and thenComparingDouble(). The comparing() and thenComparing() methods come with the same flavors.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.187.223