Verwenden von Streams zum Sammeln in TreeSet mit einem benutzerdefinierten Komparator

91

Bei der Arbeit in Java 8 habe ich Folgendes TreeSetdefiniert:

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

PositionReport ist eine ziemlich einfache Klasse, die wie folgt definiert ist:

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

Das funktioniert gut.

Jetzt möchte ich Einträge aus dem entfernen , TreeSet positionReportswo timestampälter ist als ein Wert. Aber ich kann nicht die richtige Java 8-Syntax herausfinden, um dies auszudrücken.

Dieser Versuch wird tatsächlich kompiliert, gibt mir aber einen neuen TreeSetmit einem undefinierten Komparator:

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

Wie drücke ich aus, dass ich TreeSetmit einem Komparator wie in einem sammeln möchte Comparator.comparingLong(PositionReport::getTimestamp)?

Ich hätte so etwas gedacht

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

Dies kompiliert / scheint jedoch keine gültige Syntax für Methodenreferenzen zu sein.

tbsalling
quelle

Antworten:

115

Methodenreferenzen gelten für den Fall, dass Sie eine Methode (oder einen Konstruktor) haben, die / der bereits zur Form des Ziels passt, das Sie erfüllen möchten. In diesem Fall können Sie keine Methodenreferenz verwenden, da die Form, auf die Sie abzielen, Supplierkeine Argumente akzeptiert und eine Auflistung zurückgibt. Sie verfügen jedoch über einen TreeSetKonstruktor, der ein Argument akzeptiert, und Sie müssen angeben, was dieses Argument ist ist. Sie müssen also den weniger präzisen Ansatz wählen und einen Lambda-Ausdruck verwenden:

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
quelle
4
Beachten Sie, dass Sie den Komparator nicht benötigen, wenn der Typ Ihres TreeSet (in diesem Fall PositionReport) vergleichbar implementiert ist.
XtrakBandit
35
Wenn Sie @xtrakBandit nachverfolgen - auch wenn Sie den Komparator nicht angeben müssen (natürliche Sortierung) - können Sie ihn sehr präzise gestalten:.collect(Collectors.toCollection(TreeSet::new));
Joshua Goldberg
Ich habe diesen Fehler erhalten:toCollection in class Collectors cannot be applied to given types
Bahadir Tasdemir
@ BahadirTasdemir Der Code funktioniert. Stellen Sie sicher, dass Sie nur ein Argument an Folgendes übergeben Collectors::toCollection: a Supplier, das a zurückgibt Collection. Supplierist ein Typ mit nur einer abstrakten Methode, was bedeutet, dass er wie in dieser Antwort das Ziel eines Lambda-Ausdrucks sein kann. Der Lambda-Ausdruck darf keine Argumente annehmen (daher die leere Argumentliste ()) und eine Sammlung mit einem Elementtyp zurückgeben, der dem Typ der Elemente in dem Stream entspricht, den Sie sammeln (in diesem Fall a TreeSet<PositionReport>).
Gdejohn
15

Dies ist einfach, verwenden Sie einfach den nächsten Code:

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

Sie können am Ende einfach in ein SortedSet konvertieren (vorausgesetzt, die zusätzliche Kopie macht Ihnen nichts aus).

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

return new TreeSet(positionReports);
Daniel Scott
quelle
7
Sie müssen dabei vorsichtig sein. Sie könnten dabei Elemente verlieren. Wie in der oben gestellten Frage unterscheidet sich der natürliche Komparator der Elemente von dem, den OP verwenden möchte. Da es sich bei der anfänglichen Konvertierung um eine Menge handelt, können einige Elemente verloren gehen, die der andere Komparator möglicherweise nicht hat (dh der erste Komparator gibt möglicherweise compareTo() 0 zurück, während der andere möglicherweise nicht für einige Vergleiche verwendet wird. Alle Elemente , bei denen compareTo()0 ist ist verloren, da dies ein Satz ist.)
looneyGod
6

Hierfür gibt es eine Methode für die Sammlung, ohne Streams verwenden zu müssen : default boolean removeIf(Predicate<? super E> filter). Siehe Javadoc .

Ihr Code könnte also so aussehen:

positionReports.removeIf(p -> p.timestamp < oldestKept);
Michael Damone
quelle
0

Das Problem mit TreeSet ist, dass der Komparator, den wir zum Sortieren der Elemente benötigen, auch zum Erkennen von Duplikaten beim Einfügen von Elementen in das Set verwendet wird. Wenn die Komparatorfunktion für zwei Elemente 0 ist, wird eines fälschlicherweise verworfen, wenn es als doppelt betrachtet wird.

Die Erkennung von Duplikaten sollte durch eine separate korrekte hashCode-Methode der Elemente erfolgen. Ich bevorzuge es, ein einfaches HashSet zu verwenden, um Duplikate mit einem HashCode unter Berücksichtigung aller Eigenschaften (ID und Name im Beispiel) zu verhindern und beim Abrufen der Elemente eine einfache sortierte Liste zurückzugeben (im Beispiel nur nach Namen sortieren):

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