2017-02-17 2 views
4

Я столкнулся со следующим странным случаем неполноты спецификации Java/JVM. Предположим, что у нас есть классы (мы будем использовать Java 1.8 и HotSpot):Бинарная совместимость изменения класса со статическими методами для интерфейса в Java

public class Class { 
    public static void foo() { 
    System.out.println("hi"); 
    } 
} 

public class User { 
    public static void main(String[] args) { 
    Class.foo(); 
    } 
} 

Затем перекомпилировать класс быть интерфейс без перекомпиляции Пользователь:

public interface Class { 
    public static void foo() { 
    System.out.println("hi"); 
    } 
} 

Запуск User.main() теперь производит тот же вывод 'hi'. Это кажется очевидным, но я бы ожидать, что она не в состоянии с IncompatibleClassChangeError и вот почему:

Я знаю, что изменение класса с интерфейсом является бинарное Несовместимость согласно JVM 5.3.5#3 statement:

Если класс или интерфейс названный в качестве прямого суперкласса C, является, по сути, интерфейсом, загрузка вызывает IncompatibleClassChangeError.

Но предположим, что у нас нет наследников класса. Теперь нам нужно отнести спецификацию JVM о разрешении метода. Первая версия компилируется в этом байткод:

public static void main(java.lang.String[]); 
    Code: 
     0: invokestatic #2     // Method examples/Class.foo:()V 
     3: return 

Таким образом, мы имеем здесь что-то под названием «CONSTANT_Methodref_info» в classpool.

Процитируем действия invokestatic.

... Во время выполнения элемента постоянного пула по этому индексу должен быть символической ссылкой на методе или метод интерфейса (§5.1), который дает имя и дескриптор (§ 4.3. 3) метода, а также символическую ссылку на класс или интерфейс, в котором этот метод должен быть найден. Именованный метод разрешен (§5.4.3.3).

Таким образом, JVM обрабатывает метод и методы интерфейса другим способом. В нашем случае JVM видит метод как метод класса (а не интерфейса). JVM пытается решить его соответственно 5.4.3.3 Метод Разрешение:

Согласно спецификации JVM, виртуальной машина должна провалиться по следующему высказыванию:

1) Если C является интерфейсом, разрешение метод выдает IncompatibleClassChangeError ,

потому что класс на самом деле не класс, а интерфейс.

К сожалению, я не нашел упоминаний о бинарной совместимости изменения класса для интерфейса в Спецификации языка Java Глава 13. Двоичная совместимость. Более того, о таком сложном случае упоминания одного и того же статического метода ничего не сказано.

Не могли бы кто-нибудь подробно остановиться на этом и показать мне, если я что-то пропустил?

+1

Кстати, я нашел [упоминание] (https://wiki.eclipse.org/Evolving_Java-based_APIs_2#Evolving_API_packages): «Генетические изменения интерфейса API-интерфейса прерывают двоичную совместимость, даже в тех случаях, когда класс/интерфейс используется клиентом, но не реализован. Это связано с тем, что байт-коды Java VM для вызова метода, объявленного в интерфейсе, отличаются от тех, которые используются для вызова метода, объявленного в классе'. Но это нечто неофициальное. –

ответ

3

Во-первых, поскольку ваш пример не содержит наследования, нам не нужно «предполагать, что у нас нет наследников класса», их просто нет. Поэтому приведенная часть п. 5.3.5 не имеет значения для этого примера.

Процитированная часть § 6.5, обозначающая «символическая ссылка на метод или метод интерфейса», по иронии судьбы - change made an Java 8 to relax the restrictions. Инструкция invokestatic явно разрешена для вызова методов интерфейса, если они равны static.

Первая пуля §5.4.3.3, на которую вы ссылаетесь в конце, заявив, что разрешение метода должно завершиться безоговорочно, если объявление типа interface, действительно нарушено, но это не имеет никакого смысла. Поскольку он упоминается безоговорочно, то есть в документации invokestatic не указано, что для методов интерфейса должен использоваться другой алгоритм поиска, это подразумевает, что использование метода static для interface вообще невозможно.

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

В вашем примере, класс вызывающий действительно нарушает спецификацию, а именно §4.4.2, как он относится к методу интерфейса через CONSTANT_Methodref_info вместо CONSTANT_InterfaceMethodref_info, после того, как объявить класс был изменен. Но текущее состояние документации инструкции invokestatic не дает права изменять поведение, основанное на типе элемента с постоянным пулом (это может быть фактическое намерение, но его там нет). И, как сказано, соблюдение текущей формулировки будет означать отказ от любого invokestatic на interface.

Поэтому спецификация нуждается в другой очистке, но поскольку различие между Methodref_info и InterfaceMethodref_info далеко не так полезно, как могло бы быть до Java 8 (сравните с ответом, связанным выше), я не удивлюсь, если окончательное исцеление, как оказалось, полностью исключает различие в будущем.

Смежные вопросы