Ссылка на конструктор для внутреннего класса завершается с VerifyError во время выполнения
Я создаю поставщика для конструктора внутреннего класса, используя лямбду ctx -> new SpectatorSwitcher(ctx)
, IntelliJ предложил мне изменить его на SpectatorSwitcher::new
вместо. SpectatorSwitcher является нестатическим внутренним классом класса, в котором я работаю. Предлагаемый код компилируется нормально (с использованием maven), но я получаю следующее VerifyError при выполнении:
Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
Location:
Test.lambda$runTest$8(LTest$Worker;)V @2: invokedynamic
Reason:
Type 'Test$Worker' (current frame, stack[1]) is not assignable to 'Test'
Current Frame:
bci: @2
flags: { }
locals: { 'Test$Worker' }
stack: { 'Test$Worker', 'Test$Worker' }
Bytecode:
0000000: 2a2a ba00 0b00 00b6 000c b1
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2688)
at java.lang.Class.getMethod0(Class.java:2937)
at java.lang.Class.getMethod(Class.java:1771)
at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526)
Почему javac / maven не завершается с ошибкой при компиляции, но все еще выдает неверный байт-код?
Изменить: Проблема, кажется, гораздо сложнее, чем простой вызов, это код, необходимый для его воспроизведения:
import java.util.function.Function;
/**
* @author Yawkat
*/
public class Test {
public static void main(String[] args) { new Test().runTest(); }
private void runTest() {
Worker worker = new Worker();
run(() -> worker.print(field -> new SomeClass(field)));
run(() -> worker.print(SomeClass::new));
}
private void run(Runnable runnable) {
runnable.run();
}
private class SomeClass {
final Object field;
SomeClass(Object field) {
this.field = field;
}
}
private static class Worker {
void print(Function<Object, Object> i) {
System.out.println(i.apply(null));
}
}
}
1 ответ
Даже после того, как я почти час врезался в байт-код, я не смог прийти к разумному выводу о том, почему это происходит. Удивительно, но изменив свой метод на это:
private void runTest() {
Worker worker = new Worker();
run(() -> worker.print(field -> new SomeClass(field)));
Function<Object, Object> function = SomeClass::new;
run(() -> worker.print(function));
}
работает отлично. Также избавляемся от run()
вызов метода и просто вызов worker.print()
:
private void runTest() {
Worker worker = new Worker();
worker.print(field -> new SomeClass(field));
worker.print(SomeClass::new);
}
тоже работает.
Похоже, использование ссылки на конструктор, так как в вашем случае не может передать включающий экземпляр Test
класс к SomeClass
конструктор, который требуется. Хотя два случая здесь могут пройти Test
экземпляр к SomeClass
конструктор.
Но я не мог прийти к точной причине. Приведенные выше рассуждения вполне могут быть ошибочными. Но я только пришел к этому после того, как начал работать.
Возможно, вы захотите пройти лямбда-перевод, чтобы понять внутреннюю работу. Я до сих пор не очень понимаю, как переводятся ссылки на лямбды и методы.
Я нашел тему в лямбда-рассылке о подобной проблеме. Кроме того, этот пост также связан.
Следующие runtTest()
метод:
public void runTest() {
Worker worker = new Worker();
run(() -> worker.print((field) -> new SomeClass(field)));
run(() -> worker.print(SomeClass::new));
Function<Object, Object> func = SomeClass::new;
run(() -> worker.print(func));
worker.print(SomeClass::new);
}
Компилируется в следующий байт-код:
public void runTest();
Code:
0: new #2 // class SO$Worker
3: dup
4: invokespecial #3 // Method SO$Worker."<init>":()V
7: astore_1
8: aload_0
9: aload_0
10: aload_1
11: invokedynamic #4, 0 // InvokeDynamic #0:run:(LSO;LSO$Worker;)Ljava/lang/Runnable;
16: invokevirtual #5 // Method run:(Ljava/lang/Runnable;)V
19: aload_0
20: aload_1
21: invokedynamic #6, 0 // InvokeDynamic #1:run:(LSO$Worker;)Ljava/lang/Runnable;
26: invokevirtual #5 // Method run:(Ljava/lang/Runnable;)V
29: aload_0
30: invokedynamic #7, 0 // InvokeDynamic #2:apply:(LSO;)Ljava/util/function/Function;
35: astore_2
36: aload_0
37: aload_1
38: aload_2
39: invokedynamic #8, 0 // InvokeDynamic #3:run:(LSO$Worker;Ljava/util/function/Function;)Ljava/lang/Runnable;
44: invokevirtual #5 // Method run:(Ljava/lang/Runnable;)V
47: aload_1
48: aload_0
49: invokedynamic #7, 0 // InvokeDynamic #2:apply:(LSO;)Ljava/util/function/Function;
54: invokevirtual #9 // Method SO$Worker.print:(Ljava/util/function/Function;)V
57: return
Я вижу только второе run()
вызов метода не проходит LSO
аргумент, в то время как другие передают его. Вы можете запустить команду - javap -c -s -verbose Test
, чтобы увидеть методы Bootstrap для #0
, #1
и т.д. Я думаю, мы можем определенно сказать, что это ошибка. Возможно, вы можете подать один.