This post gives a few examples of how to use Comparator with Java 8 streams to Sort and to find the Maximum and Minimum elements in a stream.
Comparing Simple types with Comparator.comparing* methods
The
Comparator.comparingDouble
,
Comparator.comparingInt
and
Comparator.comparingLong
etc. methods can be used to do sorting or finding max and min from stream. The following example uses the comparingDouble method to compare and sort a stream of doubles generated using the
Stream.generate()
method. The Stream.generate has been explained my earlier post "
Java 8 Streams"
// Sort a stream of Doubles
Stream.generate(Math::random).limit(10).sorted(Comparator.comparingDouble(Double::valueOf)).forEach(System.out::println);
Comparator.comparing method
The
Comparator.comparing
method gives a more flexible implementation which can be used to compare Objects of any type. There are a couple of variations of this method which we will see in the following examples.
Comparator.comparing method to compare Double
In the following example, we collect a stream of doubles generated using the
Stream.generate
method and print the numbers followed by using the
Comparator.comparing
method.
// Max from a list of Doubles
List<Double> doubles = Stream.generate(Math::random).limit(10).collect(Collectors.toList());
doubles.stream().forEach(System.out::println);
System.out
.println("Max from random number : " + doubles.stream().max(Comparator.comparing(Function.identity())).get());
Comparator.comparing method to compare Dates
In the following example, we use the
Comparator.comparing
method to compare a list of
LocalDate
objects.
// Max and min from a stream of dates
LocalDate start = LocalDate.now();
List<LocalDate> dates = Stream.iterate(start, date -> date.plusDays(1)).limit(100).collect(Collectors.toList());
Using Comparators on Streams with User Defined Classes
For the following few examples, we will use the Country Class defined below, and corresponding list shown below. The Country class consists of the name, Captial city and GDP (in trillions) of the country. I have changed a couple of values which are close enough to be equal in order to demonstrate a few variations of the
Comparator.comparable
method. This class also defines a natural ordering on the GDP by implementing the Comparable interface.
package beans;
public class Country implements Comparable<Country> {
public String name;
public String capital;
public Double gdp;
public Country(String name, String capital, Double gdp) {
this.name = name;
this.capital = capital;
this.gdp = gdp;
}
@Override
public int compareTo(Country o) {
return gdp.compareTo(o.getGdp());
}
// ... Getters Setters and toString
List countries = new ArrayList();
countries.add(new Country("United States", "Washington DC", 18.56));
countries.add(new Country("China", "Beijing", 11.21));
countries.add(new Country("Japan", "Tokyo", 4.93));
countries.add(new Country("Germany", "Berlin", 3.46));
countries.add(new Country("United Kingdom", "London", 2.62));
countries.add(new Country("France", "Paris", 2.46));
countries.add(new Country("India", "New Delhi", 2.46));
countries.add(new Country("Italy", "Rome", 1.85));
countries.add(new Country("Brazil", "BrasÃlia", 1.85));
Comparator.comparing with Natural Ordering
In the first variation, the comparing method takes a Function which returns a Comparable as a parameter. Since our Country class implements the Comparable method, we will use the
Function.identity()
method to return the Country object. The Function.identity method behaves similar to the lambda
x->x
. In the following piece of code, we use the comparing method to print the country with the max and min GDP from the list
// Countries with Max and Min GDPs from a list of countries
Comparator<Country> compareNatural = Comparator.comparing(Function.identity());
Country maxGdp = countries.stream().max(compareNatural).get();
Country minGdp = countries.stream().min(compareNatural).get();
System.out.println("Country with Max GDP in the list : " + maxGdp.getName());
System.out.println("Country with Min GDP in the list : " + minGdp.getName());
Comparator.comparing using specific fields in the class
In the following few examples, we use the comparing method to sort the list of countries using different fields. Since all the fields in the Country class are comparable, we can use the get methods to return Comparable objects as shown below.
// Sort Countries by Capital
Comparator<Country> compareCapital = Comparator.comparing(Country::getCapital);
countries.stream().sorted(compareCapital)
.forEach(x -> System.out.println(x.getName() + " : " + x.getCapital()));
// Sort Countries by Name
Comparator<Country> compareName = Comparator.comparing(Country::getName);
countries.stream().sorted(compareName).forEach(x -> System.out.println(x.getName()));
// Sort by GDP
Comparator<Country> compareGdp = Comparator.comparing(Country::getGdp);
countries.stream().sorted(compareGdp)
.forEach(x -> System.out.println(x.getName() + " : " + x.getGdp()));
Using Comparator.comparing method with custom comparators
The
Comparator.comparing
method has a variation that takes in a custom Comparator as an additional parameter to override the natural ordering. In the following example, we use the custom comparator to see if the Countries have equal GDP, in which case we will go to the second ordering which is by the Country name.
// Compare with custom comparator different from natural ordering
Comparator<? super Country> customComparator = (c1, c2) -> {
return (c1.compareTo(c2) != 0)
? c1.compareTo(c2) // Compare by natural ordering if not equal
: ((Country) c2).getName().compareTo(((Country) c1).getName()); // Compare reverse by name if equal by natural ordering
};
countries.stream().sorted(Comparator.comparing(Function.identity(), customComparator)).forEach(System.out::println);
The thenComparing method
The thenComparing method allows using multiple sort keys on the stream. The functionality is similar to the above example where we manually compare the fields one after the other.
// Compare and then
Comparator<Country> thenComparing = Comparator
.comparing(Country::getGdp)
.thenComparing(Country::getName);
countries.stream()
.sorted(thenComparing)
.forEach(System.out::println);
Here is the full code
streams.StreamsComparator
package streams;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import beans.Country;
public class StreamsComparator {
public static void main(String[] args) {
// Sort a stream of Doubles
Stream.generate(Math::random).limit(10).sorted(Comparator.comparingDouble(Double::valueOf)).forEach(System.out::println);
// Max from a list of Doubles
List<Double> doubles = Stream.generate(Math::random).limit(10).collect(Collectors.toList());
doubles.stream().forEach(System.out::println);
System.out
.println("Max from random number : " + doubles.stream().max(Comparator.comparing(Function.identity())).get());
// Max and min from a stream of dates
LocalDate start = LocalDate.now();
List<LocalDate> dates = Stream.iterate(start, date -> date.plusDays(1)).limit(100).collect(Collectors.toList());
LocalDate maxDate = dates.stream().max(Comparator.comparing(Function.identity())).get();
LocalDate minDate = dates.stream().min(Comparator.comparing(Function.identity())).get();
System.out.println("Last Date = " + maxDate);
System.out.println("First Date = " + minDate);
List<Country> countries = new ArrayList<Country>();
countries.add(new Country("United States", "Washington DC", 18.56));
countries.add(new Country("China", "Beijing", 11.21));
countries.add(new Country("Japan", "Tokyo", 4.93));
countries.add(new Country("Germany", "Berlin", 3.46));
countries.add(new Country("United Kingdom", "London", 2.62));
countries.add(new Country("France", "Paris", 2.46));
countries.add(new Country("India", "New Delhi", 2.46));
countries.add(new Country("Italy", "Rome", 1.85));
countries.add(new Country("Brazil", "BrasÃlia", 1.85));
// Countries with Max and Min GDPs from a list of countries
Comparator<Country> compareNatural = Comparator.comparing(Function.identity());
Country maxGdp = countries.stream().max(compareNatural).get();
Country minGdp = countries.stream().min(compareNatural).get();
System.out.println("Country with Max GDP in the list : " + maxGdp.getName());
System.out.println("Country with Min GDP in the list : " + minGdp.getName());
// Sort Countries by Capital
Comparator<Country> compareCapital = Comparator.comparing(Country::getCapital);
countries.stream().sorted(compareCapital)
.forEach(x -> System.out.println(x.getName() + " : " + x.getCapital()));
// Sort Countries by Name
Comparator<Country> compareName = Comparator.comparing(Country::getName);
countries.stream().sorted(compareName).forEach(x -> System.out.println(x.getName()));
// Sort by GDP
Comparator<Country> compareGdp = Comparator.comparing(Country::getGdp);
countries.stream().sorted(compareGdp)
.forEach(x -> System.out.println(x.getName() + " : " + x.getGdp()));
// Compare with custom comparator different from natural ordering
Comparator<? super Country> customComparator = (c1, c2) -> {
return (c1.compareTo(c2) != 0)
? c1.compareTo(c2) // Compare by natural ordering if not equal
: ((Country) c2).getName().compareTo(((Country) c1).getName()); // Compare reverse by name if equal by natural ordering
};
countries.stream().sorted(Comparator.comparing(Function.identity(), customComparator)).forEach(System.out::println);;
// Compare and then
Comparator<Country> thenComparing = Comparator
.comparing(Country::getGdp)
.thenComparing(Country::getName);
countries.stream()
.sorted(thenComparing)
.forEach(System.out::println);
}
}
beans.Country
package beans;
public class Country implements Comparable<Country> {
public String name;
public String capital;
public Double gdp;
public Country(String name, String capital, Double gdp) {
this.name = name;
this.capital = capital;
this.gdp = gdp;
}
@Override
public int compareTo(Country o) {
return gdp.compareTo(o.getGdp());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCapital() {
return capital;
}
public void setCapital(String capital) {
this.capital = capital;
}
public Double getGdp() {
return gdp;
}
public void setGdp(Double gdp) {
this.gdp = gdp;
}
@Override
public String toString() {
return "Country [name=" + name + ", capital=" + capital + ", gdp=" + gdp + "]";
}
}
No comments:
Post a Comment