2017-03-26 3 views
9

ich die folgende Sammlung Klasse haben, die ein Verfahren zum Gruppieren der Elemente in einer Karte enthält, wobei jeder Wert den Typ der Klasse hat den Aufruf esJava-Sammlung mit generischen Methode und Unterklassen

class TaskCollection<E extends Task> extends HashSet<E> { 
    <K> Map<K, ? extends TaskCollection<E>> groupBy(Function<E, K> groupingFunction) { 
     return this.stream() 
      .collect(Collectors.groupingBy(
        groupingFunction, 
        Collectors.toCollection(this.collectionConstructor()) 
      )); 
    } 

    Supplier<? extends TaskCollection<E>> collectionConstructor() { 
     return TaskCollection::new; 
    } 
} 

Was ich will, ist in der Lage sein, Unterklassen zu erstellen, die die groupBy-Methode verwenden, die neue Eigenschaften von sich selbst als Kartenwerte zurückgibt.
Das Folgende ist ein Beispiel

class AssertionCollection extends TaskCollection<Assertion> { 
    Map<Person, AssertionCollection> groupByPerson() { 
     return this.groupBy(Assertion::assignedPerson); 
    } 

    @Override 
    Supplier<AssertionCollection> collectionConstructor() { 
     return AssertionCollection::new; 
    } 
} 

Das Problem in der groupByPerson Methode. Der Compiler gibt einen Fehler für den Aufruf groupBy.

Error:(15, 28) java: incompatible types: no instance(s) of type variable(s) K exist so that java.util.Map<K,? extends TaskCollection<Assertion>> conforms to java.util.Map<Person,AssertionCollection>

Ich bin neu in Java so bin ich ziemlich sicher, dass es etwas dumm ich sehe nicht

+2

Ein Kommentar zu Ihrer Implementierung: Es scheint, dass 'TaskCollection' einen' HashSet' hat, der sinnvoller ist als 'TaskCollection' ein' HashSet'. Die Verwendung von 'HashSet' wirkt wie ein Implementierungsdetail. Müssen Sie von 'HashSet' erben? – scottb

Antwort

4

Die Absicht ist, dass für jede Klasse X die TaskCollection erstreckt, wenn ein groupBy Betrieb durchgeführt, ist die für die Kartenwerte verwendete Sammlung auch eine Instanz der Klasse X.

In diesem Fall wird die nächstgelegene Sie dazu kommen kann, ist so etwas wie die folgenden:

class Task {} 

class Assertion extends Task {} 

abstract class TaskCollection<E extends Task, C extends TaskCollection<E, C>> extends HashSet<E> { 

    <K> Map<K, C> groupBy(Function<E, K> groupingFunction) { 
     return this.stream() 
      .collect(Collectors.groupingBy(
        groupingFunction, 
        Collectors.toCollection(this.collectionSupplier()) 
      )); 
    } 

    protected abstract Supplier<C> collectionSupplier(); 
} 

class AssertionCollection extends TaskCollection<Assertion, AssertionCollection> { 

    @Override 
    protected Supplier<AssertionCollection> collectionSupplier() { 
     return AssertionCollection::new; 
    } 
} 

Beachten Sie, dass die Definition von TaskCollection oben nicht ganz aufhören Subklassen unter Verwendung eines anderen TaskCollection Klasse für ihre groupBy Kartenwerte . Zum Beispiel würde dies auch kompilieren:

class AssertionCollectionOther extends TaskCollection<Assertion, AssertionCollectionOther> {...} 

class AssertionCollection extends TaskCollection<Assertion, AssertionCollectionOther> {...} 

Leider ist es nicht möglich, eine solche Beschränkung auferlegen, zumindest für jetzt, da Sie nicht in Bezug auf die Klasse machen, die in der C-Typ-Parameter Wildcard deklariert wird.

Wenn Sie davon ausgehen können, dass Abkömmlinge einen Parameter free constructor als Sammelanbieter haben, können Sie eine Standardimplementierung für collectionSupplier bereitstellen. Der Preis, den Sie zahlen, ist die Notwendigkeit, eine "ungeprüfte" Warnung (kein echtes Problem) zum Schweigen zu bringen und dass nicht konforme Klassen (die den Parameter-freien Konstruktor nicht bereitstellen) nicht zur Kompilierungszeit ausfallen, sondern zur Laufzeit, was weniger ideal ist : effektiv mit dem Vorbehalt, Sie würden Subklassen zwingen, immer Instanzen ihrer eigenen Klasse zurückgeben, dass ein, dann nicht-Sinn, Erklärung

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

class Task {} 

class Assertion extends Task {} 

class TaskCollection<E extends Task, C extends TaskCollection<E, C>> extends HashSet<E> { 

    <K> Map<K, C> groupBy(Function<E, K> groupingFunction) { 
     return this.stream() 
      .collect(Collectors.groupingBy(
        groupingFunction, 
        Collectors.toCollection(this.collectionSupplier()) 
      )); 
    } 

    @SuppressWarnings("unchecked") 
    protected Supplier<C> collectionSupplier() { 
     return() -> { 
     try { 
      return (C) this.getClass().newInstance(); 
     } catch (Exception ex) { 
      throw new RuntimeException(String.format("class %s is not a proper TaskCollection", this.getClass()), ex); 
     } 
     }; 
    } 
} 

class AssertionCollection extends TaskCollection<Assertion, AssertionCollection> { 
    // This override is not needed any longer although still could 
    // be included in order to produce a slightly faster 
    // customized implementation: 
    //@Override 
    //protected Supplier<AssertionCollection> collectionSupplier() { 
    // return AssertionCollection::new; 
    //} 
} 

Wenn Sie collectionSupplier als final erklären wie class AssertionCollection extends TaskCollection<Assertion, AssertionCollectionOther> noch und produziert Laufzeit kompilieren würde Ausnahmen auf die Straße werfen.

+1

Das habe ich gesucht! Danke – Fed03

+1

Ich habe meine hässliche Antwort gelöscht. nette Antwort und Abstimmung. –

Verwandte Themen