Apakah ekspresi lambda memiliki kegunaan selain menyimpan baris kode?

120

Apakah ekspresi lambda memiliki kegunaan selain menyimpan baris kode?

Apakah ada fitur khusus yang disediakan oleh lambda yang memecahkan masalah yang tidak mudah dipecahkan? Penggunaan tipikal yang pernah saya lihat adalah alih-alih menulis ini:

Comparator<Developer> byName = new Comparator<Developer>() {
  @Override
  public int compare(Developer o1, Developer o2) {
    return o1.getName().compareTo(o2.getName());
  }
};

Kita bisa menggunakan ekspresi lambda untuk mempersingkat kode:

Comparator<Developer> byName =
(Developer o1, Developer o2) -> o1.getName().compareTo(o2.getName());
Vikash
sumber
27
Perhatikan bahwa itu bisa lebih pendek: (o1, o2) -> o1.getName().compareTo(o2.getName())
Thorbjørn Ravn Andersen
88
atau lebih pendek lagi:Comparator.comparing(Developer::getName)
Thomas Kläger
31
Saya tidak akan meremehkan manfaat itu. Ketika segala sesuatunya jauh lebih mudah, Anda cenderung melakukan sesuatu dengan cara yang "benar". Seringkali ada beberapa ketegangan antara melakukan penulisan kode yang lebih mudah dipelihara dalam jangka panjang, vs yang lebih mudah untuk ditulis dalam jangka pendek. Lambdas mengurangi ketegangan itu, dan membantu Anda menulis kode yang lebih bersih.
yshavit
5
Lambdas adalah closure, yang membuat penghematan ruang lebih besar, karena sekarang Anda tidak perlu menambahkan konstruktor berparameter, field, kode tugas, dll ke kelas pengganti Anda.
CodesInChaos

Jawaban:

116

Ekspresi lambda tidak mengubah kumpulan masalah yang dapat Anda selesaikan dengan Java secara umum, tetapi yang pasti membuat pemecahan masalah tertentu lebih mudah, hanya untuk alasan yang sama kami tidak lagi memprogram dalam bahasa assembly. Menghapus tugas yang berlebihan dari pekerjaan programmer membuat hidup lebih mudah dan memungkinkan untuk melakukan hal-hal yang bahkan tidak akan Anda sentuh sebaliknya, hanya untuk jumlah kode yang harus Anda hasilkan (secara manual).

Tetapi ekspresi lambda tidak hanya menyimpan baris kode. Ekspresi Lambda memungkinkan Anda untuk menentukan fungsi , sesuatu yang dapat Anda gunakan kelas dalam anonim sebagai solusi sebelumnya, itulah mengapa Anda bisa mengganti kelas dalam anonim dalam kasus ini, tetapi tidak secara umum.

Terutama, ekspresi lambda didefinisikan secara independen ke antarmuka fungsional tempat mereka akan dikonversi, sehingga tidak ada anggota yang diwariskan yang dapat mereka akses, lebih jauh, mereka tidak dapat mengakses instance dari tipe yang mengimplementasikan antarmuka fungsional. Dalam ungkapan lambda, thisdan supermemiliki arti yang sama dengan konteks sekitarnya, lihat juga jawaban ini . Selain itu, Anda tidak dapat membuat variabel lokal baru yang membayangi variabel lokal dari konteks sekitarnya. Untuk tugas yang dimaksudkan dalam mendefinisikan suatu fungsi, tindakan ini menghapus banyak sumber kesalahan, tetapi juga menyiratkan bahwa untuk kasus penggunaan lain, mungkin ada kelas dalam anonim yang tidak dapat diubah menjadi ekspresi lambda, meskipun mengimplementasikan antarmuka fungsional.

Lebih lanjut, new Type() { … }jaminan konstruksi untuk menghasilkan instance baru yang berbeda (seperti newbiasa). Instance kelas dalam anonim selalu menyimpan referensi ke instans luarnya jika dibuat dalam non- statickonteks. Sebaliknya, ekspresi lambda hanya menangkap referensi thissaat dibutuhkan, yaitu jika mereka mengakses thisatau bukan staticanggota. Dan mereka menghasilkan instance dari identitas yang sengaja tidak ditentukan, yang memungkinkan implementasi untuk memutuskan pada waktu proses apakah akan menggunakan kembali instance yang ada (lihat juga " Apakah ekspresi lambda membuat objek di heap setiap kali dijalankan? ").

