Можно ли восстановить переопределенный метод родительского класса? - PullRequest
0 голосов
/ 19 апреля 2020

Я сейчас играю с объектами и кое-что заметил, когда я набрал toString() для удобства чтения. Обратите внимание на эти классы и результаты.

    class Point {
        int x;
        int y;
        Point(int x, int y) {
            this.x = x; this.y = y;
        }
        public String toString() {
            return String.format("(%d,%d)",x,y);
        }
    }
    class Line extends Point {
        int l;
        Line(int x, int y, int l) {
            super(x,y);
            this.l = l;
        }
        public String toString() {
            return String.format("Length is %d", this.l);
        }
    }
JShell REPL:
> new Point(0,0)
==> (0,0)

> new Line(0,1,4)
==> Length is 4

> Point p = new Line(0,1,3);
p ==> Length is 3

> p.x
==> 0

> p.y
==> 1

> p.l
| Error    //Expected, p is a Point reference

> ((Line) p).l
==> 3

> Line l = (Line) p
l ==> Length is 3

> l.x
==> 0

> l.y
==> 1    //referencing x and y this way were surprising, but it makes sense

> ((Line) p).x
==> 0

> l.l
==> 3

> ((Point) l).toString()
==> "Length is 3"

Экземпляр объекта должен использовать правильную ссылку для получения методов в нужном классе. Итак, почему toString() трактуется по-другому? Похоже, что toString() вызывается для класса, с которым он построен, независимо от того, какой тип ссылки я использую.

Редактировать: Поскольку toString() переопределяется дважды, как я могу вызвать Point.toString() через тип литья?

Ответы [ 3 ]

1 голос
/ 19 апреля 2020

Похоже, что toString () вызывается для класса, с которым он построен, независимо от того, какой тип ссылки я использую.

Да. Вот что такое динамическая отправка c в Java. Проверьте Что такое метод отправки Dynami c и как он связан с наследованием? , чтобы узнать больше об этом.

Демонстрация:

public class Main {
    public static void main(String[] args) {
        Point p1 = new Point(10, 20);
        Object p2 = new Point(5, 15);

        Point l1 = new Line(3, 8, 7);
        Line l2 = new Line(10, 20, 20);
        Object l3 = new Line(5, 15, 25);

        System.out.println(p1);
        System.out.println(p2);
        System.out.println(l1);
        System.out.println(l2);
        System.out.println(l3);
    }
}

Выход:

(10,20)
(5,15)
Length is 7
Length is 20
Length is 25
1 голос
/ 19 апреля 2020

Вы неправильно поняли.

ALL методы в java разрешить до того, с которым он был построен.

Java использует двойной удар модель, чтобы выяснить, какой фактический метод вызывать:

  1. Привязать к правильной подписи (stati c).
  2. Найти правильную перегрузку для вызова (динамическая 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 (определяется во время компиляции).

0 голосов
/ 19 апреля 2020

Вы не перегружаете метод toString(), но вы переопределяете его в дочернем классе.

Если у вас остались какие-либо вопросы, опубликуйте код, написанный в методе public static void main(String args[]){}.

...