Özel karşılaştırıcıyla TreeSet'e toplamak için akışları kullanma


92

Java 8'de çalışıyorum, şöyle bir TreeSettanımım var :

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

PositionReport şu şekilde tanımlanan oldukça basit bir sınıftır:

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

Bu iyi çalışıyor.

Şimdi girdileri bir değerden daha eski olan TreeSet positionReportsyerden kaldırmak istiyorum timestamp. Ancak bunu ifade etmek için doğru Java 8 sözdizimini bulamıyorum.

Bu girişim aslında derler, ancak bana TreeSettanımsız bir karşılaştırıcıyla yeni bir şey verir :

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

TreeSetBir karşılaştırıcı ile toplamak istediğimi nasıl ifade ederim Comparator.comparingLong(PositionReport::getTimestamp)?

Şöyle bir şey düşünürdüm

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

Ancak bu, yöntem referansları için geçerli sözdizimi derlemez / görünmez.

Yanıtlar:


118

Yöntem referansları, tatmin etmeye çalıştığınız hedefin şekline zaten uyan bir yönteme (veya yapıcıya) sahip olduğunuz zamanlar içindir. Bu durumda bir yöntem referansı kullanamazsınız çünkü hedeflediğiniz şekil Supplierhiçbir argüman almayan ve bir koleksiyon döndüren bir şeklidir , ancak sahip TreeSetolduğunuz şey, argüman alan bir kurucu ve bu argümanı belirtmeniz gerekir. dır-dir. Bu nedenle, daha az özlü yaklaşımı benimsemeli ve bir lambda ifadesi kullanmalısınız:

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
Unutulmaması gereken bir nokta, TreeSet'inizin türü (bu durumda PositionReport) karşılaştırılabilir nitelikteyse, karşılaştırıcıya ihtiyacınız olmamasıdır.
xtrakBandit

35
@XtrakBandit'i takip etmek - yine karşılaştırıcıyı belirtmeniz gerekmiyorsa (doğal sıralama) - bunu çok kısa bir şekilde yapabilirsiniz:.collect(Collectors.toCollection(TreeSet::new));
Joshua Goldberg

Bu hatayı aldım:toCollection in class Collectors cannot be applied to given types
Bahadır Taşdemir

@BahadirTasdemir Kod çalışıyor. Yalnızca bir bağımsız değişken ilettiğinizden emin olun Collectors::toCollection: a Supplierdöndüren a Collection. Suppliertek bir soyut yöntemi olan bir türdür, yani bu cevapta olduğu gibi bir lambda ifadesinin hedefi olabilir. Lambda ifadesi hiçbir argüman almamalı (dolayısıyla boş argüman listesi ()) ve topladığınız akıştaki öğelerin türüyle eşleşen öğe türüne sahip bir koleksiyon (bu durumda a TreeSet<PositionReport>) döndürmelidir .
gdejohn

15

Bu çok kolay, sadece bir sonraki kodu kullanın:

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

9

Sonunda bir SortedSet'e dönüştürebilirsiniz (ek kopyayı önemsemediğiniz sürece).

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

return new TreeSet(positionReports);

7
Bunu yaparken dikkatli olmalısın. Bunu yaparken öğelerinizi kaybedebilirsiniz. Yukarıda sorulan soruda olduğu gibi, elementlerin doğal karşılaştırıcısı, OP'nin kullanmak istediğinden farklıdır. Dolayısıyla, ilk dönüştürmede siz, bir küme olduğundan, diğer karşılaştırıcının sahip olamayacağı bazı öğeleri kaybedebilir (yani, ilk karşılaştırıcı compareTo() 0 olarak dönebilirken , diğer bazı karşılaştırmalar için olmayabilir. Tüm olanlar compareTo()0 olan bu bir set olduğu için kayboldu.)
looneyGod

6

Akışları kullanmak zorunda kalmadan bunun için Koleksiyonu'ndan bir yöntem yoktur: default boolean removeIf(Predicate<? super E> filter). Görmek Javadoc .

Yani kodunuz şöyle görünebilir:

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

1

TreeSet ile ilgili sorun, öğeleri sıralamak için istediğimiz karşılaştırıcının aynı zamanda sete öğe eklerken kopyaları tespit etmek için de kullanılmasıdır. Dolayısıyla, karşılaştırma işlevi iki öğe için 0 ise, birini yineleme olarak kabul ederek yanlışlıkla atar.

Yineleme tespiti, öğelerin ayrı bir doğru hashCode yöntemi ile yapılmalıdır. Tüm özellikleri (örnekteki id ve ad) dikkate alarak bir hashCode ile yinelemeleri önlemek için basit bir HashSet kullanmayı ve öğeleri alırken basit bir sıralı Liste döndürmeyi tercih ediyorum (örnekte yalnızca ada göre sıralama):

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

1
positionReports = positionReports.stream()
                             .filter(p -> p.getTimeStamp() >= oldestKept)
                             .collect(Collectors.toCollection(() -> new 
TreeSet<PositionReport>(Comparator.comparingLong(PositionReport::getTimestamp))));
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.