Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace CompareToBuilder with Java 8's Comparator.comparing(...).thenComparing(...)

Before Java 8, we implemented Comparable.compareTo(...) like this:

public int compare(Person a, Person b) {
    return new CompareToBuilder()
            .append(a.getLastName(), b.getLastName())
            .append(a.getFirstName(), b.getFirstName())
            .toComparison();
}

As of Java 8, we can do it like this:

public int compare(Person a, Person b) {
    return Comparator
            .comparing(Person::getLastName)
            .thenComparing(Person::getFirstName)
            .compare(a, b);
}

The new Java 8 way might allow us to drop the commons-lang3 dependency. Is that new Java 8 way faster? Is there a way to automatically migrate? I didn't find an IntelliJ intention for it.


Notice that it becomes a bit more complex when there are reverse orders and non natural comparison is involved:

public int compare(SingleBenchmarkResult a, SingleBenchmarkResult b) {
    return new CompareToBuilder()
            .append(b.hasAnyFailure(), a.hasAnyFailure()) // Reverse
            .append(a.getAverageScore(), b.getAverageScore(), resilientScoreComparator)
            .toComparison();
}

becomes

public int compare(SingleBenchmarkResult a, SingleBenchmarkResult b) {
    return Comparator
            .comparing(SingleBenchmarkResult::hasAnyFailure, Comparator.reverseOrder()) // Reverse
            .thenComparing(SingleBenchmarkResult::getAverageScore, resilientScoreComparator)
            .compare(a, b);
}
like image 360
Geoffrey De Smet Avatar asked Jul 04 '16 02:07

Geoffrey De Smet


2 Answers

If you write it this way

public int compare(Person a, Person b) {
    return Comparator
            .comparing(Person::getLastName)
            .thenComparing(Person::getFirstName)
            .compare(a, b);
}

you are wasting performance by constructing a new Comparator for each comparison. And it should be obviously nonsensical when looking at the surrounding code. The compare(Person a, Person b) method surely is part of a class implementing Comparator<Person>, which you instantiate at some place to get the desired comparator. You should replace that instance by a sole Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName) instance instead, used throughout the entire operation.

E.g.

// reusable
static final Comparator<Person> By_NAME = Comparator
             .comparing(Person::getLastName).thenComparing(Person::getFirstName);

or ad hoc

listOfPersons.sort(Comparator.comparing(Person::getLastName)
                             .thenComparing(Person::getFirstName));

If you use it that way, it’s very likely to be faster. However, you should see, that there is no simple pattern-based replacement possible. You have to replace the use sites of the class with that simple declarative construct and make a decision whether to use a shared comparator instance for multiple use sites or create it ad-hoc. Then, you can remove the entire old implementation class or at least, remove the comparator functionality from it if it still serves other purposes.

like image 74
Holger Avatar answered Sep 28 '22 02:09

Holger


I don't think there is any pre-defined inspection for that. You might try to use IntelliJ's structural-search, although I think it might by quite tricky to do that for every possible case. One possibility for a simple case with two comparisons might be the following:

search template (occurence count of $TYPE$ and $z$ is 2):

$ReturnType$ $MethodName$($TYPE$ $z$) {
        return new CompareToBuilder()
                .append($A$.$m$(), $B$.$m$())
                .append($A$.$m1$(), $B$.$m1$())
                .toComparison();
    }

replacement template:

$ReturnType$ $MethodName$($TYPE$ $z$) {
    return java.util.Comparator
            .comparing($TYPE$::$m$)
            .thenComparing($TYPE$::$m1$)
            .compare($A$, $B$);
}

I am not an expert on structural search, but I guess you would have to make another pattern for calls with more or less comparisons.

like image 44
user140547 Avatar answered Sep 28 '22 00:09

user140547