Подтвердить что ты не робот

Что действительно происходит при наследовании на Java?

Предположим, что у меня есть два класса Parent и Child, а Child - от Parent. У меня есть три метода в Parent, из которых два являются общедоступными, а один - частными.

Обычно мы говорим, что все не частные методы наследуются в класс Child, но я смущен точно о том, что происходит. Является ли Java копией методов в классе Child или использует какую-то ссылку для поддержания отношений?

class Parent{
    // Private method
    private void method1(){
        System.out.println("In private method of Parent class");
    }
    void method2(){
    // calling private method
        method1();
    }
    void method3(){
    // calling private method
        method1();
    }
}

class Child extends Parent{

}

class MainClass{
   public static void main(String[] args){
       Child child = new Child();
       // calling non-private method which internally calls the private method
       child.method2();
   }
}
4b9b3361

Ответ 1

Является ли Java копией методов в классе Child или использует некоторую ссылку для поддержания отношения?

последний. Посмотрите invokespecial. Java рекурсивно ищет методы по одному классу за раз. Вызывается первый метод сопоставления. Вот почему вызов виртуального (по умолчанию) метода медленнее, чем вызов метода final.

В общем, фактический код внутри метода не копируется, когда вы наследуете класс, содержащий этот метод.

Ответ 2

Ни.

Наследование - это концепция языка программирования, а не реальное действие. Когда вы исследуете скомпилированный класс Child, например. с javap, вы не найдете артефактов, связанных с тремя методами Parent. Будет информация, что Child имеет суперкласс Parent, но не упоминает унаследованные методы, ни как ссылки, ни как копии.

Тот факт, что Child концептуально наследует методы из Parent, вступает в игру, когда вы на самом деле пытаетесь вызвать одну из них через переменную, тип которой во время компиляции Child, как в вашем классе Test. Тогда компилятор должен найти унаследованные методы, чтобы правильно скомпилировать код, содержащий вызов. Это также зависит от того, выполняет ли он иерархию классов каждый раз, когда разрешает целевой метод вызова или собирает ли существующие методы в определенных структурах данных, чтобы ускорить последующие проверки и какие структуры данных он использует.

Этот процесс более сложный, чем вы думаете. Может быть несколько кандидатов, среди которых компилятор должен выбрать один, и он может даже потерпеть неудачу из-за двусмысленности. Результатом процесса будет решение о том, является ли вызов действительным и, в случае его действительности, единственным целевым методом и его фактической сигнатурой.

Скомпилированный вызов будет содержать имя и подпись целевого метода и ссылку на тип времени компиляции ссылки, на которую он вызывается, т.е. Child. Он не будет содержать информацию о том, что фактический метод был унаследован от Parent. Это намеренно. Child объявляет сам метод, наследует его от Parent или переопределяет метод Parent, не должен влиять на код invoker Test и совместимость между скомпилированными файлами классов.


Это означает, что во время выполнения класс типа Test может содержать ссылку, заданную именем и сигнатурой, методу в Child, который не хранится в файле класса Child, другими словами, JVM также отвечает за то, чтобы сделать концепцию наследования работать. В то же время он должен гарантировать, что концепция переопределения работает, когда выполняется вызов метода.

Способ, которым он реализует это, также неуточнен, оставляя место для разных стратегий. Поиск иерархии классов по каждому вызову будет легальным, но приведет к низкой производительности.

Общей методикой является vtable. Связанным с каждым классом является таблица (массив), содержащая ссылки на доступные методы, будь она объявлена ​​или унаследована. При инициализации подкласса его таблица будет начинаться с копии таблицы суперкласса, имея записи для новых методов, добавленных в конце, и изменения переопределенных методов. В некоторый момент вызов метода будет связан путем нахождения индекса записи vtable, соответствующего указанному имени и сигнатуре. Типичная стратегия - это поиск первого вызова. Затем инструкция модифицируется, чтобы ссылаться на индекс, чтобы избежать последующих поисков. Затем последующие исполнения состоят в получении vtable для класса среды выполнения объектов и получении ссылки на метод из записи таблиц.

