ace
ace

Reputation: 12024

How do I deal with null and duplicate values in a Java 8 Comparator?

I have a Photo object:

public class Photo {
    @Id
    private String id;
    private LocalDateTime created;
    private Integer poNumber;
}

poNumber can be null for some photos or all photos in a set. I want to sort a set of photos according to poNumber, so that the lowest poNumber appears first in the sorted set. poNumber may also be duplicated in the set. If poNumber is duplicated then sort according to created (earliest created photo appears first). If poNumber is null then sort according to created.

I tried the below code:

Set<Photo> orderedPhotos = new TreeSet<>(
    Comparator.nullsFirst(Comparator.comparing(Photo::getPoNumber))
              .thenComparing(Photo::getCreated));

for (Photo photo : unOrderedPhotos) {
    orderedPhotos.add(photo);
}

But it throws a NullPointerException whenever poNumber is null. If poNumber is not null then it works fine. How can I solve this issue?

Upvotes: 14

Views: 1309

Answers (4)

Oleksandr Pyrohov
Oleksandr Pyrohov

Reputation: 16216

You can use a two-argument Comparator.comparing overload, for example:

import static java.util.Comparator.*; // for the sake of brevity

Set<Photo> orderedPhotos = new TreeSet<>(
    Comparator.comparing(Photo::getPoNumber, nullsFirst(naturalOrder()))
              .thenComparing(Photo::getCreated));

Upvotes: 8

glglgl
glglgl

Reputation: 91017

We need a solution which turns the Function<> to a Comparator, but in a way which adds the said null checking to the chain.

And that's where Oleksandr's answer enters the scene: it creates a Comparator which maps the compared Photo to comparing of Integers, and then adds a Comparator which naturally orders these Integers with the nulls first:

Comparator.comparing(Photo::getPoNumber, Comparator.nullsFirst(Comparator.naturalOrder()))

And this comparator is then chained (for the case of equality) with another comparator which considers the creation date.

Upvotes: 10

Peter Lawrey
Peter Lawrey

Reputation: 533492

The way you have written it, the Photo key could be null but nothing else.

Comparator.nullsFirst( // Photo could be null.
    Comparator.comparing(Comparator.nullsFirst(Photo::getPoNumber)) // poNumber could be null.
              .thenComparing(Comparator.nullsFirst(Photo::getCreated))) // created could be null

If any of these can't be null you can remove the Comparator.nullsFirst

Upvotes: 3

Joe
Joe

Reputation: 1342

This will put null values at the beginning

Integer getPoNumber() { return poNumber == null ? Integer.MIN_VALUE : poNumber };

This will put null values at the end

Integer getPoNumber() { return poNumber == null ? Integer.MAX_VALUE: poNumber };

Otherwise, implement your own comparator to handle the null values

Upvotes: 1

Related Questions