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
applymethod is declared inApplicable, but not inSomeApplicable, the anonymous class should work, and the lambda should not. Let's examine the bytecode.Anonymous class Test$1
javacgenerates both the implementation of interface methodapply(Applicable)and the overriden methodapply(SomeApplicable). Neither of methods refer to inaccessible interfaceApplicable, except in the method signature. That is,Applicableinterface is not resolved (JVMS §5.4.3) anywhere in the code of anonymous class.Note that
apply(Applicable)can be successfully called fromTest, because types in the method signature are not resolved during the resolution ofinvokeinterfaceinstruction (JVMS §5.4.3.4).Lambda
An instance of lambda is obtained by execution of
invokedynamicbytecode with the bootstrap methodLambdaMetafactory.metafactory:The static arguments used to construct lambda are:
void (a.Applicable);void (a.SomeApplicable).All these arguments are resolved during
invokedynamicbootstrap 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.Applicableon behalf ofTestclass, and fails withIllegalAccessError. Then, according to the spec ofinvokedynamic, the error is wrapped intoBootstrapMethodError.Bridge method
To work around
IllegalAccessError, you need to explicitly add a bridge method in publicly accessibleSomeApplicableinterface:In this case lambda will implement
apply(SomeApplicable)method instead ofapply(Applicable). The correspondinginvokedynamicinstruction will refer to(La/SomeApplicable;)VMethodType, which will be successfully resolved.Note: it is not enough to change just
SomeApplicableinterface. You'll have to recompileTestwith the new version ofSomeApplicablein order to generateinvokedynamicwith 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.