Java泛型模棱两可的方法

Jam*_*eeh 16 java generics

我无法理解以下代码的行为。任何理解的帮助将不胜感激。

class Binder {

    <T> void bind(Class<T> clazz, Type<T> type) {
        System.out.println("clazz type");
    }

    <T> void bind(T obj, Type<T> type) {
        System.out.println("obj type");
    }
}

class Type<T> {
    Type(T obj) { }
}

Binder binder = new Binder();

binder.bind(String.class, new Type<String>("x")) //works

binder.bind(Object.class, new Type<Object>(new Object()))  //ambiguous
Run Code Online (Sandbox Code Playgroud)

上面的代码将失败

ERROR: reference to bind is ambiguous
  both method <T>bind(java.lang.Class<T>,Type<T>) in Binder and method <T>bind(T,Type<T>) in Binder match
Run Code Online (Sandbox Code Playgroud)

如果我要删除每个方法的第二个参数,则两个绑定调用都将执行第一个方法

class Binder {

    <T> void bind(Class<T> clazz) {
        System.out.println("clazz");
    }

    <T> void bind(T obj) {
        System.out.println("obj");
    }
}

Binder binder = new Binder();

binder.bind(String.class)

binder.bind(Object.class)
Run Code Online (Sandbox Code Playgroud)

上面将打印两次“ clazz”。

And*_*ner 2

我认为JLS 15.12.2.5 选择最具体的方法充分解释了这种行为:

非正式的直觉是,如果第一个方法处理的任何调用可以传递给另一个方法而不会出现编译时错误,则一个[适用]方法比另一个[适用方法]更具体。

换句话说,如果以下任一陈述为真,则一种方法比另一种方法更具体:

  • 您在第一个方法的有效调用中传递的任何参数也可以在第二个方法的有效调用中传递。
  • 您在第二个方法的有效调用中传递的任何参数也可以在第一个方法的有效调用中传递。

除非第一种方法和第二种方法相同,否则这些陈述最多有一个是正确的。


选择最具体的方法的重要一点是,仅当不止一种方法适用于给定参数时才需要这样做。

binder.bind(String.class, new Type<String>("x"))没有歧义,因为该<T> void bind(T, Type<T>)方法不适用:如果将 a 传递Type<String>给该方法,则唯一可以推断的类型TString(因为 aType<T>不是 a Type<Object>)。

因此,您必须将 a 传递String给该方法。String.class是 a Class<String>,而不是 a String,因此该方法不适用,因此没有歧义需要解决,因为只有一种可能的方法 - the <T> void bind(Class<T>, Type<T>)- 适用。


在不明确的情况下,我们传递 aType<Object>作为第二个参数。这意味着,如果两个重载都适用,则第一个参数需要分别为 aClass<Object>和 an ObjectObject.class确实是这两件事,因此两个重载都适用。

为了证明这些是不明确的重载,我们可以找到一个反例来反驳“第一个方法处理的任何调用都可以传递给另一个方法”的说法。

这里的关键字是any:这与此处传入的特定参数无关,而仅与方法签名中的类型有关。

  • 成功的调用 ( binder.bind(String.class, new Type<String>("x"))) 无法调用bind(T, Type<T>)重载,因为String.class它不是String.
  • binder.bind("", new Type<String>(""))无法调用bind(Class<T>, Type<T>)重载,因为""是 a String,而不是 a Class<String>

量子ED。

这也可以通过为其中一个方法指定不同的名称(例如 )bind2并尝试传递这些参数来证明。

<T> void bind(Class<T> clazz, Type<T> type) { ... }
<T> void bind2(T obj, Type<T> type) { ... }

binder.bind(String.class, new Type<String>("x")); // compiles
binder.bind2(String.class, new Type<String>("x")); // doesn't compile

binder.bind("", new Type<String>("x")) // doesn't compile
binder.bind2("", new Type<String>("x")) // compiles
Run Code Online (Sandbox Code Playgroud)

给出不同的名称可以消除歧义的可能性,因此您可以直接查看参数是否适用。


在 1 个参数的情况下,您可以传递给的任何内容<T> void bind(Class<T>)也可以传递给<T> void bind(T)。这是因为Class<T>是 的子类,并且在第二种情况下Object界限T退化为,因此它接受任何内容。Object

因此,<T> void bind(Class<T>)比 更具体<T> void bind(T)

重做上面的重命名演示:

<T> void bind3(Class<T> clazz) { ... }
<T> void bind4(T obj) { ... }

binder.bind3(String.class); // compiles
binder.bind4(String.class); // compiles

binder.bind3("") // doesn't compile
binder.bind4("") // compiles
Run Code Online (Sandbox Code Playgroud)

显然,String.class可以传递给两者bind3并且bind4不能证明不存在可以被bind3but not接受的参数bind4。我首先陈述一种非正式的直觉,所以我将以非正式的直觉“真的,不存在”作为结束。