Каковы критерии, по которым компилятор выбирает между временем компиляции и привязкой функции времени выполнения в Java

#java

#java

Вопрос:

Прежде всего, я чувствую, что очень странно, что Java иногда использует привязку во время выполнения, а иногда и привязку во время компиляции. Разве не было бы намного проще, если бы это всегда было привязкой во время выполнения? В любом случае. Мой вопрос заключается в следующем:

В следующем коде:

 public class Animal {
    public void eat() {
        System.out.println("Generic Animal Eating Generically");
    }
}

public class Horse extends Animal {
    @override
    public void eat() {//overridden eat method
        System.out.println("Horse eating hay ");
    }
    public void eat(String s) {//overloaded eat method
        System.out.println("Horse eating "   s);
    }
}
  

Вопрос:
1.In из приведенного ниже кода я знаю, что вопрос «какой метод eat запускать?» решается во время компиляции. Слишком очевидно. Но каковы критерии для его решения?

 Animal a = new Animal();
a.eat();
  

2. Как насчет следующего кода.

 Animal a = new Horse();//line 1
a.eat();
  

Я знаю, что в этом случае метод переопределяется и происходит привязка во время выполнения. Но каковы критерии, по которым компилятор пропускает привязку времени компиляции?

Мое предположение: видит ли он, что тип данных и вызов конструктора относятся к другому классу (в строке 1), и из-за этой разницы он пропускает привязку времени компиляции?

3.In приведенный ниже код:

  Animal a = new Horse();
 a.eat("some string");//compile time error
  

Если мое предположение верно, этот код не должен приводить к ошибке времени компиляции, поскольку привязка времени компиляции уже пропущена. Я не могу понять, почему это вызывает ошибку времени компиляции.
Может кто-нибудь уточнить?

Комментарии:

1. Нет «пропуска привязки времени компиляции».

2. Привязка времени компиляции не «пропущена». Когда вы вызываете виртуальный метод, компилятор знает, что в определенном слоте будет реализован метод, который будет найден во время выполнения. Он не знает, что a у него будет eat(String) метод, потому что тип a ( Animal ) не указывает такой метод.

3. @Whatzs Как насчет кода 2?

4. Это не пропущено, компилятор просто знает, что оно будет определено во время выполнения.

Ответ №1:

Правила вызова метода экземпляра довольно просты:

  • Сигнатура метода выбирается во время компиляции.
  • Конкретная реализация метода с этой сигнатурой выбирается во время выполнения. Это остается верным, даже если метод и / или класс являются окончательными, потому что класс может быть перекомпилирован и введено новое переопределение. Эти изменения не повлияют на вызывающий класс.

Комментарии:

1. Ссылкой для обеих точек является JLS: docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12

2. (Для статических методов как сигнатура, так и реализация выбираются во время компиляции.)

Ответ №2:

Это почти всегда привязка во время выполнения. Т.Е. Все методы Java являются виртуальными методами. За исключением статических методов, которые не являются виртуальными.

Методы Final и private также не могут быть переопределены.

Тем не менее, у нас есть некоторые проверки типов во время компиляции в java. В следующем коде у вас ошибка компиляции, потому что у класса Animal нет eat(String) метода.

 Animal a = new Horse();
a.eat("some string");//compile time error
  

Если Animal бы был eat(String) метод, то код был бы скомпилирован. Но из-за привязки во время выполнения он будет выполняться Horse.eat() .

В динамически типизированных языках, таких как python или javascript, такой проверки типа нет. Если метод существует во время выполнения, он вызывается. В противном случае выдается ошибка. Это также называется утиной типизацией.

Комментарии:

1. Да, упоминания «виртуальных методов» и «динамической отправки» было бы достаточно. Люди, которые работают с Java, должны знать эти понятия.

Ответ №3:

Java использует фактический класс для поиска требуемого метода во время выполнения, но он использует интерфейс, чтобы определить, существует ли на самом деле метод во время компиляции. Ваш класс Animal неявно также определяет интерфейс Animal, который в данном случае состоит исключительно из метода без аргументов ‘eat ()’. Он НЕ определяет метод с одним аргументом ‘eat (String)’. Класс Horse неявно определяет интерфейс Horse, который расширяет интерфейс Animal с помощью метода с одним аргументом.

В ваших примерах (1) и (2), независимо от того, какой фактический класс вы создаете, вы присваиваете переменной, реализующей интерфейс Animal. Поэтому, когда компилятор видит оператор ‘a.eat ();’, он проверяет, реализует ли интерфейс ‘a’ метод без аргументов ‘eat ()’, что он и делает. В вашем примере (3) фактическим классом является Horse, НО переменная по-прежнему реализует только интерфейс Animal; в операторе ‘a.eat («некоторая строка»); ‘ он проверяет, реализует ли ‘a’ ‘eat (String)’, чего он не делает, отсюда и ошибка компиляции.