2016-05-25 13 views
4

Ich versuche zu verstehen, die Implementierung der Downstream-Reduktion in JDK. Hier ist es:Understanding Downstream-Reduktion-Implementierung verstehen

public static <T, K, D, A, M extends Map<K, D>> 
    Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier, 
            Supplier<M> mapFactory, 
            Collector<? super T, A, D> downstream) { 
     Supplier<A> downstreamSupplier = downstream.supplier(); 
     BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator(); 
     BiConsumer<Map<K, A>, T> accumulator = (m, t) -> { 
      K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key"); 
      A container = m.computeIfAbsent(key, k -> downstreamSupplier.get()); 
      downstreamAccumulator.accept(container, t); 
     }; 
     BinaryOperator<Map<K, A>> merger = Collectors.<K, A, Map<K, A>>mapMerger(downstream.combiner()); 
     @SuppressWarnings("unchecked") 
     Supplier<Map<K, A>> mangledFactory = (Supplier<Map<K, A>>) mapFactory; 

     if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { 
      return new CollectorImpl<>(mangledFactory, accumulator, merger, CH_ID); 
     } 
     else { 
      @SuppressWarnings("unchecked") 
      Function<A, A> downstreamFinisher = 
         (Function<A, A>) downstream.finisher(); //1, <------------- HERE 
      Function<Map<K, A>, M> finisher = intermediate -> { 
       intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); 
       @SuppressWarnings("unchecked") 
       M castResult = (M) intermediate; 
       return castResult; 
      }; 
      return new CollectorImpl<>(mangledFactory, accumulator, merger, finisher, CH_NOID); 
     } 
    } 

Bei //1, die downstreamFinisher ist vom Typ Function<A, D>. Gemessen an den Typparameter Deklarationen <T, K, D, A, M extends Map<K, D>> ist der Typ nicht von A abhängig. Warum also werfen wir es auf . Ich denke, der Typ D darf nicht einmal eine Unterklasse von A sein.

Was habe ich vermisst?

Antwort

5

Dieser Collector verletzt die generische Typsicherheit der Map, wenn der nachgeschaltete Collector keinen Identity Finisher hat und die Finisher-Funktion einen anderen Typ als den Zwischencontainertyp zurückgibt.

Während des Sammelvorgangs enthält die Karte Objekte vom Typ A, d. H. Den Typ des Zwischencontainers. Am Ende des Vorgangs wird der Finisher groupingBy durch die Karte gehen und die Finisher-Funktion auf jeden Wert anwenden und durch das Endergebnis ersetzen.

Natürlich kann dies nicht ohne ungeprüfte Operationen implementiert werden. Es gibt mehrere Möglichkeiten dafür, die von Ihnen gepostete Variante ändert den Typ des Kartenlieferanten von Supplier<M> zu Supplier<Map<K, A>> (der erste ungeprüfte Vorgang). Der Compiler akzeptiert also, dass die Map Werte vom Typ A anstelle von D enthält. Deshalb muss die finisher Funktion in Function<A,A> (der zweite ungeprüfte Vorgang) geändert werden, so dass es in der replaceAll Operation der Karte verwendet werden kann, die Objekte des Typs A erfordert, obwohl es eigentlich ist. Schließlich muss die Ergebniszuordnung an M (die dritte ungeprüfte Operation) übergeben werden, um ein Objekt des erwarteten Ergebnistyps M zu erhalten, das der Lieferant tatsächlich geliefert hat.

Die korrekte Typ-sichere Alternative wäre, verschiedene Maps zu verwenden und die Endbearbeitung durchzuführen, indem die Ergebniskarte mit dem Ergebnis der Konvertierung der Werte der Zwischenkarte gefüllt wird. Dies wäre nicht nur eine teure Operation, sondern würde auch einen zweiten Lieferanten für die Zwischenkarte erfordern, da der zur Verfügung gestellte Lieferant nur Karten produziert, die für das Endergebnis geeignet sind. Offensichtlich entschieden die Entwickler, dass dies ein akzeptabler Verstoß gegen die Typsicherheit ist.

Beachten Sie, dass die unsicheren Betrieb feststellen können, wenn Sie versuchen, eine Map Implementierung zu verwenden, die Typsicherheit erzwingt:

Stream.of("foo", "bar").collect(Collectors.groupingBy(String::length, 
    () -> Collections.checkedMap(new HashMap<>(), Integer.class, Long.class), 
    Collectors.counting())); 

produzieren eine ClassCastException mit dieser Implementierung, da sie einen Zwischenbehälter zu bringen versucht (ein Array) anstelle der Long in die Karte.