Вы неправильно поняли.
ALL методы в java разрешить до того, с которым он был построен.
Java использует двойной удар модель, чтобы выяснить, какой фактический метод вызывать:
- Привязать к правильной подписи (stati c).
- Найти правильную перегрузку для вызова (динамическая c диспетчеризация) .
Привязать к правильной подписи
Методы в java имеют подпись. Сигнатура включает в себя имя, типы параметров и тип возвращаемого значения (хотя последний обычно не имеет значения; javac не будет компилировать код с двумя методами, отличающимися только типом возвращаемого значения и ничем иным) , Обратите внимание, что генерики здесь не учитываются.
Пример:
public void foo()
[1] public void foo(String[] x)
[2] public void foo(String... x)
[2] public void foo(List<String> x)
[3] public void foo(List<Integer> x)
[3] public void foo(List<String> x, boolean whatever)
[4]
здесь каждая запись с одинаковым номером имеет одну и ту же сигнатуру: [3] считаются одной и той же сигнатурой, потому что универсальные элементы удаляются первыми, а [2] считаются одной и той же сигнатурой, потому что varargs реализован в виде массива.
Java будет использовать тип выражения , который вы вызываете для метода , чтобы выяснить, какую подпись вы пытаетесь вызвать. Это полностью дело времени компиляции!
Пример:
class Parent {
public void foo(Object arg) { System.out.println("Parent"); }
}
class Child extends Parent {
public void foo(String arg) { System.out.println("Child"); }
// note: not the same signature!
}
Parent p = new Child();
p.foo("Hello");
javac (компилятор) смотрит на строку p.foo("Hello")
, и будет делать "какая подпись это?" dance: p относится к типу Parent
(тот факт, что он указывает на объект типа Child, не имеет значения; компилятор не может этого знать и не учитывает), просматривает все методы типа Parent
Имеет, видит только один foo
, и, следовательно, этот вызов использует подпись: void foo(Object)
. Этот код будет печатать Parent
при запуске.
Dynami c dispatch
Как только компилятор определит сигнатуру, которая закодирована в файле класса. Когда этот файл класса выполняется, используется динамическая диспетчеризация c: проверяется фактический тип объекта и вызывается наиболее конкретная c версия. Пример:
class Parent {
public void foo(Object arg) { System.out.println("Parent"); }
}
class Child extends Parent {
public void foo(Object arg) { System.out.println("Child"); }
// note: now it IS the same signature!
}
Parent p = new Child();
p.foo("Hello");
обратите внимание, что это тот же пример, за исключением того, что теперь подпись дочернего метода совпадает с сигнатурой родительского - теперь это «тот же метод». Таким образом, хотя компиляция вызова p.foo("Hello")
не изменилась вообще (фактически вы можете перекомпилировать JUST Child. java и не перекомпилировать код, который вызывает p.foo, и вы увидите это: печатает Child
. Эта часть полностью связана с runtime .
Я настоятельно советую вам использовать аннотацию @Override
каждый раз, когда вы намереваетесь «переопределить» метод, поэтому foo(Object)
в файле Child.java
должна иметь эту аннотацию. Эта аннотация делает только одну вещь: ЕСЛИ вы помещаете ее в метод, который ничего не переопределяет (как, например, нет родительского типа с таким же методом (сигнатура и все!) как это), это ошибка компилятора, в противном случае это не имеет никакого эффекта. Проверенная компилятором документация - это хорошо.
Если бы вы поставили @Override
на public void foo(String arg)
, вы бы получили ошибка. Хотя имя этого метода совпадает с именем метода в родительском элементе, это не та же сигнатура.
TL; DR: в java имена методов включают в себя все типы параметров, свободные от универсальных параметров. так что если списки параметров не совпадают, это не тот же метод. Но если они это сделают, динамическая c диспетчеризация всегда используется, вы не можете отказаться от этого в java: метод, который будет вызван, будет тем же объектом, к которому вы его вызываете.
NB. Если методы имеют статус c, динамическая c диспетчеризация вообще отсутствует. Это все stati c (определяется во время компиляции).