JavaRush /Java Blog /Random-ID /Populer tentang ekspresi lambda di Jawa. Dengan contoh da...
Стас Пасинков
Level 26
Киев

Populer tentang ekspresi lambda di Jawa. Dengan contoh dan tugas. Bagian 1

Dipublikasikan di grup Random-ID
Untuk siapa artikel ini?
  • Bagi yang merasa sudah mengetahui Java Core dengan baik, namun belum mengetahui tentang ekspresi lambda di Java. Atau, mungkin, Anda pernah mendengar sesuatu tentang lambda, tetapi tanpa detailnya.
  • bagi mereka yang sudah memahami ekspresi lambda, tetapi masih takut dan tidak biasa menggunakannya.
Jika Anda tidak termasuk dalam salah satu kategori ini, Anda mungkin menganggap artikel ini membosankan, tidak benar, dan secara umum “tidak keren”. Dalam hal ini, silakan lewati, atau, jika Anda berpengalaman dalam topik tersebut, sarankan di komentar bagaimana saya dapat meningkatkan atau melengkapi artikel tersebut. Materinya tidak menuntut nilai akademis apa pun, apalagi kebaruan. Sebaliknya, sebaliknya: di dalamnya saya akan mencoba menggambarkan hal-hal yang kompleks (untuk beberapa) sesederhana mungkin. Saya terinspirasi untuk menulis oleh permintaan untuk menjelaskan stream api. Saya memikirkannya dan memutuskan bahwa tanpa memahami ekspresi lambda, beberapa contoh saya tentang “aliran” tidak akan dapat dipahami. Jadi mari kita mulai dengan lambda. Populer tentang ekspresi lambda di Jawa.  Dengan contoh dan tugas.  Bagian 1 - 1Pengetahuan apa yang diperlukan untuk memahami artikel ini:
  1. Pengertian Pemrograman Berorientasi Objek (selanjutnya disebut OOP), yaitu :
    • pengetahuan tentang apa itu kelas dan objek, apa perbedaan di antara keduanya;
    • pengetahuan tentang apa itu antarmuka, perbedaannya dengan kelas, apa hubungan di antara keduanya (antarmuka dan kelas);
    • pengetahuan tentang apa itu metode, bagaimana cara memanggilnya, apa itu metode abstrak (atau metode tanpa implementasi), apa parameter/argumen suatu metode, bagaimana meneruskannya ke sana;
    • pengubah akses, metode/variabel statis, metode/variabel akhir;
    • pewarisan (kelas, antarmuka, pewarisan berganda antarmuka).
  2. Pengetahuan tentang Java Core: generik, koleksi (daftar), utas.
Baiklah, mari kita mulai.

Sedikit sejarah

Ekspresi Lambda datang ke Java dari pemrograman fungsional, dan dari matematika. Pada pertengahan abad ke-20 di Amerika, ada Gereja Alonzo yang bekerja di Universitas Princeton, yang sangat menyukai matematika dan segala macam abstraksi. Gereja Alonzo-lah yang menemukan kalkulus lambda, yang pada awalnya merupakan sekumpulan ide abstrak dan tidak ada hubungannya dengan pemrograman. Pada saat yang sama, ahli matematika seperti Alan Turing dan John von Neumann bekerja di Universitas Princeton yang sama. Semuanya bersatu: Church menciptakan sistem kalkulus lambda, Turing mengembangkan mesin komputasi abstraknya, yang sekarang dikenal sebagai “mesin Turing.” Nah, von Neumann mengusulkan diagram arsitektur komputer, yang menjadi dasar komputer modern (dan sekarang disebut “arsitektur von Neumann”). Pada saat itu, gagasan Gereja Alonzo tidak sepopuler karya rekan-rekannya (kecuali di bidang matematika “murni”). Namun, beberapa saat kemudian, John McCarthy (juga lulusan Universitas Princeton, pada saat cerita ini dibuat - seorang karyawan Institut Teknologi Massachusetts) menjadi tertarik dengan ide-ide Gereja. Berdasarkan mereka, pada tahun 1958 ia menciptakan bahasa pemrograman fungsional pertama, Lisp. Dan 58 tahun kemudian, ide pemrograman fungsional bocor ke Java sebagai nomor 8. Belum genap 70 tahun berlalu... Sebenarnya, ini bukanlah jangka waktu terlama untuk menerapkan ide matematika dalam praktik.

Intinya

