2017-02-17 96 views
1

У меня есть следующий фрагмент, и он отлично работает.Как общий метод в интерфейсе java конвертирует супер объект в конкретный объект подкласса?

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 будет вызываться, как ожидалось. Кажется, что java бросает объект Fruit в его реальный тип автоматически при вызове бетона handle. Как java достигает этого?

+1

Вы вопрос: Как Java реализовать полиморфизм? –

+0

@DavidChoweller, NO. Мой вопрос: как java конвертирует объект Fruit в объект Apple или Banana при вызове метода конкретной ручки (это называется полиморфизмом)? – expoter

ответ

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 
0

Код работает, потому что вы используете необработанные типы, используя Handler в своем Map.

Используя необработанные типы, у вас есть что-то вроде Map<Class<Object>, Handler<Object>>, поэтому любой объект может быть использован для получения правильного Handler<Object>. Тот факт, что код работает, просто определяется тем фактом, что mappers правильно настроены. Ничто не мешает вам делать

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

так снаружи не забросов не требуются направить плод на правильный обработчик, но это также означает, что нет никакого способа для компилятора, чтобы гарантировать, что отправка будет правильной во время выполнения.

0
public void process(Fruit fruit) { 
    Handler handler = handlerMap.get(fruit.getClass()); 

Здесь, параметр является Fruit, но фактический класс Banana. Поэтому, когда вы говорите fruit.getClass() это возвращение Banana, так это получение Banana обработчика

+0

Да, ты прав. Но Banana.handle получил объект Fruit, как и когда он был брошен на объект Banana? – expoter

+0

@expoter нет, вы в замешательстве. процессу был дан банан. Банан является подклассом Fruit, поэтому, когда метод процесса называется, фруктовый объект на самом деле является бананом, а не плодом, который является полиморфизмом. Там нет конверсии. – ControlAltDel

0

Я думаю, если вы избавитесь от интерфейса Handler, и поставить метод handle() в Fruit/Apple/... бы пример лучше.

В java мы обращаемся к объекту (скажем, типа T) через ссылочную переменную, это единственный способ. Когда переменная ref создается для объекта (T или subTypes of T), тип ссылочной переменной не может быть изменен, ее можно переназначить другой переменной. Тип ссылочной переменной будет определять методы, которые он может вызывать на объекте.

Вы создаете объект с new Apple(), реф-вар был объявлен в компании Apple объекта, независимо от того, что является Fruit a = new Apple() или Apple a = new apple() одновременно относятся к объекту Apple, даже позже вы Object o = a;, то o относится к объект Apple тоже. Итак, если вы делаете Fruit a = new Apple(); a.doSomething(), java найдет объект Apple и выполнит метод doSomething() класса/объекта Apple.

+0

Спасибо за ваш совет! – expoter