Fed*_*d03 9 java generics java-8
我有以下集合类,其中包含一个方法,用于对映射中的元素进行分组,其中每个值都具有调用它的类的类型
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;
}
}
Run Code Online (Sandbox Code Playgroud)
我想要的是能够创建使用groupBy方法的子类,该方法返回自己的新的istances作为地图值.
以下是一个例子
class AssertionCollection extends TaskCollection<Assertion> {
Map<Person, AssertionCollection> groupByPerson() {
return this.groupBy(Assertion::assignedPerson);
}
@Override
Supplier<AssertionCollection> collectionConstructor() {
return AssertionCollection::new;
}
}
Run Code Online (Sandbox Code Playgroud)
问题出在groupByPerson方法上.编译器会为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>
我是Java的新手,所以我很确定我看不到有些蠢事
其目的是,对于任何X扩展的类TaskCollection,当执行 groupBy 操作时,用于映射值的集合也是 class 的实例X。
在这种情况下,您可以得到的最接近的结果如下所示:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,上面的定义TaskCollection并不能完全阻止子类使用另一个TaskCollection类作为其 groupBy 映射值。例如,这也可以编译:
class AssertionCollectionOther extends TaskCollection<Assertion, AssertionCollectionOther> {...}
class AssertionCollection extends TaskCollection<Assertion, AssertionCollectionOther> {...}
Run Code Online (Sandbox Code Playgroud)
不幸的是,至少目前无法施加这样的约束,因为您无法引用在 C 类型参数通配符中声明的类。
如果您可以假设后代有一个无参数构造函数作为集合提供者,您可以为
collectionSupplier. 您付出的代价是需要消除“未经检查”的警告(不是真正的问题),并且不兼容的类(不提供无参数构造函数)不会在编译时失败,但在运行时失败,这是不太理想的:
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;
//}
}
Run Code Online (Sandbox Code Playgroud)
如果您声明collectionSupplier为final,您将有效地强制子类始终返回它们自己的类的实例,但需要注意的是,毫无意义的声明,例如,class AssertionCollection extends TaskCollection<Assertion, AssertionCollectionOther>仍然会编译并产生运行时强制转换异常。