![](/img/trans.png)
[英]@OnClick method from Butterknife crashes app with java.lang.BootstrapMethodError
[英]Lambda expression fails with a java.lang.BootstrapMethodError at runtime
在一個包( a
)中,我有兩個功能接口:
package a;
@FunctionalInterface
interface Applicable<A extends Applicable<A>> {
void apply(A self);
}
-
package a;
@FunctionalInterface
public interface SomeApplicable extends Applicable<SomeApplicable> {
}
superinterface中的apply
方法將self
作為A
,否則,如果使用Applicable<A>
,則類型將不會在包外顯示,因此無法實現該方法。
在另一個包( b
)中,我有以下Test
類:
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);
}
}
第一個實現使用匿名類,它沒有問題。 另一方面,第二個編譯正常,但在運行時失敗,在嘗試訪問Applicable
接口時拋出java.lang.BootstrapMethodError
引起的java.lang.IllegalAccessError
。
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
我認為如果lambda表達式像匿名類一樣工作或者給出編譯時錯誤會更有意義。 所以,我只是想知道這里發生了什么。
我嘗試刪除超級接口並在SomeApplicable
聲明方法,如下所示:
package a;
@FunctionalInterface
public interface SomeApplicable {
void apply(SomeApplicable self);
}
這顯然使其工作,但允許我們看到字節碼中的不同之處。
從lambda表達式編譯的合成lambda$0
方法在兩種情況下都是相同的,但我可以發現bootstrap方法下方法參數的一個區別。
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
#59
從(La/Applicable;)V
變為(La/SomeApplicable;)V
。
我真的不知道lambda metafactory是如何工作的,但我認為這可能是一個關鍵的區別。
我也嘗試在SomeApplicable
明確聲明apply
方法,如下所示:
package a;
@FunctionalInterface
public interface SomeApplicable extends Applicable<SomeApplicable> {
@Override
void apply(SomeApplicable self);
}
現在方法apply(SomeApplicable)
實際存在,編譯器生成一個apply(Applicable)
的橋接方法apply(Applicable)
。 在運行時仍會拋出相同的錯誤。
在字節碼級別,它現在使用LambdaMetafactory.altMetafactory
而不是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
據我所知,JVM做的一切都是正確的。
當apply
方法在Applicable
聲明,但不在SomeApplicable
中SomeApplicable
,匿名類應該工作,而lambda不應該。 我們來看看字節碼。
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
生成接口方法apply(Applicable)
和overriden方法apply(SomeApplicable)
。 除方法簽名外,這兩種方法都不是指不可訪問的接口Applicable
。 也就是說,在匿名類的代碼中的任何地方都沒有解決Applicable
接口(JVMS§5.4.3) 。
請注意,可以從Test
成功調用apply(Applicable)
,因為在解析invokeinterface
指令(JVMS§5.4.3.4)期間,方法簽名中的類型不會被解析。
通過使用引導方法LambdaMetafactory.metafactory
執行invokedynamic
字節碼來獲得lambda的實例:
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
用於構造lambda的靜態參數是:
void (a.Applicable)
: void (a.Applicable)
; void (a.SomeApplicable)
。 所有這些參數都在invokedynamic
引導過程(JVMS§5.4.3.6)中得到解決。
現在關鍵點:解析MethodType解析其方法描述符中給出的所有類和接口(JVMS§5.4.3.5) 。 特別是,JVM嘗試代表Test
類解析a.Applicable
,並因IllegalAccessError
而失敗。 然后,根據invokedynamic
的規范,錯誤被包裝到BootstrapMethodError
。
要解決IllegalAccessError
,您需要在可公開訪問的SomeApplicable
接口中顯式添加橋接方法:
public interface SomeApplicable extends Applicable<SomeApplicable> {
@Override
void apply(SomeApplicable self);
}
在這種情況下,lambda將實現apply(SomeApplicable)
方法而不是apply(Applicable)
。 相應的invokedynamic
指令將引用(La/SomeApplicable;)V
MethodType,它將成功解析。
注意:僅更改SomeApplicable
接口是不夠的。 您必須使用新版本的SomeApplicable
重新編譯Test
,以便使用正確的MethodTypes生成invokedynamic
。 我已經在從8u31到最新的9-ea的幾個JDK上驗證了這一點,並且所討論的代碼沒有錯誤。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.