在guava中,为什么只使用“T”,而不是可能使用“? super T”?

8
为什么实用工厂方法常常使用特定的泛型参数(如 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>

这是因为期望使用 FunctionPredicate 的消费者具有必要的有界通配符参数,从而使这种情况不必要吗?例如,Iterables#find 上的通用边界允许在 Iterable<Integer> 上使用 Predicate<Number>

public static <T> T find(Iterable<T> iterable,
                         Predicate<? super T> predicate)

还有其他原因吗?

2个回答

6

是的,我们确实希望消费者具有正确的有界通配符参数,但还有一些其他原因:

  • 一般来说,我们不会扩大泛型方法的类型,除非有特定的原因。这个策略已经多次得到证明。
  • Java的类型推断并不能总是自动地解决更高级别的泛型问题,因此保持较窄的泛型可以减少需要显式指定 T 的用户数量。

关于第一点,这不是把消费者推向更糟糕的强制转换吗?另外很好奇,当你说“我们”时,是指你自己开发了这个还是只是泛指软件社区? - djechlin
更具体地说,这项政策在实践中的回报如何?目前我只能想象在消费者绕过向派生类型转换的情况下才会有所影响。 - djechlin
当我说“我们”时,我指的是我所在的Google Java核心库团队,负责开发Guava。但是消费者永远不需要进行强制转换:Predicate<? super T> 可以直接使用,在任何您使用 Predicate<T> 的地方都可以使用。无需进行类型转换;它们可以应用于完全相同的值。 - Louis Wasserman
@LouisWasserman感谢您的出色回答。我也很好奇是否有任何具体的例子表明保持狭窄类型是值得的。我的一个担忧是(理论上)必须在某些未声明通配符的第三方代码中使用Predicate,这似乎可能需要一个包装器来“向上转换”参数,或者在Predicate本身上进行显式转换。但我想当涉及到多个通配符时,情况可能会变得非常混乱,所以尽可能避免这种麻烦可能是明智的 :) - matts

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()等方法来在不同的函数类型之间进行转换。


forPredicate(isPositive).apply(42) 没有问题,因为在这种情况下 T 是最具体的类型,即 NumberInteger 继承自 Number,所以 apply 调用是正确的。但如果你传入一个字符串时它能编译通过,那么它可能是使用了 rawtypes 或其他什么东西进行编译的。 - matts
我的想法是:假设我在我的类中有一个私有的Predicate<Number>,并且我想将其作为Function<Integer,Boolean>从方法中返回,我不应该向调用者公开它实际上是一个Predicate<Number>。由于它接受所有的Number,因此它也将接受所有的Integer。现在要做到这一点,我必须使用包装类或显式类进行转换。 - matts
政治正确的回答应该是你的方法返回类型应该是 Function<? super Integer, Boolean>。但这样太丑了。 - irreputable
你是指 Function<? extends Integer, Boolean> 吗?它接受 Integer 和子类型,而不是 Integer 和超类型。 - matts
不是的,这是一个接受 X 参数的函数,其中 XInteger 的未知超类型。由于该函数接受 X,因此也接受 Integer - irreputable

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接