Java 8 lambdas, Function.identity () atau t-> t

240

Saya punya pertanyaan tentang penggunaan Function.identity() metode ini.

Bayangkan kode berikut:

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.

Apakah ada alasan mengapa Anda harus menggunakan Function.identity()daripada str->str(atau sebaliknya). Saya pikir pilihan kedua lebih mudah dibaca (soal selera tentu saja). Tetapi, adakah alasan "nyata" mengapa seseorang harus lebih disukai?

Przemysław Głębocki
sumber
6
Pada akhirnya, tidak, ini tidak akan membuat perbedaan.
fge
50
Baik baik saja. Pergi dengan mana pun yang menurut Anda lebih mudah dibaca. (Jangan khawatir, berbahagialah.)
Brian Goetz
3
Saya lebih suka t -> thanya karena lebih ringkas.
David Conrad
3
Pertanyaan yang sedikit tidak berhubungan, tetapi apakah ada yang tahu mengapa perancang bahasa membuat identitas () mengembalikan instance Function alih-alih memiliki parameter tipe T dan mengembalikannya sehingga metode dapat digunakan dengan referensi metode?
Kirill Rakhman
Saya berpendapat ada gunanya menjadi fasih dengan kata "identitas," karena memiliki arti penting dalam bidang pemrograman fungsional lainnya.
orbfish

Jawaban:

312

Pada implementasi JRE saat ini, Function.identity()akan selalu mengembalikan instance yang sama sementara setiap kemunculan identifier -> identifiertidak hanya akan membuat instance sendiri tetapi bahkan memiliki kelas implementasi yang berbeda. Untuk detail lebih lanjut, lihat di sini .

Alasannya adalah bahwa kompiler menghasilkan metode sintetis yang memegang tubuh sepele dari ekspresi lambda (dalam kasus x->x, setara denganreturn identifier; ) dan memberitahu runtime untuk membuat implementasi dari antarmuka fungsional yang memanggil metode ini. Jadi runtime hanya melihat metode target yang berbeda dan implementasi saat ini tidak menganalisis metode untuk mengetahui apakah metode tertentu setara.

Jadi, Function.identity()alih-alih menggunakan x -> xmungkin menghemat memori, tetapi itu seharusnya tidak mendorong keputusan Anda jika Anda benar-benar berpikir itu x -> xlebih mudah dibaca daripada Function.identity().

Anda juga dapat mempertimbangkan bahwa ketika kompilasi dengan informasi debug diaktifkan, metode sintetik akan memiliki atribut debug baris yang menunjuk ke baris kode sumber yang menahan ekspresi lambda, oleh karena itu Anda memiliki peluang untuk menemukan sumber Functioninstance tertentu saat debugging . Sebaliknya, ketika menemukan instance yang dikembalikan oleh Function.identity()selama debugging suatu operasi, Anda tidak akan tahu siapa yang memanggil metode itu dan meneruskan instance tersebut ke operasi.

Holger
sumber
5
Jawaban bagus. Saya ragu tentang debugging. Bagaimana ini bisa bermanfaat? Sangat tidak mungkin untuk mendapatkan jejak tumpukan pengecualian yang melibatkan x -> xbingkai. Apakah Anda menyarankan untuk mengatur breakpoint ke lambda ini? Biasanya tidak mudah untuk menempatkan breakpoint ke dalam ekspresi tunggal lambda (setidaknya di Eclipse) ...
Tagir Valeev
14
@ Tarir Valeev: Anda dapat men-debug kode yang menerima fungsi sewenang-wenang dan masuk ke metode penerapan fungsi itu. Maka Anda mungkin berakhir pada kode sumber ekspresi lambda. Dalam kasus ekspresi lambda yang eksplisit, Anda akan tahu dari mana fungsi itu berasal dan memiliki kesempatan untuk mengenali di mana keputusan untuk dilewati meskipun fungsi identitas dibuat. Saat menggunakan Function.identity()informasi itu hilang. Kemudian, rantai panggilan dapat membantu dalam kasus-kasus sederhana tetapi pikirkan, misalnya evaluasi multi-utas di mana inisiator asli tidak berada dalam jejak tumpukan ...
Holger
2
Menarik dalam konteks ini: blog.codefx.org/java/instansi-non-capturing-lambdas
Wim Deblauwe
13
@ Win Deblauwe: Menarik, tetapi saya akan selalu melihatnya sebaliknya: jika metode pabrik tidak secara eksplisit menyatakan dalam dokumentasinya bahwa itu akan mengembalikan contoh baru pada setiap doa, Anda tidak dapat berasumsi bahwa itu akan terjadi. Jadi seharusnya tidak mengejutkan jika tidak. Lagi pula, itu salah satu alasan utama untuk menggunakan metode pabrik bukan new. new Foo(…)jaminan untuk membuat instance baru dari tipe yang tepat Foo, sedangkan, Foo.getInstance(…)dapat mengembalikan instance yang ada (subtipe dari) Foo...
Holger
93

