Mark Renouf
Mark Renouf

Reputation: 31010

How do I resolve ambiguous methods caused by intersection types in Java generics?

I just recently discovered that you can specify multiple types in a single type parameter bound (see example). Like any new tool, I've been trying to explore the possibilities of how this can be used (and misused). I crafted this example to help illustrate.

On the sample below, the compiler is giving me an error

dispatch(new AlphabetSoup());

The method dispatch(Demo.Soup) is ambiguous for the type Demo

I can understand this because either method signature matches. My question is how could this be resolved without changing the methods? If I wanted force a call to the Soup version I could downcast to Soup:

dispatch((Soup) new AlphabetSoup())

But I'm unsure how you'd force a call to the other version. Is it possible?

public class Demo {

    interface HasA { public char getA(); }
    interface HasB { public char getB(); }
    interface HasC { public char getC(); }

    interface Soup { 
        public void eat();
    }

    class Alphabet implements HasA, HasB, HasC {
        public char getA() { return 'a'; }
        public char getB() { return 'b'; }
        public char getC() { return 'c'; }
    }

    class AlphabetSoup implements Soup,  HasA, HasB, HasC  { 
        public void eat() { System.out.println("Mmm Mmm Good!"); }
        public char getA() { return 'a'; }
        public char getB() { return 'b'; }
        public char getC() { return 'c'; }
    }

    public void dispatch(Soup soup) {
        System.out.println("Eating some soup...");
        soup.eat();
    }

    public <T extends HasA & HasB & HasC> void dispatch(T letters) {
        System.out.println("Reciting ABCs...");
        System.out.println(letters.getA());
        System.out.println(letters.getB());
        System.out.println(letters.getC());
    }

    public void test() {
        dispatch(new Alphabet());
        dispatch(new AlphabetSoup());
    }


    public static void main(String[] args) {
        new Demo().test();
    }
}

-- Edit: Just learned that "multiple bounded type parameters are formally referred to as "Intersection Types"

Upvotes: 10

Views: 28502

Answers (5)

AZ_
AZ_

Reputation: 21899

Let me explain this with a very simple program:

Code below illustrated couse of The method is ambiguous for the type compiler error.

public class AmbiguousMethodOwner {
            void ambiguousMethod(Comparable c){}
            void ambiguousMethod(Serializable c){}
            void test() {
                   ambiguousMethod("bar");
           }
     }

The problem now is obvious: since String implements both Comparable and Serializable, the compiler cannot know which method you intend to call.

A simple cast will solve the problem:

ambiguousMethod((Comparable)"bar");

http://www.javaneverdie.com/java/the-method-is-ambiguous-for-the-type/

In our case method dispatch is creating problem. See

class AlphabetSoup implements Soup,  HasA, HasB, HasC 

and

public void dispatch(Soup soup)
 public <T extends HasA & HasB & HasC> void dispatch(T letters) {

now if you call dispatch(new AlphabetSoup()); compiler would be confused as to which version of dispatch should be called ?

Upvotes: 5

Steve Reed
Steve Reed

Reputation: 2541

Not that you should keep the overloaded dispatch method (I upvoted Uri for that reason), but you can force the generic version to be called by trying:

demo.<AlphabetSoup>dispatch(new AlphabetSoup());

or call the soup version with:

demo.dispatch((Soup) new AlphabetSoup());

The better way around this, though, is to not have the overloaded dispatch method in the first place.

void dispatchSoup(Soup soup);
<T extends HasA & HasB & HasC> void dispatchLetters(T letters);

Upvotes: 1

GClaramunt
GClaramunt

Reputation: 3158

Note that the error is not related to generics, you get the same result if you use interfaces and a type is the intersection:

public class AA {

    interface XX{};
    interface YY{};

    public void doSomething(XX x){}
    public void doSomething(YY x){}

    class XY implements XX,YY{

    }

    public void runner(){
        doSomething(new XY());
    }
}

You get the same error in "doSomething", the compiler cannot resolve the ambiguity. Do you want to interpret as XX or as YY? You have to specify it with a cast. But if you have a hierarchy, like "YY extends XX" and "XY implements YY", the compiler can infer the correct method to call.

Upvotes: 11

Uri
Uri

Reputation: 89819

The compiler is right, and saves you from a mess.

AlphaBetSoup is a subtype of soup and also a subtype of HasA, HasB, and HasC

Therefore, it fits the bill for both versions of Dispatch

Since Soup is not a subtype of HasA, HasB, or HasC, it also can't say that one version is more "specific" than the other.

Therefore you'll get the error correctly.

Overloaded method should not be ambiguous. If you have a type that mixes both types and you had an overload for each, change your hierarchy or get rid of an overload. It's wrong use of subtyping and overloading.

Upvotes: 8

Yuval Adam
Yuval Adam

Reputation: 165312

Note that you do not have a real problem, as the methods you are interested in calling are already called due to dynamic binding.

Running dispatch((Soup) new AlphabetSoup()); yields:

Reciting ABCs...
a
b
c
Eating some soup...
Mmm Mmm Good!

Hence, the AlphabetSoup method are already called due to basic polymorphic behavior.

Upvotes: 1

Related Questions