Наследование: действительно ли код из суперкласса * скопирован * в подкласс, или на него ссылается подкласс *?

10

Класс Subявляется подклассом класса Sup. Что это значит практически? Или, другими словами, каково практическое значение слова «наследование»?

Вариант 1: Код из Sup фактически скопирован в Sub. (как в 'copy-paste', но без скопированного кода, визуально видимого в подклассе).

Пример: methodA()метод изначально в Sup. Sub расширяет Sup, поэтому methodA()(виртуально) копируется в Sub. Теперь у Sub есть метод с именем methodA(). Он идентичен Sup methodA()в каждой строке кода, но полностью принадлежит Sub - и не зависит от Sup или связан с Sup каким-либо образом.

Вариант 2: Код из Sup на самом деле не копируется в Sub. Это все еще только в суперклассе. Но этот код может быть доступен через подкласс и может использоваться подклассом.

Пример: methodA()это метод в Sup. Sub расширяет Sup, так что теперь methodA()можно получить через Sub следующим образом: subInstance.methodA(). Но это на самом деле вызовет methodA()в суперклассе. Это означает, что methodA () будет работать в контексте суперкласса, даже если он был вызван подклассом.

Вопрос: Какой из двух вариантов действительно работает? Если ни один из них не подходит, то, пожалуйста, опишите, как эти вещи на самом деле работают.

Авив Кон
источник
Это легко проверить самостоятельно - напишите код, изучите файлы классов (даже контрольная сумма сделает это), измените суперкласс, снова скомпилируйте, снова посмотрите на файлы классов. Вы также можете прочитать главу 3. Компиляция для виртуальной машины Java спецификации JVM, которая поможет вам разобраться (особенно в разделе 3.7).
@MichaelT " виртуально скопировано" - это ключевое слово. Кроме того, даже если код буквально копируется, это может произойти только после загрузки класса.
@delnan было бы любопытно, если бы Hotspot (или другие оптимизаторы времени выполнения) в какой-то момент встроили код, но это становится деталью реализации JVM, которая может отличаться от одной JVM к другой и поэтому не может дать правильный ответ. Лучшее, что можно сделать, это посмотреть на скомпилированный байт-код (и использовать специальный код операции invokespecial, который описывает, что на самом деле происходит)

Ответы:

13

Вариант 2

На байт-код ссылаются динамически во время выполнения: именно поэтому, например, происходят ошибки LinkageErrors .

Например, предположим, что вы скомпилировали два класса:

public class Parent {
  public void doSomething(String x) { ... }
}

public class Child extends Parent {
  @Override
  public void doSomething(String x) {
    super.doSomething(x);
    ...
  }
}

Теперь измените и перекомпилируйте родительский класс без изменения или перекомпиляции дочернего класса :

public class Parent {
  public void doSomething(Collection<?> x) { ... }
}

Наконец, запустите программу, которая использует дочерний класс. Вы получите NoSuchMethodError :

Брошенный, если приложение пытается вызвать указанный метод класса (статический или экземпляр), и у этого класса больше нет определения этого метода.

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


источник
7

Давайте начнем с двух простых классов:

package com.michaelt.so.supers;

public class Sup {
    int methodA(int a, int b) {
        return a + b;
    }
}

а потом

package com.michaelt.so.supers;

public class Sub extends Sup {
    @Override
    int methodA(int a, int b) {
        return super.methodA(a, b);
    }
}

Компилируя methodA и глядя на байт-код, мы получаем:

  methodA(II)I
   L0
    LINENUMBER 6 L0
    ALOAD 0
    ILOAD 1
    ILOAD 2
    INVOKESPECIAL com/michaelt/so/supers/Sup.methodA (II)I
    IRETURN
   L1
    LOCALVARIABLE this Lcom/michaelt/so/supers/Sub; L0 L1 0
    LOCALVARIABLE a I L0 L1 1
    LOCALVARIABLE b I L0 L1 2
    MAXSTACK = 3
    MAXLOCALS = 3

И вы можете сразу увидеть с помощью метода invokespecial, который выполняет поиск в методе класса SupA ().

Код операции invokespecial имеет следующую логику:

  • Если C содержит объявление для метода экземпляра с тем же именем и дескриптором, что и у разрешенного метода, то этот метод будет вызван. Процедура поиска завершается.
  • В противном случае, если C имеет суперкласс, эта же процедура поиска выполняется рекурсивно с использованием прямого суперкласса C. Метод, который должен быть вызван, является результатом рекурсивного вызова этой процедуры поиска.
  • В противном случае выдается AbstractMethodError.

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

Компилятор этого не делает, и в классе нет копии источника Sup.

Однако история еще не закончена. Это просто скомпилированный код. Как только код попадет в JVM, HotSpot может подключиться.

К сожалению, я не очень много знаю об этом, поэтому я обращусь к авторитету по этому вопросу и перейду к Inlining in Java, где говорится, что HotSpot может использовать встроенные методы (даже не финальные).

Переходя к документам, отмечается, что если конкретный вызов метода становится горячей точкой вместо того, чтобы выполнять этот поиск каждый раз, эта информация может быть встроена - эффективно копируя код из Sup methodA () в Sub methodA ().

Это делается во время выполнения, в памяти, в зависимости от поведения приложения и оптимизации, необходимой для повышения производительности.

Как указано в HotSpot Internals for OpenJDK, «методы часто являются встроенными. Статические, частные, окончательные и / или« специальные »вызовы легко встроить».

Если вы покопаетесь в опциях для JVM, вы найдете опцию -XX:MaxInlineSize=35(35 по умолчанию), которая является максимальным числом байтов, которое может быть встроено. Я укажу, что именно поэтому Java любит иметь много маленьких методов - потому что они могут быть легко встроены. Эти маленькие методы становятся быстрее, когда их вызывают больше, потому что они могут быть встроены. И хотя с этим числом можно играть и увеличивать его, другие оптимизации могут быть менее эффективными. (связанный вопрос SO: стратегия встраивания HotSpot JIT, которая указывает на ряд других вариантов, чтобы взглянуть на внутреннюю часть встраивания, которую делает HotSpot).

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

И все, что я пишу о встраивании HotSpot, относится только к HotSpot JVM, распространяемой Oracle. Если вы посмотрите на список виртуальных машин Java в Википедии, существует гораздо больше, чем просто HotSpot, и способ, которым эти JVM обрабатывают встраивание, может полностью отличаться от того, что я описал выше. Apache Harmony, Dalvik, ART - там все может работать по-другому.

Сообщество
источник
0

код не копируется, доступ к нему осуществляется по ссылке:

  • подкласс ссылается на свои методы и суперкласс
  • суперкласс ссылается на свои методы

компиляторы могут оптимизировать то, как это представляется / выполняется в памяти, но это в основном структура

Стивен А. Лоу
источник