Выполнение программы Java требует двух этапов: компиляции и выполнения (интерпретации). В то же время Java является объектно-ориентированным языком программирования. Если подкласс и родительский класс имеют один и тот же метод, и подкласс переопределяет метод родительского класса, когда программа вызывает метод во время выполнения, должна ли она вызывать метод родительского класса или переопределенный метод подкласса This? должен быть вопрос, когда мы впервые изучаем Java. Здесь сначала мы определим, какой метод вызывать или операция с переменными называется привязкой.
В Java существует два метода привязки: один — статическая привязка, также называемая ранней привязкой. Другой вариант — динамическое связывание, также известное как позднее связывание.
Сравнение различий
1. Статическая привязка происходит во время компиляции, а динамическая — во время выполнения.
2. Используйте переменные или методы, модифицированные с помощью приватных, статических или окончательных значений, и используйте статическую привязку. Виртуальные методы (методы, которые могут быть переопределены подклассами) будут динамически привязаны к объекту времени выполнения.
3. Статическая привязка выполняется с использованием информации о классе, тогда как динамическая привязка должна выполняться с использованием информации об объекте.
4. Перегруженный метод завершается с использованием статической привязки, а переопределяющий метод — с использованием динамической привязки.
Пример перегруженного метода
Вот пример перегруженных методов.
Скопируйте код кода следующим образом:
общественный класс TestMain {
public static void main(String[] args) {
Строка ул = новая строка ();
Вызывающий абонент = новый вызывающий абонент ();
вызывающий.вызов(стр);
}
статический класс Caller {
общественный недействительный вызов (Object obj) {
System.out.println("Экземпляр объекта в вызывающем объекте");
}
общественный недействительный вызов (String str) {
System.out.println("Экземпляр String в Caller");
}
}
}
Результат выполнения
Скопируйте код кода следующим образом:
22:19 $javaTestMain
экземпляр String в Caller
В приведенном выше коде есть две перегруженные реализации метода вызова: одна получает объект типа Object в качестве параметра, а другая — объект типа String. str является объектом String, и будут вызываться все методы вызова, которые получают параметры типа String. Привязка здесь является статической привязкой, основанной на типе параметра во время компиляции.
проверять
Простой взгляд на внешний вид не может доказать, что выполняется статическая привязка. Вы можете проверить это, используя javap для его компиляции.
Скопируйте код кода следующим образом:
22:19 $ javap -c TestMain
Скомпилировано из "TestMain.java"
общественный класс TestMain {
общественный TestMain();
Код:
0: aload_0
1: вызвать специальный #1 // Метод java/lang/Object."<init>":()V
4: возвращение
public static void main(java.lang.String[]);
Код:
0: новый #2 // класс java/lang/String
3: дубль
4: вызвать специальный #3 // Метод java/lang/String."<init>":()V
7: astore_1
8: новый #4 // класс TestMain$Caller
11: дуп
12: вызывать специальный #5 // Метод TestMain$Caller."<init>":()V
15: astore_2
16: загрузка_2
17: загрузка_1
18: вызвать виртуальный #6 // Метод TestMain$Caller.call:(Ljava/lang/String;)V
21: возвращение
}
Я видел эту строку 18: ignorevirtual #6 // Метод TestMain$Caller.call:(Ljava/lang/String;)V действительно статически связан, что подтверждает, что вызывается вызывающий метод, который получает объект String в качестве параметра.
Пример переопределения метода
Скопируйте код кода следующим образом:
общественный класс TestMain {
public static void main(String[] args) {
Строка ул = новая строка ();
Вызывающий абонент = новый SubCaller();
вызывающий.вызов(стр);
}
статический класс Caller {
общественный недействительный вызов (String str) {
System.out.println("Экземпляр String в Caller");
}
}
статический класс SubCaller расширяет Caller {
@Override
общественный недействительный вызов (String str) {
System.out.println("Экземпляр String в SubCaller");
}
}
}
Результат выполнения
Скопируйте код кода следующим образом:
22:27 $javaTestMain
экземпляр String в SubCaller
В приведенном выше коде есть реализация метода вызова в Caller. SubCaller наследует Caller и перезаписывает реализацию метода вызова. Мы объявили переменную callerSub типа Caller, но эта переменная указывает на объект SubCaller. По результатам видно, что он вызывает реализацию метода вызова SubCaller вместо метода вызова Caller. Причина такого результата заключается в том, что динамическая привязка происходит во время выполнения, и в процессе привязки необходимо определить, какую версию реализации метода вызова следует вызывать.
проверять
Динамическую привязку невозможно проверить напрямую с помощью javap, и если доказано, что статическая привязка не выполняется, это означает, что выполняется динамическая привязка.
Скопируйте код кода следующим образом:
22:27 $ javap -c TestMain
Скомпилировано из "TestMain.java"
общественный класс TestMain {
общественный TestMain();
Код:
0: aload_0
1: вызвать специальный #1 // Метод java/lang/Object."<init>":()V
4: возвращение
public static void main(java.lang.String[]);
Код:
0: новый #2 // класс java/lang/String
3: дубль
4: вызвать специальный #3 // Метод java/lang/String."<init>":()V
7: astore_1
8: новый #4 // класс TestMain$SubCaller
11: дуп
12: вызвать специальный #5 // Метод TestMain$SubCaller."<init>":()V
15: astore_2
16: загрузка_2
17: загрузка_1
18: вызвать виртуальный #6 // Метод TestMain$Caller.call:(Ljava/lang/String;)V
21: возвращение
}
Как указано выше, 18: вызвать виртуальный #6 // Метод TestMain$Caller.call:(Ljava/lang/String;)V Это TestMain$Caller.call вместо TestMain$SubCaller.call, поскольку не удается определить вызывающую подпрограмму. во время компиляции Класс по-прежнему является реализацией родительского класса, поэтому его можно обрабатывать только посредством динамической привязки во время выполнения.
Когда перезагрузка встречается с перезаписью
Следующий пример немного ненормален. В классе Caller есть две перегрузки метода. Более сложным является то, что SubCaller интегрирует Caller и переопределяет эти два метода. Фактически данная ситуация представляет собой сложную ситуацию двух вышеуказанных ситуаций.
Следующий код сначала выполнит статическую привязку, чтобы определить метод вызова, параметром которого является объект String, а затем выполнит динамическую привязку во время выполнения, чтобы определить, следует ли выполнять реализацию вызова подкласса или родительского класса.
Скопируйте код кода следующим образом:
общественный класс TestMain {
public static void main(String[] args) {
Строка ул = новая строка ();
Вызывающий абонент callerSub = новый SubCaller();
callerSub.call(стр);
}
статический класс Caller {
общественный недействительный вызов (Object obj) {
System.out.println("Экземпляр объекта в вызывающем объекте");
}
общественный недействительный вызов (String str) {
System.out.println("Экземпляр String в Caller");
}
}
статический класс SubCaller расширяет Caller {
@Override
общественный недействительный вызов (Object obj) {
System.out.println("Экземпляр объекта в SubCaller");
}
@Override
общественный недействительный вызов (String str) {
System.out.println("Экземпляр String в SubCaller");
}
}
}
Результат выполнения
Скопируйте код кода следующим образом:
22:30 $javaTestMain
экземпляр String в SubCaller
проверять
Поскольку он был представлен выше, я буду публиковать здесь только результаты декомпиляции.
Скопируйте код кода следующим образом:
22:30 $ javap -c TestMain
Скомпилировано из "TestMain.java"
общественный класс TestMain {
общественный TestMain();
Код:
0: aload_0
1: вызвать специальный #1 // Метод java/lang/Object."<init>":()V
4: возвращение
public static void main(java.lang.String[]);
Код:
0: новый #2 // класс java/lang/String
3: дубль
4: вызвать специальный #3 // Метод java/lang/String."<init>":()V
7: astore_1
8: новый #4 // класс TestMain$SubCaller
11: дуп
12: вызвать специальный #5 // Метод TestMain$SubCaller."<init>":()V
15: astore_2
16: загрузка_2
17: загрузка_1
18: вызвать виртуальный #6 // Метод TestMain$Caller.call:(Ljava/lang/String;)V
21: возвращение
}
Любопытные вопросы
Разве нельзя использовать динамическую привязку?
Фактически, теоретически привязка определенных методов также может быть достигнута с помощью статической привязки. например:
Скопируйте код кода следующим образом:
public static void main(String[] args) {
Строка ул = новая строка ();
последний вызывающий абонент callerSub = новый SubCaller();
callerSub.call(стр);
}
Например, здесь callerSub содержит объект subCaller, а переменная callerSub является конечной, и метод вызова выполняется немедленно. Теоретически компилятор может узнать, что метод вызова SubCaller должен быть вызван путем достаточного анализа кода.
Но почему нет статической привязки?
Предположим, что наш Caller наследует от класса BaseCaller определенного фреймворка, реализующего метод вызова, а BaseCaller наследует от SuperCaller. Метод вызова также реализован в SuperCaller.
Предположим, что BaseCaller и SuperCaller в определенном фреймворке 1.0
Скопируйте код кода следующим образом:
статический класс SuperCaller {
общественный недействительный вызов (Object obj) {
System.out.println("Экземпляр объекта в SuperCaller");
}
}
статический класс BaseCaller расширяет SuperCaller {
общественный недействительный вызов (Object obj) {
System.out.println("Экземпляр объекта в BaseCaller");
}
}
Мы реализовали это с помощью фреймворка 1.0. Caller наследует от BaseCaller и вызывает метод super.call.
Скопируйте код кода следующим образом:
общественный класс TestMain {
public static void main(String[] args) {
Объект obj = новый объект();
SuperCaller callerSub = новый SubCaller();
callerSub.call(объект);
}
статический класс Caller расширяет BaseCaller{
общественный недействительный вызов (Object obj) {
System.out.println("Экземпляр объекта в вызывающем объекте");
супер.вызов(объект);
}
общественный недействительный вызов (String str) {
System.out.println("Экземпляр String в Caller");
}
}
статический класс SubCaller расширяет Caller {
@Override
общественный недействительный вызов (Object obj) {
System.out.println("Экземпляр объекта в SubCaller");
}
@Override
общественный недействительный вызов (String str) {
System.out.println("Экземпляр String в SubCaller");
}
}
}
Затем мы скомпилировали файл класса на основе версии 1.0 этой платформы. Предполагая, что статическая привязка может определить, что super.call вышеуказанного Caller реализован как BaseCaller.call.
Затем мы снова предполагаем, что BaseCaller не переписывает метод вызова SuperCaller в версии 1.1 этой платформы. Тогда приведенное выше предположение о том, что реализация вызова, которая может быть статически привязана, вызовет проблемы в версии 1.1, поскольку super.call должен использовать SuperCall в версии. 1.1. Реализация метода вызова, вместо того, чтобы предполагать, что реализация метода вызова BaseCaller определяется статической привязкой.
Таким образом, некоторые вещи, которые на самом деле могут быть связаны статически, просто связываются динамически с учетом безопасности и согласованности.
Вдохновение для оптимизации получено?
Поскольку при динамическом связывании необходимо определить, какую версию реализации метода или переменной следует выполнить во время выполнения, это требует больше времени, чем статическое связывание.
Поэтому, не затрагивая общий дизайн, мы можем рассмотреть возможность изменения методов или переменных с помощью частных, статических или окончательных.