Sử dụng các luồng để thu thập vào TreeSet với bộ so sánh tùy chỉnh


92

Làm việc trong Java 8, tôi có một TreeSetđịnh nghĩa như sau:

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

PositionReport là một lớp khá đơn giản được định nghĩa như thế này:

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

Điều này hoạt động tốt.

Bây giờ tôi muốn loại bỏ đi từ TreeSet positionReportsnơi timestampcũ hơn một số giá trị. Nhưng tôi không thể tìm ra cú pháp Java 8 chính xác để diễn đạt điều này.

Nỗ lực này thực sự biên dịch, nhưng mang lại cho tôi một cái mới TreeSetvới bộ so sánh không xác định:

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

Làm cách nào để diễn đạt, mà tôi muốn thu thập vào một TreeSetbộ so sánh như thế Comparator.comparingLong(PositionReport::getTimestamp)nào?

Tôi sẽ nghĩ một cái gì đó như

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

Nhưng điều này không biên dịch / dường như là cú pháp hợp lệ cho các tham chiếu phương thức.

Câu trả lời:


118

Tham chiếu phương thức dành cho khi bạn có một phương thức (hoặc hàm tạo) đã phù hợp với hình dạng của mục tiêu mà bạn đang cố gắng đáp ứng. Bạn không thể sử dụng tham chiếu phương thức trong trường hợp này vì hình dạng bạn đang nhắm mục tiêu là hình dạng Supplierkhông có đối số và trả về một tập hợp, nhưng những gì bạn có là một phương thức TreeSetkhởi tạo có tham số và bạn cần chỉ định đối số đó là gì Là. Vì vậy, bạn phải thực hiện cách tiếp cận ít ngắn gọn hơn và sử dụng biểu thức 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
Một điều cần lưu ý là bạn không cần trình so sánh nếu kiểu TreeSet của bạn (trong trường hợp này là PositionReport) triển khai có thể so sánh được.
xtrakBandit

35
Theo dõi trên @xtrakBandit - một lần nữa nếu bạn không cần chỉ định bộ so sánh (sắp xếp tự nhiên) - bạn có thể làm cho nó thật ngắn gọn:.collect(Collectors.toCollection(TreeSet::new));
Joshua Goldberg

Tôi gặp lỗi này:toCollection in class Collectors cannot be applied to given types
Bahadir Tasdemir

@BahadirTasdemir Mã hoạt động. Đảm bảo rằng bạn chỉ chuyển một đối số tới Collectors::toCollection: a Suppliertrả về a Collection. Supplierlà một kiểu chỉ có một phương thức trừu tượng duy nhất, có nghĩa là nó có thể là đích của một biểu thức lambda như trong câu trả lời này. Biểu thức lambda không được nhận đối số (do đó là danh sách đối số trống ()) và trả về một tập hợp có kiểu phần tử khớp với kiểu của các phần tử trong luồng bạn đang thu thập (trong trường hợp này là a TreeSet<PositionReport>).
gdejohn

15

Điều này thật dễ dàng chỉ cần sử dụng mã tiếp theo:

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

9

Bạn chỉ có thể chuyển đổi thành SortedSet ở cuối (miễn là bạn không bận tâm đến bản sao bổ sung).

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

return new TreeSet(positionReports);

7
Bạn phải cẩn thận trong khi làm điều này. Bạn CÓ THỂ mất các yếu tố trong khi làm điều này. Giống như trong câu hỏi được hỏi ở trên, bộ so sánh tự nhiên của các phần tử khác với bộ so sánh OP muốn sử dụng. Vì vậy, bạn trong việc chuyển đổi ban đầu, vì nó là một tập hợp, nó có thể mất một số yếu tố mà so sánh khác có thể có không (tức là so sánh đầu tiên có thể quay trở lại compareTo() là 0 trong khi người kia có thể có không phải cho một số comparisions. Tất cả những nơi compareTo()là 0 bị mất vì đây là một tập hợp.)
looneyGod

6

Có một phương pháp trên Collection cho điều này mà không cần phải sử dụng dòng: default boolean removeIf(Predicate<? super E> filter). Xem Javadoc .

Vì vậy, mã của bạn có thể trông giống như sau:

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

Vấn đề với TreeSet là bộ so sánh mà chúng ta muốn sắp xếp các mục cũng được sử dụng để phát hiện các bản sao khi chèn các mục vào tập hợp. Vì vậy, nếu hàm so sánh là 0 cho hai mục, nó sẽ loại bỏ sai một mục coi nó là trùng lặp.

Việc phát hiện các bản sao phải được thực hiện bằng một phương pháp Mã băm chính xác riêng biệt của các mục. Tôi thích sử dụng HashSet đơn giản để ngăn các bản sao với Mã băm xem xét tất cả các thuộc tính (id và tên trong ví dụ) và trả về một Danh sách được sắp xếp đơn giản khi nhận các mục (chỉ sắp xếp theo tên trong ví dụ):

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());
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.