In one package (a
) I have two functional interfaces:
package a;
@FunctionalInterface
interface Applicable<A extends Applicable<A>> {
void apply(A self);
}
-
package a;
@FunctionalInterface
public interface SomeApplicable extends Applicable<SomeApplicable> {
}
The apply
method in the superinterface takes self
as an A
because otherwise, if Applicable<A>
was used instead, the type would not be visible outside the package and therefore the method couldn't be implemented.
In another package (b
), I have the following Test
class:
package b;
import a.SomeApplicable;
public class Test {
public static void main(String[] args) {
// implement using an anonymous class
SomeApplicable a = new SomeApplicable() {
@Override
public void apply(SomeApplicable self) {
System.out.println("a");
}
};
a.apply(a);
// implement using a lambda expression
SomeApplicable b = (SomeApplicable self) -> System.out.println("b");
b.apply(b);
}
}
The first implementation uses an anonymous class and it works with no problem. The second one, on the other hand, compiles fine but fails at runtime throwing a java.lang.BootstrapMethodError
caused by a java.lang.IllegalAccessError
as it tries to access the Applicable
interface.
Exception in thread "main" java.lang.BootstrapMethodError: java.lang.IllegalAccessError: tried to access class a.Applicable from class b.Test
at b.Test.main(Test.java:19)
Caused by: java.lang.IllegalAccessError: tried to access class a.Applicable from class b.Test
... 1 more
I think it would make more sense if the lambda expression either worked just like the anonymous class or gave a compile-time error. So, I'm just wondering what is going on here.
I tried removing the superinterface and declaring the method within SomeApplicable
like this:
package a;
@FunctionalInterface
public interface SomeApplicable {
void apply(SomeApplicable self);
}
This obviously makes it work but allows us to see what's different in bytecode.
The synthetic lambda$0
method compiled from the lambda expression seems identical in both cases, but I could spot one difference in the method arguments under bootstrap methods.
Bootstrap methods:
0 : # 58 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#59 (La/Applicable;)V
#62 invokestatic b/Test.lambda$0:(La/SomeApplicable;)V
#63 (La/SomeApplicable;)V
The #59
changes from (La/Applicable;)V
to (La/SomeApplicable;)V
.
I don't really know how lambda metafactory works but I think this might be a key difference.
I also tried explicitly declaring the apply
method in SomeApplicable
like this:
package a;
@FunctionalInterface
public interface SomeApplicable extends Applicable<SomeApplicable> {
@Override
void apply(SomeApplicable self);
}
Now the method apply(SomeApplicable)
actually exists and the compiler generates a bridge method for apply(Applicable)
. Still the same error is thrown at runtime.
At bytecode level it now uses LambdaMetafactory.altMetafactory
instead of LambdaMetafactory.metafactory
:
Bootstrap methods:
0 : # 57 invokestatic java/lang/invoke/LambdaMetafactory.altMetafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
Method arguments:
#58 (La/SomeApplicable;)V
#61 invokestatic b/Test.lambda$0:(La/SomeApplicable;)V
#62 (La/SomeApplicable;)V
#63 4
#64 1
#66 (La/Applicable;)V
As far as I see, JVM does everything right.
When apply
method is declared in Applicable
, but not in SomeApplicable
, the anonymous class should work, and the lambda should not. Let's examine the bytecode.
public void apply(a.SomeApplicable);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String a
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public void apply(a.Applicable);
Code:
0: aload_0
1: aload_1
2: checkcast #5 // class a/SomeApplicable
5: invokevirtual #6 // Method apply:(La/SomeApplicable;)V
8: return
javac
generates both the implementation of interface method apply(Applicable)
and the overriden method apply(SomeApplicable)
. Neither of methods refer to inaccessible interface Applicable
, except in the method signature. That is, Applicable
interface is not resolved (JVMS §5.4.3) anywhere in the code of anonymous class.
Note that apply(Applicable)
can be successfully called from Test
, because types in the method signature are not resolved during the resolution of invokeinterface
instruction (JVMS §5.4.3.4).
An instance of lambda is obtained by execution of invokedynamic
bytecode with the bootstrap method LambdaMetafactory.metafactory
:
BootstrapMethods:
0: #36 invokestatic java/lang/invoke/LambdaMetafactory.metafactory
Method arguments:
#37 (La/Applicable;)V
#38 invokestatic b/Test.lambda$main$0:(La/SomeApplicable;)V
#39 (La/SomeApplicable;)V
The static arguments used to construct lambda are:
void (a.Applicable)
;void (a.SomeApplicable)
.All these arguments are resolved during invokedynamic
bootstrap process (JVMS §5.4.3.6).
Now the key point: to resolve a MethodType all classes and interfaces given in its method descriptor are resolved (JVMS §5.4.3.5). In particular, JVM tries to resolve a.Applicable
on behalf of Test
class, and fails with IllegalAccessError
. Then, according to the spec of invokedynamic
, the error is wrapped into BootstrapMethodError
.
To work around IllegalAccessError
, you need to explicitly add a bridge method in publicly accessible SomeApplicable
interface:
public interface SomeApplicable extends Applicable<SomeApplicable> {
@Override
void apply(SomeApplicable self);
}
In this case lambda will implement apply(SomeApplicable)
method instead of apply(Applicable)
. The corresponding invokedynamic
instruction will refer to (La/SomeApplicable;)V
MethodType, which will be successfully resolved.
Note: it is not enough to change just SomeApplicable
interface. You'll have to recompile Test
with the new version of SomeApplicable
in order to generate invokedynamic
with the proper MethodTypes. I've verified this on several JDKs from 8u31 to the latest 9-ea, and the code in question worked without errors.
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