Полиморфизм во время выполнения в Java без "абстрактного"?

Я просматривал официальный учебник Oracle, где он вводит идею полиморфизма на примере иерархии классов из 3 классов; Bicycle - суперкласс, а MountainBike и RoadBike - 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();
      }
}

6 ответов

Решение

Как достигается полиморфизм во время выполнения, если printDescription() в Bicycle не объявлен как "абстрактный"?

Почему вы думаете, что абстрактные классы что-то изменит? Абстрактные классы делают 2 основные вещи

  1. Позвольте программисту объявить класс, который сам не может быть создан, принудительно создавая подклассы, и
  2. Позвольте программисту заставить подклассы предоставлять реализации методов, объявив метод абстрактным.

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

Вот и все. Другими словами, понятие абстрактного дополняет полиморфизм Java - это языковая функция, но она не имеет ничего общего с динамической диспетчеризацией, которую Java использует во время выполнения для вызова методов. Каждый раз, когда метод вызывается для экземпляра, тип экземпляра во время выполнения используется для определения того, какую реализацию метода использовать.

В Java все методы связываются во время выполнения (это то, что вы можете достичь в C++, объявив метод виртуальным).

Таким образом, JVM всегда может правильно отправить метод.

На самом деле привязка метода в Java никогда не может быть статической, потому что вы всегда имеете дело со ссылками на объекты (не можете выделить объект в стеке, как C++). Это фактически заставляет JVM всегда проверять тип времени выполнения ссылки на объект.

virtual это ключевое слово во многих языках, что означает "этот метод может быть переопределен подклассом". В Java нет этого ключевого слова, но вместо этого все нестатические методы-члены являются виртуальными и могут быть переопределены.

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

В вашем случае метод printDescription имеет полезное определение для базового класса, поэтому нет необходимости объявлять его абстрактным. Он является виртуальным по умолчанию и поэтому может быть переопределен подклассом, поэтому для этого не требуется ключевое слово.

Нет необходимости объявлять этот метод абстрактным. При полиморфизме во время выполнения соответствующий метод производного класса вызывается в зависимости от того, на какой экземпляр класса ссылается базовый класс.

Рассмотрим следующий пример:

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 или нет, вызывается соответствующий метод производного класса.

Это не C++. В Java вы всегда знаете реальный класс каждого экземпляра, поэтому, когда printDescription() вызывается, определение этого класса используется. Тем не менее, вы можете использовать только методы, доступные в ссылках на экземпляр (поэтому, если вы определите метод getMPH() в классе RoadBike и вы обрабатываете экземпляр этого класса с Bike переменная, компилятор выдаст ошибку, если вы собираетесь ее использовать).

Я думаю, что этот код:

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();

...

Другие вопросы по тегам