переопределение метода - PullRequest
       11

переопределение метода

3 голосов
/ 21 сентября 2009

class A
{
  int i=10;
  void show()
  {
    System.out.println("class A");
  }
}

class B extends A
{
  int i=5;
  public void show()
  {
    System.out.println("class B");
  }
}
class M
{
  public static void main(String s[])
  {
    A a=new B();
    a.show();
    System.out.println(a.i);
  }
}


OUTPUT= class B
        10

Если метод класса A переопределен методом класса B, то почему не переменная 'i'?

Ответы [ 8 ]

5 голосов
/ 21 сентября 2009

Не перезаписывается, а скрывается. В своем выводе вы специально запросили значение a.i, а не ((B) a) .i.

5 голосов
/ 21 сентября 2009

Поскольку переменные не являются виртуальными, используются только методы.

4 голосов
/ 21 сентября 2009

Это «особенность» реализации. В памяти это выглядит так:

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 оптимизирует поиск полей и методов.

3 голосов
/ 22 сентября 2009

Почему в Java нет переопределений полей?

Ну, потому что поиск полей экземпляра в Java происходит во время компиляции: Java просто дает вам значение поля с заданным смещением в памяти объекта (на основе информации о типе, имеющейся в наличии во время компиляции: в этом случае a объявляется как быть типа А).

void foo() {
  A a = new B();
  int val = a.i; // compiler uses type A to compute the field offset
}

Кто-то может спросить: «Почему компилятор не использовал тип B, поскольку он знает, что a на самом деле является экземпляром B? Разве это не очевидно из присваивания чуть выше?». Конечно, в приведенном выше случае это относительно очевидно, и компилятор может попытаться быть умнее и понять это.

Но это дизайн "крысиной дыры" компилятора, что если встречается "более хитрый" фрагмент кода, например:

void foo(A a) {
  int val = a.i;
}

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

2 голосов
/ 21 сентября 2009

Это дизайнерское решение разработчиков Java, и оно задокументировано в Спецификации языка Java .

Метод с той же сигнатурой метода, что и метод в его родительском классе переопределяет метод в своем родительском классе.

Переменная с тем же именем, что и переменная в родительском классе скрывает родительскую переменную.

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

Как уже отмечалось, в C ++ и C # для получения того же поведения переопределения, что и в Java, методы должны быть объявлены виртуальными.

0 голосов
/ 21 сентября 2009

Подсказка: Вы можете использовать сеттеры и геттеры, чтобы убедиться, какие элементы данных вы используете.
Или: Вы можете просто установить значения в конструкторе вместо объявления класса.

0 голосов
/ 21 сентября 2009

a является экземпляром A. Вы вызываете конструктор B (). Но это все еще класс A. Вот почему я равняюсь 10; Переопределение метода будет выполнено успешно.

Обратите внимание, что класс начинается не с

public class A()

но с;

public class A { ... }
0 голосов
/ 21 сентября 2009

Потому что по умолчанию переменные являются приватными. Вы должны объявить его как «защищенный», тогда он будет должным образом унаследован.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...