2015-03-24 8 views
8

Fondamentalmente cerco un modo per evitare di lavorare conUn modo per trasmettere una mappa come "(k, v)" invece di lavorare con (voce)?

entry -> entry.getValue 

e

entry -> entry.getKey 

simile a quello che Map.forEach() fa.

Se solo potessi avere un modo di lavorare come map.stream().filter((k,v) ->) ... e così via

sembra l'interfaccia si chiama BiConsumer. Forse con un convertitore su BiConsumer o Stream.generate() in qualche modo

+0

Puoi essere più esplicito. Probabilmente pubblica un esempio –

+1

Non c'è alcun modo, come si può facilmente capire dall'API di 'Mappa'. Ma cosa c'è di sbagliato nell'approccio 'entrySet()'? –

+0

Devo eseguire i.getEntry e i.getKey un sacco di volte – Whimusical

risposta

10

Poiché si tratta di una domanda ricorrente, lancerò una soluzione completa sul ring. È un tipo PairStream che per impostazione predefinita è un semplice wrapper attorno a un ordinario Stream (sebbene, essendo uno interface, siano possibili alternative).

Si concentra sulla fornitura convenienti operazioni intermedie e le operazioni del terminale che non possono essere facilmente eseguite chiamando uno dei metodi keys(), values() o entries() di tornare ad un singolo elemento convenzionale Stream e catena un'operazione terminale. Ad esempio, PairStream.from(map).filterValue(predicate).keys().findAny() è il modo diretto per ottenere una chiave per la quale il valore mappato corrisponde al predicato. filterValue è una comoda operazione intermedia e keys torna a un normale Stream consentendo un'operazione di terminale arbitraria per le chiavi.

Alcuni esempi

Map<String,Integer> m=new HashMap<>(); 
    m.put("foo", 5); 
    m.put("bar", 7); 
    m.put("baz", 42); 
    // {b=49, f=5} 
    Map<Character,Integer> m2=PairStream.from(m) 
     .mapKey(s->s.charAt(0)) 
     .toMap(Integer::sum); 

    // foo bar 
    String str=PairStream.from(m) 
     .filterValue(i->i<30) 
     .keys().sorted(Comparator.reverseOrder()) 
     .collect(Collectors.joining(" ")); 

Map<String,Integer> map=new HashMap<>(); 
    map.put("muhv~", 26); 
    map.put("kfool", 3); 
    String str = PairStream.from(map) 
     .sortedByValue(Comparator.naturalOrder()) 
     .flatMapToInt((s,i)->s.codePoints().map(c->c^i)) 
     .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) 
     .toString(); 

Ecco la classe completa (non ho ancora testato tutte le operazioni, tuttavia la maggior parte di esso è straight-forward):

import java.util.*; 
import java.util.function.*; 
import java.util.stream.*; 

public interface PairStream<K,V> { 
    static <K,V> PairStream<K,V> from(Map<K,V> map) { 
     return from(map.entrySet().stream()); 
    } 
    static <K,V> PairStream<K,V> from(Stream<Map.Entry<K,V>> s) { 
     return()->s; 
    } 
    static <K,V> PairStream<K,V> from(Stream<K> s, Function<? super K, ? extends V> f) { 
     return()->s.map(k->new AbstractMap.SimpleImmutableEntry<>(k, f.apply(k))); 
    } 

