【问题标题】:How generic method in java interface convert super object to concrete subclass object?java接口中的泛型方法如何将超对象转换为具体的子类对象?
【发布时间】:2017-02-17 14:04:52
【问题描述】:

我有以下sn-p,它工作正常。

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 来处理BananaAppleHandlerBananaHandler 都实现了一个通用接口Handler,它有一个通用方法handlehandle 方法在App.process 方法中被赋予了一个Fruit 对象,具体的handle 方法将按预期被调用。当调用具体的handle 方法时,java 似乎自动将Fruit 对象转换为其真实类型。 java是如何实现的呢?

【问题讨论】:

  • 你的问题是:Java是如何实现多态的?
  • @DavidChoweller,没有。我的问题是:java如何在调用具体句柄方法时将Fruit对象转换为Apple或Banana对象(这称为多态)?

标签: java generics interface polymorphism


【解决方案1】:

似乎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

【讨论】:

    【解决方案2】:
    public void process(Fruit fruit) {
        Handler handler = handlerMap.get(fruit.getClass());
    

    在这里,参数是Fruit,但实际的类是Banana。所以当你说fruit.getClass() 返回Banana 时,它会得到Banana 处理程序

    【讨论】:

    • 是的,你是对的。但是,Banana.handle 被赋予了一个 Fruit 对象,它是如何以及何时被转换为 Banana 对象的?
    • @expoter 不,你很困惑。过程中获得了香蕉。 Banana 是 Fruit 的子类,所以当调用 process 方法时,fruit 对象实际上是一个 Banana,而不是一个正在发生的多态性的 Fruit。没有发生转化。
    【解决方案3】:

    我认为如果你去掉 Handler 接口,并将 handle() 方法放在 Fruit/Apple/... 中会使示例更好。

    在 java 中,我们通过引用变量访问一个对象(例如,键入T),这是唯一的方法。一旦 ref 变量被创建到一个对象(T 或 T 的子类型),引用变量的类型就不能改变,它可以重新分配给其他变量。引用变量的类型将决定它可以在对象上调用的方法。

    你用new Apple()创建了一个对象,给Apple对象声明了一个ref-var,无论是Fruit a = new Apple()还是Apple a = new apple()都是引用@987654328的对象@,甚至稍后你会做Object o = a;o 也指的是Apple 对象。所以如果你做Fruit a = new Apple(); a.doSomething(),java会找到Apple对象,并执行Apple类/对象的doSomething()方法。

    【讨论】:

    • 感谢您的建议!
    【解决方案4】:

    代码有效,因为您在 Map 中使用 Handler 来利用原始类型。

    通过使用原始类型,您可以获得类似Map&lt;Class&lt;Object&gt;, Handler&lt;Object&gt;&gt; 的内容,因此可以使用任何类型的对象来检索正确的Handler&lt;Object&gt;。代码工作的事实仅仅是映射器设置正确这一事实。没有什么能阻止你做事

    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");
      }
    }
    

    所以从外部不需要强制转换来将水果分派给正确的处理程序,但这也意味着编译器无法确保在运行时分派是正确的。

    【讨论】:

      猜你喜欢
      • 2013-01-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多