25

いくつかの基本的なラムダの演習を行っているときに、明らかに同一の匿名内部クラスからの出力が、ラムダとは異なる出力を与えていました。

interface Supplier<T> {

    T get(T t);
}

シナリオ #1

Supplier<Integer> s1 = new Supplier<Integer>() {
    @Override
    public Integer get(Integer t) {
        return t;
    }
};
Supplier<Integer> s2 = t -> t;
System.out.println(s1.get(2));
System.out.println(s2.get(2));

出力2および2。ここには新しいものはありません。


しかし、私がこれを行うとき:

シナリオ 2

Supplier<Integer> s1 = new Supplier<Integer>() {
    @Override
    public Integer get(Integer t) {
        return t++;
    }
};
Supplier<Integer> s2 = t -> t++;
System.out.println(s1.get(2));
System.out.println(s2.get(2));

出力2および3

質問: 両方の出力が同一であるべきではありませんか? 何か不足していますか?


完全を期すために: シナリオ #3

Supplier<Integer> s1 = new Supplier<Integer>() {
    @Override
    public Integer get(Integer t) {
        return ++t;
    }
};
Supplier<Integer> s2 = t -> ++t;
System.out.println(s1.get(2));
System.out.println(s2.get(2));

出力3および3。ここでも新しいことは何もありません。

更新: 1.8.0-b132 から引き続き同じ出力が得られます

更新 #2: バグ レポート: https://bugs.openjdk.java.net/browse/JDK-8038420

更新 #3: バグは javac で修正されました。現在、同じ結果を取得できるはずです。

4

1 に答える 1

14

生成されたバイトコードによると:

Java(TM) SE ランタイム環境 (ビルド 1.8.0-b132)

ラムダ:

 private static java.lang.Integer lambda$main$0(java.lang.Integer);
   descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer;
   flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
   Code:
     stack=2, locals=2, args_size=1
        0: aload_0
        1: invokevirtual #9                  // Method java/lang/Integer.intValue:()I
        4: iconst_1
        5: iadd
        6: invokestatic  #6                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        9: dup
       10: astore_0
       11: astore_1
       12: aload_0
       13: areturn
     LineNumberTable:
       line 20: 0
     LocalVariableTable:
       Start  Length  Slot  Name   Signature
           0      14     0     t   Ljava/lang/Integer;

匿名クラス:

  public java.lang.Integer get(java.lang.Integer);
    descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=2
         0: aload_1
         1: astore_2
         2: aload_1
         3: invokevirtual #2                  // Method java/lang/Integer.intValue:()I
         6: iconst_1
         7: iadd
         8: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        11: dup
        12: astore_1
        13: astore_3
        14: aload_2
        15: areturn
      LineNumberTable:
        line 16: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  this   LTest$1;
            0      16     1     t   Ljava/lang/Integer;

ご覧のとおり、匿名クラスでは、ローカル変数テーブル (メソッド パラメーターt ) から変数をロードした後、別の変数 ( astore_2 ) にパラメーターのコピーをランタイムに格納し、このパラメーターのコピーを戻り値として使用します。

Lambda メソッドはパラメータのコピーを作成しません (load -> unbox -> add 1 -> box -> store -> load -> return)。

アップデート

それは間違いなくjavacのバグです。

http://hg.openjdk.java.net/jdk8u/jdk8uからソースを入手しました

匿名クラスとラムダは、次の中間表現に変換されます。

@Override()
public Integer get(Integer t) {
    return (let /*synthetic*/ final Integer $112619572 = t in 
       (let /*synthetic*/ final Integer $1295226194 = t = Integer.valueOf((int)(t.intValue() + 1)) in $112619572));
}

/*synthetic*/ private static Integer lambda$main$0(final Integer t) {
    return (let /*synthetic*/ final Integer $1146147158 = t = Integer.valueOf((int)(t.intValue() + 1)) in t);
}

LambdaToMethod トランスレーターはすべてのパラメーターを FINAL としてマークするため (ソース コードLambdaTranslationContext.translate(…):1899に従って)、ラムダで生成されたメソッド パラメーターは final としてマークされます。

次に、式ビルダーに変数フラグをチェックさせ、最終的な場合は一時変数の生成を省略します (ソース コードLower.abstractRval(…):2277によると)。これは、変更が禁止されていると見なされるためです。

可能な解決策:

  1. ラムダまたは内部でのパラメーターの変更を禁止する
  2. ラムダ生成メソッドのローカル変数 ( LambdaTranslationContext.translate(…):1894 ) とパラメーター ( LambdaTranslationContext.translate(…):1899 )から FINAL フラグを削除します。

     case LOCAL_VAR:
       ret = new VarSymbol(FINAL, name, types.erasure(sym.type), translatedSym);
     ...
    
     case PARAM:
       ret = new VarSymbol(FINAL | PARAMETER, name, types.erasure(sym.type), translatedSym);
     ...
    

FINAL フラグを削除し、次のテストで期待される結果を得ました: https://bugs.openjdk.java.net/browse/JDK-8038420

于 2014-03-26T08:53:33.377 に答える