java接口中的泛型方法如何将超级对象转换为具体的子类对象?

时间:2017-02-17 14:04:53

标签: java generics interface polymorphism

我有以下代码段,它运行正常。

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来处理AppleBananaHandler来处理Banana }。 AppleHandlerBananaHandler都实现了通用接口Handler,它具有通用方法handle。在handle方法中,Fruit方法被赋予App.process个对象,并且将按预期调用具体的handle方法。在调用具体的Fruit方法时,java似乎会自动将handle对象强制转换为其真实类型。 java如何实现这一目标?

4 个答案:

答案 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方法。