为什么实用工厂方法经常使用特定的通用参数(如T
)而不是有界通配符参数(如? super T
)?
例如,Functions#forPredicate的签名是:
public static <T> Function<T, Boolean> forPredicate(Predicate<T> predicate)
为什么不使用:
public static <T> Function<T, Boolean> forPredicate(Predicate<? super T> predicate)
哪种可能会产生以下类似的东西?
Predicate<Number> isPositivePredicate = ...
Function<Integer, Boolean> isPositiveInteger = Functions.forPredicate(isPositivePredicate);
// above line is compiler error:
// Type mismatch: cannot convert from Function<Number,Boolean> to Function<Integer,Boolean>
是否因为Function
和Predicate
的使用者需要具有必要的有界通配符参数才能使其不必要?例如,Iterables#find上的通用边界将允许在Predicate<Number>
上使用Iterable<Integer>
:
public static <T> T find(Iterable<T> iterable,
Predicate<? super T> predicate)
还有其他原因吗?
答案 0 :(得分:6)
是的,绝对准确的是我们希望消费者拥有正确的有界通配符参数,但还有一些其他原因让人想起:
T
的用户数。答案 1 :(得分:2)
在find()
示例中,T
始终可以毫不含糊地推断出来。
在forPredicate[1]()
示例中,T也可以明确推断。
在forPredicate[2]()
示例中,T
应该是不确定的。如果将方法的结果分配给目标类型,则可以从目标类型确定T
。否则它有点头疼:
forPredicate(isPositive).apply(42); // T is a subtype of Number, but which one?
在java5 / 6中,它不应该编译。 (我在java 6上测试它并且它确实编译了,但它可能是一个bug,因为java 6也编译forPredicate(p).apply("str")
)
Java 7改进了一点,新规则恰好指示T=Number
。它有效,但感觉更像是仲裁。
理想情况下,我们不需要担心通配符。如果我需要一个整数的谓词,我应该声明一个Predicate<Integer>
参数。另一个故事是Predicate<Number>
论证也可以接受的事实。将Predicate<Number>
转换为Predicate<Integer>
应该是编译器的工作 - 我们可以在不修改现有java泛型类型系统的情况下完成它,只需要一个新的转换规则。还可以提供转换库
Predicate<Number> pn = ...;
Predicate<Integer> pi = convert(pn);
Iterable<Integer> iter1 = ...;
Iterable<Number> iter2 = convert(iter1);
所有convert()
方法都可以机械生成。
Java 8使事情变得容易一些。我们仍然无法做到
Predicate<Number> pn = n -> n.intValue()>0;
Predicate<Integer> pi = pn; // error!
但我们可以做到
Predicate<Integer> pi = pn::test; // ok
// it means pi = (Integer i)->pn.test(i)
也
Function<Integer, Boolean> f = pn::test; // ok
相当于f = forPredicate[2](pn)
。在java 8中,我们很少需要forPredicate()
等来在功能类型之间进行转换。