简体   繁体   中英

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.

Method references are for when you have a method (or constructor) that already 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 and returns a collection, but what you have is a TreeSet constructor that 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))
        )
    );
}

This is easy just use next code:

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

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

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM