Chain of comparators in java

2019-04-22 19:26发布

问题:

Reading the Java Tutorial by Oracle on interfaces which gives a example on Card (Playing cards) I was trying to understand the default methods in interfaces. Here's the link, section "Integrating default methods in existing interfaces". Now in the last section they sorted the Cards first by rank and then by suits. Following logics have been given. Assume that whatever interfaces, functions or classes that are used have been defined and sort function takes a Comparator

Logic 1:

package defaultmethods;

import java.util.*;
import java.util.stream.*;
import java.lang.*;

public class SortByRankThenSuit implements Comparator<Card> {
    public int compare(Card firstCard, Card secondCard) {
        int compVal =
            firstCard.getRank().value() - secondCard.getRank().value();
        if (compVal != 0)
            return compVal;
        else
            return firstCard.getSuit().value() - secondCard.getSuit().value(); 
    }
}

Logic 2:

myDeck.sort(
    Comparator
        .comparing(Card::getRank)
        .thenComparing(Comparator.comparing(Card::getSuit)));

Now I am having some problems in understanding the second logic. I read the comparator interfaces and the new static methods which have been included in Java 1.8 . Now I understand something like this myDeck.sort(Comparator.comparing(Card::getRank)) which sorts by rank but after reading the documentation for thenComparing , I am unable to understand how thenComparing returns a Comparator which achieves the above Logic 1. Does it internally build something like the if-else construct as specified in Logic 1 ?

回答1:

Yes, it creates internally something similar, just with more intermediate lambdas. Assuming that your getRank and getSuit methods return some instances of comparable classes Rank and Suit, in your case you effectively have:

Function<Card, Rank> toRank = Card::getRank;
Comparator<Card> comp1 = (a, b) -> toRank.apply(a).compareTo(toRank.apply(b));
Function<Card, Suit> toSuit = Card::getSuit;
Comparator<Card> comp2 = (a, b) -> toSuit.apply(a).compareTo(toSuit.apply(b));
Comparator<Card> result = (a, b) -> {
  int res = comp1.compare(a, b);
  return res != 0 ? res : comp2.compare(a, b);
};

So after inlining (which might be performed by JIT-compiler) you may have something like this:

Comparator<Card> result = (a, b) -> {
  int res = a.getRank().compareTo(b.getRank());
  return res != 0 ? res : a.getSuit().compareTo(b.getSuit());
};

Note that you can use the simpler version:

myDeck.sort(
    Comparator
        .comparing(Card::getRank)
        .thenComparing(Card::getSuit));


回答2:

This is the implementation on my machine (Oracle JDK 8u40)

default Comparator<T> thenComparing(Comparator<? super T> other) {
    Objects.requireNonNull(other);
    return (Comparator<T> & Serializable) (c1, c2) -> {
        int res = compare(c1, c2);
        return (res != 0) ? res : other.compare(c1, c2);
    };
}

so yes, it is essentially if else (more precisely, the ternary operator).