Does two generics method at the same call can cause a compile error when trying to use them?

Viewed 129

I'm read Deital's book :"Java How To Program", and in Generic Chapter the sentence is written:"If the compiler doesn’t find a method declaration that matches a method call exactly, but does find two or more methods that can satisfy the method call, a compilation error occurs". Can someone give me an example of the situation, because when i didn't success to get the above compile error. my code:

public class Animal
{
    public Animal ()
    {

    }

    public String bark()
    {
        return "Animal";
    }
}

public class Dog extends Animal  
{
    public String bark()
    {
        return "howhow";
    }
}

public class DogSon extends Dog
{
    public String bark()
    {
        return "I'm  Dog Son";
    }
}

public class Test
{
    public static void main(String[] args)
    {
        Test t = new Test();
        DogSon d = new DogSon();
        System.out.println(t.helpMethod(d));
    }

    public <T extends Dog> String helpMethod(T dog)
    {
        System.out.println("aaa");
        return dog.bark();

    }
    public <T extends Animal> String  helpMethod(T animal)
    {
        return animal.bark();
    }
}   

Since there isn't method that get exactly son object, and there are two generic methods that can be suit here. Isn't this the situation Deital talking about?

3 Answers

Not quite; in your example, the T dog variant is more specific than the T animal variant, and therefore, it is the one selected.

I cannot guarantee that I know what the author is referring to. But I can guess:

public class Example<T> {
    public void m(T arg) {
        // At compile time, 'T', as it has a lower bound of Object,
        // is treated as Object. Its signature therefore does not clash
        // with the next m, and thus this can be compiled.
        System.out.println("T-based");
    }

    public void m(Dog arg) {
        System.out.println("Dog-based");
    }
}

new Example<Dog>().m(new Dog()); // this line causes a compiler error.

The error generated is:

Animal.java:16: error: reference to m is ambiguous
    new Example<Dog>().m(d);
                      ^
both method m(T) in Example and method m(Dog) in Example match
where T is a type-variable:
    T extends Object declared in class Example
1 error

So, what's going on here: At compile time (Of Example.java itself), the two m methods are not neccessarily ambiguous yet; The T-variant is treated, for most purposes, as if it was equal to its lower bound (so, Object), and in java you can have an m(Object arg) and an m(Dog arg) in the same class, no problem.

However, after generics, the 2 m methods effectively DO clash: They now both take as argument anything of type Dog. Thus, it is no longer possible to call either m method on any new Example<Dog>. The proper solution is not to overload (that's what it is called when you have 2 different methods that have the same name) when generics is involved and the type params could overlap. Presumably, the author of that section of your book is trying to tell you WHY you should never overload in such a case.

The reason you're not getting a compilation error in your case is because both the methods

public <T extends Dog> String helpMethod(T t) {

    return t.bark();
 }
public <T extends Animal> String helpMethod(T t) {

   return t.bark();
}

tell the compiler to resolve T to Dog & Animal respectively. Which is actually same as writing

public String helpMethod(Dog t) {

    return t.bark();
}
public String helpMethod(Animal t) {

    return t.bark();
}

As Dog & Animal are two different types despite being in a parent-child relationship. The compiler won't complain.

However, if you're looking for a compilation error, try this

public <T extends Dog> String helpMethod(T t) {

    return t.bark();
}
public <T extends Dog> String helpMethod(T t) {

    return t.bark();
}

What you have to understand is called Erasure of Generic Methods

It means, the method's type parameter is converted to Object if it's unbound
or
it's first bound class when it's bound.

Bound means, -staying at your example- you have declared a relation of the type parameter T to another class, like you did:

// 1. method  
public <T extends Dog> String helpMethod(T dog)
// 2. method
public <T extends Animal> String  helpMethod(T animal)

T is bounded to Dog in the 1. method, and bounded to Animal in the 2. method.

So, when it comes to compiling, Java changes the T into Dog in the 1. method, and to Animal in the 2. method.
There is no ambiguity here, they are two different methods.

However, if you would declare a 3. method:

// 3. method
public <T> String helpMethod(T dog)

Then the T is unbound, you haven't declared a relation between T and another class, therefore when it comes to compiling, Java changes the T into Object.

Now, if you would try to declare a 4. method:

// 4. method
public  String helpMethod(Object dog)

There would be a compilation error, as the 3. method during type erasure would have the exact same method signature as your 4. method.
The compiler cannot decide which one you want to call, and throws an error.

With the above in mind, the short answer is:

In you code, you are using two different methods, there is no ambiguity, so no error occurs.

If you want to see that compilation error, you should declare another generic method which signature after compiling would be the same as an existing generic/non-generic method in your class.

Related