我有以下代码段,它运行正常。
public class Test {
public static void main(String[] args) {
App app = new App();
app.setHandler(Apple.class, new AppleHandler());
app.setHandler(Banana.class, new BananaHandler());
app.process(new Apple());
app.process(new Banana());
}
}
class Fruit {}
class Apple extends Fruit {}
class Banana extends Fruit {}
interface Handler<T extends Fruit> {
public void handle(T fruit);
}
class AppleHandler implements Handler<Apple> {
@Override
public void handle(final Apple fruit) {
System.out.println("This is an apple.");
}
}
class BananaHandler implements Handler<Banana> {
@Override
public void handle(final Banana fruit) {
System.out.println("This is a banana.");
}
}
class App {
Map<Class, Handler> handlerMap = new HashMap<>();
public void setHandler(Class clazz, Handler handler) {
handlerMap.put(clazz, handler);
}
public void process(Fruit fruit) {
Handler handler = handlerMap.get(fruit.getClass());
handler.handle(fruit);// HERE, how java convert Fruit object to concrete subclass object automatically?
}
}
我希望App
类以不同的方式处理不同的Fruit
,因此我定义AppleHandler
来处理Apple
和BananaHandler
来处理Banana
}。 AppleHandler
和BananaHandler
都实现了通用接口Handler
,它具有通用方法handle
。在handle
方法中,Fruit
方法被赋予App.process
个对象,并且将按预期调用具体的handle
方法。在调用具体的Fruit
方法时,java似乎会自动将handle
对象强制转换为其真实类型。 java如何实现这一目标?
答案 0 :(得分:2)
似乎java在调用具体句柄方法时会自动将Fruit对象强制转换为其真实类型。 java如何实现这一目标?
这是对的。编译器根据泛型参数插入强制转换。在你的代码中看到这很棘手,因为你使用了很多原始类型。
当你这样做时:
Handler handler = handlerMap.get(fruit.getClass());
handler.handle(fruit);
handler
的通用参数未知,因此将fruit
传递给handle
方法的已擦除形式,其类似于:
public void handle(Fruit fruit)
所以代码编译。但是在那时,动态调度会调用handle
的被覆盖实现,例如你在AppleHandler
中定义的实现:
@Override
public void handle(final Apple fruit) {
System.out.println("This is an apple.");
}
此时,通用参数已知,参数将转换为Apple
。
此演员表也可以在AppleHandler
类的字节码中看到:
public void handle(test.Fruit);
Code:
0: aload_0
1: aload_1
2: checkcast #35 // class test/Apple
5: invokevirtual #37 // Method handle:(Ltest/Apple;)V
8: return
答案 1 :(得分:0)
代码有效,因为您在Handler
中使用Map
来利用原始类型。
通过使用原始类型,您可以使用类似Map<Class<Object>, Handler<Object>>
的内容,因此可以使用任何类型的对象来检索正确的Handler<Object>
。代码工作的事实仅仅是由于映射器正确设置的事实。没有什么能阻止你做
app.setHandler(Apple.class, new BananaHandler());
app.setHandler(Banana.class, new AppleHandler());
产生ClassCastException
:
tests.Main$Apple cannot be cast to tests.Main$Banana
所以这里没有黑魔法,只是尝试使用泛型类型变量,你会发现相同的代码在没有强制Handler
的类型的情况下不会工作。
由于类型擦除,您的AppleHandler
实现编译为
class AppleHandler {
void handle(Object object) {
Apple apple = (Apple)object;
System.out.println("This is an apple");
}
}
因此从外部不需要强制转换将水果分派给正确的处理程序,但这也意味着编译器无法确保在运行时调度是正确的。
答案 2 :(得分:0)
public void process(Fruit fruit) {
Handler handler = handlerMap.get(fruit.getClass());
就在这里,参数是Fruit
,但实际的类是Banana
。所以当你说fruit.getClass()返回Banana
时,它就会得到Banana
处理程序
答案 3 :(得分:0)
我认为如果你摆脱了Handler
界面,并将handle()
方法放在Fruit/Apple/...
中会使示例更好。
在java中,我们通过引用变量访问对象(例如,键入T
),这是唯一的方法。一旦为对象创建了ref变量(T或T的子类型),就无法更改引用变量的类型,可以将其重新分配给其他变量。引用变量的类型将决定它可以在对象上调用的方法。
您使用new Apple()
创建了一个对象,向 Apple 对象声明了ref-var,无论它是Fruit a = new Apple()
还是Apple a = new apple()
对Apple
的对象,即使稍后你做Object o = a;
,o
也会引用Apple
对象。因此,如果执行Fruit a = new Apple(); a.doSomething()
,java将找到Apple
对象,并执行doSomething()
类/对象的Apple
方法。