Perbedaan ini berlaku untuk contoh Anda. Konstruksi kelas dalam anonim Anda akan selalu menghasilkan instance baru, juga dapat menangkap referensi ke instance luar, sedangkan Anda (Developer o1, Developer o2) -> o1.getName().compareTo(o2.getName())adalah ekspresi lambda non-capturing yang akan mengevaluasi ke singleton dalam implementasi tipikal. Selanjutnya, itu tidak menghasilkan .classfile di hard drive Anda.

Mengingat perbedaan terkait keduanya, semantik dan kinerja, ekspresi lambda dapat mengubah cara pemrogram akan memecahkan masalah tertentu di masa mendatang, tentu saja, juga karena API baru yang merangkul gagasan pemrograman fungsional yang memanfaatkan fitur bahasa baru. Lihat juga ekspresi lambda Java 8 dan nilai kelas satu .

Holger
sumber
1
Pada dasarnya, ekspresi lambda adalah jenis pemrograman fungsional. Mengingat bahwa tugas apa pun dapat dilakukan dengan pemrograman fungsional atau pemrograman imperatif, tidak ada keuntungan kecuali dari keterbacaan dan pemeliharaan , yang mungkin lebih besar daripada masalah lainnya.
Draco18s tidak lagi mempercayai SE
1
@Trilarion: Anda dapat mengisi seluruh buku dengan definisi fungsi . Anda juga harus memutuskan apakah akan fokus pada tujuan atau pada aspek yang didukung oleh ekspresi lambda Java. Tautan terakhir dari jawaban saya (yang ini ) sudah membahas beberapa aspek ini dalam konteks Jawa. Tetapi untuk memahami jawaban saya, cukup memahami bahwa fungsi adalah konsep yang berbeda dari kelas. Untuk lebih jelasnya, sudah ada sumber daya dan Tanya Jawab…
Holger
1
@Trilarion: keduanya, fungsi tidak memungkinkan untuk menyelesaikan lebih banyak masalah, kecuali jika Anda menganggap jumlah ukuran kode / kompleksitas atau solusi yang diperlukan oleh solusi lain sebagai tidak dapat diterima (seperti yang dikatakan di paragraf pertama), dan sudah ada cara untuk mendefinisikan fungsi , seperti yang dikatakan, kelas dalam dapat digunakan sebagai solusi untuk tidak adanya cara yang lebih baik untuk mendefinisikan fungsi (tetapi kelas dalam bukanlah fungsi, karena kelas tersebut dapat melayani lebih banyak tujuan). Ini sama dengan OOP — itu tidak memungkinkan untuk melakukan apa pun yang tidak bisa Anda lakukan dengan perakitan, tetapi tetap, dapatkah Anda membayangkan aplikasi seperti Eclipse yang ditulis dalam perakitan?
Holger
3
@EricDuminil: bagi saya, Kelengkapan Turing terlalu teoretis. Misalnya, apakah Java adalah Turing lengkap atau tidak, Anda tidak dapat menulis driver perangkat Windows di Java. (Atau di PostScript, yang juga merupakan Turing complete) Menambahkan fitur ke Java yang memungkinkan melakukan itu, akan mengubah kumpulan masalah yang dapat Anda selesaikan dengannya, namun, itu tidak akan terjadi. Jadi saya lebih suka titik Majelis; karena semua yang dapat Anda lakukan, diimplementasikan dalam instruksi CPU yang dapat dieksekusi, tidak ada yang tidak dapat Anda lakukan di Assembly yang mendukung setiap instruksi CPU (juga lebih mudah dipahami daripada formalisme mesin Turing).
Holger
2
@abelito itu umum untuk semua konstruksi pemrograman tingkat yang lebih tinggi, untuk memberikan lebih sedikit "visibilitas ke apa yang sebenarnya terjadi", karena itu semua tentang, lebih sedikit detail, lebih fokus pada masalah pemrograman yang sebenarnya. Anda dapat menggunakannya untuk membuat kode menjadi lebih baik atau lebih buruk; itu hanya alat. Seperti fitur voting; Ini adalah alat yang dapat Anda gunakan dengan cara yang diinginkan, untuk memberikan penilaian yang tepat tentang kualitas sebenarnya dari sebuah pos. Atau Anda menggunakannya untuk memberi suara rendah pada jawaban yang diuraikan dan masuk akal, hanya karena Anda membenci lambda.
Holger
52

Bahasa pemrograman bukan untuk dijalankan oleh mesin.

Mereka adalah untuk dipikirkan oleh para programmer .

Bahasa adalah percakapan dengan kompiler untuk mengubah pikiran kita menjadi sesuatu yang dapat dijalankan oleh mesin. Salah satu keluhan utama tentang Jawa dari orang-orang yang datang ke sana dari bahasa lain (atau tinggalkan itu untuk bahasa lain) yang digunakan untuk menjadi bahwa itu memaksa model mental tertentu pada programmer (yaitu semuanya kelas).

