@popov654
Специалист в области веб-технологий

Почему в Java наследование реализовано именно так?

Всем доброго времени суток,

Недавно писал проект на Java. При этом несколько удивило поведение методов родительских классов.

Цитата из Вики:

Виртуальный метод (виртуальная функция) — в объектно-ориентированном программировании метод (функция) класса, который может быть переопределён в классах-наследниках так, что конкретная реализация метода для вызова будет определяться во время исполнения. Таким образом, программисту необязательно знать точный тип объекта для работы с ним через виртуальные методы: достаточно лишь знать, что объект принадлежит классу или наследнику класса, в котором метод объявлен. Одним из переводов слова virtual с английского языка может быть «фактический», что больше подходит по смыслу.

Виртуальные методы — один из важнейших приёмов реализации полиморфизма. Они позволяют создавать общий код, который может работать как с объектами базового класса, так и с объектами любого его класса-наследника. При этом базовый класс определяет способ работы с объектами и любые его наследники могут предоставлять конкретную реализацию этого способа.


Теперь конкретный случай.

Есть класс A с полем type типа String, есть класс B, наследующий A.
Предполагается, что A и все его классы наследники должны иметь метод getType(), выводящий строку типа (в общем-то, функционал одинаковый, и даже переопределять его не требуется).

Что имеем в итоге:

1. Попытка сделать так
public class A {
    ...

    public String getType() {
        return this.type;
    }
    private String type = "null";
}

public class B {
    ...
    private String type = "type_B";
}

приводит к тому, что всегда будет выводиться null. В общем-то, можно было предположить такое развитие событий, поскольку поле - private, и такое поле недоступно для объектов классов-потомков. Хотя формально такие поля, вроде бы, наследуются (?). IDE во всяком случае пишет, что "field has private access in class A", а не "field does not exist".

2. Попытка сделать поле protected в A и в B, оставив всё остальное как есть - приводит к предупреждению в IDE о том, что поле скрывает поле родительского класса, но результат исполнения совершенно не меняется.

3. Рабочие способы (две штуки):
- Полностью скопировать реализацию метода getType() в B, добавив Override аннотацию. Неплохо, позволяет даже оставить оба поля private. Но это дублирование совершенно одинакового кода, к тому же, это просто элементарно можно забыть сделать по невнимательности.
- Создать в B конструктор, назначающий значение полю type, убрать объявление поля type из B, в A сделать его protected. Работает, неплохо выглядит, разве что - в моём случае пришлось ради этого делать конструктор там, где он был не нужен, так как классы очень простые.

Вопрос:
Почему родительская реализация работает с полями класса-предка? В случае private поля это ещё относительно понятно, но в случае protected поля - нет. Ведь даже в момент вызова объект лежал в переменной, имеющей тип B. При этом вызывается реализация из A, это нормально, если в B я не переопределил её другой, но проблема в том, что и значения полей берутся из A! Почему так реализовано? В JS, например, с его цепочкой прототипов, результат был бы совсем другим.

P.S. Можно в коде реализации метода из A использовать Reflections, в принципе, чтобы получить фактическое значение поля объекта, но это уже совсем изврат.
P.P.S. Вариант с конструктором плох ещё и тем, что даже если в B предполагается свой конструктор - можно забыть там назначить значения нужным полям, опять же в силу невнимательности. Программисту интуитивно логичнее объявлять поля класса вместе с их дефолтными значениями внизу, единым блоком. Иногда это даже первое, что делается, когда создаётся класс-наследник. До момента написания конструктора про это банально можно забыть, и никто нам не напомнит, что у нас, оказывается, критичные protected поля не переназначены :)
  • Вопрос задан
  • 258 просмотров
Решения вопроса 1
Нельзя переопределить значения поля, как это работает с методами. В вашем примере вы просто определили новое поле с таким же именем, что привело к так называемому name shadowing.

Если вы пытаетесь реализовать рефлексию, лучше воспользуйтесь стандартными средствами.

Если хотите задавать в наследнике значение родительского поля, сделайте в родителе конструктор с соответствующим параметром.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через TM ID
Похожие вопросы
21 июля 2018, в 01:45
800 руб./в час
20 июля 2018, в 23:46
800 руб./за проект