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


92

在Java 8中,我有一个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早于某个值的位置删除条目。但是我无法找出正确的Java 8语法来表达这一点。

该尝试实际上可以编译,但是为我提供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))
            )
        );

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

Answers:


118

方法引用适用于您拥有已经适合您要满足的目标形状的方法(或构造函数)的情况。在这种情况下,您不能使用方法引用,因为您要定位的形状是Supplier不带任何参数并返回一个集合的a形,但是您拥有的是一个TreeSet确实带有一个参数的构造函数,并且您需要指定该参数的形式是。因此,您必须采用不太简洁的方法,并使用lambda表达式:

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

4
需要注意的一件事是,如果TreeSet的类型(在本例中为PositionReport)实现可比的,则不需要比较器。
xtrakBandit,2015年

35
继续@xtrakBandit -如果不需要指定比较器(自然排序),则可以使其非常简洁:.collect(Collectors.toCollection(TreeSet::new));
Joshua Goldberg

我收到此错误:toCollection in class Collectors cannot be applied to given types
Bahadir Tasdemir

@BahadirTasdemir该代码有效。确保仅将一个参数传递给Collectors::toCollectionSupplier返回a的a CollectionSupplier是只有一个抽象方法的类型,这意味着它可以是此表达式中lambda表达式的目标。lambda表达式必须不带任何参数(因此,参数列表为空()),并返回一个元素类型与要收集的流中的元素类型相匹配的集合(在本例中为a TreeSet<PositionReport>)。
gdejohn

15

这很容易,只需使用下面的代码:

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

9

您可以在最后将其转换为SortedSet(前提是您不介意其他副本)。

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

return new TreeSet(positionReports);

7
执行此操作时必须小心。在执行此操作时,您可能会丢失元素。就像上面的问题一样,元素的自然比较器不同于一个OP要使用的比较器。因此,在初始转换中,因为它是一个集合,所以它可能会丢失一些其他比较器可能没有的元素(即,第一个比较器可能返回compareTo() 0,而另一个比较器可能没有比较。所有这些compareTo()都是0)失去了,因为这是一个集合。)
looneyGod

6

在Collection上有一种无需使用流的方法:default boolean removeIf(Predicate<? super E> filter)。参见Javadoc

因此您的代码可能看起来像这样:

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

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

0

TreeSet的问题在于,在将项目插入集合中时,我们想要对项目进行排序的比较器还用于检测重复项。因此,如果比较器函数的两项为0,则错误地将一项视为重复项而丢弃一项。

重复项检测应通过单独的正确的hashCode方法进行。我更喜欢使用一个简单的HashSet来防止带有hashCode的重复项考虑所有属性(示例中的id和name),并在获取项目时返回一个简单的有序列表(在示例中仅按名称排序):

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());
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.