Theopile
Theopile

Reputation: 918

Java Polymorphic Generic Calls

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?

Upvotes: 2

Views: 163

Answers (3)

zapl
zapl

Reputation: 63955

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)

Upvotes: 1

exception1
exception1

Reputation: 1249

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.

Upvotes: 2

Hungry Blue Dev
Hungry Blue Dev

Reputation: 1325

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.

Upvotes: 1

Related Questions