Gibt es eine Alternative zu instanceof beim Filtern eines Java-Streams nach Klassen?


8

Ich habe eine unerwartete Situation in einem Projekt, in dem alle Typen, die eine Klasse erweitern, in eine Java-Sammlung gepackt werden. Aber nur eine bestimmte Erweiterung dieser Klasse enthält eine zusätzliche Methode. Nennen wir es "also ()"; und lassen Sie mich klar sein, dass keine andere Erweiterung es hat. Kurz bevor ich eine Aufgabe für jedes Element in dieser Sammlung ausführe, muss ich auch () für jedes Element aufrufen, das es implementiert.

Der einfachste Weg ist folgender:

stuff.stream().filter(item -> item instanceof SpecificItem)
            .forEach(item -> ((SpecificItem)item).also()));
stuff.stream().forEach(item -> item.method());

Es funktioniert gut, aber ich bin mit der "Instanz von" dort nicht zufrieden. Das ist im Allgemeinen ein Hinweis auf schlechten Codegeruch. Es ist sehr wahrscheinlich, dass ich diese Klasse umgestalten werde, nur um sie loszuwerden. Bevor ich jedoch etwas so Dramatisches mache, dachte ich, ich würde mich bei der Community erkundigen, ob jemand mit mehr Erfahrung mit Streams oder Sammlungen eine einfachere Lösung hat.

Ist es als Beispiel (sicherlich nicht exklusiv) möglich, eine Ansicht einer Sammlung zu erhalten, die Einträge nach Klassen filtert?


2
Wenn Sie nach filtern SpecificItemmüssen und dies der richtige Weg ist, um das Prädikat anzugeben, warum sind Sie dann zimperlich, instanceofwenn Sie dort sind?
Robert Harvey

Implementieren die Objekte im Stream eine gemeinsame Schnittstelle, über die Sie zwischen Klassen unterscheiden können? Einige .getKind()oder .isSpecific()?
9000

@ 9000 Leider nein, und ich glaube, dass das Hinzufügen von so etwas es etwas weiter durcheinander bringen würde.
Michael Eric Oberlin

8
Gibt es einen Grund, warum die SpecificItemImplementierung dann nicht erst also()zuerst aufgerufen wird?
Tristan Burnside

1
@ LuísGuilherme Iterablehat keine stream()Methode, aber Sie können forEach()direkt auf eine aufrufen Iterable.
Shmosel

Antworten:


3

Ich würde vorschlagen, einen .mapAnruf zu tätigen , um die Besetzung für Sie zu erledigen. Dann kann Ihr späterer Code die "echte Sache" verwenden.

Vor:

stuff.stream().filter(item -> item instanceof SpecificItem)
            .forEach(item -> ((SpecificItem)item).also()));

Nach:

stuff.stream().filter(item -> item instanceof SpecificItem)
            .map(item -> (SpecificItem)item)
            .forEach(specificItem -> specificItem.also()));

Das ist nicht perfekt, scheint aber die Dinge ein wenig aufzuräumen.


Es ist mein Interesse, so viel Casting wie möglich aus dem Arbeitscode zu entfernen, und Sie haben Recht, die Karte ist genau dafür gedacht.
Michael Eric Oberlin

Funktioneller: stuff.stream().filter(item -> item instanceof SpecificItem).map(SpecificItem.class::cast).forEach(SpecificItem::also);
Strickli

3

Ich habe eine unerwartete Situation in einem Projekt, in dem alle Typen, die eine Klasse erweitern, in eine Java-Sammlung gepackt werden. Aber nur eine Erweiterung dieser Klasse implementiert eine Methode. Nennen wir es "also ()". Kurz bevor ich eine Aufgabe für jedes Element in dieser Sammlung ausführe, muss ich auch () für jedes Element aufrufen, das es implementiert.

Das ist offensichtlich ein fehlerhaftes Design. Aus dem, was Sie geschrieben haben, geht nicht hervor, was es bedeutet, dass eine Klasse die Methode nicht implementiert . Wenn es einfach nichts tut , spielt es keine Rolle, daher gehe ich davon aus, dass es eine unerwünschte Nebenwirkung gibt.

Es funktioniert gut, aber ich bin mit der "Instanz von" dort nicht zufrieden.

Dein Mut ist richtig. Gutes objektorientiertes Design würde ohne weiteres Wissen funktionieren, was genau ein Objekt ist oder ob es sich um eine Instanz einer besonderen Art handelt.

Bevor ich jedoch etwas so Dramatisches mache, dachte ich, ich würde mich bei der Community erkundigen, ob jemand mit mehr Erfahrung mit Streams oder Sammlungen eine einfachere Lösung hat.

Refactoring ist nicht dramatisch. Es verbessert die Codequalität.

Mit Ihrer angegebenen Codebasis wäre die einfachste Lösung, um sicherzustellen, dass Sie zwei separate Sammlungen haben, eine mit der übergeordneten und eine mit der untergeordneten Klasse. Das ist aber nicht ganz sauber.


Ich habe meine Frage genauer bearbeitet. Um dies weiter zu verdeutlichen, ist dies ein Knaller für eine Änderung, die sofort benötigt wird. Eine Erweiterung der Klasse muss berücksichtigt werden. Zum Refactoring sagen wir einfach, dass es ein wesentlicher Bestandteil des Programmierens ist. Ich suche jedoch nach dem am wenigsten dramatischen Refactor, der möglich ist.
Michael Eric Oberlin