Ekspresi lambda adalah fungsi seperti itu. Anda dapat menganggap ini sebagai metode reguler di Java, satu-satunya perbedaan adalah metode ini dapat diteruskan ke metode lain sebagai argumen. Ya, tidak hanya angka, string, dan kucing yang dapat diteruskan ke metode, tetapi juga metode lain! Kapan kita membutuhkan ini? Misalnya, jika kita ingin meneruskan beberapa panggilan balik. Kita memerlukan metode yang kita panggil agar dapat memanggil metode lain yang kita teruskan ke sana. Artinya, agar kita mempunyai kesempatan untuk mengirimkan satu panggilan balik dalam beberapa kasus, dan panggilan balik lainnya dalam kasus lain. Dan metode kami, yang akan menerima panggilan balik kami, akan memanggil mereka. Contoh sederhananya adalah pengurutan. Katakanlah kita menulis semacam penyortiran rumit yang terlihat seperti ini:
public void mySuperSort() {
    // ... do something here
    if(compare(obj1, obj2) > 0)
    // ... and here we do something
}
Dimana, ifkita memanggil metode tersebut compare(), meneruskan ke sana dua objek yang kita bandingkan, dan kita ingin mengetahui objek mana yang “lebih besar”. Kita akan menempatkan yang “lebih besar” sebelum yang “lebih kecil”. Saya menulis "lebih banyak" dalam tanda kutip karena kita sedang menulis metode universal yang akan dapat mengurutkan tidak hanya dalam urutan menaik tetapi juga dalam urutan menurun (dalam hal ini, "lebih" akan menjadi objek yang pada dasarnya lebih kecil, dan sebaliknya) . Untuk menetapkan aturan bagaimana kita ingin mengurutkan, kita perlu meneruskannya ke mySuperSort(). Dalam hal ini, kita akan dapat “mengendalikan” metode kita saat sedang dipanggil. Tentu saja, Anda dapat menulis dua metode terpisah mySuperSortAsc()untuk mySuperSortDesc()mengurutkan dalam urutan menaik dan menurun. Atau berikan beberapa parameter di dalam metode (misalnya, booleanif true, urutkan dalam urutan menaik, dan if falsedalam urutan menurun). Tetapi bagaimana jika kita ingin mengurutkan bukan struktur sederhana, tetapi, misalnya, daftar array string? Bagaimana metode kita mySuperSort()mengetahui cara mengurutkan array string ini? Untuk ukuran? Berdasarkan total panjang kata? Mungkin berdasarkan abjad, bergantung pada baris pertama dalam array? Namun bagaimana jika, dalam beberapa kasus, kita perlu mengurutkan daftar array berdasarkan ukuran array, dan dalam kasus lain, berdasarkan total panjang kata dalam array? Saya pikir Anda sudah mendengar tentang pembanding dan dalam kasus seperti itu kita cukup meneruskan objek pembanding ke metode pengurutan kita, di mana kita menjelaskan aturan yang ingin kita gunakan untuk mengurutkan. Karena metode standar sort()diterapkan dengan prinsip yang sama seperti , mySuperSort()dalam contoh saya akan menggunakan metode standar sort().
String[] array1 = {"Mother", "soap", "frame"};
String[] array2 = {"I", "Very", "I love", "java"};
String[] array3 = {"world", "work", "May"};

List<String[]> arrays = new ArrayList<>();
arrays.add(array1);
arrays.add(array2);
arrays.add(array3);

Comparator<String[]> sortByLength = new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
};

Comparator<String[]> sortByWordsLength = new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        int length1 = 0;
        int length2 = 0;
        for (String s : o1) {
            length1 += s.length();
        }
        for (String s : o2) {
            length2 += s.length();
        }
        return length1 - length2;
    }
};

arrays.sort(sortByLength);
Hasil:
  1. ibu mencuci bingkai itu
  2. perdamaian Buruh mungkin
  3. Saya sangat menyukai bahasa Jawa
Di sini array diurutkan berdasarkan jumlah kata di setiap array. Array dengan kata yang lebih sedikit dianggap “lebih kecil”. Itu sebabnya hal itu terjadi di awal. Kata yang memiliki lebih banyak kata dianggap “lebih banyak” dan berakhir di akhir. Jika sort()kita meneruskan komparator lain ke metode tersebut (sortByWordsLength), maka hasilnya akan berbeda:
  1. perdamaian Buruh mungkin
  2. ibu mencuci bingkai itu
  3. Saya sangat menyukai bahasa Jawa
