There are a couple of awkward things with your example class:
- it's called People while it has a
priceandinfo(more something for objects, not people); - when naming a class as a plural of something, it suggests it is an abstraction of more than one thing.
Anyway, here's a demo of how to use a Comparator<T>:
public class ComparatorDemo {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Joe", 24),
new Person("Pete", 18),
new Person("Chris", 21)
);
Collections.sort(people, new LexicographicComparator());
System.out.println(people);
Collections.sort(people, new AgeComparator());
System.out.println(people);
}
}
class LexicographicComparator implements Comparator<Person> {
@Override
public int compare(Person a, Person b) {
return a.name.compareToIgnoreCase(b.name);
}
}
class AgeComparator implements Comparator<Person> {
@Override
public int compare(Person a, Person b) {
return a.age < b.age ? -1 : a.age == b.age ? 0 : 1;
}
}
class Person {
String name;
int age;
Person(String n, int a) {
name = n;
age = a;
}
@Override
public String toString() {
return String.format("{name=%s, age=%d}", name, age);
}
}
EDIT
And an equivalent Java 8 demo would look like this:
public class ComparatorDemo {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Joe", 24),
new Person("Pete", 18),
new Person("Chris", 21)
);
Collections.sort(people, (a, b) -> a.name.compareToIgnoreCase(b.name));
System.out.println(people);
Collections.sort(people, (a, b) -> a.age < b.age ? -1 : a.age == b.age ? 0 : 1);
System.out.println(people);
}
}
Answer from Bart Kiers on Stack OverflowVideos
There are a couple of awkward things with your example class:
- it's called People while it has a
priceandinfo(more something for objects, not people); - when naming a class as a plural of something, it suggests it is an abstraction of more than one thing.
Anyway, here's a demo of how to use a Comparator<T>:
public class ComparatorDemo {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Joe", 24),
new Person("Pete", 18),
new Person("Chris", 21)
);
Collections.sort(people, new LexicographicComparator());
System.out.println(people);
Collections.sort(people, new AgeComparator());
System.out.println(people);
}
}
class LexicographicComparator implements Comparator<Person> {
@Override
public int compare(Person a, Person b) {
return a.name.compareToIgnoreCase(b.name);
}
}
class AgeComparator implements Comparator<Person> {
@Override
public int compare(Person a, Person b) {
return a.age < b.age ? -1 : a.age == b.age ? 0 : 1;
}
}
class Person {
String name;
int age;
Person(String n, int a) {
name = n;
age = a;
}
@Override
public String toString() {
return String.format("{name=%s, age=%d}", name, age);
}
}
EDIT
And an equivalent Java 8 demo would look like this:
public class ComparatorDemo {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Joe", 24),
new Person("Pete", 18),
new Person("Chris", 21)
);
Collections.sort(people, (a, b) -> a.name.compareToIgnoreCase(b.name));
System.out.println(people);
Collections.sort(people, (a, b) -> a.age < b.age ? -1 : a.age == b.age ? 0 : 1);
System.out.println(people);
}
}
Here's a super short template to do the sorting right away :
Collections.sort(people, new Comparator<Person>() {
@Override
public int compare(final Person lhs, Person rhs) {
// TODO return 1 if rhs should be before lhs
// return -1 if lhs should be before rhs
// return 0 otherwise (meaning the order stays the same)
}
});
If it's hard to remember, try to just remember that it's similar (in terms of the sign of the number) to:
lhs-rhs
That's in case you want to sort in ascending order : from smallest number to largest number.
is
Comparator.comparing()used for converting a single argument lambda expression to a double argument?
Yes, you can sort of think of it like that.
When sorting things, you are supposed to specify "given two things a and b, which of them is greater, or are they equal?" using a Comparator<T>. The a and b is why it has 2 lambda parameters, and you return an integer indicating your answer to that question.
However, a much more convenient way to do this is to specify "given a thing x, what part of x do you want to sort by?". And that is what you can do with the keyExtractor argument of Comparator.comparing.
Compare:
/*
given two people, a and b, the comparison result between a and b is the
comparison result between a's name and b's name
*/
Comparator<Person> personNameComparator =
(a, b) -> a.getName().compareTo(b.getName());
/*
given a person x, compare their name
*/
Comparator<Person> personNameComparator =
Comparator.comparing(x -> x.getName()); // or Person::getName
The latter is clearly much more concise and intuitive. We tend to think about what things to sort by, rather than how exactly to compare two things, and the exact number to return depending on the comparison result.
As for the declaration for comparing:
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
The <T, U extends Comparable<? super U>> part first declares two generic type parameters - T is what the comparator compares (Person in the above case), and U is the type that you are actually comparing (String in the above case), hence it extends Comparable.
keyExtractor is the parameter you pass in, such as x -> x.getName(), that should answer the question of "when given a T, what is a U that you want to compare by?".
If you are confused by the ? super and ? extends, read What is PECS?.
If you haven't realised already, the implementation of comparing basically boils down to:
return (a, b) -> keyExtractor.apply(a).compareTo(keyExtractor.apply(b));
Comparator#compare(T o1, T o2) Compare two objects and returns an integer value based on this criteria:
- A negative value if o1 < o2
- A positive value if o1 > o2
- Zero if they are equal.
Comparator.comparing(Function<? super T, ? extends U> key) returns a Comparator<T> that compares by that sort key.
The main difference is that compare method provides a single point of comparison, whereas comparing chained to other functions to provide multiple points of comparison.
Suppose you have a class Person
public class Person implements Comparable<Person> {
private String firstName;
private String lastName;
private int age;
// rest of class omitted
}
if you compare two Person instances p1 vs p2 using compare(p1, p2), the comparison will be executed and the two objects will be sorted based on some natural ordering prescribed by the class. In contrast, if you want to compare the same two instances using comparing(), the comparison will be executed based on whichever criteria you choose to compare based on some attribute of the class. For example: Comparator.comparing(Person::getFirstName).
Because comparing returns a Comparator rather than a value, as I stated before, you can chain multiple comparisons. For instance: Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName);
As for the meaning of the return type <T, U extends Comparable<? super U>> Comparator<T>, you can find the explanation here.
I want to add that, classes must be comparable in order for compare(T o1, T o2) to work. String objects are comparable because they implement this interface. That said, if a class is not Comparable, you can still use comparing method because as I stated, you get to choose which attribute of the class you would like to use for the comparison and those attributes are likely to be comparable (i.e. String in the case of person's name or age in the above example).
Comparator#compare
What is the goal of the Comparator#compare method?
Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
Given your formula of ( numOfWins2 - numOfWins1 ):
- For the case of numOfWins1 = 30 and numOfWins2 = 20, ( 20 - 30 ) is -10, a negative number. So the first should be sorted after the second.
- For the case of numOfWins1 = 20 and numOfWins2 = 30, ( 30 - 20 ) is 10, a positive number. So the first should be sorted above the second.
- For the case of numOfWins1 = 25 and numOfWins2 = 25, ( 25 - 25 ) is 0, zero. So the two items are tied for sorting.
Complete example
Here is some example code. You can tweak the constructors of Alice and Carol to see the sorting in action.
This code uses the new compact record definition of a class, in Java 16+. But using record is beside the point of this Answer. You can just as well use a conventional class definition.
Copypackage work.basil.example;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class Comparing
{
public static void main ( String[] args )
{
Comparing app = new Comparing();
app.demo();
}
private void demo ( )
{
List < FootballClub > clubs =
List.of(
new FootballClub( "Alice" , 25 ) ,
new FootballClub( "Bob" , 7 ),
new FootballClub( "Carol" , 25 )
);
Comparator < FootballClub > sortByWinsDescending = new Comparator < FootballClub >()
{
public int compare ( FootballClub footballClub1 , FootballClub footballClub2 )
{
int numOfWins1 = footballClub1.wins();
int numOfWins2 = footballClub2.wins();
/* Returns the number of goals scored in descending order */
return numOfWins2 - numOfWins1;
}
};
List < FootballClub > sorted = new ArrayList <>( clubs );
sorted.sort( sortByWinsDescending );
System.out.println( "clubs = " + clubs );
System.out.println( "sorted = " + sorted );
}
record FootballClub(String name , Integer wins)
{
}
}
Not recommended
This code return ( numOfWins2 - numOfWins1 ) ; is the kind of “clever” programming that should be avoided. This code is confusing and troublesome. I even confused myself while writing this Answer.
Better would be letting objects compare themselves. In my example code, we use the class Integer. Well, Integer objects already know how to compare themselves. So call Integer#compareTo.
Copyreturn footballClub1.wins().compareTo( footballClub2.wins() ) ;
Or in the case of int primitives as seen in your code, call the static utility method Integer.compare( int x , int y ):
Copy int numOfWins1 = footballClub1.getNumOfWins();
int numOfWins2 = footballClub2.getNumOfWins();
/* Returns the number of goals scored in descending order */
return Integer.compare ( numOfWins1 , numOfWins2 ) ;
By the way, we can look at the OpenJDK source code to see how this Integer.compare( int x , int y ) method is implemented. They use a nested pair of ternary operators. A ?: ternary works this way: If the predicate test is true, use item following the ?, else if false use the item following the :.
Copy// Copyright (c) 1994, 2020, Oracle and/or its affiliates. All rights reserved.
// Excerpt from https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/Integer.java
// See licensing terms in that original file.
return (x < y) ? -1 : ((x == y) ? 0 : 1);
Make a habit of writing code this is as plain and simple as possible. Humans spend much more time reading code than writing code. On a job, when you have a bug in one of your report-generating methods, and you are debugging in the middle of the night, you do not want to be trying to figure out if return numOfWins2 - numOfWins1 is for ascending or descending.
As a bonus, plain and simple code that is readable by humans is often more likely to be an opportunity for the Java compiler and runtime to optimize for performance.
For fun, add this line before your return statement to see the individual comparisons.
CopySystem.out.println( "numOfWins1: " + numOfWins1 + " | numOfWins2: " + numOfWins2 + " | numOfWins2 - numOfWins1: " + ( numOfWins2 - numOfWins1 ) );
Ascending order
Tip: If you wanted ascending order rather than descending, just multiply the result of that call Integer#compareTo or Integer.compare by negative 1 (-1). Doing so flips the sign of the result, negative becomes positive, positive becomes negative, and zero remains zero.
Copyreturn ( -1 * Integer.compare ( numOfWins1 , numOfWins2 ) ) ; // Ascending rather than descending sort order.
Method reference
As Ole V.V. commented, if you are comfortable with lambdas and method references, you can collapse your entire Comparator implementation to a single-line solution for ascending sort by calling Comparator.comparingInt.
CopyComparator < FootballClub > sortByWinsDescending = Comparator.comparingInt( FootballClub :: wins );
Our example above becomes quite shorter.
Copypackage work.basil.example;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class Comparing2
{
public static void main ( String[] args )
{
Comparing2 app = new Comparing2();
app.demo();
}
private void demo ( )
{
List < FootballClub > clubs =
List.of(
new FootballClub( "Alice" , 20 ) ,
new FootballClub( "Bob" , 7 ) ,
new FootballClub( "Carol" , 30 )
);
Comparator < FootballClub > sortByWinsDescending = Comparator.comparingInt( FootballClub :: wins );
List < FootballClub > sorted = new ArrayList <>( clubs );
sorted.sort( sortByWinsDescending );
System.out.println( "clubs = " + clubs );
System.out.println( "sorted = " + sorted );
}
record FootballClub(String name , Integer wins)
{
}
}
try this code:
Copy public static Comparator<FootballClub> sortNumOfWins = new Comparator<FootballClub>() {
public int compare(FootballClub footballClub1, FootballClub footballClub2) {
Integer numOfWins1 = footballClub1.getNumOfWins();
Integer numOfWins2 = footballClub2.getNumOfWins();
return numOfWins2.compareTo(numOfWins1);
}
};
when you compare integer field only and this compare is typical, you can use standard method compareTo from Integer type.