3

I want to sort a list of my POJO by a property that is also a POJO. The Comparator should move null values to the end of the list. The nulls are already at second POJO level.

Assume following POJOs:

public class Foo {
    private Bar bar;
    private boolean set;
    // getter and setter omitted
}

public class Bar {
    private String name1;
    private String name2;
    // getter and setter omitted
}

Now I have a List<Foo> which should be sorted by Bar#name1.

So far I have following code:

Collections.sort(fullList, Comparator.comparing(Foo::getBar::getName1,
        Comparator.nullsLast(Comparator.naturalOrder())));

This sadly doesn't work, as the :: operator can't be chained. So I updated Bar class to implement Comparator<Bar>

public class Bar implements Comparator<Bar> {
    private String name1;
    private String name2;
    // getter and setter omitted

    @Override
    public int compare(Bar o1, Bar o2) {
        return o1.getName1().compareTo(o2.getName1());
    }
}

and changed my sorting code to:

Collections.sort(fullList, Comparator.comparing(Foo::getBar,
        Comparator.nullsLast(Comparator.naturalOrder())));

But this gives me compilation errors:

The method comparing(Function, Comparator) in the type Comparator is not applicable for the arguments (Foo::getBar, Comparator>>)

as well as

The type Foo does not define getBar(T) that is applicable here

What am I doing wrong on my second part? How do I fix it?


EDIT:

I just realized that a crucial part is missing.

In the list, Foos are never null, but Bar might be null. In the case that Bar is null, the Foo should be moved to the end of the list.

XtremeBaumer
  • 6,275
  • 3
  • 19
  • 65

2 Answers2

2

you could simply use the lambda with your first approach:

fooList.sort(Comparator.comparing(f -> f.getBar().getName1(),
            Comparator.nullsLast(Comparator.naturalOrder())))

While you are looking towards making Bar comparable, you should do:

@Getter
class Bar implements Comparable<Bar> {
    private String name1;
    private String name2;

    @Override
    public int compareTo(Bar o) {
        return name1.compareTo(o.getName1());
    }
} 

and follow it up with

fooList.sort(Comparator.comparing(Foo::getBar,
        Comparator.nullsLast(Comparator.naturalOrder())));
Naman
  • 27,789
  • 26
  • 218
  • 353
  • `f -> f.getBar().getName1()` requires me to cast `((Foo)f).getBar()...` to work, even though the list is definitly typed. I guess there is no way around it? I also just realized a little mistake in my question. Please have a look at the updated version – XtremeBaumer Jan 17 '20 at 12:14
  • 1
    @XtremeBaumer Related to casting, the minimal code `@Getter public class Foo { private Bar bar; private boolean set; } @Getter public class Bar { private String name1; private String name2; } public void sorting(List fooList) { fooList.sort(Comparator.comparing(f -> f.getBar().getName1(), Comparator.nullsLast(Comparator.naturalOrder()))); }` doesn't require it. See the update for the edit. – Naman Jan 17 '20 at 12:26
  • Ugh, now I see my mistake... I was using the wrong interface on `Bar`... Thanks man! – XtremeBaumer Jan 17 '20 at 12:42
2

For Comparator<T>, the signature is:

static <T,U extends Comparable<? super U>> Comparator<T>
  comparing(Function<? super T,? extends U> keyExtractor)

So the result of Foo::getBar must implement Comparable. Bar implements Comparator, not Comparable.

Oracle's documentation on the comparing​ method is elaborated with (emphasis mine):

Accepts a function that extracts a Comparable sort key from a type T..

Scratte
  • 3,056
  • 6
  • 19
  • 26