繁体   English   中英

Java泛型模糊方法

[英]Java generics ambiguous method

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

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

上面的代码将失败

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

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

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)

以上将两次打印“clazz”。

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

非正式的直觉是,如果第一种方法处理的任何调用都可以传递给另一种没有编译时错误的调用,那么一种[适用]方法比另一种[适用方法]更具体。

换句话说,如果其中任何一个语句为真,则一个方法比另一个方法更具体:

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

除非第一种和第二种方法相同,否则这些陈述中至多有一种是真的。


选择最具体方法的一个重点是,只有当多个方法适用于给定的参数时,才需要这样做。

binder.bind(String.class, new Type<String>("x"))不明确,因为<T> void bind(T, Type<T>)方法不适用:如果传递Type<String>对于该方法,唯一可以推断出TType<T>String (因为Type<T>不是,例如Type<Object> )。

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


在不明确的情况下,我们传递一个Type<Object>作为第二个参数。 这意味着,如果两个重载都适用,则第一个参数需要分别是Class<Object>Object Object.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>)重载,因为""String ,而不是Class<String>

QED。

这也可以通过给其中一个方法提供不同的名称(例如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

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


在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

显然,事实String.class可以传递给两个bind3bind4并不能证明没有可以被接受的参数bind3但不bind4 我首先提出了非正式的直觉,所以我将以“ 非常 ,没有一个”的非正式直觉结束。

让我根据我的理解修改系统输出:

public class Binder
{
    class Type<T>
    {
        Type( T obj )
        {
            System.out.println( "Type class: " + obj.getClass( ) );
        }
    }
}

我们可以逐个测试每个案例:

对象调用是如何模糊的?

1)类上的测试对象调用:

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

@Test
public void bind_Object( )
{
    Binder binder = new Binder( );
    binder.bind(Object.class, new Type<Object>(new Object());
}

输出:

Type class: class java.lang.Object
test clazz bind
Clazz class: class java.lang.Object

我的解释:

在这种情况下,T被选为Object。 所以函数声明变成了bind(Class<Object> obj, Type<Object>)这很好,因为我们用bind(Object.class, new Type<Object)调用,其中Object.class is assignable to Class<Object>所以这个打电话很好。

2)T上的测试对象调用:

<T> void bind( T obj, Type<T> type )
{
    System.out.println( "test obj bind" );
    System.out.println( "Obj class: " + obj.getClass() );
}

@Test
public void bind_Object( )
{
    Binder binder = new Binder( );

    binder.bind(Object.class, new Type<Object>(new Object());
}

输出:

Type class: class java.lang.Object
test obj bind
Obj class: class java.lang.Class

我的解释:

在这种情况下,T被选为对象。 因此,函数声明变为bind(Object obj, Type<Object>) ,因为我们使用bind(Object.class, new Type<Object), Class<Object>进行调用bind(Object.class, new Type<Object), Class<Object>可以作为第一个param分配给Object

因此,这两种方法都适用于Object调用。 但为什么String调用不含糊? 我们来测试一下:

字符串调用如何不明确?

3)Class上的测试字符串调用:

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

@Test
public void bind_String( )
{
    Binder binder = new Binder( );

    binder.bind( String.class, new Type<String>( "x") );
}

输出:

 Type class: class java.lang.String

 test clazz bind 

 Clazz class: class java.lang.String

我的解释:

在这种情况下,T被选为String。 因此,函数声明变为bind(Class<String> clazz, Type<String> type) ,这很好,因为我们使用bind(String.class, new Type<String)调用,这是可以分配的。 T绑定怎么样?

4)T上的测试字符串调用:

<T> void bind( T obj, Type<T> type )
{
    System.out.println( "test obj bind" );
    System.out.println( "Obj class: " + obj.getClass() );
}

@Test
public void bind_String( )
{
    Binder binder = new Binder( );

    binder.bind( String.class, new Type<String>( "x") );
}

输出:

编译错误

我的解释:

在这种情况下,T被选为String。 因此,函数声明变为bind(String obj, Type<String> type) ,因为我们使用bind(String.class, new Type<String)调用,所以它bind(String.class, new Type<String) String.class which means Class<String> 因此我们尝试使用(Class, Type<String)输入调用(String, Type<String>)函数,这些函数不可分配。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM