È possibile trasmettere un flusso in Java 8?


160

È possibile trasmettere un flusso in Java 8? Supponiamo di avere un elenco di oggetti, posso fare qualcosa del genere per filtrare tutti gli oggetti aggiuntivi:

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

Dopo questo però, se voglio fare qualcosa con i clienti, dovrei lanciare ciascuno di essi:

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

Sembra un po 'brutto. È possibile trasmettere un intero stream a un tipo diverso? Come cast Stream<Object>a un Stream<Client>?

Si prega di ignorare il fatto che fare cose del genere significherebbe probabilmente un cattivo design. Facciamo cose del genere durante la mia lezione di informatica, quindi stavo esaminando le nuove funzionalità di Java 8 ed ero curioso di sapere se fosse possibile.


3
Dal punto di vista del runtime Java i due tipi di Stream sono già uguali, quindi non è richiesto alcun cast. Il trucco è di passarlo di soppiatto al compilatore. (Cioè, supponendo che abbia senso farlo.)
Hot Licks

Risposte:


283

Non penso che ci sia un modo per farlo immediatamente. Una soluzione forse più pulita sarebbe:

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

oppure, come suggerito nei commenti, è possibile utilizzare il castmetodo: il primo potrebbe essere più semplice da leggere:

Stream.of(objects)
    .filter(Client.class::isInstance)
    .map(Client.class::cast)
    .map(Client::getID)
    .forEach(System.out::println);

Questo è praticamente quello che stavo cercando. Immagino di aver trascurato il fatto che lanciarlo su Client mapavrebbe restituito a Stream<Client>. Grazie!
Fiction

+1 nuovi modi interessanti, anche se rischiano di entrare in spaghetti-code di un tipo di nuova generazione (orizzontale, non verticale)
robermann

@LordOfThePigs Sì, funziona anche se non sono sicuro che il codice sia più chiaro. Ho aggiunto l'idea alla mia risposta.
Assylias,

38
È possibile "semplificare" il filtro instanceOf con:Stream.of(objects).filter(Client.class::isInstance).[...]
Nicolas Labrot il

La parte senza freccia è davvero bella <3
Fabich

14

Sulla falsariga della risposta di ggovan , faccio questo come segue:

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

Utilizzando questa funzione di supporto:

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

10

In ritardo alla festa, ma penso che sia una risposta utile.

flatMap sarebbe il modo più breve per farlo.

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

Se oè un Clientquindi creare un flusso con un singolo elemento, altrimenti utilizzare il flusso vuoto. Questi flussi verranno quindi appiattiti in a Stream<Client>.


Ho provato a implementarlo, ma ho ricevuto un avvertimento che diceva che la mia classe "utilizza operazioni non controllate o non sicure" - è prevedibile?
aweibell,

Sfortunatamente sì. Se dovessi utilizzare un operatore if/elseanziché l' ?:operatore, non ci sarebbe alcun avviso. Siate certi che potete sopprimere in sicurezza l'avvertimento.
ggovan,

3
In realtà questo è più lungo Stream.of(objects).filter(o->o instanceof Client).map(o -> (Client)o)o addirittura Stream.of(objects).filter(Client.class::isInstance).map(Client.class::cast).
Didier L

4

Sembra un po 'brutto. È possibile trasmettere un intero stream a un tipo diverso? Come cast Stream<Object>a un Stream<Client>?

No, non sarebbe possibile. Questo non è nuovo in Java 8. Questo è specifico per i generici. A List<Object>non è un super tipo di List<String>, quindi non puoi semplicemente lanciare List<Object>a a List<String>.

Simile è il problema qui. Non puoi lanciare Stream<Object>a Stream<Client>. Ovviamente puoi lanciarlo indirettamente in questo modo:

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

ma questo non è sicuro e potrebbe non funzionare in fase di esecuzione. Il motivo alla base di ciò è che i generici in Java vengono implementati usando la cancellazione. Pertanto, non sono disponibili informazioni sul tipo su quale tipo Streamè in fase di esecuzione. È tutto giusto Stream.

A proposito, cosa c'è di sbagliato nel tuo approccio? Mi sta bene.


2
@DR Generics in C#è implementato usando la reificazione, mentre in Java, è implementato usando la cancellazione. Entrambi sono implementati in modo diverso sottostante. Quindi non puoi aspettarti che funzioni allo stesso modo in entrambe le lingue.
Rohit Jain,

1
@DR Capisco che la cancellazione pone molti problemi ai principianti per capire il concetto di generici in Java. E dal momento che non uso C #, non posso entrare in molti dettagli sul confronto. Ma l'intera motivazione alla base dell'implementazione in questo modo IMO era di evitare importanti cambiamenti nell'implementazione di JVM.
Rohit Jain,

1
Perché "certamente fallire in fase di esecuzione"? Come dici tu non ci sono informazioni di tipo (generico), quindi nulla da verificare per il runtime. Potrebbe eventualmente sicuro in fase di esecuzione, se il tipo sbagliato sono alimentati attraverso, ma non c'è "certezza" di quella sorta.
Hot Licks

1
@RohitJain: non sto criticando il concetto generico di Java, ma questa singola conseguenza crea ancora un brutto codice ;-)
DR

1
@DR - I generici Java sono brutti sin dall'inizio. Principalmente solo lussuria delle funzionalità C ++.
Hot Licks
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.