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

Ian*_*son 15 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);
  }
}
Run Code Online (Sandbox Code Playgroud)

这在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
Run Code Online (Sandbox Code Playgroud)

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

我的问题是:JDK 8下的javac是否正确地标记了一个模糊的解决方案,还是JDK 7下的javac没有引起歧义?根据答案,我需要提交JDK错误或ecj错误.

Ste*_*e K 3

在调用中,当显式设置 T 来调用构造函数时,不会产生歧义:

OverloadTest<Object> o1 = new OverloadTest<Object>(source);
Run Code Online (Sandbox Code Playgroud)

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

public OverloadTest(OverloadTest<Object> other) {}
public OverloadTest(HashSet<Object> source) {}
Run Code Online (Sandbox Code Playgroud)

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

If the second constructor was changed to properly reflect what I imagine is the desired operation (that since OverloadTest is a HashSet of Lists of T, then passing in a HashSet of Lists of T should be possible) like so:

public OverloadTest(HashSet<List<? extends T>> source) {}
Run Code Online (Sandbox Code Playgroud)

...then the ambiguity is resolved. But as it currently stands there will be the conflict when you ask the compiler to resolve that ambiguous invocation.

The compiler will see the diamond operator and will attempt to resolve T based on what was passed in and what the various constructors expect. But the way that the HashSet constructor is written will ensure that no matter which class is passed in, both constructors will remain valid, because after erasure, T is always replaced with Object. And when T is Object, the HashSet constructor and the OverloadTest constructor have similar erasures because OverloadTest is a valid instance of HashSet. And because the one constructor doesn't override the other (because OverloadTest<T> does not extend HashSet<T>), it can't actually be said that one is more specific than the other, so it won't know how to make a choice, and will instead throw a compile error.

This only occurs because by using T as a boundary you are enforcing the compiler to do type-checking. If you simply made it <?> instead of <? extends T> it would compile just fine. The Java 8 compiler is stricter about types and erasure than Java 7 was, partially because many of the new features in Java 8 (like interface defender methods) required them to be a little bit more pedantic about generics. Java 7 was not correctly reporting these things.