簡體   English   中英

Lambda表達式在運行時失敗並帶有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聲明,但不在SomeApplicableSomeApplicable ,匿名類應該工作,而lambda不應該。 我們來看看字節碼。

匿名類測試$ 1

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)期間,方法簽名中的類型不會被解析。

LAMBDA

通過使用引導方法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的靜態參數是:

  1. 已實現接口的void (a.Applicable)void (a.Applicable) ;
  2. 直接MethodHandle到實現;
  3. lambda表達式的有效MethodType: 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM