ストリームを使用してカスタムコンパレータを使用して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ます。しかし、これを表現するための正しい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))
            )
        );

しかし、これはコンパイルされない/メソッド参照の有効な構文であるように見えます。

回答:


118

メソッド参照は、満たそうとしているターゲットの形状にすでに適合しているメソッド(またはコンストラクター)がある場合に使用します。この場合、ターゲットとするシェイプはSupplier引数をとらずにコレクションを返すシェイプであるため、メソッド参照を使用できませんが、引数をとるTreeSetコンストラクターがあり、その引数を指定する必要があります。です。したがって、簡潔ではないアプローチを取り、ラムダ式を使用する必要があります。

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::toCollection:aに1つだけ渡すようにしてください。は単一の抽象メソッドを持つタイプです。つまり、この回答のようにラムダ式のターゲットになることができます。ラムダ式は引数を取ってはならず(したがって空の引数リスト)、収集しているストリーム内の要素のタイプ(この場合はa )と一致する要素タイプのコレクションを返す必要があります。SupplierCollectionSupplier()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 2017

6

ストリームを使用せずにこれを行うためのコレクションのメソッドがあります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の問題は、アイテムを並べ替えるために必要なコンパレータが、アイテムをセットに挿入するときに重複を検出するためにも使用されることです。したがって、2つのアイテムのコンパレータ関数が0の場合、重複していると見なして1つを誤って破棄します。

重複の検出は、アイテムの個別の正しいhashCodeメソッドによって実行する必要があります。単純なHashSetを使用して、すべてのプロパティ(例ではidとname)を考慮したhashCodeとの重複を防ぎ、アイテムを取得するときに単純なソート済みリストを返す(例では名前のみでソート)ことを好みます。

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());
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.