Почему приведение типов к базовому не заставляет выполнять метод базового типа?

95
03 марта 2021, 23:00

Учусь программировать. У меня есть классы (Базовый и производный). Когда в тестере вызываю метод draw(), то почему то отрабатывает как "PensilWithPen DRAW". Хотя я же привел к типу "Pen", и метод должен выполнятся из его тела?

Как мне сделать так, чтобы у объекта value вызвать метод draw() и чтобы он выполнил "PEN DRAW"

public class FirstClassTester {
public static void main(String[] args) {
    Box<Pen> box = new Box();
    box.set(new PensilWithPen());
    Pen value = box.get();
    value.draw();
}
}
class Pen{
      public void draw(){
           System.out.println("PEN DRAW");
      }
}
class PensilWithPen extends Pen{
      public void draw(){
           System.out.println("PensilWithPen DRAW");
      } 
}
Answer 1

Это называется полиморфизм.

В Java если метод переопределен, то при вызове выполняется реализация метода, соответствующая реальному (PensilWithPen) типу объекта, а не объявленному (Pen) типу переменной. Методы с такой механикой вызова называются виртуальными. В Java все методы виртуальны.

Виртуальные методы позволяют обращаться к разным реализациям используя метод, определенный в родителе. Например:

//получаем pen, сгенерированный в другом классе
Pen pen = (new PenGenerator()).createPen();
//ручка рисует
pen.draw();

Так мы можем в любой момент заменить ручку в генераторе ручек (PenGenerator). При этом вызывающий код не нужно будет переписывать.

Еще пример:

Pen[] pens = new Pen[]{pen, penWithPencil};
for(Pen pen : pens) {
    //каждая ручка рисует по-своему, но метод всегда вызывается одинаково
    pen.draw();
}

Та же логика применяется к методам интерфейсов. Поэтому к ним можно обратиться через интерфейс:

interface Pen {
    void draw();
}
class PensilWithPen implements Pen{
      public void draw(){
           System.out.println("PensilWithPen DRAW");
      } 
}
...
Pen pen = new PensilWithPen();
//ручка рисует, хотя в самом Pen нет реализации
pen.draw();

Почитайте: Как на практике применяется полиморфизм?

Как вызвать метод родителя?

В том виде, в котором Вы реализовали два класса — никак.

Если нужно вызывать от объекта класса-наследника обе реализации, то потребуется определить два метода.

Либо не переопределять метод вообще, а добавить новый:

class PensilWithPen extends Pen{
      public void drawWithPencil(){
           System.out.println("PensilWithPen DRAW");
      } 
}
...
PensilWithPen pensilWithPen = new PensilWithPen();
Pen pen = pensilWithPen;
//рисуем
pen.draw();
//рисуем по-новому
pensilWithPen.drawWithPencil();

Либо переопределить метод, но оставить вариант для вызова старого метода:

class PensilWithPen extends Pen{
      public void draw(){
           System.out.println("PensilWithPen DRAW");
      } 
      public void drawPen(){
           super.draw();
      } 
}
...
PensilWithPen pensilWithPen = new PensilWithPen();
Pen pen = pensilWithPen;
//рисуем по-новому
pen.draw();
//рисуем по-старому
pensilWithPen.drawPen();

Смотрите еще: Можно ли вызвать метод класса-родителя после его переопределения (Java)?

Answer 2

Потому, что так и задумано.

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

READ ALSO
видимость значения при синхронизированном изменении

видимость значения при синхронизированном изменении

Насколько известно, изменив volatile переменную, мы остаёмся уверенны, что остальные потоки, которые будут её читать, получат новое значениеПричина...

101
Почему при записи файла файл пустой?

Почему при записи файла файл пустой?

файл сохраняется но он пустойпри дебаге выяснилось что output пустой

119
Почему не работает click()?

Почему не работает click()?

Всё хорошо работает до выполнения click()Выдаёт исключение: org

94
React и серверный рендеринг

React и серверный рендеринг

Пишу проекты на ReactПериодически требуется рендерить для ботов SPA на сервере

88