Sekarang array diurutkan berdasarkan jumlah total huruf dalam kata-kata dari array tersebut. Dalam kasus pertama ada 10 huruf, yang kedua 12, dan yang ketiga 15. Jika kita hanya menggunakan satu pembanding, maka kita tidak dapat membuat variabel terpisah untuknya, tetapi cukup membuat objek kelas anonim tepat di waktu memanggil metode tersebut sort(). Seperti itu:
String[] array1 = {"Mother", "soap", "frame"};
String[] array2 = {"I", "Very", "I love", "java"};
String[] array3 = {"world", "work", "May"};

List<String[]> arrays = new ArrayList<>();
arrays.add(array1);
arrays.add(array2);
arrays.add(array3);

arrays.sort(new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
});
Hasilnya akan sama seperti pada kasus pertama. Tugas 1 . Tulis ulang contoh ini sehingga mengurutkan array bukan dalam urutan jumlah kata dalam array, tetapi dalam urutan menurun. Kita sudah mengetahui semua ini. Kita tahu cara meneruskan objek ke metode, kita dapat meneruskan objek ini atau itu ke suatu metode tergantung pada apa yang kita perlukan saat ini, dan di dalam metode tempat kita meneruskan objek tersebut, metode yang kita tulis implementasinya akan dipanggil . Timbul pertanyaan: apa hubungannya ekspresi lambda dengan itu? Mengingat lambda adalah objek yang berisi tepat satu metode. Ini seperti objek metode. Sebuah metode yang dibungkus dalam suatu objek. Mereka hanya memiliki sintaks yang sedikit tidak biasa (tetapi akan dibahas lebih lanjut nanti). Mari kita lihat lagi entri ini
arrays.sort(new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
});
Di sini kita mengambil daftar kita arraysdan memanggil metodenya sort(), di mana kita meneruskan objek pembanding dengan satu metode compare()(tidak masalah bagi kita apa namanya, karena itu satu-satunya di objek ini, kita tidak akan melewatkannya). Metode ini mengambil dua parameter, yang selanjutnya kita kerjakan. Jika Anda bekerja di IntelliJ IDEA , Anda mungkin pernah melihat bagaimana ia menawarkan kode ini untuk mempersingkat secara signifikan:
arrays.sort((o1, o2) -> o1.length - o2.length);
Begitulah enam baris berubah menjadi satu baris pendek. 6 baris ditulis ulang menjadi satu baris pendek. Sesuatu telah hilang, tetapi saya jamin tidak ada hal penting yang hilang, dan kode ini akan berfungsi persis sama dengan kelas anonim. Tugas 2 . Cari tahu cara menulis ulang solusi untuk masalah 1 menggunakan lambda (sebagai upaya terakhir, minta IntelliJ IDEA untuk mengubah kelas anonim Anda menjadi lambda).

Mari kita bicara tentang antarmuka

Pada dasarnya, antarmuka hanyalah daftar metode abstrak. Ketika kita membuat sebuah kelas dan mengatakan bahwa kelas tersebut akan mengimplementasikan semacam antarmuka, kita harus menulis di kelas kita sebuah implementasi dari metode yang tercantum dalam antarmuka (atau, sebagai upaya terakhir, tidak menulisnya, tetapi membuat kelas tersebut abstrak ). Ada antarmuka dengan banyak metode berbeda (misalnya List), ada antarmuka dengan hanya satu metode (misalnya, Comparator atau Runnable yang sama). Ada antarmuka tanpa metode tunggal sama sekali (disebut antarmuka penanda, misalnya Serializable). Antarmuka yang hanya memiliki satu metode disebut juga antarmuka fungsional . Di Java 8 mereka bahkan ditandai dengan anotasi @FunctionalInterface khusus . Ini adalah antarmuka dengan satu metode yang cocok untuk digunakan oleh ekspresi lambda. Seperti yang saya katakan di atas, ekspresi lambda adalah metode yang dibungkus dalam suatu objek. Dan ketika kita meneruskan objek seperti itu ke suatu tempat, sebenarnya kita meneruskan satu metode ini. Ternyata bagi kita tidak masalah apa sebutan metode ini. Yang penting bagi kami hanyalah parameter yang digunakan metode ini, dan, pada kenyataannya, kode metode itu sendiri. Ekspresi lambda pada dasarnya adalah. implementasi antarmuka fungsional. Jika kita melihat antarmuka dengan satu metode, itu berarti kita dapat menulis ulang kelas anonim tersebut menggunakan lambda. Jika antarmuka memiliki lebih/kurang dari satu metode, maka ekspresi lambda tidak cocok untuk kita, dan kita akan menggunakan kelas anonim, atau bahkan kelas biasa. Saatnya menggali lebih dalam lambda. :)

Sintaksis

