22

1 つのパッケージ ( a) には、2 つの機能インターフェイスがあります。

package a;

@FunctionalInterface
interface Applicable<A extends Applicable<A>> {

    void apply(A self);
}

-

package a;

@FunctionalInterface
public interface SomeApplicable extends Applicable<SomeApplicable> {
}

スーパーインターフェイスの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);
    }
}

最初の実装は匿名クラスを使用しており、問題なく動作します。一方、2 番目のものは正常にコンパイルされますが、インターフェイスにアクセスしようとすると、 ajava.lang.BootstrapMethodErrorが原因で実行時に a をスローして失敗します。java.lang.IllegalAccessErrorApplicable

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

ラムダ式が匿名クラスと同じように機能するか、コンパイル時にエラーが発生するかのどちらかであれば、より理にかなっていると思います。だから、私はここで何が起こっているのだろうと思っています。


スーパーインターフェースを削除して、次のようにメソッドを宣言しようとしましたSomeApplicable

package a;

@FunctionalInterface
public interface SomeApplicable {

    void apply(SomeApplicable self);
}

これは明らかに機能しますが、バイトコードの違いを確認できます。

ラムダ式からコンパイルされた合成lambda$0メソッドはどちらの場合も同じように見えますが、ブートストラップ メソッドの下のメソッド引数に 1 つの違いを見つけることができました。

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

ラムダメタファクトリーがどのように機能するかはよくわかりませんが、これが重要な違いかもしれません。


また、次のようにapplyメソッドを明示的に宣言しようとしました。SomeApplicable

package a;

@FunctionalInterface
public interface SomeApplicable extends Applicable<SomeApplicable> {

    @Override
    void apply(SomeApplicable self);
}

これでメソッドapply(SomeApplicable)が実際に存在し、コンパイラは のブリッジ メソッドを生成し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
4

1 に答える 1

12

私が見る限り、JVM はすべて正しく動作します。

applymethod が で宣言されているが で宣言されApplicableていない場合SomeApplicable、匿名クラスは機能するはずですが、ラムダは機能しません。バイトコードを調べてみましょう。

匿名クラス Test$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)とオーバーライドされたメソッドの両方を生成しますapply(SomeApplicable)Applicableどちらのメソッドも、メソッド シグネチャを除いて、アクセスできないインターフェイスを参照していません。つまり、Applicableインターフェイスは無名クラスのコードのどこにも解決されません(JVMS §5.4.3) 。

メソッド シグネチャの型は命令の解決中に解決されないため(JVMS §5.4.3.4)apply(Applicable)から を正常に呼び出すことができることに注意してください。Testinvokeinterface

ラムダ

invokedynamicブートストラップ メソッドを使用してバイトコードを実行すると、ラムダのインスタンスが取得されます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

ラムダの構築に使用される静的引数は次のとおりです。

  1. 実装されたインターフェースの MethodType: void (a.Applicable);
  2. MethodHandle を実装に向けます。
  3. ラムダ式の有効な MethodType: void (a.SomeApplicable)

これらの引数はすべて、invokedynamicブートストラップ プロセス中に解決されます(JVMS §5.4.3.6)

ここで重要な点: MethodType を解決するには、そのメソッド記述子で指定されたすべてのクラスとインターフェースが解決されます(JVMS §5.4.3.5)。特に、JVM はクラスa.Applicableに代わって解決を試み、. で失敗します。次に、 の仕様に従って、エラーは にラップされます。TestIllegalAccessErrorinvokedynamicBootstrapMethodError

ブリッジ方式

を回避IllegalAccessErrorするには、公開されているSomeApplicableインターフェイスにブリッジ メソッドを明示的に追加する必要があります。

public interface SomeApplicable extends Applicable<SomeApplicable> {
    @Override
    void apply(SomeApplicable self);
}

この場合、ラムダはapply(SomeApplicable)の代わりにメソッドを実装しapply(Applicable)ます。対応するinvokedynamic命令は(La/SomeApplicable;)V、正常に解決される MethodType を参照します。

SomeApplicable注:インターフェイスだけを変更するだけでは十分ではありません。適切な MethodTypes で生成するにTestは、新しいバージョンの で再コンパイルする必要があります。8u31 から最新の 9-ea までのいくつかの JDK でこれを確認しましたが、問題のコードはエラーなしで動作しました。SomeApplicableinvokedynamic

于 2016-10-30T23:15:27.757 に答える