Reputation: 918
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
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
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
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:
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