    default PairStream<K,V> distinct() { 
     return from(entries().distinct()); 
    } 
    default PairStream<K,V> peek(BiConsumer<? super K, ? super V> action) { 
     return from(entries().peek(e->action.accept(e.getKey(), e.getValue()))); 
    } 
    default PairStream<K,V> skip(long n) { 
     return from(entries().skip(n)); 
    } 
    default PairStream<K,V> limit(long maxSize) { 
     return from(entries().limit(maxSize)); 
    } 
    default PairStream<K,V> filterKey(Predicate<? super K> mapper) { 
     return from(entries().filter(e->mapper.test(e.getKey()))); 
    } 
    default PairStream<K,V> filterValue(Predicate<? super V> mapper) { 
     return from(entries().filter(e->mapper.test(e.getValue()))); 
    } 
    default PairStream<K,V> filter(BiPredicate<? super K, ? super V> mapper) { 
     return from(entries().filter(e->mapper.test(e.getKey(), e.getValue()))); 
    } 
    default <R> PairStream<R,V> mapKey(Function<? super K,? extends R> mapper) { 
     return from(entries().map(e->new AbstractMap.SimpleImmutableEntry<>(
      mapper.apply(e.getKey()), e.getValue() 
     ))); 
    } 
    default <R> PairStream<K,R> mapValue(Function<? super V,? extends R> mapper) { 
     return from(entries().map(e->new AbstractMap.SimpleImmutableEntry<>(
      e.getKey(), mapper.apply(e.getValue()) 
     ))); 
    } 
    default <R> Stream<R> map(BiFunction<? super K, ? super V,? extends R> mapper) { 
     return entries().map(e->mapper.apply(e.getKey(), e.getValue())); 
    } 
    default DoubleStream mapToDouble(ToDoubleBiFunction<? super K, ? super V> mapper) { 
     return entries().mapToDouble(e->mapper.applyAsDouble(e.getKey(), e.getValue())); 
    } 
    default IntStream mapToInt(ToIntBiFunction<? super K, ? super V> mapper) { 
     return entries().mapToInt(e->mapper.applyAsInt(e.getKey(), e.getValue())); 
    } 
    default LongStream mapToLong(ToLongBiFunction<? super K, ? super V> mapper) { 
     return entries().mapToLong(e->mapper.applyAsLong(e.getKey(), e.getValue())); 
    } 
    default <RK,RV> PairStream<RK,RV> flatMap(
      BiFunction<? super K, ? super V,? extends PairStream<RK,RV>> mapper) { 
     return from(entries().flatMap(
      e->mapper.apply(e.getKey(), e.getValue()).entries())); 
    } 
    default <R> Stream<R> flatMapToObj(
      BiFunction<? super K, ? super V,? extends Stream<R>> mapper) { 
     return entries().flatMap(e->mapper.apply(e.getKey(), e.getValue())); 
    } 
    default DoubleStream flatMapToDouble(
      BiFunction<? super K, ? super V,? extends DoubleStream> mapper) { 
     return entries().flatMapToDouble(e->mapper.apply(e.getKey(), e.getValue())); 
    } 
    default IntStream flatMapToInt(
      BiFunction<? super K, ? super V,? extends IntStream> mapper) { 
     return entries().flatMapToInt(e->mapper.apply(e.getKey(), e.getValue())); 
    } 
    default LongStream flatMapToLong(
      BiFunction<? super K, ? super V,? extends LongStream> mapper) { 
     return entries().flatMapToLong(e->mapper.apply(e.getKey(), e.getValue())); 
    } 
    default PairStream<K,V> sortedByKey(Comparator<? super K> comparator) { 
     return from(entries().sorted(Map.Entry.comparingByKey(comparator))); 
    } 
    default PairStream<K,V> sortedByValue(Comparator<? super V> comparator) { 
     return from(entries().sorted(Map.Entry.comparingByValue(comparator))); 
    } 

    default boolean allMatch(BiPredicate<? super K,? super V> predicate) { 
     return entries().allMatch(e->predicate.test(e.getKey(), e.getValue())); 
    } 
    default boolean anyMatch(BiPredicate<? super K,? super V> predicate) { 
     return entries().anyMatch(e->predicate.test(e.getKey(), e.getValue())); 
    } 
    default boolean noneMatch(BiPredicate<? super K,? super V> predicate) { 
     return entries().noneMatch(e->predicate.test(e.getKey(), e.getValue())); 
    } 
    default long count() { 
     return entries().count(); 
    } 

