Поддерживает ли Java динамический вызов метода?

class A           { void F() { System.out.println("a"); }}
class B extends A { void F() { System.out.println("b"); }}

public class X {
    public static void main(String[] args) {
        A objA = new B();
        objA.F();
    }
}

Вот, F() вызывается динамически, не так ли?

Эта статья говорит:

... байт-код Java не поддерживает динамический вызов метода. Существует три поддерживаемых режима вызова: invokestatic, invokespecial, invokeinterface или invokevirtual. Эти режимы позволяют вызывать методы с известной сигнатурой. Мы говорим о строго типизированном языке. Это позволяет сделать некоторые проверки непосредственно во время компиляции.

С другой стороны, динамические языки используют динамические типы. Таким образом, мы можем вызвать метод, неизвестный во время компиляции, но это абсолютно невозможно с байт-кодом Java.

Что мне не хватает?

5 ответов

Решение

Вы путаете динамический вызов с динамическим связыванием.

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

Что это значит?

Это означает, что в вашем примере Java будет вызывать реализацию объекта B потому что тип времени выполнения objA переменная B; и он будет компилироваться, потому что он знает, что B это A таким образом, вызов метода не завершится с ошибкой во время выполнения (objA будет иметь F реализация точно).

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

Просто для мелочей: invokedynamic эта функция будет добавлена ​​в Java7, поскольку многие языки сценариев были написаны для работы поверх JVM, а отсутствие функции динамического вызова вынудило разработчиков этих языков добавить промежуточный слой между сценарием и реальной JVM, которая заботится о динамическом вызове. используя отражение. Конечно, такой подход вызывает много накладных расходов (подумайте о MetaClass), поэтому Sun решила помочь им..

В вашем примере вызывается правильный метод, потому что полиморфно экземпляр B выглядит как экземпляр A. Метод можно найти, изучив тип времени выполнения объекта; то есть B; в отличие от типа ссылки на объект во время компиляции, A. Другая важная часть - это сигнатура метода - они должны всегда совпадать (конечно, полиморфно).

Это отличается от динамических языков, потому что в тех, которые по существу не имеют времени компиляции для объекта - и все должно быть решено во время выполнения.

На самом деле, вам не хватает того, что это часть invokevirtual, которая описана в статье.

Вы просто переопределяете метод, который использует таблицу виртуальных методов для вызова правильного метода.

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

Более правильный пример "динамического" вызова метода будет включать отражение (см. Класс Method). Таким образом, методы, существование которых неизвестно при типе компиляции, могут быть вызваны во время выполнения (это широко используется фреймворками, а не прикладным кодом).

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

Вы можете сделать функциональные интерфейсы.

class Logger {
  private BiConsumer<Object, Integer> logger = null;

  // ...

  private Logger(Object logger) {
      this.akkaLogger = (LoggingAdapter) logger;
      this.logger = (message, level) -> {
          switch (level) {
              case INFO:  akkaInfo(message);
                          break;
              case DEBUG: akkaDebug(message);
                          break;
              case ERROR: akkaError(message);
                          break;
              case WARN:  akkaWarn(message);
                          break;
          }
      };
  }

  private Logger() {
      this.logger = (message, level) -> System.out.println(message);
  }

  // ...
}
Другие вопросы по тегам