Динамическое связывание Java и процесс переопределения методов

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

class Cake {
    public void taste (Cake c) {
        System.out.println("In taste of Cake class");
    }
}

class ChocolateCake extends Cake {
    public void taste(Cake c) {
        System.out.println("In taste (Cake version) of ChocolateCake class");
    }
    public void taste(ChocolateCake cc) {
        System.out.println("In taste (ChocolateCake version) of ChocolateCake class");
    }
}

class BirthdayCake extends ChocolateCake {
    public void taste(Cake c) {
        System.out.println("In taste (Cake version) of BirthdayCake class");
    }
    public void taste (ChocolateCake cc) {
        System.out.println("In taste (ChocolateCake version) of BirthdayCake class");
    }
    public void taste(BirthdayCake bc) {
        System.out.println("In taste (BirthdayCake version) of BirthdayCake class");
    }
}

Были созданы следующие объекты:

Cake c1 = new Cake();
ChocolateCake cc = new ChocolateCake();
Cake c2 = new ChocolateCake();
Cake c3 = new BirthdayCake();

Вывод показан ниже:

c1.taste(cc);//Output: In taste of Cake class
cc.taste(cc);//Output: In taste (ChocolateCake version) of ChocolateCake class
c2.taste(cc);//Output: In taste (Cake version) of ChocolateCake class
((BirthdayCake) c3).taste(cc);//Output: In taste (ChocolateCake version) of BirthdayCake class
((BirthdayCake) c3).taste((BirthdayCake) c3);//Output: In taste (BirthdayCake  version) of BirthdayCake class

По сути, мой вопрос: почему c2.taste(cc) вызывает taste(Cake c)метод в классе ChocolateCake?

Вот моя мысль: статический тип c2 - это Cake, который решает, что метод в Cake будет вызываться. Когда дело доходит до времени выполнения, динамический тип c2, а именно ChocolateCake, решает, будет ли вызываться метод в ChocolateCakecake. И так как тип его параметра ChocolateCake решил, что в конечном итоге будет вызван taste(ChocolateCake cc).

Видимо, эта мысль ошибочна. И если я предполагаю, что сигнатура метода была установлена ​​во время компиляции, поскольку статический тип c2 равен Cake, а в классе Cake есть только один метод. Когда дело доходит до времени выполнения, он вызывает метод переопределения в классе ChocolateCake. Все это имеет смысл. Меня смущает то, почему это работает так, а не по-прежнему?

Еще одна вещь, которую я не понимаю, заключается в том, что нам не разрешено писать оператор, подобный приведенному ниже, поскольку это приведет к ошибке компиляции:

ChocolateCake cc = new Cake();.

Но почему ссылка на тип ChocolateCake может, наконец, передать объект Cake, поскольку он должен вызывать метод taste(Cake c) в классе ChocolateCake для получения правильного вывода, как указано выше.

Я думаю, что до сих пор не понимаю всего процесса вызова метода по ссылке на объект. Например, что происходит при выборе наиболее подходящего метода во время компиляции и что происходит после этого, скажем, во время выполнения (я не уверен, что в этом процессе есть какая-то другая фаза).

Может ли кто-нибудь помочь проиллюстрировать этот процесс? Большое спасибо!


person Oscar Zhang    schedule 20.10.2017    source источник
comment
Проблема в том, что привязка параметров не является динамической. Параметр по-прежнему будет Cake во время выполнения, поскольку он был привязан к Cake.cake(Cake c) во время компиляции, но вызов метода (будучи динамическим) в конечном итоге будет ChocolateCake.cake(Cake c). Вероятно, для этого вопроса есть хороший дубликат, давайте посмотрим, смогу ли я его найти.   -  person Kayaman    schedule 20.10.2017
comment
Спасибо, ваш ответ в значительной степени решил мой вопрос. Но я все еще запутался с передачей параметров, как я упоминал в последних нескольких частях. А именно, почему возможно, что ссылка типа ChocolateCake, наконец, передает объект Cake, в то время как оператор типа ChocolateCake cc = new Cake(); даже не разрешен?   -  person Oscar Zhang    schedule 20.10.2017
comment
Это просто базовое наследование. Все торты Cakes, но Cake не ChocolateCake (может быть, но это не точно). Вы бы не написали Woman w = new Human();, потому что это тоже не обязательно верно.   -  person Kayaman    schedule 20.10.2017


Ответы (2)


Позвольте мне попытаться упростить пример и пройтись по шагам. Я также добавил @Override для ясности.

class Cake {
    public void taste (Cake c) {
        System.out.println("In taste of Cake class");
    }
}

class ChocolateCake extends Cake {
    @Override
    public void taste(Cake c) {
        System.out.println("In taste (Cake version) of ChocolateCake class");
    }
    public void taste(ChocolateCake cc) {
        System.out.println("In taste (ChocolateCake version) of ChocolateCake class");
    }
}

ChocolateCake param = new ChocolateCake();    
Cake cake = new ChocolateCake();
cake.taste(param);

Когда вы вызываете cake.taste(param);, компилятор java выбирает время компиляции, какой метод будет вызываться, на основе типа ссылки, а не фактического типа объекта, на который указывает ссылка.

Ссылочным типом cake является Cake, поэтому компилятор ищет в базовом классе Cake метод с именем taste и принимает Cake в качестве параметра. Поскольку ChocolateCake равно Cake (через наследование), компилятор находит совпадение.

Поскольку по сути у вас есть переопределение базового метода taste, во время выполнения и из-за динамической отправки JVM разрешает фактический тип ссылки cake, который равен ChocolateType, и вызывает переопределение уже выбранного метода.

person hovanessyan    schedule 20.10.2017

Я пробовал использовать следующий main():

public static void main(String[] args)
    {
        ChocolateCake cc = new ChocolateCake();
        Cake c = new ChocolateCake();
        Cake c1 = new Cake();
        Cake c2 = new ChocolateCake();
        Cake c3 = new BirthdayCake();

        ChocolateCake c4 = new BirthdayCake();

        c1.taste(cc);
        c1.taste(c);

        c2.taste(cc);
        c2.taste(c);

        c3.taste(cc);
        c3.taste(c);

        c4.taste(cc);
        c4.taste(c);

    }

вывод такой:

In taste of Cake class
In taste of Cake class
In taste (Cake version) of ChocolateCake class
In taste (Cake version) of ChocolateCake class
In taste (Cake version) of BirthdayCake class
In taste (Cake version) of BirthdayCake class
In taste (ChocolateCake version) of BirthdayCake class
In taste (Cake version) of BirthdayCake class

до сих пор мое понимание выглядит следующим образом:

  1. в каком классе искать метод, зависит от фактического типа объекта (вы можете использовать динамическую привязку)
  2. в этом целевом классе, какой метод будет вызываться, в этом случае зависит как от ссылочного типа объекта (который вызывает метод), так и от ссылочного типа этого параметра. мы выбираем более общий (ссылочный тип) из двух и интерпретируем вызываемый метод.

e.g.

c4.taste(cc);//c4:ChocolateCake cc:ChocolateCake -> ChocolateCake version
c4.taste(c);//c4:ChocolateCake c:Cake -> Cake version

просто некоторое собственное понимание, также очень новое для java;)

person 程博雅    schedule 21.11.2018