#java #наследование
Пытаюсь разбираться с наследованием в Java. Есть некоторый код: class A { A getThis() { System.out.println("call getThis() from A"); return this; //(3) } //(3) Object getSuper() { System.out.println("call getSuper() from A"); return null;} } class B extends A { B getThis() { System.out.println("call getThis() from B"); return this; } A getSuper() { System.out.println("call getSuper() from B"); return super.getThis(); } } class Tester { public static void main (String[] args) { Object a = new B().getSuper(); //(1) System.out.println(a); a = new B().getSuper().getSuper(); //(2) System.out.println(a); } } В результате на консоль выводится следующий текст: call getSuper() from B call getThis() from A B@25154f call getSuper() from B call getThis() from A call getSuper() from B call getThis() from A B@10dea4e Я ожидал, что при отработке строки (1) в a будет лежать экземпляр класса A, а после отработки строки (2) в a будет лежать null. Почему в результате возврата this из строки (3) возвращается ссылка не на родительский класс, а на исходный? Как я понимаю, при создании экземпляра класса B, в нём хранится ссылка на экземпляр родительского класса A. Я вижу косвенное подтверждение своих слов: При вызове конструктора класса B происходит вызов конструктора класса A. Даже при переопределении метода f в классе B можно добраться до метода f класса A через конструкцию super.f(); Несмотря на это, при работе с экземпляром класса B, я не вижу способа вернуть из него ссылку на экземпляр класса A, который используется в нём. Есть ли какой-то способ всё же реализовать такую возможность?
Ответы
Ответ 1
Мне кажется вы путаете понятия Класс и объект. Класс – это способ описания сущности, определяющий состояние и поведение, зависящее от этого состояния, а также правила для взаимодействия с данной сущностью (контракт). Объект (экземпляр) – это отдельный представитель класса, имеющий конкретное состояние и поведение, полностью определяемое классом. Ключевое слово this указывает не на класс, а на экземпляр класса (объект). Ключевое слово super обеспечивает доступ к свойствам и методам этого же объекта но описанным в суперклассе. Например: Вы создали объект бирюса класса Холодильник, а класс Холодильник наследуется от класса БытоваяТехника. Объект бирюса является и холодильником, и бытовой техникой. this вернет ссылку на объект бирюса, не важно через какой класс получена ссылка Холодильник или БытоваяТехника, объект от этого не меняется. Так и у вас объект a является экземпляром класса B, одновременно является экземпляром класса А, так как от него наследуется. Когда Вы вызываете метод getSuper() по цепочке в итоге возвращается this, то есть ссылка на объект a Определения класса и объекта взяты отсюда.Ответ 2
Данный ответ был дан на вопрос «Ссылка на суперкласс», влитым сюда. Он имел несколько иную формулировку. Как я понимаю, при создании экземпляра класса B, в нём хранится ссылка на экземпляр родительского класса A. Нет, не хранится. Экземпляр класса B — это и есть экземпляр класса A. Чтобы стало понятнее, рассмотрим, как реализован механизм наследования в Java, C++ и ряде других языков. Пусть класс A задан следующим образом: public class A { public int Foo; private short Bar; } Тогда экземпляры данного класса будут иметь в памяти следующий вид: ref -> +-----------------------+ | Внутренние данные JVM | +=======================+ | Foo | +-----------------------+ | Bar | +-----------------------+ где ref — ссылка на область памяти с данными экземпляра класса. На вышеприведённой схеме можно заметить две особенности: Открытые и закрытые поля класса хранятся вперемешку; модификаторы доступа действуют лишь на этапе компиляции при проверке правомерности обращений к полю. Виртуальная машина Java хранит рядом с данными собственно класса ещё и свои, внутренние данные. В зависимости от конкретной виртуальной машины это могут быть: счётчик «умного» указателя, ссылка на метаинформацию или же ссылка на таблицу указателей на переопределённые методы функций. В случае наследования: public class B extends A { public AnotherClass Foo2; private int Bar2; } к данным, присущим родительскому классу, в конец приписываются данные, присущие классу дочернему: ref -> +-----------------------+ | Внутренние данные JVM | +=======================+ | Foo | +-----------------------+ | Bar | +=======================+ | Foo2 | +-----------------------+ | Bar2 | +-----------------------+ Вся эта связка и обозначает экземпляр класса B. Можно заметить, что ссылка на него в точности совпадает со ссылкой на экземпляр класса A. Как результат, приведение ссылок вверх по иерархии — это просто переобозначение уже имеющейся ссылки. А «ссылка на экземпляр родительского класса» используется в классах вложенных, которые действительно ссылаются, вот только не на родительский, а на замыкающий класс. Несмотря на это, при работе с экземпляром класса B, я не вижу способа вернуть из него ссылку на экземпляр класса A, который используется в нём. Восходящее преобразование типов всегда выполняется неявно (автоматически): B foo = getFoo(); A bar = foo; При вызове конструктора класса B происходит вызов конструктора класса A. Даже при переопределении метода f в классе B можно добраться до метода f класса A через конструкцию super.f(); Вся эта магия происходит за счёт того, что виртуальной машине известна вся иерархия всех классов, за счёт чего она может явно или неявно подставлять обращение к методам вверх по иерархии.Ответ 3
Для того, чтобы понять почему нельзя получить ссылку на экземпляр базового класса при вызове метода этого класса, в наследнике нужно понять, как работает вызов унаследованного метода в java. Первое, у нас создается один объект в памяти со всеми полями, унаследованными от родителей. И этот объект доступен по одной ссылке и никаких вложенных родителей он не содержит. Сравните ссылки this при вызове конструктора A и B. public A(){ System.out.println(this); } public B(){ System.out.println(this); } Второе, методы экземпляра хранятся отдельно от полей объекта и унаследованные методы от класса A вызываются даже для объекта B через класс А. Но при вызове унаследованных методов, в них передается ссылка на экземпляр потомка, их вызывающего. Таким образом, метод как бы вызывается на фактическом экземпляре созданного объекта. Note that methods called using the invokespecial instruction always pass this to the invoked method as its first argument.Ответ 4
Вызов конструктора класса А при создании класса-наследника В выполняется на уровне работы JVM и только для того, чтобы инициализировать поля объекта класса В, программно эту ссылку получить нельзя, т.к. объекта класса A не существует. А вот если бы класс В был inner классом класса А, то в нём бы хранилась ссылка на объект класса А.
Комментариев нет:
Отправить комментарий