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.
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