Saya tidak akan mempertimbangkan apakah itu baik atau buruk: semuanya timbal balik. Tetapi Java 8 lambda memungkinkan pemrogram untuk berpikir dalam kaitannya dengan fungsi , yang sebelumnya tidak dapat Anda lakukan di Java.

Ini sama dengan programmer prosedural yang belajar berpikir dalam istilah kelas ketika mereka datang ke Java: Anda melihat mereka secara bertahap berpindah dari kelas yang mengagungkan struct dan memiliki kelas 'pembantu' dengan sekumpulan metode statis dan beralih ke sesuatu yang lebih menyerupai desain OO rasional (mea culpa).

Jika Anda hanya menganggapnya sebagai cara yang lebih singkat untuk mengekspresikan kelas dalam anonim maka Anda mungkin tidak akan menganggapnya sangat mengesankan dengan cara yang sama seperti pemrogram prosedural di atas mungkin tidak menganggap kelas sebagai peningkatan yang besar.

Jared Smith
sumber
5
Hati-hati dengan itu, saya dulu berpikir bahwa OOP adalah segalanya, dan kemudian saya sangat bingung dengan arsitektur entitas-komponen-sistem (whaddaya berarti Anda tidak memiliki kelas untuk setiap jenis objek game ?! dan setiap objek game? bukan objek OOP ?!)
user253751
3
Dengan kata lain, berpikir dalam OOP, daripada hanya berpikir * o f * class sebagai alat lain, y membatasi pemikiran saya dengan cara yang buruk.
pengguna253751
2
@immibis: ada banyak cara berbeda untuk "berpikir dalam OOP", jadi selain kesalahan umum yang membingungkan "berpikir dalam OOP" dengan "berpikir di kelas", ada banyak cara untuk berpikir dengan cara yang kontra produktif. Sayangnya, itu terlalu sering terjadi sejak awal, karena bahkan buku dan tutorial pun mengajarkan yang lebih rendah, menunjukkan contoh anti pola dan menyebarkan pemikiran itu. Asumsi kebutuhan untuk "memiliki kelas untuk setiap jenis" apa pun, hanyalah satu contoh, bertentangan dengan konsep abstraksi, demikian juga, tampaknya ada mitos "OOP berarti menulis sebanyak mungkin boilerplate", dll
Holger
@immibis meskipun dalam contoh itu tidak membantu Anda, anekdot itu benar-benar mendukung intinya: bahasa dan paradigma menyediakan kerangka kerja untuk menyusun pemikiran Anda dan mengubahnya menjadi sebuah desain. Dalam kasus Anda, Anda menabrak tepi kerangka, tetapi dalam konteks lain melakukan itu mungkin membantu Anda tersesat ke dalam bola lumpur besar dan menghasilkan desain yang jelek. Oleh karena itu, saya berasumsi, "semuanya trade-off". Menjaga manfaat terakhir sambil menghindari masalah sebelumnya adalah pertanyaan kunci ketika memutuskan seberapa "besar" untuk membuat bahasa.
Leushenko
@immibis itulah mengapa penting untuk mempelajari sekumpulan paradigma dengan baik : masing-masing menjangkau Anda tentang kekurangan yang lain.
Jared Smith
36

Menyimpan baris kode dapat dilihat sebagai fitur baru, jika memungkinkan Anda untuk menulis sebagian besar logika dengan cara yang lebih pendek dan lebih jelas, yang membutuhkan lebih sedikit waktu bagi orang lain untuk membaca dan memahami.

Tanpa ekspresi lambda (dan / atau referensi metode), Streampipeline akan jauh lebih sulit dibaca.

Pikirkan, misalnya, bagaimana Streampipeline berikut akan terlihat jika Anda mengganti setiap ekspresi lambda dengan instance kelas anonim.

List<String> names =
    people.stream()
          .filter(p -> p.getAge() > 21)
          .map(p -> p.getName())
          .sorted((n1,n2) -> n1.compareToIgnoreCase(n2))
          .collect(Collectors.toList());

Itu akan:

List<String> names =
    people.stream()
          .filter(new Predicate<Person>() {
              @Override
              public boolean test(Person p) {
                  return p.getAge() > 21;
              }
          })
          .map(new Function<Person,String>() {
              @Override
              public String apply(Person p) {
                  return p.getName();
              }
          })
          .sorted(new Comparator<String>() {
              @Override
              public int compare(String n1, String n2) {
                  return n1.compareToIgnoreCase(n2);
              }
          })
          .collect(Collectors.toList());