Sintaks umumnya kira-kira seperti ini:
(параметры) -> {тело метода}
Artinya, tanda kurung, di dalamnya terdapat parameter metode, sebuah "panah" (ini adalah dua karakter berturut-turut: minus dan lebih besar), setelah itu isi metode berada dalam kurung kurawal, seperti biasa. Parameternya sesuai dengan yang ditentukan di antarmuka saat menjelaskan metode. Jika jenis variabel dapat didefinisikan dengan jelas oleh kompiler (dalam kasus kita, diketahui dengan pasti bahwa kita bekerja dengan array string, karena diketik Listsecara tepat oleh array string), maka jenis variabel String[]tidak perlu ditulis.
Jika Anda tidak yakin, tentukan jenisnya, dan IDEA akan menyorotnya dengan warna abu-abu jika tidak diperlukan.
Anda dapat membaca lebih lanjut di tutorial Oracle , misalnya. Ini disebut "pengetikan target" . Anda dapat memberi nama apa pun pada variabel, tidak harus nama yang ditentukan di antarmuka. Jika tidak ada parameter, cukup tanda kurung saja. Jika hanya ada satu parameter, nama variabelnya saja tanpa tanda kurung. Kami telah memilah parameternya, sekarang tentang isi ekspresi lambda itu sendiri. Di dalam kurung kurawal, tulis kode seperti metode biasa. Jika seluruh kode Anda hanya terdiri dari satu baris, Anda tidak perlu menulis kurung kurawal sama sekali (seperti halnya if dan loop). Jika lambda Anda mengembalikan sesuatu, tetapi tubuhnya terdiri dari satu baris, maka returntidak perlu menulis sama sekali. Tetapi jika Anda memiliki kurung kurawal, seperti pada metode biasa, Anda perlu menulis return.

Contoh

Contoh 1.
() -> {}
Pilihan paling sederhana. Dan yang paling tidak berarti :) Karena tidak menghasilkan apa-apa. Contoh 2.
() -> ""
Juga pilihan yang menarik. Ia tidak menerima apa pun dan mengembalikan string kosong ( returndihilangkan karena tidak perlu). Sama, tetapi dengan return:
() -> {
    return "";
}
Contoh 3. Halo dunia menggunakan lambda
() -> System.out.println("Hello world!")
Tidak menerima apa pun, tidak mengembalikan apa pun (kita tidak dapat meletakkan returnsebelum panggilan System.out.println(), karena tipe pengembalian dalam metode ini println() — void), cukup menampilkan tulisan di layar. Ideal untuk mengimplementasikan antarmuka Runnable. Contoh yang sama lebih lengkap:
public class Main {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("Hello world!")).start();
    }
}
Atau seperti ini:
public class Main {
    public static void main(String[] args) {
        Thread t = new Thread(() -> System.out.println("Hello world!"));
        t.start();
    }
}
Atau kita bahkan dapat menyimpan ekspresi lambda sebagai objek bertipe Runnable, dan kemudian meneruskannya ke konstruktor thread’а:
public class Main {
    public static void main(String[] args) {
        Runnable runnable = () -> System.out.println("Hello world!");
        Thread t = new Thread(runnable);
        t.start();
    }
}
Mari kita lihat lebih dekat momen menyimpan ekspresi lambda ke dalam variabel. Antarmuka Runnablememberi tahu kita bahwa objeknya harus memiliki metode public void run(). Menurut antarmuka, metode run tidak menerima apa pun sebagai parameter. Dan itu tidak mengembalikan apa pun (void). Oleh karena itu, ketika menulis dengan cara ini, sebuah objek akan dibuat dengan beberapa metode yang tidak menerima atau mengembalikan apapun. Yang cukup konsisten dengan metode run()di antarmuka Runnable. Itu sebabnya kami dapat memasukkan ekspresi lambda ini ke dalam variabel seperti Runnable. Contoh 4
() -> 42
Sekali lagi, ia tidak menerima apa pun, tetapi mengembalikan angka 42. Ekspresi lambda ini dapat ditempatkan dalam variabel bertipe Callable, karena antarmuka ini hanya mendefinisikan satu metode, yang terlihat seperti ini:
V call(),
di mana Vadalah jenis nilai yang dikembalikan (dalam kasus kami int). Oleh karena itu, kita dapat menyimpan ekspresi lambda sebagai berikut:
Callable<Integer> c = () -> 42;
Contoh 5. Lambda dalam beberapa baris
() -> {
    String[] helloWorld = {"Hello", "world!"};
    System.out.println(helloWorld[0]);
    System.out.println(helloWorld[1]);
}
Sekali lagi, ini adalah ekspresi lambda tanpa parameter dan tipe pengembaliannya void(karena tidak ada return). Contoh 6
x -> x
Di sini kita mengambil sesuatu ke dalam variabel хdan mengembalikannya. Perlu diketahui bahwa jika hanya satu parameter yang diterima, maka tanda kurung disekitarnya tidak perlu ditulis. Sama, tetapi dengan tanda kurung:
(x) -> x
Dan inilah opsi dengan yang eksplisit return:
x -> {
    return x;
}
Atau seperti ini, dengan tanda kurung dan return:
(x) -> {
    return x;
}
Atau dengan indikasi eksplisit jenisnya (dan, karenanya, dengan tanda kurung):
(int x) -> x
Contoh 7
x -> ++x
Kami menerimanya хdan mengembalikannya, tetapi untuk 1lebih. Anda juga dapat menulis ulang seperti ini:
x -> x + 1
Dalam kedua kasus tersebut, kami tidak menunjukkan tanda kurung di sekitar parameter, isi metode, dan kata return, karena hal ini tidak diperlukan. Varian dengan tanda kurung dan return dijelaskan pada Contoh 6. Contoh 8
(x, y) -> x % y
Kami menerima sebagian хdan уmengembalikan sisa pembagian xsebesar y. Tanda kurung di sekitar parameter sudah diperlukan di sini. Ini opsional hanya jika hanya ada satu parameter. Seperti ini dengan indikasi jenis yang jelas:
(double x, int y) -> x % y
Contoh 9
(Cat cat, String name, int age) -> {
    cat.setName(name);
    cat.setAge(age);
}
Kami menerima objek Cat, string dengan nama dan umur bilangan bulat. Dalam metodenya sendiri, kami menetapkan nama dan usia yang diteruskan ke Kucing. Karena variabel catkita adalah tipe referensi, objek Cat di luar ekspresi lambda akan berubah (ia akan menerima nama dan umur yang dimasukkan di dalamnya). Versi yang sedikit lebih rumit yang menggunakan lambda serupa:
public class Main {
    public static void main(String[] args) {
        // create a cat and print to the screen to make sure it's "blank"
        Cat myCat = new Cat();
        System.out.println(myCat);

        // create lambda
        Settable<Cat> s = (obj, name, age) -> {
            obj.setName(name);
            obj.setAge(age);
        };

        // call the method, to which we pass the cat and the lambda
        changeEntity(myCat, s);
        // display on the screen and see that the state of the cat has changed (has a name and age)
        System.out.println(myCat);
    }