Учитывая это, вы можете сказать, что во время выполнения наследование обычно реализуется как некоторая ссылка, но ждать.

Реализации, такие как JVM Oracles, способны выполнять оптимизацию точек доступа, в которых рассматривается контекст часто исполняемого кода. Может быть, например, унаследованный метод, который сам вызывает методы, которые были переопределены в некоторых подклассах. Когда JVM выясняет, что этот метод вызывается очень часто на одном конкретном подклассе, он может создать оптимизированную версию для этого конкретного случая. Затем копия кода методов будет отправной точкой для последующих преобразований кода, встраивания кода конкретного подкласса и т.д.

Поскольку такие сложные реализации будут использовать ссылки для неоптимизированного кода при использовании оптимизированных копий в других случаях, альтернатива первоначальному ответу ответа может быть:

И.

Ответ 3

Тело метода не копируется в тело метода undefined подкласса. Вместо этого, когда вы вызываете

Child child1 = new Child();
child1.method1();

Он будет просматривать свою иерархию, поднимаясь на уровень каждый раз, когда не может найти реализацию. Сначала он проверит Child, у которого нет реализации. На одном уровне выше, чем там, где есть родитель, поэтому он будет использовать этот. Как правильно упомянуто @Dante выше, это достигается с помощью вызова super() в конструкторе дочернего класса. Это изображение может помочь вам получить лучшую картину: введите описание изображения здесь

Я думаю, что ваше замешательство каким-то образом связано с точкой не наследования частных методов. Итак, я хотел бы обратиться к этому также

Почему частные члены не наследуются?

Можно сказать, что частные члены не наследуются, потому что нигде не может Child явно ссылаться на значение. То есть любой код типа this.value не может использоваться в Child, и не может obj.value использоваться из некоторого кода вызова (очевидно). Однако в другом смысле вы можете сказать, что значение наследуется. Если вы считаете, что каждый экземпляр Child также является экземпляром Parent, то этот объект должен содержать 'value', как определено в Parent. Даже если класс Child ничего не знает об этом, значение имени частного члена все еще существует в каждом экземпляре Child. Итак, в этом смысле вы можете сказать, что значение "унаследовано" в Child. Так что без использования слова "наследование" просто помните, что дочерние классы не знают о частном члене, определенном в родительских классах. Но также помните, что те частные члены все еще существуют в пределах экземпляров дочернего класса.

Примечание. Состояние JLS (http://docs.oracle.com/javase/specs/jls/se5.0/html/classes.html#8.2):

Члены класса, объявленные частным, не наследуются подклассы этого класса. Только участники класса, объявленные protected или public наследуются подклассами, объявленными в пакете кроме того, в котором объявлен класс.

Ответ 4

Java не создает копию методов. Наследуемые методы остаются только с родительским классом.

Теперь вы должны меня задаться вопросом: как класс доступа к ним получает доступ?

Ответ заключается в понимании ключевого слова super.

Ключевое слово super используется для ссылки на объект ближайшего родительского класса. Самое первое, что делает super, это инициализировать объект родительского класса. Это означает, что он отвечает за создание объекта родительского класса и используется для ссылки на этот объект.

super(); неявно является первым выражением внутри конструктора дочернего класса, однако вы также можете сделать это явно.

Теперь важная часть:

Если у вас есть следующий:

super();

не как ваш первый оператор внутри конструктора дочерних классов, тогда генерируется ошибка компиляции:

call to super must be first statement in constructor.

Причина такова:

Поскольку предполагается, что дочерний класс имеет унаследованные методы, но не имеет их копии.

Унаследованные методы в основном состоят из родительского класса, поэтому важно сначала создать объект родительского класса, чтобы всякий раз, когда используется следующий

instance_of_child.method_of_parent();

Метод будет фактически вызываться из объекта родительского класса, который уже был создан супер (явно или неявно).

Вот почему у вас может быть конструктор дочернего класса:

Child()
{
 super();
 parent_method();
}

но не нравится:

Child()
{
 parent_method();
 super();
}

потому что parent_method(), то есть унаследованный метод требует ссылки на объект родительского класса, который предоставляется super().