2016-03-23 10 views
5

Sto avendo un flusso Java 8 di numeri:uso dei consumatori specifici per tipo di elemento con Java 8 flusso

Stream<Number> numbers = ...; 

mi piacerebbe iterare il flusso e invocare un consumatore specifico in base al tipo di ciascun elemento. Cioè per Integer elementi Vorrei richiamare un Consumer<Integer>, per Long un Consumer<Long> ecc

C'è il metodo forEach() ma questo prevede un Consumer<? super Number>, richiede che l'attuazione (solitamente un'espressione lambda) per InstanceOf-interruttore del tipo esatto si. ci

numbers.forEach(callbacks -> { 
    callbacks.on(Integer.class, i -> { /* handle Integer */ }); 
    callbacks.on(Long.class, l -> { /* handle Long */ }) 
}); 

è un qualsiasi API esistente che mi avrebbe permesso di registrare un consumatore specifico per elemento flusso sub-tipo in un modo:

In termini di un'API, sto essenzialmente alla ricerca di qualcosa di simile simile a questo?

+3

Non conosco alcuna libreria esistente che faccia esattamente questo, ma in base allo schizzo dell'API che hai fatto, sembra abbastanza facile da implementare. – biziclop

+0

Come soluzione, è possibile scrivere una singola richiamata (magari con un factory built-in) che distribuisce il lavoro al worker appropriato. – BPS

+0

@biziclop, sì, non è difficile da implementare (è quello che ho iniziato, ma poi ho iniziato a chiedermi se sto reinventando la ruota qui). – Gunnar

risposta

5

Sei sicuro di non voler eseguire lo stream due volte? Sarà più leggibile.

Ma se si vuole, è possibile definire un consumatore tipo di controllo in questo modo:

public static<T> Consumer<Object> acceptType(Class<T> clazz, Consumer<? super T> cons) { 
    return t -> { 
     if (clazz.isInstance(t)) { 
      cons.accept(clazz.cast(t)); 
     } 
    }; 
} 

È quindi possibile combinare più i consumatori utilizzando andThen:

Consumer<Object> combined = acceptType(Integer.class, i -> ...) 
        .andThen(acceptType(Long.class, lng -> ...)) 

Se si desidera nascondere andThen, puoi definire

static<T> Consumer<T> doAll(Consumer<T>... consumers) { 
    return Arrays.stream(consumers) 
      .reduce(Consumer::andThen) 
      .orElse(t -> {}); 
} 

Quindi diventa

nums.forEach(doAll(
      acceptType(Integer.class, i -> ...), 
      acceptType(Long.class, lng -> ..), 
      ... 
)); 
+0

Quali sono i problemi di leggibilità nel restituire t -> Optional.of (t) .filter (clazz :: isInstance) .map (clazz :: cast) .ifPresent (consumer) in acceptType? – srborlongan

+1

Beh, esploderà sui valori 'null', che possono o meno essere quelli che l'OP vuole. A parte questo, una questione di preferenze personali. – Misha

+2

Quando la decisione di eseguire un blocco di codice o un'altra dipende dalla classe di un oggetto, sospetto immediatamente di un cattivo progetto. Questo costrutto con 'Consumer.andThen()' sembra molto elegante, però. Ma se guardi da vicino, ti renderai presto conto che la catena 'Consumer.andThen()' (e anche l'approccio 'doAll (consumer)') non è altro che una grande istruzione 'switch' che viene elaborata per ogni elemento di il flusso. Come @LouisWasserman ha commentato sotto la domanda, questo dovrebbe essere implementato con lo schema del visitatore. –

1

Non so di queste built-in, ecco un'opzione: Raccogliere tutti i Consumer s in un Map digitato dal tipo che consuma, e cercare il consumatore giusto. Consiglierei questa soluzione se lavori con molti tipi e questi tipi possono cambiare dinamicamente. La soluzione più efficiente (per consumo di CPU) sarebbe probabilmente quella di utilizzare switch su item.getType().

public class Casts { 
    public static void main(String[] args) { 

     Stream<Number> stream = Arrays.stream(new Number[] { 3, 4L }); 

     Map<Class<?>, Consumer<? super Number>> consumers = new HashMap<>(); 
     putCastConsumer(consumers, Long.class, 
       i -> System.out.println("Mapped long " + i)); 
     putCastConsumer(consumers, Integer.class, 
       i -> System.out.println("Mapped int " + i)); 

     consumeByType(stream, consumers); 
    } 

    public static <U, T extends U> void putCastConsumer(
      final Map<Class<?>, Consumer<? super U>> map, 
      final Class<T> clazz, 
      final Consumer<T> consumer) { 
     map.put(clazz, value -> consumer.accept(clazz.cast(value))); 
    } 

    public static <T> void consumeByType(
      final Stream<T> stream, 
      final Map<Class<?>, Consumer<? super T>> consumers) { 
     stream.forEach(item -> consumers.get(item.getClass()).accept(item)); 
    } 
}