Menggunakan aliran untuk dikumpulkan ke TreeSet dengan pembanding khusus

92

Bekerja di Java 8, saya memiliki TreeSetdefinisi seperti ini:

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

PositionReport adalah kelas yang agak sederhana yang didefinisikan seperti ini:

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

Ini bekerja dengan baik.

Sekarang saya ingin menghapus entri dari TreeSet positionReportsmana timestampyang lebih tua dari beberapa nilai. Tetapi saya tidak dapat menemukan sintaks Java 8 yang benar untuk mengungkapkan ini.

Upaya ini sebenarnya mengkompilasi, tetapi memberi saya yang baru TreeSetdengan pembanding yang tidak ditentukan:

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

Bagaimana cara saya mengungkapkan, bahwa saya ingin mengumpulkan menjadi TreeSetdengan pembanding seperti Comparator.comparingLong(PositionReport::getTimestamp)?

Saya akan memikirkan sesuatu seperti

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

Tetapi ini tidak mengkompilasi / tampaknya sintaks yang valid untuk referensi metode.

tbsalling
sumber

Jawaban:

118

Referensi metode digunakan saat Anda memiliki metode (atau konstruktor) yang sudah sesuai dengan bentuk target yang ingin Anda penuhi. Anda tidak dapat menggunakan referensi metode dalam kasus ini karena bentuk yang Anda targetkan adalah Supplieryang tidak memerlukan argumen dan mengembalikan koleksi, tetapi yang Anda miliki adalah TreeSetkonstruktor yang tidak mengambil argumen, dan Anda perlu menentukan argumen itu. adalah. Jadi, Anda harus mengambil pendekatan yang kurang ringkas dan menggunakan ekspresi 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))
        )
    );
}
gdejohn
sumber
4
Satu hal yang perlu diperhatikan adalah Anda tidak memerlukan pembanding jika tipe TreeSet Anda (dalam hal ini PositionReport) mengimplementasikan yang sebanding.
xtrakBandit
35
Menindaklanjuti @xtrakBandit - sekali lagi jika Anda tidak perlu menentukan pembanding (penyortiran alami) - Anda dapat membuatnya sangat ringkas:.collect(Collectors.toCollection(TreeSet::new));
Joshua Goldberg
Saya mendapat error ini:toCollection in class Collectors cannot be applied to given types
Bahadir Tasdemir
@BahadirTasdemir Kode bekerja. Pastikan Anda hanya menyampaikan satu argumen ke Collectors::toCollection: a Supplieryang mengembalikan a Collection. Supplieradalah tipe dengan hanya satu metode abstrak, yang berarti dapat menjadi target ekspresi lambda seperti dalam jawaban ini. Ekspresi lambda tidak boleh mengambil argumen (karena itu daftar argumen kosong ()) dan mengembalikan koleksi dengan tipe elemen yang cocok dengan tipe elemen dalam aliran yang Anda kumpulkan (dalam hal ini a TreeSet<PositionReport>).
gdejohn
15

Ini mudah, cukup gunakan kode berikutnya:

    positionReports = positionReports
        .stream()
        .filter(p -> p.timestamp >= oldestKept)
        .collect(
            Collectors.toCollection(()->new TreeSet<>(Comparator.comparingLong(PositionReport::getTimestamp)
)));
Владимир Дворник
sumber
9

Anda bisa mengubahnya menjadi SortedSet di akhir (asalkan Anda tidak keberatan dengan salinan tambahan).

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

return new TreeSet(positionReports);
Daniel Scott
sumber
7
Anda harus berhati-hati saat melakukan ini. Anda BISA kehilangan elemen saat melakukan ini. Seperti dalam pertanyaan yang ditanyakan di atas, pembanding alami elemen berbeda dari yang ingin digunakan OP. Jadi, Anda dalam konversi awal, karena ini adalah himpunan, mungkin kehilangan beberapa elemen yang mungkin tidak dimiliki oleh pembanding lainnya (yaitu, pembanding pertama mungkin kembali compareTo() sebagai 0 sedangkan yang lain mungkin tidak untuk beberapa perbandingan. Semua yang compareTo()memiliki 0 hilang karena ini adalah satu set.)
looneyGod
6

Ada metode pada Koleksi untuk ini tanpa harus menggunakan aliran: default boolean removeIf(Predicate<? super E> filter). Lihat Javadoc .

Jadi kode Anda bisa saja terlihat seperti ini:

positionReports.removeIf(p -> p.timestamp < oldestKept);
Michael Damone
sumber
1

Masalah dengan TreeSet adalah bahwa pembanding yang kita inginkan untuk menyortir item digunakan juga untuk mendeteksi duplikat saat memasukkan item ke dalam set. Jadi, jika fungsi pembanding adalah 0 untuk dua item, salah satu item akan dibuang karena dianggap sebagai duplikat.

Deteksi duplikat harus dilakukan dengan metode hashCode terpisah yang benar dari item tersebut. Saya lebih suka menggunakan HashSet sederhana untuk mencegah duplikat dengan hashCode mempertimbangkan semua properti (id dan nama dalam contoh) dan mengembalikan Daftar yang diurutkan sederhana saat mendapatkan item (hanya mengurutkan berdasarkan nama dalam contoh):

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