Это «особенность» реализации. В памяти это выглядит так:
a:
pointer to class A
int i
b:
pointer to class B
int i (from A)
int i (from B)
Когда вы обращаетесь к i
в экземпляре B
, Java должна знать, какую переменную вы имеете в виду. Он должен распределить оба, так как методы из класса A
будут хотеть получить доступ к своему собственному полю i
, в то время как методы из B
будут хотеть свои собственные i
(так как вы решили создать новое поле i
в B
вместо того, чтобы сделать A.i
видимым в B
). Это означает, что есть два i
и применяются стандартные правила видимости: победит тот, кто ближе.
Теперь вы говорите A a=new B();
, и это немного сложно, потому что говорит Java "обрабатывать результат с правой стороны, как если бы он был экземпляром A
".
Когда вы вызываете метод, Java следует за указателем на класс (первым делом в объекте в памяти). Там он находит список методов. Методы перезаписывают друг друга, поэтому при поиске метода show()
он найдет метод, определенный в B
. Это ускоряет доступ к методам: вы можете просто объединить все видимые методы в (внутреннем) списке методов класса B
, и каждый вызов будет означать один доступ к этому списку. Вам не нужно искать все классы вверх для соответствия.
Доступ к полю аналогичен. Java не любит поиск. Поэтому, когда вы говорите B b = new B();
, b.i
явно от B
. Но вы сказали A a = new B()
, сказав Java, что предпочитаете относиться к новому экземпляру как к типу A
. Java, какой бы ленивой она ни была, смотрит в A
, находит поле i
, проверяет, что вы можете видеть это поле, и даже не удосуживается взглянуть на реальный тип a
(потому что это будет) быть медленным и б) эффективно помешает вам получить доступ к обоим i
полям путем приведения).
Итак, в конце концов, это потому, что Java оптимизирует поиск полей и методов.