    private static <T extends WithNameAndAge>  void changeEntity(T entity, Settable<T> s) {
        s.set(entity, "Murzik", 3);
    }
}

interface WithNameAndAge {
    void setName(String name);
    void setAge(int age);
}

interface Settable<C extends WithNameAndAge> {
    void set(C entity, String name, int age);
}

class Cat implements WithNameAndAge {
    private String name;
    private int age;

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
Hasil: Cat{name='null', age=0} Cat{name='Murzik', age=3} Seperti yang Anda lihat, pada awalnya objek Cat memiliki satu status, tetapi setelah menggunakan ekspresi lambda, status tersebut berubah . Ekspresi Lambda berfungsi baik dengan obat generik. Dan jika kita perlu membuat sebuah kelas Dog, misalnya, yang juga akan mengimplementasikan WithNameAndAge, maka dalam metode tersebut main()kita dapat melakukan operasi yang sama dengan Dog, tanpa mengubah ekspresi lambda itu sendiri sama sekali. Tugas 3 . Tulis antarmuka fungsional dengan metode yang mengambil angka dan mengembalikan nilai Boolean. Tuliskan implementasi antarmuka tersebut dalam bentuk ekspresi lambda yang kembali truejika bilangan yang diteruskan habis dibagi 13 tanpa sisa.Tugas 4 . Tulis antarmuka fungsional dengan metode yang mengambil dua string dan mengembalikan string yang sama. Tulis implementasi antarmuka tersebut dalam bentuk lambda yang mengembalikan string terpanjang. Tugas 5 . Tulis antarmuka fungsional dengan metode yang menerima tiga bilangan pecahan: a, b, cdan mengembalikan bilangan pecahan yang sama. Tulis implementasi antarmuka tersebut dalam bentuk ekspresi lambda yang mengembalikan diskriminan. Siapa yang lupa, D = b^2 - 4ac . Tugas 6 . Menggunakan antarmuka fungsional dari tugas 5, tulis ekspresi lambda yang mengembalikan hasil operasi a * b^c. Populer tentang ekspresi lambda di Jawa. Dengan contoh dan tugas. Bagian 2.
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION