Using streams to collect into TreeSet with custom comparator

Working in Java 8, I have a TreeSet defined like this:

private TreeSet<PositionReport> positionReports = 
        new TreeSet<>(Comparator.comparingLong(PositionReport::getTimestamp));

PositionReport is a rather simple class defined like this:

public static final class PositionReport implements Cloneable {
    private final long timestamp;
    private final Position position;

    public static PositionReport create(long timestamp, Position position) {
        return new PositionReport(timestamp, position);
    }

    private PositionReport(long timestamp, Position position) {
        this.timestamp = timestamp;
        this.position = position;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public Position getPosition() {
        return position;
    }
}

This works fine.

Now I want to remove entries from the TreeSet positionReports where timestamp is older than some value. But I cannot figure out the correct Java 8 syntax to express this.

This attempt actually compiles, but gives me a new TreeSet with an undefined comparator:

positionReports = positionReports
        .stream()
        .filter(p -> p.timestamp >= oldestKept)
        .collect(Collectors.toCollection(TreeSet::new))

How do I express, that I want to collect into a TreeSet with a comparator like Comparator.comparingLong(PositionReport::getTimestamp) ?

I would have thought something like

positionReports = positionReports
        .stream()
        .filter(p -> p.timestamp >= oldestKept)
        .collect(
            Collectors.toCollection(
                TreeSet::TreeSet(Comparator.comparingLong(PositionReport::getTimestamp))
            )
        );

But this does not compile / appear to be valid syntax for method references.


Solution 1:

Method references can be used when you have a method (or constructor) that fits the shape of the target you're trying to satisfy. You can't use a method reference in this case because the shape you're targeting is a Supplier, which takes no arguments, but what you have is a TreeSet constructor, which does take an argument, and you need to specify what that argument is. So you have to take the less concise approach and use a lambda expression:

TreeSet<Report> toTreeSet(Collection<Report> reports, long timestamp) {
    return reports.stream().filter(report -> report.timestamp() >= timestamp).collect(
        Collectors.toCollection(
            () -> new TreeSet<>(Comparator.comparingLong(Report::timestamp))
        )
    );
}

Solution 2:

This is easy just use next code:

    positionReports = positionReports
        .stream()
        .filter(p -> p.timestamp >= oldestKept)
        .collect(
            Collectors.toCollection(()->new TreeSet<>(Comparator.comparingLong(PositionReport::getTimestamp)
)));

Solution 3:

You can just convert into a SortedSet at the end (provided that you don't mind the additional copy).

positionReports = positionReports
                .stream()
                .filter(p -> p.getTimeStamp() >= oldestKept)
                .collect(Collectors.toSet());

return new TreeSet(positionReports);

Solution 4:

There is a method on Collection for this without having to use streams: default boolean removeIf(Predicate<? super E> filter). See Javadoc.

So your code could just look like this:

positionReports.removeIf(p -> p.timestamp < oldestKept);