Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to choose appropriate method using Java Generics

Tags:

java

generics

This program doesn't do what I wanted. It prints "sad" twice, whereas I was hoping it would print "happy" and then "sad".

public class Woof {

    public static class Arf<T> {
        T yap;
        public Arf(T yap) {
            this.yap = yap;
        }

        public String woof() {
            /*
             * Should select which doYapStuff() based on whether T
             * happens to be an Integer, or something else.
             */
            return doYapStuff(yap);
        }

        /* Special case implementation of doYapStuff() where T is Integer */
        public String doYapStuff(Integer x) {
            return "happy";
        }

        /* Default implementation of doYapStuff() where T is something else */
        public String doYapStuff(T x) {
            return "sad";
        }
    }

    public static void main(String[] args) {
        Integer i = 5;
        Arf<Integer> arf1 = new Arf<Integer>(i);
        System.out.println(arf1.woof()); // Should print "happy"

        String s = "foo";
        Arf<String> arf2 = new Arf<String>(s);
        System.out.println(arf2.woof()); // Should print "sad"
    }

}
like image 219
user1807948 Avatar asked Dec 06 '14 03:12

user1807948


1 Answers

This is explained in this blog: http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ050

relevant quote:

How can this happen? We pass an argument of type String to the overloaded method and yet the version for type Object is called. The reason is that the compiler creates only one byte code representation per generic type or method and maps all instantiations of the generic type or method to that one representation.

In our example the generic method is translated to the following representation:

(edit: I changed the method to match this question)

public String doYapStuff(Object x) {
            return "sad";
}

Considering this translation, it should be obvious why the Object version of the overloaded method is invoked. It is entirely irrelevant what type of object is passed to the generic method and then passed along to the overloaded method. We will always observe a call of the Object version of the overloaded method.

More generally speaking: overload resolution happens at compile time, that is, the compiler decides which overloaded version must be called. The compiler does so when the generic method is translated to its unique byte code representation. During that translation type erasure is performed, which means that type parameters are replaced by their leftmost bound or Object if no bound was specified. Consequently, the leftmost bound or Object determines which version of an overloaded method is invoked. What type of object is passed to the method at runtime is entirely irrelevant for overload resolution.

You can see this if you look at the compiled bytecode:

 4  getfield Woof$Arf.yap : java.lang.Object [16]
 7  invokevirtual java.io.PrintStream.println(java.lang.Object) : void [32]
10  aload_0 [this]
11  aload_0 [this]
12  getfield Woof$Arf.yap : java.lang.Object [16]
15  invokevirtual Woof$Arf.doYapStuff(java.lang.Object) : java.lang.String [37]
18  areturn

To achieve what you want, you should probably use the Strategy Pattern.

public interface YapSound{
     String     doYapSound();
}

public class HappySound implements YapSound{

    @Override
    public String doYapSound() {
          return "happy";
     }

}

public class SadSound implements YapSound{

    @Override
    public String doYapSound() {
        return "sad";
    }

}

public class Arf {
    YapSound yap;
    public Arf(YapSound yap) {
        this.yap = yap;
    }

    public String woof() {
        /*
         * Should select which doYapStuff() based on whether T
         * happens to be an Integer, or something else.
         */
        return yap.doYapSound();
    }


}

public static void main(String[] args) {

    Arf arf1 = new Arf(new HappySound());
    System.out.println(arf1.woof()); // Should print "happy"

    Arf arf2 = new Arf(new SadSound());
    System.out.println(arf2.woof()); // Should print "sad"
}
like image 182
dkatzel Avatar answered Oct 06 '22 15:10

dkatzel