泛型类型推断失败?

8

示例A

研究以下代码片段:

public class ExampleA {
   static class Pair<F,S> { }

   static <F,S> Pair<F,S> anyPair() { return null; }

   static <F,S> void process(Pair<F,S> p1, Pair<F,S> p2) { return; }

   public static void main(String[] args) {
      Pair<String,Integer> p = anyPair();

      process(p, anyPair()); // doesn't compile
   }
}

有人能解释一下为什么类型推断适用于分配给本地变量 p,但不适用于传递给process的第二个实际参数吗?


示例B

这可能更容易理解:

public class ExampleB {     
   public static <E> void process(Set<E> s1, Set<E> s2) { return; }

   public static void main(String[] args) {
      process(new HashSet<String>(), Collections.emptySet()); // doesn't compile
   }
}

同样的问题:为什么它不能编译?

我希望Collections.emptySet()可以适用于任何参数化的Set类型。


再次赞美你,polygenelubricants :) - BalusC
1
好消息!这些示例现在可以在Java 8中编译。也许目标类型推断拯救了这一天? - Lii
2个回答

8
你对anyPair()的第二次调用没有指定类型,所以它默认为<Object, Object>
编译器会将process(p, anyPair());分解成各个部分并逐一处理。在处理时,需要首先处理参数以确定其类型,然后才能在处理process时使用。
当处理anyPair()时,该部分没有可用的类型信息,因为此时它还不知道它是process的一部分。它默认为<Object, Object>,这会导致查看process时出现类型不匹配。
你的第二个示例也是类似的情况。 Collections.emptySet()需要单独处理,但无法确定所需的类型。
有两种方法可以解决这个问题:
第一种方法是像你在第一次调用anyPair()时那样,通过将正确的类型存储在临时变量中,为类型推断提供编译器所需的信息。
第二种方法(感谢@BalusC)是使用ExampleA.<String, Integer>anyPair()。这种语法明确设置所需的类型,而无需查看调用之外的内容。

5
第二种方法:将它作为GenericsQ.<String,Integer>anyPair()传递进去。 - BalusC
你能详细解释一下吗?我以前从来没见过GenericsQ,也找不到相关的信息。 - Alan Geleynse
1
那是定义该方法的类名。Polygenelubricants将其编辑为“ExampleA”。 - BalusC
啊,谢谢,这解释了为什么我没有看到它。我把它加到我的答案中,因为我错了,以为只有一种方法可以修复它。 - Alan Geleynse
这对我来说不太令人信服。如果类型推断对变量赋值有效,那么它也应该适用于实际参数。局部变量有类型,但形式参数也有类型。正如编译器可以使用局部变量的类型来帮助推断一样,它也应该使用形式参数的类型。 - polygenelubricants
2
变量赋值能够正常工作的原因是您已经指定了变量的类型。Pair<String,Integer> p = anyPair(); 可以根据被分配的内容来推断类型。但是,process(p, anyPair()); 在确定每个参数的类型以决定使用哪个签名之前,无法查看 process。您也可以有一个 static <F,S> void process(Pair<F,S> p1, Object p2) 方法,这意味着编译器需要先评估参数。这意味着它不能从方法签名中获取任何信息,包括类型推断。 - Alan Geleynse

0

为什么:

Collections.emptySet()试图推断要返回的类型。 由于E可能是ObjectString,因此无法推断。 这两者都是process的有效匹配项。 默认情况下,泛型参数始终是不变的,而不是逆变的。 这意味着Integer extends Number,但List<Integer> 扩展List<Number>。 然而,它确实扩展了List<? extends Number>

解决方案:

使用分配来推断类型:

public <E> void process(Set<E> s1, Set<E> s2) { return; }

public void main(String[] args) {
   Set<String> s = Collections.emptySet();  // add this line
   process(new HashSet<String>(), s);
}

明确地写出类型:

public <E> void process(Set<E> s1, Set<E> s2) { return; }

public void main(String[] args) {
   process(new HashSet<String>(), Collections.<String>emptySet()); //notice <String>
}

明确允许逆变(但这可能不是您想要的):

public <E> void process(Set<E> s1, Set<? super E> s2) { // added "super"
  return;
}

public void main(String[] args) {
   process(new HashSet<String>(), Collections.emptySet());
}

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