[英]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>
对于该方法,唯一可以推断出T
的Type<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
可以传递给两个bind3
和bind4
并不能证明没有可以被接受的参数bind3
但不bind4
。 我首先提出了非正式的直觉,所以我将以“ 非常 ,没有一个”的非正式直觉结束。
让我根据我的理解修改系统输出:
public class Binder
{
class Type<T>
{
Type( T obj )
{
System.out.println( "Type class: " + obj.getClass( ) );
}
}
}
我们可以逐个测试每个案例:
<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>
所以这个打电话很好。
<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调用不含糊? 我们来测试一下:
<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绑定怎么样?
<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.