Dalam contoh Anda tidak ada perbedaan besar antara str -> strdan Function.identity()karena secara internal itu sederhana t->t.

Tetapi terkadang kita tidak bisa menggunakan Function.identitykarena kita tidak bisa menggunakan a Function. Lihatlah di sini:

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

ini akan dikompilasi dengan baik

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

tetapi jika Anda mencoba untuk mengkompilasi

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

Anda akan mendapatkan kesalahan kompilasi sejak mapToIntmengharapkan ToIntFunction, yang tidak terkait dengan Function. Juga ToIntFunctiontidak punya identity()metode.

Pshemo
sumber
3
Lihat stackoverflow.com/q/38034982/14731 untuk contoh lain di mana penggantian i -> idengan Function.identity()akan menghasilkan kesalahan kompiler.
Gili
19
Saya lebih suka mapToInt(Integer::intValue).
shmosel
4
@shelel yang OK tapi perlu disebutkan bahwa kedua solusi akan bekerja sama karena mapToInt(i -> i)penyederhanaan mapToInt( (Integer i) -> i.intValue()). Gunakan versi mana pun yang menurut Anda lebih jelas, bagi saya mapToInt(i -> i)lebih baik tunjukkan niat kode ini.
Pshemo
1
Saya pikir mungkin ada manfaat kinerja dalam menggunakan referensi metode, tetapi sebagian besar hanya preferensi pribadi. Saya menemukan ini lebih deskriptif, karena i -> iterlihat seperti fungsi identitas, yang tidak ada dalam kasus ini.
shmosel
@shelel Saya tidak bisa bicara banyak tentang perbedaan kinerja sehingga Anda mungkin benar. Tetapi jika kinerja bukan masalah saya akan tetap dengan i -> ikarena tujuan saya adalah untuk memetakan Integer ke int (yang mapToIntmenyarankan dengan sangat baik) untuk tidak secara eksplisit memanggil intValue()metode. Bagaimana pemetaan ini akan dicapai tidak terlalu penting. Jadi mari kita setuju untuk tidak setuju tetapi terima kasih karena menunjukkan kemungkinan perbedaan kinerja, saya perlu melihat lebih dekat pada suatu hari nanti.
Pshemo
44

Dari sumber JDK :

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

Jadi, tidak, asalkan secara sintaksis benar.

JasonN
sumber
8
Saya bertanya-tanya apakah ini membatalkan jawaban di atas terkait dengan lambda membuat objek - atau jika ini adalah implementasi tertentu.
orbfish
28
@orbfish: itu benar-benar sesuai. Setiap kemunculan t->tdalam kode sumber dapat membuat satu objek dan implementasi Function.identity()adalah satu kemunculan. Jadi semua situs panggilan yang memanggil identity()akan membagikan satu objek itu sementara semua situs secara eksplisit menggunakan ekspresi lambda t->takan membuat objek mereka sendiri. Metode Function.identity()ini tidak istimewa dengan cara apa pun, setiap kali Anda membuat metode pabrik merangkum ekspresi lambda yang umum digunakan dan memanggil metode itu alih-alih mengulangi ekspresi lambda, Anda dapat menghemat memori, mengingat implementasi saat ini .
Holger
Saya menduga ini karena kompiler mengoptimalkan pembuatan t->tobjek baru setiap kali metode dipanggil dan mendaur ulang objek yang sama setiap kali metode dipanggil?
Daniel Gray
1
@DanielGray keputusan dibuat saat runtime. Kompiler memasukkan invokedynamicinstruksi yang dihubungkan pada eksekusi pertama dengan mengeksekusi yang disebut metode bootstrap, yang dalam kasus ekspresi lambda terletak di LambdaMetafactory. Implementasi ini memutuskan untuk mengembalikan pegangan ke konstruktor, metode pabrik, atau kode yang selalu mengembalikan objek yang sama. Mungkin juga memutuskan untuk mengembalikan tautan ke pegangan yang sudah ada (yang saat ini tidak terjadi).
Holger
@ Holger Apakah Anda yakin panggilan untuk identitas ini tidak akan digarisbawahi sehingga berpotensi menjadi monomorphized (dan disejajarkan lagi)?
JasonN