I am creating a supplier for an inner class constructor using the lambda ctx -> new SpectatorSwitcher(ctx)
. IntelliJ suggested that I change it to SpectatorSwitcher::new
instead. SpectatorSwitcher is a non-static inner class of the class I'm working in. The suggested code compiles fine (using maven) but I get the following VerifyError on execution:
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)
Why is javac / maven not failing while compiling but still producing invalid byte code?
Edit: The problem appears to be far more complex than the simple call, this is the code needed to reproduce it:
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));
}
}
}
Even after smacking my head into the bytecode for almost an hour, I've not been able to come to a reasonable conclusion as to why this is happening. Surprisingly, changing your method to this:
private void runTest() {
Worker worker = new Worker();
run(() -> worker.print(field -> new SomeClass(field)));
Function<Object, Object> function = SomeClass::new;
run(() -> worker.print(function));
}
works fine. Also, getting rid of run()
method invocation, and just calling worker.print()
:
private void runTest() {
Worker worker = new Worker();
worker.print(field -> new SomeClass(field));
worker.print(SomeClass::new);
}
also works.
It seems like, using the constructor reference as in your case is not able to pass the enclosing instance of Test
class to the SomeClass
constructor which is required. While the two cases here are able to pass Test
instance to the SomeClass
constructor.
But I couldn't come to the exact reason. The above reasoning might very well be wrong. But I've just come to that after getting to those working approach.
You might want to go through lambda translation, to understand the inner working. I'm still not very much clear about how lambdas and method references are translated.
I found a thread in lambda mailing list about similar issue. Also, this SO post is also related.
The following runtTest()
method:
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);
}
Is compiled to following bytecode:
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
I can see only the second run()
method invocation doesn't pass LSO
argument, while others do pass it. You can run the command - javap -c -s -verbose Test
, to see Bootstrap methods for #0
, #1
, etc. I guess we can definitely say that this is a bug. Perhaps you can file one.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With