Auf Ihre Frage gibt es eine klare Antwort: Wenn der Filter wissen muss, mit welcher Instanz er sich befassen muss, führt kein Weg an einer Instanz vorbei (außer einer Art seltsamer Reflexionsmagie, die eine kryptischere Art zu fragen ist Instanz von ). Die Zeit, die Sie jetzt für eine schnelle und schmutzige Lösung sparen, wird später im Produktlebenszyklus doppelt für die Behebung von Fehlern aufgrund eines schlechten Designs aufgewendet.
Thomas Junk

@MichaelEricOberlin: Wenn ich Thomas also richtig höre, scheinen Sie die Wahl zu haben: Ändern Sie das Design oder lassen Sie es so, wie es ist.
Robert Harvey

@RobertHarvey hat es so ausgedrückt, es klingt tautologisch;) Wenn er nicht umgestalten will, gibt es keine andere Möglichkeit, als den aktuellen Weg zu gehen.
Thomas Junk

2

Es ist schwer, spezifische Empfehlungen zu geben, ohne zu wissen, was SpecificItemund was also()tatsächlich sind, aber:

Definieren Sie also()auf der Oberklasse von SpecificItem. Geben Sie ihm eine Standardimplementierung, die nichts bewirkt (geben Sie ihm einen leeren Methodenkörper). Einzelne Unterklassen können es bei Bedarf überschreiben. Je nachdem, was alsotatsächlich ist, müssen Sie es möglicherweise in etwas umbenennen, das für alle beteiligten Klassen sinnvoll ist.


Das ist ziemlich vernünftig, und ich werde möglicherweise irgendwann umgestalten, um das zu tun.
Michael Eric Oberlin

1

eine Alternative:

  1. Lassen Sie SpecificItem eine Schnittstelle implementieren (z. B. "Filterbar").
  2. Erstellen Sie eine neue Klasse, die Stream erweitert
  3. Erstellen Sie eine neue Filtermethode, die Objekte akzeptiert, die Ihre Schnittstelle implementieren
  4. Überschreiben Sie die ursprüngliche Stream-Methode und leiten Sie sie zu Ihrer Implementierung um (indem Sie den Prädikatparameter in Ihre Schnittstelle umwandeln).

Auf diese Weise können nur Objekte, die Ihre Schnittstelle implementieren, Themeselves an Ihre Methode übergeben. Sie müssen keine Instanz von verwenden

public class MyStream extends Stream
{
    //...

    Stream<Filterable> filter(Predicate<? implements Filterable> predicate)
    {
         return super.filter(predicate);
    }
}

3
Das ist nicht der Weg: schlechtes Design zu verbessern, indem man es schlechter macht.
Thomas Junk

Ich muss Thomas Junk hier zustimmen. Schritt drei macht im Grunde das, was ich bereits mache; und ohne instanceof erneut zu verwenden, sehe ich nicht, wie Ihre Methode dies beheben würde.
Michael Eric Oberlin

1
@ MichaelEricOberlin: Genau darum habe ich mir Sorgen gemacht. Erstellen eines Rube Goldbergs, um die Vorstellung einer "Best Practice" zu befriedigen.
Robert Harvey

1

Ich hoffe, ich vermisse hier nichts Offensichtliches (auch wie in @Tristan Burnsides Kommentar vorgeschlagen ), aber warum kann ich nicht zuerst SpecificItem.method()anrufen also()?

public class SpecificItem extends Item {
    ...
    public void method() {
        also();
        super.method();
    }
}

Ist es als Beispiel (sicherlich nicht exklusiv) möglich, eine Ansicht einer Sammlung zu erhalten, die Einträge nach Klassen filtert?

Ein Strom-y Weg , ich, auf Kosten der denken kann , vielleicht Auswirkungen einiger Leistung (YMMV), ist collect() über Collectors.groupingBy()auf die Klasse als Schlüssel, dann wählen Sie, was Sie aus dem resultierenden wollen Map. Die Werte werden als a gespeichert. ListWenn Sie also damit gerechnet haben, eine solche Filterung für a durchzuführen, Setund Sie hoffen, eine Filterung Setdaraus zu erhalten, benötigen Sie einen zusätzlichen Schritt, um die Werte auch in eine resultierende Filterung einzufügen Set.


-1

Hier ist mein Ansatz:

stuff.stream().filter(item -> SpecificItem.class.isInstance(item)).map(item  -> SpecificItem.class.cast(item)).forEach(item -> item.also());
stuff.stream().forEach(item -> item.method());

Keine Instanz , keine expliziten Casts (eigentlich ist es eine explizite Cast, aber durch einen Methodenaufruf maskiert). Ich denke, das ist so gut wie es nur geht.


1
Ist das nicht nur eine weniger lesbare Version des Originals?
Grahamparks

Nun, es macht tatsächlich das gleiche auf typsichere Weise. Je nach Kontext des Codes kann dies einfacher sein. Ich verwende dies zum Filtern bestimmter Typen in einer generischen Liste, mit Ausnahme des forEach-Teils.
zwischen dem
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.