Java8中的模糊过载 - 是ECJ还是javac对吗?

时间:2014-10-06 18:30:21

标签: java eclipse javac jls ecj

我有以下课程:

import java.util.HashSet;
import java.util.List;

public class OverloadTest<T> extends  HashSet<List<T>> {
  private static final long serialVersionUID = 1L;

  public OverloadTest(OverloadTest<? extends T> other) {}

  public OverloadTest(HashSet<? extends T> source) {}

  private OverloadTest<Object> source;

  public void notAmbigious() {
    OverloadTest<Object> o1 = new OverloadTest<Object>(source);
  }

  public void ambigious() {
    OverloadTest<Object> o2 = new OverloadTest<>(source);
  }
}

这在JDK 7的javac以及eclipse(合规性设置为1.7或1.8)下编译得很好。但是,尝试在JDK 8的javac下编译,我收到以下错误:

[ERROR] src/main/java/OverloadTest.java:[18,35] reference to OverloadTest is ambiguous
[ERROR] both constructor <T>OverloadTest(OverloadTest<? extends T>) in OverloadTest and constructor <T>OverloadTest(java.util.HashSet<? extends T>) in OverloadTest match

请注意,此错误仅适用于ambigous()方法中的构造函数调用,而不适用于notAmbiguous()方法中的构造函数调用。唯一的区别是ambiguous()依赖于钻石运算符。

我的问题是:JDK 8下的javac是否正确标记了一个模棱两可的解决方案,还是JDK 7下的javac未能解决模糊问题?根据答案,我需要提交JDK错误或ecj错误。

2 个答案:

答案 0 :(得分:3)

在调用中,当使用T set显式调用构造函数时,没有歧义:

OverloadTest<Object> o1 = new OverloadTest<Object>(source);

因为T是在构造函数调用时定义的,所以Object传递了?在编译时扩展Object检查就好了,没有问题。当T明确设置为Object时,两个构造函数的选择变为:

public OverloadTest(OverloadTest<Object> other) {}
public OverloadTest(HashSet<Object> source) {}

在这种情况下,编译器很容易选择第一个。在另一个示例中(使用菱形运算符)未明确设置T,因此编译器首先尝试通过检查实际参数的类型来确定T,这是第一个选项不需要做的。

如果第二个构造函数被更改为正确反映我想象的是期望的操作(因为OverloadTest是T的列表的HashSet,那么传入T的列表的HashSet应该是可能的),如下所示:

public OverloadTest(HashSet<List<? extends T>> source) {}

......然后解决了歧义。但是,当你要求编译器解决这种模糊的调用时,目前就会出现冲突。

编译器将看到菱形运算符,并将尝试根据传入的内容以及各种构造函数所期望的内容来解析T.但是编写HashSet构造函数的方式将确保无论传入哪个类,两个构造函数都将保持有效,因为在擦除之后,T总是被Object替换。当T是Object时,HashSet构造函数和OverloadTest构造函数具有相似的擦除,因为OverloadTest是HashSet的有效实例。并且因为一个构造函数不会覆盖另一个(因为OverloadTest&lt; T&gt;不会扩展HashSet&lt; T&gt;),实际上不能说一个比另一个更具体,所以它赢了&# 39;知道如何做出选择,而是抛出编译错误。

这只会发生,因为通过使用T作为边界,您强制执行编译器进行类型检查。如果你只是做了它&lt;?&gt;而不是&lt;?延伸T>它会编译得很好。 Java 8编译器对Java 7的类型和擦除更为严格,部分原因是Java 8中的许多新功能(如界面防御方法)要求他们对泛型更加迂腐。 Java 7没有正确报告这些内容。

答案 1 :(得分:0)

很抱歉破坏了聚会,但Java中的重载解析比目前看到的评论和答案更复杂。

两个构造函数都适用:

OverloadTest(OverloadTest<? extends T>)
OverloadTest(HashSet<? extends T>) 

此时Java 8使用"More Specific Method Inference",它分析所有候选对。

在检查前一个构造函数是否比后者更具体时,会成功解析以下约束( T#0 是{{1}的类型参数T的推理变量}}):

HashSet

解决方案是将 T#0 实例化为OverloadTest<? extends T> <: HashSet<? extends T#0>

反向尝试无法解决以下约束(此处 T#0 List<T>的类型参数T的推理变量):

OverloadTest

无法找到 T#0 的实例化来满足该约束条件。

由于一个方向成功而另一个方向失败,前一个构造函数被认为比后者更具体,从而解决了歧义。

Ergo:接受该程序似乎是编译器的正确答案。

PS:我并不反对Java 8 中必要的更多迂腐类型检查,但事实并非如此。