Ini jauh lebih sulit untuk ditulis daripada versi dengan ekspresi lambda, dan jauh lebih rentan terhadap kesalahan. Ini juga lebih sulit untuk dipahami.

Dan ini adalah jalur pipa yang relatif pendek.

Agar ini dapat dibaca tanpa ekspresi lambda dan referensi metode, Anda harus menentukan variabel yang menampung berbagai contoh antarmuka fungsional yang digunakan di sini, yang akan memisahkan logika pipeline, sehingga lebih sulit untuk dipahami.

Eran
sumber
17
Saya pikir ini adalah wawasan kunci. Dapat dikatakan bahwa sesuatu secara praktis tidak mungkin dilakukan dalam bahasa pemrograman jika overhead sintaksis dan kognitif terlalu besar untuk digunakan, sehingga pendekatan lain akan lebih unggul. Oleh karena itu, dengan mengurangi overhead sintaksis dan kognitif (seperti yang dilakukan lambda dibandingkan dengan kelas anonim), pipeline seperti di atas menjadi mungkin. Lebih banyak omong kosong, jika menulis sepotong kode membuat Anda dipecat dari pekerjaan Anda, dapat dikatakan bahwa itu tidak mungkin.
Sebastian Redl
Nitpick: Anda tidak benar-benar membutuhkannya Comparatorkarena sortedperbandingannya secara alami. Mungkin mengubahnya menjadi membandingkan panjang string atau serupa, untuk membuat contoh menjadi lebih baik?
tobias_k
@tobiask tidak masalah. Saya mengubahnya compareToIgnoreCaseuntuk membuatnya berperilaku berbeda dari sorted().
Eran
1
compareToIgnoreCasemasih merupakan contoh yang buruk, karena Anda cukup menggunakan String.CASE_INSENSITIVE_ORDERkomparator yang ada . Saran @ tobias_k untuk membandingkan berdasarkan panjang (atau properti lain yang tidak memiliki pembanding bawaan) lebih cocok. Dilihat dari contoh kode SO Q&A, lambda menyebabkan banyak implementasi komparator yang tidak perlu…
Holger
Apakah saya satu-satunya yang menganggap contoh kedua lebih mudah dipahami?
Clark Battle
8

Iterasi internal

Saat mengulang Koleksi Java, sebagian besar pengembang cenderung mendapatkan elemen dan kemudian memprosesnya . Ini, keluarkan item itu dan kemudian gunakan, atau masukkan kembali, dll. Dengan Java versi pra-8, Anda dapat mengimplementasikan kelas dalam dan melakukan sesuatu seperti:

numbers.forEach(new Consumer<Integer>() {
    public void accept(Integer value) {
        System.out.println(value);
    }
});

Sekarang dengan Java 8 Anda dapat melakukan lebih baik dan lebih sedikit verbose dengan:

numbers.forEach((Integer value) -> System.out.println(value));

atau lebih baik

numbers.forEach(System.out::println);

Perilaku sebagai argumen

Tebak kasus berikut:

public int sumAllEven(List<Integer> numbers) {
    int total = 0;

    for (int number : numbers) {
        if (number % 2 == 0) {
            total += number;
        }
    } 
    return total;
}

Dengan antarmuka Java 8 Predicate Anda dapat melakukan lebih baik seperti ini:

public int sumAll(List<Integer> numbers, Predicate<Integer> p) {
    int total = 0;

    for (int number : numbers) {
        if (p.test(number)) {
            total += number;
        }
    }
    return total;
}

Menyebutnya seperti:

sumAll(numbers, n -> n % 2 == 0);

Sumber: DZone - Mengapa Kami Membutuhkan Ekspresi Lambda di Jawa

sama-sama
sumber
Mungkin kasus pertama adalah cara untuk menyimpan beberapa baris kode, tetapi ini membuat kode lebih mudah dibaca
juga
4
Bagaimana iterasi internal merupakan fitur lambda? Masalah sederhananya adalah secara teknis lambda tidak membiarkan Anda melakukan apa pun yang tidak dapat Anda lakukan sebelum java-8. Ini hanya cara yang lebih ringkas untuk menyebarkan kode.
Ousmane D.
@ Aominè Dalam hal ini numbers.forEach((Integer value) -> System.out.println(value));ekspresi lambda dibuat dari dua bagian, satu di kiri simbol panah (->) yang mencantumkan parameternya dan yang di kanan berisi tubuhnya . Kompilator secara otomatis mengetahui bahwa ekspresi lambda memiliki tanda tangan yang sama dari satu-satunya metode antarmuka Konsumen yang tidak diimplementasikan.
sama
5
Saya tidak bertanya apa itu ekspresi lambda, saya hanya mengatakan iterasi internal tidak ada hubungannya dengan ekspresi lambda.
Ousmane D.
1
@ Aominè Saya mengerti maksud Anda, tidak ada apa-apa dengan lambda itu sendiri , tapi seperti yang Anda tunjukkan, itu lebih seperti cara menulis yang lebih bersih. Saya pikir dalam hal efisiensi sama baiknya dengan versi pra-8
semuanya
4