    Stream<Map.Entry<K,V>> entries(); 
    default Stream<K> keys() { 
     return entries().map(Map.Entry::getKey); 
    } 
    default Stream<V> values() { 
     return entries().map(Map.Entry::getValue); 
    } 
    default Optional<Map.Entry<K,V>> maxByKey(Comparator<? super K> comparator) { 
     return entries().max(Map.Entry.comparingByKey(comparator)); 
    } 
    default Optional<Map.Entry<K,V>> maxByValue(Comparator<? super V> comparator) { 
     return entries().max(Map.Entry.comparingByValue(comparator)); 
    } 
    default Optional<Map.Entry<K,V>> minByKey(Comparator<? super K> comparator) { 
     return entries().min(Map.Entry.comparingByKey(comparator)); 
    } 
    default Optional<Map.Entry<K,V>> minByValue(Comparator<? super V> comparator) { 
     return entries().min(Map.Entry.comparingByValue(comparator)); 
    } 
    default void forEach(BiConsumer<? super K, ? super V> action) { 
     entries().forEach(e->action.accept(e.getKey(), e.getValue())); 
    } 
    default void forEachOrdered(BiConsumer<? super K, ? super V> action) { 
     entries().forEachOrdered(e->action.accept(e.getKey(), e.getValue())); 
    } 

    default Map<K,V> toMap() { 
     return entries().collect(
      Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); 
    } 
    default Map<K,V> toMap(BinaryOperator<V> valAccum) { 
     return entries().collect(
      Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, valAccum)); 
    } 
} 
6

No, non c'è modo di farlo; Stream funziona solo con un singolo tipo di elemento. Non c'è modo di aggirare l'approccio getKey e getValue.

(Streaming le chiavi e chiamando get in realtà non sembrano essere meglio dal vostro punto di vista, ed è probabile che sia rigorosamente meno efficiente.)

9

È un po 'un peccato che il modo principale per elaborare un mappa utilizzando gli stream è per lo streaming delle voci della mappa. Ciò significa che devi estrarre le chiavi e i valori delle voci. Questo è piuttosto prolisso, ma non è poi così male. Tuttavia, potrebbe essere ragionevole considerare alcuni tipi di metodi di supporto che adattano uno BiPredicate o BiFunction in modo che possano essere utilizzati nelle fasi filter o map di un flusso di voci di mappa. Sembrano questo:

static <K,V> Predicate<Map.Entry<K,V>> p(BiPredicate<? super K, ? super V> bip) { 
    return entry -> bip.test(entry.getKey(), entry.getValue()); 
} 

static <K,V,R> Function<Map.Entry<K,V>,R> m(BiFunction<? super K, ? super V, R> bif) { 
    return entry -> bif.apply(entry.getKey(), entry.getValue()); 
} 

Una volta che avete questi, si possono usare per semplificare un flusso di entrata carta (?). Supponiamo di avere un Map<String,Integer> e di voler selezionare le voci in cui la lunghezza della chiave di stringa è maggiore del valore intero, quindi formattare la chiave e il valore in una stringa. Convenzionalmente faresti questo:

map.entrySet().stream() 
        .filter(e -> e.getKey().length() > e.getValue()) 
        .map(e -> e.getKey() + ":" + e.getValue()) 
        .forEach(System.out::println); 

Con le funzioni di supporto di cui sopra, si potrebbe riscrivere questo gasdotto come:

map.entrySet().stream() 
        .filter(p((k, v) -> k.length() > v)) 
        .map(m((k, v) -> k + ":" + v)) 
        .forEach(System.out::println); 

OK, si salva un paio di caratteri. Ne vale la pena?

+3

Ne vale sicuramente la pena, perché è possibile nominare i parametri lambda qualcos'altro che "chiave" e "valore" che porta a un codice di auto-documentazione migliore. –

Problemi correlati