I'm having trouble understanding the behavior behind the below code. Any help in understanding would be appreciated.
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
The above code will fail with
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
If I were to remove the second argument to each method, both bind calls would execute the first method
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)
The above will print "clazz" twice.
This ambiguous method call error always comes with method overloading where compiler fails to find out which of the overloaded method should be used. Suppose we have a java program like below. Above program compiles perfectly and when we run it, it prints “String”. So the method foo(String s) was called by the program.
The ambiguities are those issues that are not defined clearly in the Java language specification. The different results produced by different compilers on several example programs support our observations.
Ambiguous Invocation. □ Sometimes there may be two or more possible. matches for an invocation of a method, but the. compiler cannot determine the most specific match. This is referred to as ambiguous invocation.
The method assertEquals(Object, Object) is ambiguous for the type ... What this error means is that you're passing a double and and Double into a method that has two different signatures: assertEquals(Object, Object) and assertEquals(double, double) both of which could be called, thanks to autoboxing.
I think that this behaviour is adequately explained in JLS 15.12.2.5 Choosing the Most Specific Method:
The informal intuition is that one [applicable] method is more specific than another [applicable method] if any invocation handled by the first method could be passed on to the other one without a compile-time error.
To state this another way, one method is more specific than the other if either of these statements are true:
Unless the first and second methods are the same, at most one of these statements can be true.
An important point about choosing the most specific method is that this is only necessary when more than one method is applicable for the given arguments.
binder.bind(String.class, new Type<String>("x"))
is not ambiguous because the <T> void bind(T, Type<T>)
method is not applicable: if you pass a Type<String>
to that method, the only type which can be inferred for T
is String
(because a Type<T>
is not, say, a Type<Object>
).
As such, you would have to pass a String
to that method. String.class
is a Class<String>
, not a String
, so that method is not applicable, so there is no ambiguity to resolve as only one possible method - the <T> void bind(Class<T>, Type<T>)
- applies.
In the ambiguous case, we are passing a Type<Object>
as the second parameter. This means that, if both overloads are applicable, the first parameter would need to be a Class<Object>
and an Object
respectively. Object.class
is indeed both of those things, hence both overloads are applicable.
To prove that these are ambiguous overloads, we can find a counter example to refute the claim that "any invocation handled by the first method could be passed on to the other" for both methods with respect to the other.
The key word here is any: this has nothing to do with the specific arguments that are being passed in here, but is only to do with the types in the method signature.
binder.bind(String.class, new Type<String>("x"))
) couldn't invoke the bind(T, Type<T>)
overload, because String.class
isn't a String
.binder.bind("", new Type<String>(""))
couldn't invoke the bind(Class<T>, Type<T>)
overload, because ""
is a String
, not a Class<String>
.QED.
This can also be demonstrated by giving one of the methods a different name, say, bind2
, and attempting to pass these parameters.
<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
Giving different names removes the possibility of ambiguity, so you can directly see if the parameters are applicable.
In the 1-argument case, anything you can pass to <T> void bind(Class<T>)
can also be passed to <T> void bind(T)
. This is because Class<T>
is a subclass of Object
, and the bound T
degenerates to Object
in the second case, so it accepts anything.
As such, <T> void bind(Class<T>)
is more specific than <T> void bind(T)
.
Redoing the renaming demonstration above:
<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
Obviously, the fact that String.class
can be passed to both bind3
and bind4
doesn't prove there isn't a parameter that can be accepted by bind3
but not bind4
. I started by stating an informal intuition, so I'll finish with the informal intuition that "really, there isn't one".
Let me revise System outs like those for my understanding:
public class Binder
{
class Type<T>
{
Type( T obj )
{
System.out.println( "Type class: " + obj.getClass( ) );
}
}
}
We can test each of the cases one by one:
<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
In this case, T is chosen as Object. So function declaration became as
bind(Class<Object> obj, Type<Object>)
which is fine because we are calling with
bind(Object.class, new Type<Object)
where Object.class is assignable to Class<Object>
so this call is fine.
<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
In this case T is chosen as Object. So function declaration became as bind(Object obj, Type<Object>)
which is fine because we are calling with bind(Object.class, new Type<Object), Class<Object>
is assignable to Object
as first param.
So both methods are appropriate for the Object call. But why String call is not ambiguous? Let's test it:
<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
In this case T is chosen as String. So function declaration became as bind(Class<String> clazz, Type<String> type)
which is fine because we are calling with bind(String.class, new Type<String)
which is assignable for sure. How about T bind?
<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") );
}
Compiler error
In this case T is chosen as String. So function declaration became as bind(String obj, Type<String> type)
which is NOT fine because we are calling with bind(String.class, new Type<String)
. String.class which means Class<String>
. So we try to call (String, Type<String>)
function with (Class, Type<String)
inputs which is not assignable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With