使用自定义比较器将流收集到 TreeSet 中

在 Java8中,我有一个如下定义的 TreeSet:

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

PositionReport是一个非常简单的类,定义如下:

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

这样挺好的。

现在我想从 TreeSet positionReports中删除一些条目,其中 timestamp比某个值更老。但是我无法找到正确的 Java8语法来表达这一点。

这种尝试实际上是编译的,但是给我一个新的带有未定义比较器的 TreeSet:

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

我如何表达,我想收集到一个 TreeSet与一个比较器,如 Comparator.comparingLong(PositionReport::getTimestamp)

我本以为

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

但这并不是编译/似乎是方法引用的有效语法。

67309 次浏览

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

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

This is easy just use next code:

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

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