Поскольку вы уже понимаете случаи 1, 3 и 4, давайте рассмотрим случай 2.
(Обратите внимание - я ни в коем случае не эксперт по внутренней работе JVM или компиляторов, но этонасколько я понимаю. Если кто-то читает это как эксперт по JVM, не стесняйтесь редактировать этот ответ на любые несоответствия, которые вы можете найти.)
Метод в подклассе с тем же именем, но с другой сигнатурой известенкак метод перегрузки.Перегрузка метода использует статическое связывание, что в основном означает, что соответствующий метод будет вынужден быть «выбранным» (то есть связанным) во время компиляции.Компилятор не имеет ни малейшего представления о типе времени выполнения (или фактическом типе) ваших объектов.Поэтому, когда вы пишете:
// Reference Type // Actual Type
Sub sub = new Sub(); // Sub Sub
Top top = sub; // Top Sub
, компилятор только «знает», что top имеет тип Top (он же ссылочный тип).Поэтому, когда вы позже напишите:
System.out.println(top.f(str)); // Prints "subobj"
, компилятор «видит» вызов top.f как ссылку на метод f класса Top.Он «знает», что str имеет тип String, который расширяет Object.Так как 1) вызов top.f ссылается на метод f класса Top, 2) в классе Top нет метода f, который принимает параметр String, и 3) поскольку str является подклассом Object, метод f класса Topявляется единственным допустимым выбором во время компиляции.Таким образом, компилятор неявно переводит str в свой родительский тип, Object, так что он может быть передан методу Top Top.(Это отличается от динамического связывания, где разрешение типов в приведенной выше строке кода будет отложено до времени выполнения, чтобы разрешаться JVM, а не компилятором.)
Затем во время выполнения, в приведенной выше строкекода, top понижается JVM до его фактического типа, sub.Тем не менее, аргумент str был отклонен компилятором для типа Object.Поэтому теперь JVM должна вызвать метод f в классе sub, который принимает параметр типа Object.
Следовательно, в приведенной выше строке кода выводится «subobj», а не «sub».
Другой очень похожий пример см. В разделе: Динамическое связывание Java и переопределение методов
Обновление. Обнаружена эта подробная статья о внутренней работе JVM:
http://www.artima.com/underthehood/invocationP.html
Я прокомментировал ваш код, чтобы прояснить, что происходит:
class Top {
public String f(Object o) {return "Top";}
}
class Sub extends Top {
public String f(String s) {return "Sub";} // Overloading = No dynamic binding
public String f(Object o) {return "SubObj";} // Overriding = Dynamic binding
}
public class Test {
public static void main(String[] args) {
// Reference Type Actual Type
Sub sub = new Sub(); // Sub Sub
Top top = sub; // Top Sub
String str = "Something"; // String String
Object obj = str; // Object String
// At Compile-Time: At Run-Time:
// Dynamic Binding
System.out.println(top.f(obj)); // Top.f (Object) --> Sub.f (Object)
// Dynamic Binding
System.out.println(top.f(str)); // Top.f (Object) --> Sub.f (Object)
// Static Binding
System.out.println(sub.f(obj)); // Sub.f (Object) Sub.f (Object)
// Static Binding
System.out.println(sub.f(str)); // Sub.f (String) Sub.f (String)
}
}