Как полиморфизм в Java работает для этого общего случая (метод с параметром)? - PullRequest
0 голосов
/ 12 октября 2018

У меня есть код для общего случая:

public class A {
    public String show(A obj) {
        return ("A and A");
    }
}

public class B extends A {
    public String show(B obj) {
        return ("B and B");
    }

    public String show(A obj) {
        return ("B and A");
    }
}

public class C extends B {

}

public class Test {
    public static void main(String[] args) {
        A a = new B();
        B b = new B();
        C c = new C();

        System.out.println("1--" + a.show(b));
        System.out.println("2--" + a.show(c));     
    }
}

Результаты:

1--B and A
2--B and A

Я знаю, что в Java есть цепочка приоритетов от высокого к низкому:

this.show(O), super.show(O), this.show((super)O), super.show((super)O)

Мое понимание ниже:

В этом коде:

A a = new B()

Произошел подъем.A является ссылкой на родительский класс, а B является ссылкой на дочерний родительский класс.Когда код компилируется и запускается, ссылка на дочерний родительский класс определяет способ выбора метода.В этом случае выбирается show(A) в классе B.

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

Может ли кто-нибудь дать более подробное объяснение показанного результата?

Ответы [ 4 ]

0 голосов
/ 12 октября 2018

Ваш тип ссылки - A, а A имеет только один метод show(A obj), который был переопределен в B и печать B and A, поэтому вы печатаете B and A всегда.

0 голосов
/ 12 октября 2018

Чтобы понять, почему вы получаете результат B and A дважды, вам нужно знать, что есть 2 части: компиляция и время выполнения.

Компиляция

При встрече с оператором a.show(b) компилятор выполняет следующие основные шаги:

  1. Просмотрите объект, для которого вызывается метод (a), и получите его объявленный тип.Этот тип A.
  2. В классе A и всех его супертипах составьте список всех методов с именем show.Компилятор найдет только show(A).Он не смотрит ни на какие методы в B или C.
  3. . Из списка найденных методов выберите тот, который лучше всего соответствует параметру (b), если таковой имеется.show(A) примет b, поэтому этот метод выбран.

То же самое произойдет для второго вызова, когда вы передаете c.Первые два шага одинаковы, а третий шаг снова найдет show(A), поскольку есть только один, и он также соответствует параметру c.Итак, для обоих ваших вызовов остальная часть процесса одинакова.

Как только компилятор выяснит, какой метод ему нужен, он создаст инструкцию с байт-кодом invokevirtual и поместит решенныйметод show(A), который следует вызвать (как показано в Eclipse, открыв .class):

invokevirtual org.example.A.show(org.example.A) : java.lang.String [35]

Runtime

Время выполнения, когда онов конце концов, для получения invokevirtual необходимо также выполнить несколько шагов.

  1. Получить объект, для которого вызывается метод (который к тому времени уже находится в стеке), то есть a.
  2. Посмотрите на фактический runtime тип этого объекта.Начиная с a = new B(), этот тип B.
  3. Найдите B и попробуйте найти метод show(A).Этот метод найден, поскольку B переопределяет его.Если бы это было не так, он бы смотрел в суперклассах (A и Object), пока такой метод не будет найден.Важно отметить, что он учитывает только методы show(A), например.show(B) из B никогда не рассматривается.
  4. Среда выполнения теперь будет вызывать метод show(A) из B, давая String B and A в качестве результата.

Более подробно об этом дано в спецификации для invokevirtual:

Если разрешенный метод не является полиморфным сигнатурой (§2.9), то инструкция invokevirtual выполняется следующим образом.

Пусть C - класс objectref.Фактический метод, который должен быть вызван, выбирается следующей процедурой поиска:

Если C содержит объявление для метода экземпляра m, который переопределяет (§5.4.5) разрешенный метод, то m - это метод, который должен быть вызвани процедура поиска завершается.

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

В противном случае вызывается AbstractMethodError.

Для вашего примера objectref равен a его класс равен B, а разрешенный метод - метод из invokevirtual (show(A) из A)


tl: dr - время компиляции определяет, какой метод вызыватьвремя выполнения определяет, откуда его вызывать.