Ada banyak manfaat menggunakan lambda daripada kelas dalam berikut ini:

  • Buat kode lebih ringkas dan ekspresif tanpa memasukkan lebih banyak semantik sintaks bahasa. Anda sudah memberi contoh dalam pertanyaan Anda.

  • Dengan menggunakan lambda, Anda senang memprogram dengan operasi gaya fungsional pada aliran elemen, seperti transformasi pengurangan peta pada koleksi. melihat java.util.function & java.util.stream paket dokumentasi.

  • Tidak ada file kelas fisik yang dibuat untuk lambda oleh kompilator. Dengan demikian, itu membuat aplikasi yang Anda kirimkan lebih kecil. Bagaimana Memori diberikan ke lambda?

  • Compiler akan mengoptimalkan pembuatan lambda jika lambda tidak mengakses variabel di luar cakupannya, yang berarti instance lambda hanya dibuat sekali oleh JVM. untuk lebih jelasnya anda dapat melihat jawaban dari pertanyaan @Holger Apakah referensi metode caching ide yang baik di Java 8? .

  • Lambdas dapat mengimplementasikan antarmuka multi penanda selain antarmuka fungsional, tetapi kelas dalam anonim tidak dapat mengimplementasikan lebih banyak antarmuka, misalnya:

    //                 v--- create the lambda locally.
    Consumer<Integer> action = (Consumer<Integer> & Serializable) it -> {/*TODO*/};
holi-java
sumber
2

Lambda hanyalah gula sintaksis untuk kelas anonim.

Sebelum lambda, kelas anonim dapat digunakan untuk mencapai hal yang sama. Setiap ekspresi lambda dapat diubah menjadi kelas anonim.

Jika Anda menggunakan IntelliJ IDEA, itu dapat melakukan konversi untuk Anda:

  • Letakkan kursor di lambda
  • Tekan alt / option + enter

masukkan deskripsi gambar di sini

Penyapu
sumber
3
... Saya pikir @StuartMarks sudah mengklarifikasi sintaksis gula-ity (sp? Gr?) Lambda? ( stackoverflow.com/a/15241404/3475600 , softwareengineering.stackexchange.com/a/181743 )
srborlongan
2

Untuk menjawab pertanyaan Anda, kenyataannya adalah lambda tidak membiarkan Anda melakukan apa pun yang tidak dapat Anda lakukan sebelum java-8, melainkan memungkinkan Anda untuk menulis kode yang lebih ringkas . Manfaatnya, adalah kode Anda akan lebih jelas dan lebih fleksibel.

Ousmane D.
sumber
Jelas @Holger telah menghilangkan keraguan dalam hal efisiensi lambda. Ini bukan hanya cara untuk membuat kode lebih bersih, tetapi jika lambda tidak menangkap nilai apa pun, itu akan menjadi kelas Singleton (dapat digunakan kembali)
semuanya
Jika Anda menggali lebih dalam, setiap fitur bahasa memiliki perbedaan. Pertanyaan yang ada pada level tinggi maka saya menulis jawaban saya pada level tinggi.
Ousmane D.
2

Satu hal yang belum saya lihat disebutkan adalah bahwa lambda memungkinkan Anda menentukan fungsionalitas tempat digunakan .

Jadi jika Anda memiliki beberapa fungsi pemilihan sederhana, Anda tidak perlu meletakkannya di tempat terpisah dengan sekumpulan boilerplate, Anda cukup menulis lambda yang ringkas dan relevan secara lokal.

Carlos
sumber
1

Ya banyak keuntungan yang ada.

  • Tidak perlu mendefinisikan seluruh kelas, kita bisa melewatkan implementasi fungsi itu sendiri sebagai referensi.
    • Pembuatan kelas secara internal akan membuat file .class sedangkan jika Anda menggunakan lambda maka pembuatan kelas dihindari oleh compiler karena di lambda Anda meneruskan implementasi fungsi alih-alih kelas.
  • Kode dapat digunakan kembali lebih tinggi dari sebelumnya
  • Dan seperti yang Anda katakan, kode lebih pendek dari implementasi normal.
Sunil Kanzar
sumber