web-dev-qa-db-ja.com

Java 8のラムダ、Function.identity()またはt-> t

Function.identity()メソッドの使い方について質問があります。

次のようなコードを想像してみてください。

Arrays.asList("a", "b", "c")
          .stream()
          .map(Function.identity()) // <- This,
          .map(str -> str)          // <- is the same as this.
          .collect(Collectors.toMap(
                       Function.identity(), // <-- And this,
                       str -> str));        // <-- is the same as this.

str->strの代わりにFunction.identity()を使用する(またはその逆)理由があるのでしょうか。私は第二の選択肢が読みやすいと思います(もちろん好みの問題)。しかし、なぜそれが好まれるべきなのか "本当の"理由はありますか?

189
user4464654

現在のJRE実装では、Function.identity()は常に同じインスタンスを返しますが、identifier -> identifierが出現するたびに独自のインスタンスが作成されるだけでなく、異なる実装クラスも作成されます。詳しくは、 ここ を参照してください。

その理由は、コンパイラーがそのラムダ式の自明な本体(x->xの場合、return identifier;と同等)を保持する合成メソッドを生成し、このメソッドを呼び出す機能インターフェースの実装を作成するようにランタイムに指示するためです。そのため、ランタイムは異なるターゲットメソッドのみを認識し、現在の実装はメソッドを分析して特定のメソッドが同等かどうかを判断しません。

そのため、x -> xの代わりにFunction.identity()を使用するとメモリを節約することができますが、x -> xの方がFunction.identity()より読みやすいと判断した場合、それで決定を下すことはできません。

デバッグ情報を有効にしてコンパイルした場合、合成メソッドはラムダ式を保持するソースコード行を指すline debug属性を持つことになります。したがって、特定のFunctionインスタンスのソースを見つける可能性があります。デバッグ対照的に、操作のデバッグ中にFunction.identity()によって返されたインスタンスに遭遇した場合、誰がそのメソッドを呼び出してインスタンスを操作に渡したのかわかりません。

241
Holger

あなたの例ではstr -> strFunction.identity()の間に大きな違いはありません。なぜなら内部的にそれは単にt->tだからです。

しかし、Functionを使用できないため、Function.identityを使用できないことがあります。ここで見てみましょう:

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);

これはうまくコンパイルされます

int[] arrayOK = list.stream().mapToInt(i -> i).toArray();

あなたがコンパイルしようとした場合しかし

int[] arrayProblem = list.stream().mapToInt(Function.identity()).toArray();

mapToIntToIntFunctionを必要とするため、コンパイルエラーになります。これはFunctionとは関係ありません。またToIntFunctionidentity()メソッドを持っていません。

75
Pshemo

JDKソースから

static <T> Function<T, T> identity() {
    return t -> t;
}

それで、それが構文的に正しい限り、いいえ。

38
JasonN