Это, похоже, ошибка в JVM / JDK / Spec / Doc, которая зависит от того, как компилятор преобразует сигнатуру полиморфного метода подписи.
compareAndSet
отмеченс @MethodHandle.PolymorphicSignature
.Это означает семантически (перефразировано), что любая сигнатура, найденная на сайте вызова, будет использоваться для вызова метода.Это в основном предотвращает упаковку аргументов.
Полная подпись compareAndSet
в VarHandle:
public final native
@MethodHandle.PolymorphicSignature
@HotSpotIntrinsicCandidate
boolean compareAndSet(Object... args);
Обратите внимание, что возвращается boolean
, но ошибка показывает намВМ пытается связать VarHandle.compareAndSet(VarHandleExample,int,int)void
, который имеет другой тип возврата.Это можно увидеть и в байт-коде, сгенерированном компилятором Eclipse:
publicIntHandle.compareAndSet(this, 10, 100); // CAS
(частично) переведен как:
25: invokevirtual #55 // Method java/lang/invoke/VarHandle.compareAndSet:(LVarHandleExample;II)V
(обратите внимание на комментарий, который показывает нам подписьссылочной константы метода в пуле констант, который используется для связывания метода)
Поэтому во время выполнения кажется, что виртуальная машина попытается найти метод с V
(то есть void
) в качестве возвратаТип, который действительно не существует.
javac
с другой стороны генерирует эту подпись:
25: invokevirtual #11 // Method java/lang/invoke/VarHandle.compareAndSet:(LVarHandleExample;II)Z
Где тип возвращаемого значения Z
(что означает boolean
) вместоиз V
.
Вы можете обойти эту проблему, явно указав тип возвращаемого значения boolean
, либо используя возвращаемое значение:
boolean b = publicIntHandle.compareAndSet(this, 10, 100); // CAS
, либо используя пробелif
в случае, если вам не нужно значение:
if(publicIntHandle.compareAndSet(this, 10, 100)); // CAS
Теперь перейдем к части юриста по языку.
Я мог бы найти ограниченную информацию о сигнатурных полиморфных методах (помеченные @PolymorphicSignature) [ 1 ] (ничего на языкеспецификации).Похоже, нет никаких полномочий относительно того, как дескриптор для полиморфного метода подписи должен быть получен и переведен компилятором в спецификации.
Возможно, наиболее интересным является этот отрывок из jvms-5.4.3.3 (выделено мной):
Если C объявляет ровно один метод с именем, указанным в ссылке на метод, а объявление является полиморфным методом подписи ( §2.9.3 ), тогда поиск метода завершается успешно.Все имена классов, упомянутые в дескрипторе, разрешаются (§5.4.3.1).
Разрешенный метод - это объявление метода полиморфной сигнатуры. Для C необязательно объявлять метод с дескриптором, указанным в ссылке на метод.
Где C
в этом случае будет VarHandle
, при этом метод являетсяпоиск был бы compareAndSet
, а дескриптор (LVarHandleExample;II)Z
или (LVarHandleExample;II)V
в зависимости от компилятора.
Также интересен javadoc о Полиморфизм сигнатур :
Когда JVM обрабатывает байт-код, содержащий полиморфные вызовы подписи, он успешно связывает любой такой вызов независимо от своего дескриптора символьного типа .(Чтобы сохранить безопасность типов, JVM будет защищать такие вызовы с помощью подходящих динамических проверок типов, как описано в другом месте.)
VarHandle
имеет только один метод с именем compareAndSet
, и онявляется сигнатурным полиморфным, поэтому поиск должен завершиться успешно.Имхо, это проблема с виртуальной машиной, что в этом случае выдается исключение, так как дескриптор и, следовательно, тип возвращаемого значения не должны иметь значения в соответствии со спецификацией.
Кажется, также существует проблема с испусканием javacZ
в качестве типа возврата в дескрипторе.В соответствии с тем же разделом javadoc:
Необычной частью является то, что дескриптор символьного типа получен из фактического аргумента и возвращаемого типа, а не из объявления метода.
Однако дескриптор, выдаваемый javac, определенно зависит от объявления метода.
Таким образом, согласно спецификации / doc здесь, похоже, есть 2 ошибки;
в виртуальной машине, которую вы используете, которая неправильно не связывает полиморфный метод подписи.Я также могу воспроизвести его с помощью OpenJDK 64-Bit Server VM (build 13-internal+0-adhoc.Jorn.jdk, mixed mode, sharing)
, который является последним источником OpenJDK.
в javac
, который испускает неправильный тип возврата для дескриптора.
Я предполагаю, что спецификация является основной, но в этом случае она кажется более вероятнойчто спецификация / документация просто не была обновлена после реализации, и это то, что кусает eclipsec.
Я получил ответ на мое письмо на jdk-dev , ответил Дэном Смитом.
За 2.)
Здесь правильный javac.См. jls-15.12.3-400-B
"Если полиморфный метод подписи либо void, либо имеет тип возвращаемого значения, отличный от Object, результат времени компиляции является результатомтип вызова объявления времени компиляции "
Неофициальное описание в javadoc, на которое вы ссылаетесь, является неполным, и я подал ошибку, чтобы исправить это: https://bugs.openjdk.java.net/browse/JDK-8216511
Итакпохоже, что Eclipse генерирует неверный дескриптор для вызова, а не javac.
For 1.)
Вы правы.Время ссылки NoSuchMethodError здесь не указано.Скорее, согласно javadoc VarHandle, мы должны увидеть WrongMethodTypeException во время выполнения.
Отчет об ошибке: https://bugs.openjdk.java.net/browse/JDK-8216520