Using streams to collect into TreeSet with custom comparator

JavaCollectionsJava 8

Java Problem Overview


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.

Java Solutions


Solution 1 - Java

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 - Java

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 - Java

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 - Java

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);

Solution 5 - Java

The problem with TreeSet is that the comparator that we want for sorting the items is used also for detecting duplicates when inserting items into the set. So if the comparator function is 0 for two items it wrongly discards one considering it as duplicate.

The duplicates detection should be done by a separate correct hashCode method of the items. I prefer to use a simple HashSet to prevent duplicates with a hashCode considering all properties (id and name in the example) and return a simple sorted List when getting the items (sorting only by name in the example):

public class ProductAvailableFiltersDTO {

    private Set<FilterItem> category_ids = new HashSet<>();

    public List<FilterItem> getCategory_ids() {
        return category_ids.stream()
            .sorted(Comparator.comparing(FilterItem::getName))
            .collect(Collectors.toList());
    }

    public void setCategory_ids(List<FilterItem> category_ids) {
        this.category_ids.clear();
        if (CollectionUtils.isNotEmpty(category_ids)) {
            this.category_ids.addAll(category_ids);
        }
    }
}


public class FilterItem {
    private String id;
    private String name;

    public FilterItem(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof FilterItem)) return false;
        FilterItem that = (FilterItem) o;
        return Objects.equals(getId(), that.getId()) &&
                Objects.equals(getName(), that.getName());
    }

    @Override
    public int hashCode() {

        return Objects.hash(getId(), getName());
    }
}

Solution 6 - Java

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontbsallingView Question on Stackoverflow
Solution 1 - JavagdejohnView Answer on Stackoverflow
Solution 2 - JavaVolodymyr DvornykView Answer on Stackoverflow
Solution 3 - JavaDaniel ScottView Answer on Stackoverflow
Solution 4 - JavaMichael DamoneView Answer on Stackoverflow
Solution 5 - JavaDaniel MoraView Answer on Stackoverflow
Solution 6 - JavaCyril SojanView Answer on Stackoverflow