2

I just recently started messing around with Generics in Java, and am running in to some odd behavior. This is the simplified version, but I have a base class that is extended by multiple classes that gets passed into a generic function. Inside this function, I call a method that has several versions that take different base or derived classes as parameters.

If I have the following:

public class A{};
public class B extends A{};
public class C extends A{};
public class Runner
{
    public static void Run( ClassA a ){Do Something};
    public static void Run( ClassB b ){Do Something};
    public static void Run( ClassC c ){Do Something};
}
void SomeRandomCall<B extends ClassA>( B b )
{
     Runner.Run( b );
}
SomeRandomCall<ClassB>( new ClassB() );

I am finding in debug that Runner.Run is calling the Run( ClassA a ) instead of the Run( ClassB b ) function. Given the two functions, shouldn't Run( ClassB b ) be called since the type specific function is provided?

If this isn't the case how would I be able to have a Generic function that would call be able to call functions that have signatures that take base and derived classes?

Touchstone
  • 5,575
  • 7
  • 41
  • 48
Theopile
  • 868
  • 3
  • 14
  • 30
  • "Generics"? U mean "inheritence" right? – Hungry Blue Dev Mar 01 '14 at 15:12
  • What is `void SomeRandomCall( B b )` supposed to be? Why is B (that i a class) in `<>` (what indicates generics) ? – exception1 Mar 01 '14 at 15:15
  • 1
    The "B" in "public class B extends A{};" is completely unrelated to the "B" in "void SomeRandomCall( B b )". It might as well be "void SomeRandomCall( Z z )" – aliteralmind Mar 01 '14 at 15:25
  • Actually, "void SomeRandomCall( B b )" is an invalid method declaration. I'm assuming you mean " void SomeRandomCall( B b )" (before the return type, and no "Class"!) – aliteralmind Mar 01 '14 at 15:27
  • [This](http://stackoverflow.com/questions/1572322/overloaded-method-selection-based-on-the-parameters-real-type) may help with your question regarding which method should be called. – Alex - GlassEditor.com Mar 01 '14 at 15:27
  • 1
    Can you replace your summary pseudo-code with the actual code you have used? There are several syntactical errors in what you have posted and the intent is not clear. – ErstwhileIII Mar 01 '14 at 15:28

3 Answers3

2

Since your abbreviations were a bit confusing to me, I made a small, runnable example. I assume that your B in SomeRandomCall is a Generic type, not the class B.

Here it is:

public class Main {

    public static class A{};
    public static class B extends A{};
    public static class C extends A{};
    public static class Runner{
        public static void Run( A a ){System.out.println("A");};
        public static void Run( B b ){System.out.println("B");};
        public static void Run( C c ){System.out.println("C");};
    }

    static <T extends A> void SomeRandomCall( T x ){
         Runner.Run( x );
    }

    public static void main(String[] args) {
        B b = new B();
        new Runner().Run( b );
    }
}

The output is: B.

That's what you expected, and this is good. At compile time, the Java compiler chooses candidates for methods, which are Run( A a ) and Run( B b ). At run time, the type of the parameter is B, so B is printed.

However, note the following example:

    public static void main(String[] args) {
        A ab = new B();
        new Runner().Run( ab );
    }

What happens now? Now the output is A. The reason is, at compile time, ab is of the type A, so we have only one candidate method to execute: Run( A a ). The actual type of ab during runtime is not important anymore, since we have only one candidate that can be executed. The output, independent whether ab is A, B or C, is A.

exception1
  • 1,239
  • 8
  • 17
1

Well, since A extends B, then an object of type B may be passed as a parameter in a method that accepts parameters as type A. The same happens here. Since run(A a) is present before run(B b), the former is executed. Hence, your error (if you're considering it).

For further elaboration consider this following example:

A Programmer is a superclass (like A). A Java Programmer is a subclass of Programmer, and so is a C++ programmer (like B and C).

Now, there are three doors, in the order of increasing distance:

  1. For Programmers.
  2. For Java Programmers.
  3. For C++ Programmers.

Since the 1st door is closest to you and the conditions are met, you enter it right away. You don't wanna spend more energy to go to the 2nd door. The same happens here.

Hungry Blue Dev
  • 1,313
  • 16
  • 30
1
void SomeRandomCall<T extends SomeClass>( T t )

is compiled as if it was

void SomeRandomCall( Someclass t )

And that's why

EnclosingClass.<ClassB>SomeRandomCall( new ClassB() );

will result in A because ClassB as compile time type is no longer visible inside the method.

This is also visible if you look at the class (Main in below example) via javap -c -s (i.e. decompiled)

  static <T extends Main$A> void SomeRandomCall(T);
    Signature: (LMain$A;)V
    Code:
       0: aload_0
       1: invokestatic  #18                 // Method Main$Runner.Run:(LMain$A;)V
       4: return

It's signature is void SomeRandomCall( A ) and it will always call Run( A ).

See also Overloading in Java and multiple dispatch why the compile time type matters with overloaded methods.


public static class Runner{
    public static void Run( A a ){System.out.println("A");};
    public static void Run( B b ){System.out.println("B");};
    public static void Run( C c ){System.out.println("C");};
}

public static void main(String[] args) {
    B b = new B();
    Runner.Run(b);
    A a = b;
    Runner.Run(a);
}

The compiler will only choose the correct overloaded method if it can. E.g. above will decompile to

  public static void main(java.lang.String[]);
    Signature: ([Ljava/lang/String;)V
    Code:
       0: new           #29                 // class Main$B
       3: dup
       4: invokespecial #31                 // Method Main$B."<init>":()V
       7: astore_1
       8: aload_1
       9: invokestatic  #32                 // Method Main$Runner.Run:(LMain$B;)V
      12: aload_1
      13: astore_2
      14: aload_2
      15: invokestatic  #18                 // Method Main$Runner.Run:(LMain$A;)V
      18: return

Every call to Run will use a different overloaded version (identified by the number after invokestatic)

Community
  • 1
  • 1
zapl
  • 63,179
  • 10
  • 123
  • 154
  • So expectations are right, but I must be passing/casting B as A somewhere before I reach SomeRandomCall. I will recheck my code on Monday, and I will post again if I can't find the casting. – Theopile Mar 02 '14 at 15:54
  • @Theopile It reaches `SomeRandomCall` as `B` but the parameter inside `SomeRandomCall` will always be `A` because that's the type of the method parameter in the signature and the call to `Run(A)` is hardcoded in that method. It can't call `Run(B)` dynamically. That would require a different method that accepts only `B`. Look into [double dispatch](http://c2.com/cgi/wiki?DoubleDispatchExample) [patterns](http://en.wikipedia.org/wiki/Visitor_pattern) if you want to be able to call the desired method. There is no way you can use a single method dipatching to 3 different ones like that. – zapl Mar 02 '14 at 23:13
  • I was trying to avoid a Dispatch, but it definitely worked thanks. – Theopile Mar 03 '14 at 16:35