16

次のクラスを検討してください。

import java.util.Objects;
import java.util.function.Predicate;

public class LambdaVsMethodRef {
    public static void main(String[] args) {
        Predicate<Object> a = Objects::nonNull;
        Predicate<Object> b = x -> x != null;
    }
}

最初の述語はメソッド参照から作成され、もう 1 つはラムダ式から作成されます。これらの述語は同じ振る舞いをします (nonNullの本体はただ ですreturn obj != null;)。ラムダは 2 文字短くなります (おそらく、ストリーム パイプラインを 1 行に収めることができます)。

コード スタイル以外に、 と の間に違いはObjects::nonNullありx -> x != nullますか? 別の言い方をすれば、どちらかを優先する必要がありますか?

lambda-dev と lambda-libs-spec-{observers,experts} のメーリング リスト メッセージではisNullnonNullisNotNull(初期の名前) に言及していましたが、この点については触れられていませんでした。(Objects メソッドをラムダで簡単に置き換えることができるので、誰も疑問を呈していないことに驚いていますが、一方で、そうですInteger::sum。)

でバイトコードも見ましたjavap。唯一の違いは、ラムダ メタファクトリ ブートストラップ メソッドに渡されるメソッド ハンドルです。

  BootstrapMethods:
0: #16 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:
    #17 (Ljava/lang/Object;)Z
    #18 invokestatic java/util/Objects.nonNull:(Ljava/lang/Object;)Z
    #17 (Ljava/lang/Object;)Z
1: #16 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:
    #17 (Ljava/lang/Object;)Z
    #20 invokestatic LambdaVsMethodRef.lambda$main$1:(Ljava/lang/Object;)Z
    #17 (Ljava/lang/Object;)Z

もちろん、メタファクトリーは JVM の気まぐれで、メソッド参照とラムダに対して異なることを行うことができるので、あまり証明されません。

4

1 に答える 1

38

ご指摘のとおり、ラムダのセマンティクスx -> x != nullとメソッド参照Objects::nonNullは実質的に同じです。リフレクションなどを使用してクラスを掘り下げる以外に、実際に観察可能な違いを考えるのは難しいです。

ラムダよりもメソッド参照を使用すると、わずかなスペースの利点があります。ラムダを使用すると、ラムダのコードが含まれているクラスのプライベートな静的メソッドにコンパイルされ、ラムダのメタファクトリがこの静的メソッドへの参照で呼び出されます。メソッド参照の場合、メソッドは既にjava.util.Objectsクラスに存在するため、ラムダ メタファクトリは既存のメソッドへの参照で呼び出されます。これにより、スペースが適度に節約されます。

これらの小さなクラスを考えてみましょう:

class LM { // lambda
    static Predicate<Object> a = x -> x != null;
}

class MR { // method reference
    static Predicate<Object> a = Objects::nonNull;
}

(興味のある読者は実行javap -private -cp classes -c -v <class>して、これらのコンパイル方法の詳細な違いを確認してください。)

これにより、ラムダの場合は 1,094 バイト、メソッド参照の場合は 989 バイトになります。(Javac 1.8.0_11.) これは大きな違いではありませんが、プログラムにこのような多数のラムダが含まれる可能性が高い場合は、メソッド参照を使用することによるスペースの節約を考慮することができます。

さらに、メソッド参照はおそらくより多く使用されるため、メソッド参照はラムダよりも JIT コンパイルおよびインライン化される可能性が高くなります。これにより、パフォーマンスがわずかに向上する可能性があります。ただし、これが実際的な違いを生む可能性は低いと思われます。

「コードスタイル以外...」と具体的に言いましたが、これは主にスタイルに関するものです。これらの小さなメソッドは、プログラマーがインライン ラムダの代わりに名前を使用できるように API に特別に追加されました。これにより、コードの理解度が向上することがよくあります。もう 1 つのポイントは、メソッド参照には、入れ子になった Comparator などの難しい型推論の場合に役立つ明示的な型情報が含まれていることが多いということです。(ただし、これは実際には当てはまりませんObjects::nonNull。) キャストまたは明示的に型指定されたラムダ パラメーターを追加すると、多くの混乱が生じるため、これらの場合、メソッド参照が明らかに有利です。

于 2014-08-22T17:07:37.250 に答える