Java多态泛型调用
我最近刚开始在Java中处理泛型,遇到了一些奇怪的行为。这是简化版本,但我有一个基类,它由多个类扩展,这些类被传递到一个泛型函数中。在这个函数中,我调用了一个方法,该方法有多个版本以不同的基类或派生类作为参数 如果我有以下资料:Java多态泛型调用,java,generics,inheritance,polymorphism,Java,Generics,Inheritance,Polymorphism,我最近刚开始在Java中处理泛型,遇到了一些奇怪的行为。这是简化版本,但我有一个基类,它由多个类扩展,这些类被传递到一个泛型函数中。在这个函数中,我调用了一个方法,该方法有多个版本以不同的基类或派生类作为参数 如果我有以下资料: public class A{}; public class B extends A{}; public class C extends A{}; public class Runner { public static void Run( ClassA a ){
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() );
公共类A{};
公共类B扩展了A{};
公共类C扩展了{};
公开课跑者
{
公共静态无效运行(ClassA a){Do Something};
公共静态void Run(ClassB){Do Something};
公共静态void Run(ClassC){Do Something};
}
无效随机呼叫(B)
{
跑步者。跑步(b);
}
SomeRandomCall(新类b());
我在debug中发现Runner.Run
调用的是Run(ClassA)
,而不是Run(ClassB)
函数。给定这两个函数,是否应该调用Run(ClassB)
,因为提供了特定于类型的函数
如果不是这样的话,我怎么能有一个泛型函数来调用它呢?我怎么能调用具有签名的函数,这些签名采用基类和派生类呢 好的,由于
A
扩展了B
,那么类型为B
的对象可以作为参数在接受类型为A
的参数的方法中传递。同样的情况也发生在这里。由于run(A)
出现在run(B)
之前,因此执行前者。因此,你的错误(如果你正在考虑的话)
为了进一步阐述,请考虑下面的例子:
public static void main(String[] args) {
A ab = new B();
new Runner().Run( ab );
}
程序员
是一个超类(类似于A
)。
Java程序员
是Programmer
的子类,而C++程序员
也是B
和C
的子类
现在,有三个门,按距离增加的顺序:
由于第一扇门离您最近,并且符合条件,因此您可以立即进入。你不想花更多的精力去第二扇门。这里也会发生同样的情况。既然
A
扩展了B
,那么B
类型的对象可以作为参数传递给一个方法,该方法接受类型为A
的参数。同样的情况也发生在这里。由于run(A)
出现在run(B)
之前,因此执行前者。因此,你的错误(如果你正在考虑的话)
为了进一步阐述,请考虑下面的例子:
public static void main(String[] args) {
A ab = new B();
new Runner().Run( ab );
}
程序员
是一个超类(类似于A
)。
Java程序员
是Programmer
的子类,而C++程序员
也是B
和C
的子类
现在,有三个门,按距离增加的顺序:
由于第一扇门离您最近,并且符合条件,因此您可以立即进入。你不想花更多的精力去第二扇门。这里也会发生同样的情况。因为你的缩写让我有点困惑,我做了一个小的、可运行的例子。我假设
SomeRandomCall
中的B
是泛型类型,而不是类B
这是:
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 );
}
}
现在发生了什么?现在输出是A。原因是,在编译时,
ab
的类型是A
,因此我们只有一个候选方法可以执行:Run(aa)
。运行时中实际的ab
类型不再重要,因为我们只有一个候选者可以执行。无论ab
是A
、B
还是C
,输出都是A。由于您的缩写对我来说有点混淆,我做了一个小的、可运行的示例。我假设SomeRandomCall
中的B
是泛型类型,而不是类B
这是:
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 );
}
}
现在发生了什么?现在输出是A。原因是,在编译时,ab
的类型是A
,因此我们只有一个候选方法可以执行:Run(aa)
。运行时中实际的ab
类型不再重要,因为我们只有一个候选者可以执行。无论ab
是A
、B
还是C
,输出都是A
这就是为什么
EnclosingClass.<ClassB>SomeRandomCall( new ClassB() );
它的签名是void SomeRandomCall(A)
,它将始终调用Run(A)
另请参见为什么编译时类型与重载方法有关
编译器只会在可能的情况下选择正确的重载方法。例如,上述内容将反编译为
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
publicstaticvoidmain(java.lang.String[]);
签名:([Ljava/lang/String;)V
代码:
0:new#29//class Main$B
3:dup
4:invokespecial#31//methodmain$B.“”:()V
7:astore_1
8:aload_1
9:invokestatic#32//方法Main$Runner.Run:(LMain$B;)V
12:aload_1
13:astore_2
14:aload_2
15:invokestatic#18//方法Main$Runner.Run:(LMain$A;)V
18:返回
每次调用Run
都将使用不同的重载版本(由invokestatic
后面的数字标识)
这就是为什么
EnclosingClass.<ClassB>SomeRandomCall( new ClassB() );
它的签名是void SomeRandomCall(A)
,它将始终调用Run(A)
另请参见为什么编译时类型与重载方法有关
编译器将只选择正确的重载方法(如果可以)。例如,上面的方法将反编译为
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
publicstaticvoidmain(java.lang.String[]);
签名:([Ljava/lang/String;)V
代码:
0:new#29//class Main$B
3:dup
4:特别是#31