#java #polymorphism #abstract
#java #полиморфизм #аннотация #динамическое связывание
Вопрос:
Я просматривал официальный учебник Oracle, в котором он представляет идею полиморфизма на примере иерархии классов из 3 классов; Велосипед является суперклассом, а горный велосипед и дорожный велосипед — 2 подкласса.
Это показывает, как 2 подкласса переопределяют метод «printDescription», объявленный в Bicycle, объявляя разные его версии.
И, наконец, ближе к концу в руководстве упоминается, что виртуальная машина Java (JVM) вызывает соответствующий метод для объекта, на который ссылается каждая переменная.
Но нигде в руководстве по полиморфизму не упоминается концепция «абстрактных» классов и методов. Как достигается полиморфизм во время выполнения, если printDescription() в Bicycle не объявлен «абстрактным»? Я имею в виду, учитывая этот пример, на основании каких подсказок компилятор решает не привязывать вызов метода к ссылочному типу во время компиляции и считает, что он должен оставить его для JVM для обработки во время выполнения?
Ниже приведен используемый пример:
public class Bicycle {
public int cadence;
public int gear;
public int speed;
public Bicycle(int startCadence, int startSpeed, int startGear) {
gear = startGear;
cadence = startCadence;
speed = startSpeed;
}
public void setCadence(int newValue) {
cadence = newValue;
}
public void setGear(int newValue) {
gear = newValue;
}
public void applyBrake(int decrement) {
speed -= decrement;
}
public void speedUp(int increment) {
speed = increment;
}
public void printDescription(){
System.out.println("nBike is " "in gear " this.gear
" with a cadence of " this.cadence
" and travelling at a speed of " this.speed ". ");
}
}
public class MountainBike extends Bicycle {
private String suspension;
public MountainBike(
int startCadence,
int startSpeed,
int startGear,
String suspensionType){
super(startCadence,
startSpeed,
startGear);
this.setSuspension(suspensionType);
}
public String getSuspension(){
return this.suspension;
}
public void setSuspension(String suspensionType) {
this.suspension = suspensionType;
}
public void printDescription() {
super.printDescription();
System.out.println("The " "MountainBike has a"
getSuspension() " suspension.");
}
}
public class RoadBike extends Bicycle{
private int tireWidth;
public RoadBike(int startCadence,
int startSpeed,
int startGear,
int newTireWidth){
super(startCadence,
startSpeed,
startGear);
this.setTireWidth(newTireWidth);
}
public int getTireWidth(){
return this.tireWidth;
}
public void setTireWidth(int newTireWidth){
this.tireWidth = newTireWidth;
}
public void printDescription(){
super.printDescription();
System.out.println("The RoadBike"
" has " getTireWidth()
" MM tires.");
}
}
public class TestBikes {
public static void main(String[] args){
Bicycle bike01, bike02, bike03;
bike01 = new Bicycle(20, 10, 1);
bike02 = new MountainBike(20, 10, 5, "Dual");
bike03 = new RoadBike(40, 20, 8, 23);
bike01.printDescription();
bike02.printDescription();
bike03.printDescription();
}
}
Комментарии:
1. Вы разработчик C ? Ваш опыт может быть важен для объяснения, которое вы получите.
2. Ссылка на упомянутый учебник: docs.oracle.com/javase/tutorial/java/IandI/polymorphism.html
3. Да, я разработчик C . И в глубине души я думал о ключевом слове «virtual», используемом для полиморфизма во время выполнения в C .
4. @softwarelover: задавая вопрос о принципе языка, постарайтесь кратко рассказать о своем прошлом, вы можете обнаружить, что ответы больше ориентированы на ваше замешательство и, следовательно, более полезны.
Ответ №1:
Как достигается полиморфизм во время выполнения, если printDescription() в Bicycle не объявлен «абстрактным»?
Почему вы думаете, что абстрактные классы что-то изменят? Абстрактные классы выполняют 2 основных действия
- Разрешить программисту объявлять класс, который сам по себе не может быть создан, вызывая создание подклассов и
- Позволяет программисту заставлять подклассы предоставлять реализации методов, объявляя метод абстрактным.
Обратите внимание, что пункт 2 не подразумевает, что полиморфизм не будет работать, если метод не объявлен абстрактным в базовом классе; скорее, он предоставляет разработчику возможность заставить подкласс предоставить реализацию, которая не требуется в сценариях создания подклассов, которые не предполагают какого-либо абстрактного использования.
Вот и все. Другими словами, понятие абстрактного дополняет полиморфизм Java — это языковая особенность, но не имеет ничего общего с динамической диспетчеризацией, которую Java использует во время выполнения для вызова методов. Каждый раз, когда метод вызывается в экземпляре, тип экземпляра во время выполнения используется для определения того, какую реализацию метода использовать.
Комментарии:
1. привет, hvgotcodes, под «в любое время» вы имеете в виду, когда есть переопределенные методы, которые существуют в разных классах? Потому что перегруженные вызовы методов разрешаются во время компиляции.
2. хорошо, теперь я понимаю, как указал гейзенбаг ниже об объектах Java, не существующих в стеке…
Ответ №2:
virtual
это ключевое слово во многих языках, которое означает «этот метод может быть переопределен подклассом». В Java нет этого ключевого слова, но вместо этого все нестатические методы-члены являются виртуальными и их можно переопределить.
abstract
то же самое, что и virtual, за исключением того, что он сообщает компилятору, что базовый класс не имеет определения для метода. Иногда это полезно, если для базового класса нет полезной функции для выполнения, но это ни в коем случае не требуется для переопределения метода базового класса.
В вашем случае метод printDescription имеет полезное определение для базового класса, поэтому нет необходимости объявлять его абстрактным. По умолчанию он виртуальный и поэтому переопределяется подклассом, поэтому ключевое слово для указания этого не требуется.
Ответ №3:
В Java все методы привязываются во время выполнения (это то, чего вы можете достичь в C , объявляя метод виртуальным).
Таким образом, JVM всегда может правильно отправить метод.
На самом деле привязка метода в Java никогда не может быть статической, потому что вы всегда имеете дело со ссылками на объекты (не можете выделить объект в стеке, например, C ). Это фактически заставляет JVM всегда проверять тип ссылки на объект во время выполнения.
Комментарии:
1. Это утверждение неверно. Метод по умолчанию является абстрактным только в интерфейсе. Вы имели в виду виртуальный ?
2. Мне все еще не нравится этот ответ (хотя он больше не является неправильным), поскольку виртуальная концепция не определена в java. Лучшим утверждением будет: «В java вызов методов по умолчанию всегда является динамическим (отправка / привязка)»
3. Если я могу присоединиться к amit и heisenbug, не могли бы вы прокомментировать ситуацию, когда метод не переопределяется, а, скорее, перегружается? Разве вызов метода не разрешается во время компиляции?
4. нет. перегруженные методы — это 2 разных метода, несмотря на то, что они имеют одно и то же имя. Вы все еще не можете добиться статического связывания в Java, потому что ничто не является статичным.
5. На самом деле это не имеет никакого отношения к stack-vs-heap. На самом деле речь идет о том, нужна ли вам виртуальная таблица — в Java вы всегда делаете это на концептуальном уровне. Обратите внимание, что JIT часто замечает, что заданный путь кода всегда получает одну и ту же реализацию, и в этом случае он заменит vtable быстрой проверкой типа, а затем неотправленным вызовом.
Ответ №4:
Нет необходимости объявлять этот метод абстрактным.. При полиморфизме во время выполнения соответствующий метод производного класса вызывается на основе того, на какой экземпляр класса указывает ссылка на базовый класс..
Рассмотрим следующий пример: —
class A {
public void doSomething() {
}
}
class B extends A {
public void doSomething() {
System.out.println("In B")
}
}
public class Test {
public static void main(String args[]) {
A obj = new B(); // Base class reference and derived class object.
obj.doSomething(); // Calls derived class B's method and prints `In B`
}
}
Процитировать утверждение, которое вы прочитали: —
в руководстве упоминается, что виртуальная машина Java (JVM) вызывает соответствующий метод для объекта, на который ссылается каждая переменная.
Чтобы оправдать приведенное выше утверждение, см. Приведенный Выше пример. Там вызывается ваш метод класса B, потому что ваша ссылка на базовый класс obj
указывает B's
на экземпляр производного класса..
Тип ссылки, указывающей на объект, всегда проверяется во время компиляции, тогда как тип объекта, на который указывает эта ссылка, проверяется во время выполнения..
Итак, решение о том, какой метод будет вызван, принимается во время выполнения. Независимо от того, является ли ваш метод базового класса abstract
или нет, вызывается соответствующий метод производного класса..
Ответ №5:
Это не C . В Java вы всегда знаете реальный класс каждого экземпляра, поэтому при printDescription()
вызове используется определение этого класса. Тем не менее, вы можете использовать только методы, доступные в ссылках на экземпляр (поэтому, если вы определяете метод getMPH()
в классе RoadBike
и обрабатываете экземпляр этого класса с Bike
помощью переменной, компилятор выдаст ошибку, если вы собираетесь его использовать).
Ответ №6:
Я думаю, что этот код:
bike01 = new Bicycle(20, 10, 1);
bike02 = new MountainBike(20, 10, 5, "Dual");
bike03 = new RoadBike(40, 20, 8, 23);
bike01.printDescription();
bike02.printDescription();
bike03.printDescription();
не лучший пример полиморфизма во время выполнения, потому что все факты (методы для вызова) известны даже во время компиляции.
Но если бы вы изменили его на:
Random r = new Random();
if(r.nextInt(2)%2==0)
{
bike01 = new Bicycle(20, 10, 1)
}
else
{
bike01 = new MountainBike(20, 10, 5, "Dual");
}
// only at run-time the right function to call is known
bike01.printDescription();
…