Ist es möglich, einen Stream in Java 8 zu übertragen?

160

Ist es möglich, einen Stream in Java 8 zu übertragen? Angenommen, ich habe eine Liste von Objekten. Ich kann so etwas tun, um alle zusätzlichen Objekte herauszufiltern:

Stream.of(objects).filter(c -> c instanceof Client)

Wenn ich danach etwas mit den Kunden machen möchte, muss ich jeden von ihnen besetzen:

Stream.of(objects).filter(c -> c instanceof Client)
    .map(c -> ((Client) c).getID()).forEach(System.out::println);

Das sieht ein bisschen hässlich aus. Ist es möglich, einen ganzen Stream in einen anderen Typ umzuwandeln? Wie Stream<Object>zu einem gegossen Stream<Client>?

Bitte ignorieren Sie die Tatsache, dass solche Dinge wahrscheinlich schlechtes Design bedeuten würden. Wir machen solche Sachen in meinem Informatikkurs, also habe ich mir die neuen Funktionen von Java 8 angesehen und war neugierig, ob dies möglich ist.

Phiction
quelle
3
Vom Standpunkt der Java-Laufzeit aus sind die beiden Stream-Typen bereits gleich, sodass keine Umwandlung erforderlich ist. Der Trick besteht darin, es am Compiler vorbei zu schleichen. (Vorausgesetzt, es macht Sinn, dies zu tun.)
Hot Licks

Antworten:

283

Ich glaube nicht, dass es einen Weg gibt, dies sofort zu tun. Eine möglicherweise sauberere Lösung wäre:

Stream.of(objects)
    .filter(c -> c instanceof Client)
    .map(c -> (Client) c)
    .map(Client::getID)
    .forEach(System.out::println);

oder, wie in den Kommentaren vorgeschlagen, können Sie die castMethode verwenden - die erstere ist jedoch möglicherweise leichter zu lesen:

Stream.of(objects)
    .filter(Client.class::isInstance)
    .map(Client.class::cast)
    .map(Client::getID)
    .forEach(System.out::println);
Assylien
quelle
Das ist so ziemlich das, wonach ich gesucht habe. Ich schätze, ich habe übersehen, dass das Casting an Client in a zurückgeben mapwürde Stream<Client>. Vielen Dank!
Phiction
+1 interessante neue Wege, obwohl sie Gefahr
laufen
@LordOfThePigs Ja, es funktioniert, obwohl ich nicht sicher bin, ob der Code klarer wird. Ich habe die Idee zu meiner Antwort hinzugefügt.
Assylias
38
Sie könnten die Instanz des Filters "vereinfachen" mit:Stream.of(objects).filter(Client.class::isInstance).[...]
Nicolas Labrot
Das Teil ohne Pfeil ist wirklich schön <3
Fabich
14

In Anlehnung an Ggovans Antwort mache ich das wie folgt:

/**
 * Provides various high-order functions.
 */
public final class F {
    /**
     * When the returned {@code Function} is passed as an argument to
     * {@link Stream#flatMap}, the result is a stream of instances of
     * {@code cls}.
     */
    public static <E> Function<Object, Stream<E>> instancesOf(Class<E> cls) {
        return o -> cls.isInstance(o)
                ? Stream.of(cls.cast(o))
                : Stream.empty();
    }
}

Verwenden dieser Hilfsfunktion:

Stream.of(objects).flatMap(F.instancesOf(Client.class))
        .map(Client::getId)
        .forEach(System.out::println);
Brandon
quelle
10

Spät zur Party, aber ich denke, es ist eine nützliche Antwort.

flatMap wäre der kürzeste Weg, es zu tun.

Stream.of(objects).flatMap(o->(o instanceof Client)?Stream.of((Client)o):Stream.empty())

Wenn dies der Fall oist, Clienterstellen Sie einen Stream mit einem einzelnen Element. Andernfalls verwenden Sie den leeren Stream. Diese Ströme werden dann zu a abgeflacht Stream<Client>.

ggovan
quelle
Ich habe versucht, dies zu implementieren, erhielt jedoch eine Warnung, dass meine Klasse "ungeprüfte oder unsichere Operationen verwendet" - ist das zu erwarten?
Aweibell
leider ja. Wenn Sie if/elseeher einen als den ?:Operator verwenden würden, gäbe es keine Warnung. Seien Sie versichert, dass Sie die Warnung sicher unterdrücken können.
Ggovan
3
Eigentlich ist das länger als Stream.of(objects).filter(o->o instanceof Client).map(o -> (Client)o)oder sogar Stream.of(objects).filter(Client.class::isInstance).map(Client.class::cast).
Didier L
4

Das sieht ein bisschen hässlich aus. Ist es möglich, einen ganzen Stream in einen anderen Typ umzuwandeln? Wie Stream<Object>zu einem gegossen Stream<Client>?

Nein, das wäre nicht möglich. Dies ist in Java 8 nicht neu. Dies gilt speziell für Generika. A List<Object>ist kein super Typ von List<String>, also kann man nicht einfach a List<Object>in a umwandeln List<String>.

Ähnlich ist das Problem hier. Sie können nicht werfen Stream<Object>zu Stream<Client>. Natürlich können Sie es indirekt so besetzen:

Stream<Client> intStream = (Stream<Client>) (Stream<?>)stream;

Dies ist jedoch nicht sicher und kann zur Laufzeit fehlschlagen. Der Grund dafür ist, dass Generika in Java durch Löschen implementiert werden. Es sind also keine Typinformationen darüber verfügbar, welcher Typ Streamzur Laufzeit vorhanden ist. Alles ist gerecht Stream.

Übrigens, was ist falsch an Ihrem Ansatz? Sieht gut aus für mich.

Rohit Jain
quelle
2
@DR Generics in C#wird mithilfe von Reification implementiert, während es in Java mithilfe von Erasure implementiert wird. Beide werden auf unterschiedliche Weise implementiert. Sie können also nicht erwarten, dass es in beiden Sprachen gleich funktioniert.
Rohit Jain
1
@DR Ich verstehe, dass das Löschen für Anfänger viele Probleme aufwirft, um das Konzept der Generika in Java zu verstehen. Und da ich kein C # verwende, kann ich nicht viel auf den Vergleich eingehen. Die ganze Motivation für die Implementierung auf diese Weise war jedoch, dass größere Änderungen bei der Implementierung von JVM vermieden wurden.
Rohit Jain
1
Warum sollte es "zur Laufzeit sicherlich scheitern"? Wie Sie sagen, gibt es keine (generischen) Typinformationen, also nichts, was die Laufzeit überprüfen könnte. Es könnte möglicherweise zur Laufzeit fehlschlagen, wenn die falschen Typen durch zugeführt werden, aber es gibt keine „Sicherheit“ über diese überhaupt.
Hot Licks
1
@RohitJain: Ich kritisiere nicht Javas generisches Konzept, aber diese einzige Konsequenz erzeugt immer noch hässlichen Code ;-)
DR
1
@DR - Java-Generika sind von Anfang an hässlich. Meistens nur C ++ Feature Lust.
Hot Licks