0 голосов
/ 12 октября 2018

В вашем примере A a = new B(), a - это полиморфная ссылка - ссылка, которая может указывать на другой объект из иерархии классов (в данном случае это ссылка на объект типа Bно может также использоваться как ссылка на объект класса A, который является самым верхним в иерархии объектов).

Что касается конкретного поведения, о котором вы спрашиваете:

Почему B напечатано в выводе?

Какой конкретный метод show(B obj) будет вызываться через переменную-ссылку, зависит от ссылки на объект, который он содержит в определенный момент времени.То есть: если он содержит ссылку на объект класса B, будет вызван метод из этого класса (это ваш случай), но если он будет указывать на объект класса A, ссылка на этот объект будетназывается.Это объясняет, почему B печатается в выходных данных.

иерархия).

Почему and A печатается в выходных данных?

Метод в подклассе с тем жеимя, но другая подпись называется перегрузка метода .Он использует статическое связывание, что означает, что соответствующий метод будет привязан во время компиляции .Компилятор не имеет ни малейшего представления о типе ваших объектов runtime .

Так что show(A obj) класса A будет привязано в этом случае.Однако когда метод будет фактически вызван в runtime , будет вызвана его реализация из класса B (show(A obj) из класса B), и поэтому вы видите B and A, а не A and A в выводе.


Ссылка для invokevirutal (инструкция JVM, вызываемая при выполнении виртуальных методов) :

Если разрешеноМетод не является сигнатурным полиморфным (§2.9), тогда инструкция invokevirtual выполняется следующим образом.

Пусть C - класс objectref.Фактический метод, который должен быть вызван, выбирается следующей процедурой поиска:

Если C содержит объявление для метода экземпляра m, который переопределяет (§5.4.5) разрешенный метод, то m - это метод, который должен быть вызвани процедура поиска завершается.

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

В противном случае вызывается AbstractMethodError.


Для a.show(c) то же самоеправила применяются так же, как и для B, поскольку C не имеет перегруженных методов и напрямую наследуется от B.

РЕДАКТИРОВАТЬ:

Шаг за шагомпошаговое объяснение того, почему a.show(c) печатает B and A:

  1. Компилятор распознает объект a как objectref для объекта класса A (время компиляции)
  2. Поскольку a имеет тип A, метод A::show(A obj) связан.
  3. Когда код фактически выполняется (т. Е. Метод show() вызывается для объекта a), среда выполнения распознает эту ссылку a полиморфно указывает на объект типа B (это из-за A a = new B()) (время выполнения)
  4. Поскольку C extends B, среда выполнения обрабатывает a.show(c) так же, как и b.show(c) (или b.show(b))), поэтому B::show(A obj) используется в этом случае, но вместо obj используется объект типа B.Вот почему печатается буква «B и A».
0 голосов
/ 12 октября 2018

Я думаю, что ваш вопрос связан с другой темой - Различение между объектом и ссылкой.От Certified Professional SE 8 Programmer II: В Java все объекты доступны по ссылке, поэтому, как разработчик, вы никогда не имеете прямого доступа к памяти самого объекта.Концептуально, однако, вы должны рассматривать объект как сущность, которая существует в памяти, выделенной средой выполнения Java.Независимо от типа ссылки на объект в памяти, сам объект не изменяется.Например, поскольку все объекты наследуют java.lang.Object, все они могут быть переназначены на java.lang.Object, как показано в следующем примере:

Lemur lemur = new Lemur();
Object lemurAsObject = lemur;

Даже если объекту Lemur был присвоенссылка с другим типом, сам объект не изменился и все еще существует в памяти как объект Lemur.Что изменилось, так это наша способность обращаться к методам в классе Lemur с помощью ссылки lemurAsObject.Без явного приведения к Лемуру, как вы увидите в следующем разделе, у нас больше нет доступа к свойствам объекта Лемура.

Мы можем обобщить этот принцип с помощью следующих двух правил:

  1. Тип объекта определяет, какие свойства существуют внутри объекта в памяти.
  2. Тип ссылки на объект определяет, какие методы и переменные доступны для программы Java.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...