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


240

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.

Function.identity()代わりにstr->str(またはその逆に)使用する理由はありますか?2番目のオプションの方が読みやすいと思います(もちろん、好みの問題です)。しかし、人が好まれるべき「本当の」理由はありますか?


6
結局のところ、違いはありません。
2015年

50
どちらでもかまいません。読みやすい方を選んでください。(心配しないで、幸せになってください。)
ブライアンゲッツ2015年

3
私はt -> tそれがより簡潔であるという理由だけで好むでしょう。
David Conrad、

3
少し関連のない質問ですが、言語設計者がidentity()にT型のパラメーターを持たずにメソッドのインスタンスを返すようにして、メソッドをメソッド参照で使用できるようにする理由を誰かが知っていますか?
Kirill Rakhman、2015年

「アイデンティティ」という言葉は、関数型プログラミングの他の分野で重要な意味を持つため、熟知することには意味があると私は主張します。
orbfish 2015年

回答:


312

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

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

したがって、のFunction.identity()代わりにを使用すると、x -> xメモリが節約される可能性がありますが、x -> xそれよりも読みやすいと本当に思っても、それによって決定が決まることはありませんFunction.identity()

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


5
素敵な答え。デバッグについて疑問があります。それはどのように役立ちますか?x -> xフレームを含む例外スタックトレースを取得することはほとんどありません。ブレークポイントをこのラムダに設定することをお勧めしますか?通常、ブレークポイントを単一式ラムダ(少なくともEclipseでは)に入れるのはそれほど簡単ではありません...
Tagir Valeev '

14
@Tagir Valeev:任意の関数を受け取るコードをデバッグし、その関数のapplyメソッドにステップインできます。その後、ラムダ式のソースコードになってしまう可能性があります。明示的なラムダ式の場合、関数がどこから来たのかがわかり、恒等関数が通過したことを決定する場所を認識する機会があります。Function.identity()その情報を使用すると失われます。その後、コールチェーンは、簡単な例で助けるが、元のイニシエータが...スタックトレースではない場合、例えばマルチスレッド評価を考えること
ホルガー


13
@Wim Deblauwe:おもしろいですが、いつも逆に見るでしょう:ファクトリメソッドがそのドキュメントで明示的に、呼び出しのたびに新しいインスタンスを返すと述べていない場合、それがそうであると想定することはできません。だから、そうでないとしても、驚くべきことではありません。結局のところ、それがの代わりにファクトリメソッドを使用する大きな理由の1つですnewnew Foo(…)正確な型の新しいインスタンスを作成するための保証をFoo、一方、Foo.getInstance(…)(のサブタイプ)の既存のインスタンスを返すことがありFoo...
ホルガー

93

あなたの例ではとの間には大きな差がないstr -> strFunction.identity()、それは単に内部からはt->t

ただしFunction.identity、を使用できないために使用できない場合がありFunctionます。ここを見てください:

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();

とは関係ないをmapToInt予期しているため、コンパイルエラーが発生ToIntFunctionFunctionます。メソッドもありToIntFunctionませんidentity()


3
で置換するとコンパイラエラーが発生する別の例については、stackoverflow.com / q / 38034982/14731を参照してください。i -> iFunction.identity()
ギリ

19
私は好むmapToInt(Integer::intValue)
shmosel

4
@shmoselは問題ありませんが、mapToInt(i -> i)が単純化されているため、両方のソリューションが同様に機能することを言及する価値がありmapToInt( (Integer i) -> i.intValue())ます。私mapToInt(i -> i)がこのコードの意図をよりよく示すので、あなたがより明確であると思うバージョンを使用してください。
プシェモ2017年

1
メソッド参照の使用にはパフォーマンス上の利点があると思いますが、それは主に個人的な好みにすぎません。i -> iアイデンティティ関数のように見えるので、この場合はそうではないので、もっとわかりやすいと思います。
shmosel

@shmoselパフォーマンスの違いについてはあまり言えないので、あなたは正しいかもしれません。しかし、パフォーマンスが問題ではない場合i -> i、私の目標は、IntegerをintにマップするmapToIntこと(かなり適切に示唆されます)であるため、明示的にintValue()メソッドを呼び出さないことです。このマッピングがどのように達成されるは、それほど重要ではありません。だから同意しないことに同意しますが、パフォーマンスの違いの可能性を指摘してくれてありがとう、私はいつかそのことを詳しく調べる必要があります。
プシェモ2017年

44

JDKソースから:

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

したがって、それが構文的に正しい限り、いいえ。


8
これがオブジェクトを作成するラムダに関する上記の答えを無効にするのか、またはこれが特定の実装であるのかと思います。
orbfish 2015年

28
@orbfish:それは完全に一致しています。t->tソースコード内のすべての出現は1つのオブジェクトを作成し、の実装Function.identity()1つの出現です。したがって、呼び出すすべての呼び出しサイトidentity()はその1つのオブジェクトを共有し、ラムダ式t->tを明示的に使用するすべてのサイトは独自のオブジェクトを作成します。このメソッドFunction.identity()は特別なものではありません。一般的に使用されるラムダ式をカプセル化するファクトリメソッドを作成し、ラムダ式を繰り返す代わりにそのメソッドを呼び出すと、現在の実装では、メモリを節約できます。
Holger

これはt->t、メソッドが呼び出されるたびにコンパイラが新しいオブジェクトの作成を最適化し、メソッドが呼び出されるたびに同じオブジェクトをリサイクルするためだと思いますか?
Daniel Grey

1
@DanielGray決定は実行時に行われます。コンパイラは、invokedynamicいわゆるブートストラップメソッドを実行することにより、最初の実行時にリンクされる命令を挿入します。これは、ラムダ式の場合、にありLambdaMetafactoryます。この実装は、常に同じオブジェクトを返すコンストラクター、ファクトリーメソッド、またはコードにハンドルを返すことを決定します。また、既存のハンドルへのリンクを返すこともできます(現在は発生しません)。
ホルガー

@Holgerアイデンティティへのこの呼び出しはインライン化されず、潜在的にモノモーフィングされる(そして再びインライン化される)と確信していますか?
JasonN、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.