public <T> List<Long> leafIds(Map<Long, ? extends List<? extends T>> childrenMap, Long parentId, Function<? extends T, Long> idGetter) {
if (!childrenMap.containsKey(parentId)) {
return Collections.singletonList(parentId);
}
List<Long> result = new ArrayList<>();
List<Long> childrenIds = new ArrayList<>();
childrenIds.add(parentId);
for (int i = 0; i < childrenIds.size(); i++) {
Long childId = childrenIds.get(i);
List<? extends T> children = childrenMap.get(childId);
if (CollectionUtils.isNotEmpty(children)) {
childrenIds.addAll(children.stream().filter(Objects::nonNull).map(idGetter).collect(Collectors.toList()));
continue;
}
result.add(childId);
}
return result;
}
第 12 行
idGetter
下有一个错误标记,它被传递给 map
方法。编辑指出idGetter
的类型错误。我该如何解决这个问题?
Function<? extends T, Long>
的意思是:
任何将某些特定但未知的事物转换为 Long 的函数,我们确实知道,无论它是什么,它要么是
T
要么是其某些子类型。
它具体并不意味着:“可以接受任何 T 并将其转换为 Long 的函数”。如果你想要这样,你需要一个
Function<T, Long>
。
List<? extends T>
的意思是:
元素受到约束的列表;他们一定都是
instanceof
某种我们不知道的类型。我们确实知道,无论它是什么类型,它至少是 T 或 T 的某些子类型。
具体来说,并不意味着:一个列表,其元素被限制为 T 的实例。因为那只是一个
List<T>
。
表达式
list.get(0)
,如果list
是一个List<T>
,它本身就是T。显然。如果 list
是 List<? extends T>
,它仍然是 T。关键的区别在于 add
:你可以调用 list.add(someInstanceOfT)
就可以了,if list
的类型是 List<T>
。但是,如果 list.add(anything)
的类型是 list
,则 List<? extends T>
无法编译。
想一想:
List<Integer> myList = new ArrayList<Integer>();
List<? extends Number> numbers = myList;
numbers.add(someDouble);
上面的无法编译 - 具体来说,第三行(
numbers.add
)不会。现在原因应该很明显了: numbers
变量指向一个约束未知的列表。也许它是 Integer
,这意味着向其添加一个双精度,已损坏。
要记住的关键是 PECS:生产-扩展-消费-超级。这是从对象的角度来看:
list.get()
调用“产生”一个值,因此,extends
边界很有用(List<? extends Number>
可以用于有意义的生产,因为PE:Produce-Extends: .get()
调用会产生 Number
。或者翻转过来,CS:List<? super Number>
可以有意义地消耗:list.add(someNumber)
是允许的,但是 list.get(0)
上的 List<? super Number>
并不是特别有用(它为您提供一个对象,因为所有列表都不能存储其他任何内容,但没有更具体的内容)。
现在回到函数。函数的第一个类型参数是仅用于消耗:第一个参数的要点是你的函数消耗它们。第二个参数仅用于用于生产。
考虑 PECS,Function<? extends Anything, _>
完全没用,因为
extends
意味着它仅对生产有用,而
Function
的第一个 typearg 仅用于消费。Java 是“正确的”,你的代码被破坏了。你真正的意思是什么?
两种选择:
选项1
Function<T
或
Function<? super T
。选项2
java
虚拟机不知道它们是什么,并且大多数类型参数都被
javac
完全删除)。也许您打算将第一个参数中使用的类型与该函数中使用的类型链接起来。然后..为此目的创建一个类型变量,这就是他们的工作。您已经这样做了,所以请修正您的边界,使它们与 PECS 保持一致:
public <T> List<Long> leafIds(Map<Long, ? extends List<? extends T>> childrenMap, Long parentId, Function<? super T, Long> idGetter) {
我所做的只是将 ? extends
交换为
? super
。这句话的意思是:有一些特定的类型。但不知道那是什么。我们就叫它T吧。
第一个参数为我们提供了一个映射到 X 列表的映射,其中 X 是未知类型 T 或其某些子类型。
第三个参数是一个映射函数,能够将某些未知类型转换为其他类型。然而,它可以转换的未知类型保证是 T 或其某些超类型。
这描绘了一幅完整的、类型一致的图片。假设映射给出整数,并且该函数可以转换任何对象(因此,T 是数字,列表是 T 的子类型:整数,检查,并且映射函数可以转换任何对象,这是 T 的超类型,所以,查看)。这样就可以了。
现在让我们反过来:
列表中有数字,映射函数只能转换双精度数。
那么如果列表中的元素之一是整数,会发生什么呢?类型安全已损坏。