Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java generics ambiguous method

Tags:

java

generics

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.

like image 545
James Kleeh Avatar asked Sep 06 '19 18:09

James Kleeh


People also ask

What is ambiguous method?

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.

What does ambiguous mean in java?

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.

What is ambiguous invocation in java explain with example?

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.

Is ambiguous for the type assert?

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.


2 Answers

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:

  • Any arguments you pass in a valid invocation of the first method can also be passed in a valid invocation of the second method.
  • Any arguments you pass in a valid invocation of the second method can also be passed in a valid invocation of the first method.

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.

  • The successful invocation (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".

like image 109
Andy Turner Avatar answered Sep 16 '22 21:09

Andy Turner


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:

How Object call is ambiguous?

1) Test Object call on 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_Object( )
{
    Binder binder = new Binder( );
    binder.bind(Object.class, new Type<Object>(new Object());
}

Output:

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

My explanation:

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.

2) Test Object call on 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());
}

Output:

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

My explanation:

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:

How String call is NOT ambiguous?

3) Test String call on 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") );
}

Output:

 Type class: class java.lang.String

 test clazz bind 

 Clazz class: class java.lang.String

My explanation:

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?

4) Test String call on 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") );
}

Output:

Compiler error

My explanation:

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.

like image 25
cmlonder Avatar answered Sep 17 '22 21:09

cmlonder