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
Caused by: java.lang.IllegalAccessError: tried to access class a.Applicable from class b.Test
at the line of the lambda expression. – WartyThe type Applicable<SomeApplicable> from the descriptor computed for the target context is not visible here.
– Rheoa
and one in packageb
. – Wartyjavac
, not with Eclipse, maybe bug. – Cockscomb