JavaRush /Blog Java /Random-MS /Popular tentang ungkapan lambda di Jawa. Dengan contoh da...

Popular tentang ungkapan lambda di Jawa. Dengan contoh dan tugasan. Bahagian 1

Diterbitkan dalam kumpulan
Untuk siapa artikel ini?
  • Bagi mereka yang berpendapat mereka sudah mengetahui Java Core dengan baik, tetapi tidak tahu tentang ungkapan lambda dalam Java. Atau, mungkin, anda sudah mendengar sesuatu tentang lambda, tetapi tanpa butiran.
  • bagi mereka yang mempunyai sedikit pemahaman tentang ungkapan lambda, tetapi masih takut dan luar biasa untuk menggunakannya.
Jika anda tidak termasuk dalam salah satu kategori ini, anda mungkin mendapati artikel ini membosankan, tidak betul, dan secara amnya "tidak keren." Dalam kes ini, sama ada berasa bebas untuk lulus, atau, jika anda mahir dalam topik ini, cadangkan dalam komen bagaimana saya boleh menambah baik atau menambah artikel. Bahan tersebut tidak menuntut sebarang nilai akademik, apalagi kebaharuan. Sebaliknya, sebaliknya: di dalamnya saya akan cuba menerangkan perkara yang kompleks (untuk beberapa) semudah mungkin. Saya mendapat inspirasi untuk menulis dengan permintaan untuk menerangkan api aliran. Saya memikirkannya dan memutuskan bahawa tanpa memahami ungkapan lambda, beberapa contoh saya tentang "strim" tidak dapat difahami. Jadi mari kita mulakan dengan lambdas. Popular tentang ungkapan lambda di Jawa.  Dengan contoh dan tugasan.  Bahagian 1 - 1Apakah pengetahuan yang diperlukan untuk memahami artikel ini:
  1. Pemahaman tentang pengaturcaraan berorientasikan objek (selepas ini dirujuk sebagai OOP), iaitu:
    • pengetahuan tentang kelas dan objek, apakah perbezaan di antara mereka;
    • pengetahuan tentang apakah antara muka, bagaimana ia berbeza daripada kelas, apakah hubungan antara mereka (antara muka dan kelas);
    • pengetahuan tentang kaedah, cara memanggilnya, kaedah abstrak (atau kaedah tanpa pelaksanaan), apakah parameter/argumen kaedah, cara menghantarnya ke sana;
    • pengubah suai capaian, kaedah/pembolehubah statik, kaedah/pembolehubah akhir;
    • pewarisan (kelas, antara muka, pewarisan berbilang antara muka).
  2. Pengetahuan Teras Java: generik, koleksi (senarai), benang.
Baiklah, mari kita mulakan.

Sedikit sejarah

Ekspresi Lambda datang ke Java daripada pengaturcaraan berfungsi, dan di sana dari matematik. Pada pertengahan abad ke-20 di Amerika, Gereja Alonzo tertentu bekerja di Universiti Princeton, yang sangat menyukai matematik dan semua jenis abstraksi. Gereja Alonzolah yang menghasilkan kalkulus lambda, yang pada mulanya merupakan satu set beberapa idea abstrak dan tiada kaitan dengan pengaturcaraan. Pada masa yang sama, ahli matematik seperti Alan Turing dan John von Neumann bekerja di Universiti Princeton yang sama. Segala-galanya bersatu: Gereja menghasilkan sistem kalkulus lambda, Turing membangunkan mesin pengkomputeran abstraknya, yang kini dikenali sebagai "Mesin Turing." Nah, von Neumann mencadangkan gambar rajah seni bina komputer, yang membentuk asas komputer moden (dan kini dipanggil "seni bina von Neumann"). Pada masa itu, idea-idea Gereja Alonzo tidak mendapat kemasyhuran sebanyak karya rakan-rakannya (dengan pengecualian bidang matematik "tulen"). Walau bagaimanapun, tidak lama kemudian, John McCarthy tertentu (juga lulusan Universiti Princeton, pada masa cerita - seorang pekerja Institut Teknologi Massachusetts) mula berminat dengan idea-idea Gereja. Berdasarkan mereka, pada tahun 1958 beliau mencipta bahasa pengaturcaraan berfungsi pertama, Lisp. Dan 58 tahun kemudian, idea-idea pengaturcaraan berfungsi bocor ke Java sebagai nombor 8. Tidak sampai 70 tahun pun berlalu... Sebenarnya, ini bukanlah tempoh masa yang paling lama untuk mengaplikasikan idea matematik dalam amalan.

Intipatinya

Ungkapan lambda ialah fungsi sedemikian. Anda boleh menganggap ini sebagai kaedah biasa di Jawa, satu-satunya perbezaan ialah ia boleh dihantar ke kaedah lain sebagai hujah. Ya, ia telah menjadi mungkin untuk menghantar bukan sahaja nombor, rentetan dan kucing kepada kaedah, tetapi juga kaedah lain! Bilakah kita mungkin memerlukannya? Sebagai contoh, jika kita ingin menghantar beberapa panggilan balik. Kami memerlukan kaedah yang kami panggil untuk dapat memanggil beberapa kaedah lain yang kami hantar kepadanya. Iaitu, supaya kami mempunyai peluang untuk menghantar satu panggilan balik dalam beberapa kes, dan satu lagi dalam yang lain. Dan kaedah kami, yang akan menerima panggilan balik kami, akan memanggil mereka. Contoh mudah ialah menyusun. Katakan kita menulis beberapa jenis pengisihan rumit yang kelihatan seperti ini:
public void mySuperSort() {
    // ... do something here
    if(compare(obj1, obj2) > 0)
    // ... and here we do something
}
Di mana, ifkami memanggil kaedah compare(), lulus dua objek yang kami bandingkan, dan kami ingin mengetahui objek mana yang "lebih hebat". Kami akan meletakkan yang "lebih" sebelum yang "lebih kecil". Saya menulis "lebih" dalam petikan kerana kami menulis kaedah sejagat yang akan dapat mengisih bukan sahaja secara menaik tetapi juga dalam susunan menurun (dalam kes ini, "lebih" akan menjadi objek yang pada dasarnya lebih kecil, dan sebaliknya) . Untuk menetapkan peraturan tentang cara kita ingin mengisih, entah bagaimana kita perlu menyampaikannya kepada mySuperSort(). Dalam kes ini, kami akan dapat "mengawal" kaedah kami semasa ia dipanggil. Sudah tentu, anda boleh menulis dua kaedah berasingan mySuperSortAsc()untuk mySuperSortDesc()menyusun mengikut tertib menaik dan menurun. Atau hantar beberapa parameter di dalam kaedah (contohnya, booleanif true, susun dalam tertib menaik, dan jika falsedalam tertib menurun). Tetapi bagaimana jika kita tidak mahu mengisih beberapa struktur mudah, tetapi, sebagai contoh, senarai tatasusunan rentetan? Bagaimanakah kaedah kami mySuperSort()mengetahui cara mengisih tatasusunan rentetan ini? Ke saiz? Dengan jumlah panjang perkataan? Mungkin mengikut abjad, bergantung pada baris pertama dalam tatasusunan? Tetapi bagaimana jika, dalam beberapa kes, kita perlu mengisih senarai tatasusunan mengikut saiz tatasusunan, dan dalam kes lain, mengikut jumlah panjang perkataan dalam tatasusunan? Saya rasa anda telah pun mendengar tentang pembanding dan dalam kes sedemikian, kami hanya menghantar objek pembanding kepada kaedah pengisihan kami, di mana kami menerangkan peraturan yang kami ingin mengisih. Oleh kerana kaedah standard sort()dilaksanakan pada prinsip yang sama seperti , mySuperSort()dalam contoh saya akan menggunakan yang standard 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);
Keputusan:
  1. ibu membasuh bingkai itu
  2. peace Buruh boleh
  3. Saya sangat suka java
Di sini tatasusunan diisih mengikut bilangan perkataan dalam setiap tatasusunan. Tatasusunan dengan perkataan yang lebih sedikit dianggap "lebih kecil". Itulah sebabnya ia datang pada permulaan. Perkataan yang terdapat lebih banyak perkataan dianggap "lebih" dan berakhir di penghujungnya. Jika sort()kita lulus pembanding lain kepada kaedah (sortByWordsLength), maka hasilnya akan berbeza:
  1. peace Buruh boleh
  2. ibu membasuh bingkai itu
  3. Saya sangat suka java
Kini tatasusunan diisih mengikut jumlah bilangan huruf dalam perkataan tatasusunan sedemikian. Dalam kes pertama terdapat 10 huruf, dalam 12 kedua, dan dalam 15 yang ketiga. Jika kita menggunakan hanya satu pembanding, maka kita tidak boleh mencipta pembolehubah berasingan untuknya, tetapi hanya mencipta objek kelas tanpa nama tepat di masa memanggil kaedah sort(). Macam 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 dalam kes pertama. Tugasan 1 . Tulis semula contoh ini supaya ia mengisih tatasusunan bukan dalam susunan menaik bilangan perkataan dalam tatasusunan, tetapi dalam susunan menurun. Kita sudah tahu semua ini. Kami tahu cara menghantar objek kepada kaedah, kami boleh menghantar objek ini atau itu kepada kaedah bergantung pada apa yang kami perlukan pada masa ini, dan di dalam kaedah di mana kami lulus objek sedemikian, kaedah yang kami tulis pelaksanaannya akan dipanggil . Timbul persoalan: apakah kaitan ungkapan lambda dengannya? Memandangkan lambda ialah objek yang mengandungi tepat satu kaedah. Ia seperti objek kaedah. Kaedah yang dibungkus dalam objek. Mereka hanya mempunyai sintaks yang sedikit luar biasa (tetapi lebih lanjut mengenainya kemudian). Jom kita tengok lagi entry ni
arrays.sort(new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
});
Di sini kami mengambil senarai kami arraysdan memanggil kaedahnya sort(), di mana kami lulus objek pembanding dengan satu kaedah tunggal compare()(tidak penting bagi kami apa namanya, kerana ia adalah satu-satunya dalam objek ini, kami tidak akan terlepasnya). Kaedah ini mengambil dua parameter, yang kami bekerjasama seterusnya. Jika anda bekerja dalam IntelliJ IDEA , anda mungkin telah melihat cara ia menawarkan kepada anda kod ini untuk memendekkan dengan ketara:
arrays.sort((o1, o2) -> o1.length - o2.length);
Begitulah enam baris bertukar menjadi satu baris pendek. 6 baris ditulis semula menjadi satu baris pendek. Sesuatu telah hilang, tetapi saya menjamin bahawa tiada apa yang penting telah hilang, dan kod ini akan berfungsi sama seperti dengan kelas tanpa nama. Tugasan 2 . Fikirkan cara untuk menulis semula penyelesaian kepada masalah 1 menggunakan lambdas (sebagai pilihan terakhir, minta IntelliJ IDEA untuk menukar kelas tanpa nama anda kepada lambda).

Mari kita bercakap tentang antara muka

Pada asasnya, antara muka hanyalah senarai kaedah abstrak. Apabila kita mencipta kelas dan mengatakan bahawa ia akan melaksanakan beberapa jenis antara muka, kita mesti menulis dalam kelas kita pelaksanaan kaedah yang disenaraikan dalam antara muka (atau, sebagai pilihan terakhir, bukan menulisnya, tetapi menjadikan kelas itu abstrak ). Terdapat antara muka dengan banyak kaedah yang berbeza (contohnya List), terdapat antara muka dengan hanya satu kaedah (contohnya, Comparator atau Runnable yang sama). Terdapat antara muka tanpa satu kaedah sama sekali (yang dipanggil antara muka penanda, contohnya Serializable). Antara muka yang mempunyai hanya satu kaedah juga dipanggil antara muka berfungsi . Dalam Java 8 mereka juga ditandakan dengan anotasi @FunctionalInterface khas . Ia adalah antara muka dengan satu kaedah tunggal yang sesuai untuk digunakan oleh ungkapan lambda. Seperti yang saya katakan di atas, ungkapan lambda ialah kaedah yang dibungkus dalam objek. Dan apabila kita melepasi objek sedemikian di suatu tempat, kita, sebenarnya, lulus satu kaedah tunggal ini. Ternyata tidak penting bagi kami kaedah ini dipanggil. Apa yang penting bagi kami ialah parameter yang digunakan oleh kaedah ini, dan, sebenarnya, kod kaedah itu sendiri. Ungkapan lambda, pada asasnya. pelaksanaan antara muka berfungsi. Apabila kita melihat antara muka dengan satu kaedah, ini bermakna kita boleh menulis semula kelas tanpa nama menggunakan lambda. Jika antara muka mempunyai lebih/kurang daripada satu kaedah, maka ungkapan lambda tidak sesuai dengan kami, dan kami akan menggunakan kelas tanpa nama, atau malah kelas biasa. Sudah tiba masanya untuk menggali lambda. :)

Sintaks

Sintaks umum adalah seperti ini:
(параметры) -> {тело метода}
Iaitu, kurungan, di dalamnya terdapat parameter kaedah, "anak panah" (ini adalah dua aksara berturut-turut: tolak dan lebih besar), selepas itu badan kaedah berada dalam pendakap kerinting, seperti biasa. Parameter sepadan dengan yang dinyatakan dalam antara muka apabila menerangkan kaedah. Jika jenis pembolehubah boleh ditakrifkan dengan jelas oleh pengkompil (dalam kes kami, diketahui dengan pasti bahawa kami bekerja dengan tatasusunan rentetan, kerana ia Listditaip dengan tepat oleh tatasusunan rentetan), maka jenis pembolehubah String[]tidak perlu ditulis.
Jika anda tidak pasti, nyatakan jenisnya dan IDEA akan menyerlahkannya dalam warna kelabu jika tidak diperlukan.
Anda boleh membaca lebih lanjut dalam tutorial Oracle , sebagai contoh. Ini dipanggil "menaip sasaran" . Anda boleh memberikan sebarang nama kepada pembolehubah, tidak semestinya nama yang dinyatakan dalam antara muka. Jika tiada parameter, maka hanya kurungan. Jika terdapat hanya satu parameter, hanya nama pembolehubah tanpa tanda kurungan. Kami telah menyusun parameter, kini mengenai badan ungkapan lambda itu sendiri. Di dalam pendakap kerinting, tulis kod seperti kaedah biasa. Jika keseluruhan kod anda hanya terdiri daripada satu baris, anda tidak perlu menulis pendakap kerinting sama sekali (seperti jika dan gelung). Jika lambda anda mengembalikan sesuatu, tetapi badannya terdiri daripada satu baris, ia returntidak perlu menulis sama sekali. Tetapi jika anda mempunyai pendakap kerinting, maka, seperti dalam kaedah biasa, anda perlu menulis secara eksplisit return.

Contoh

Contoh 1.
() -> {}
Pilihan yang paling mudah. Dan yang paling tidak bermakna :) Kerana ia tidak melakukan apa-apa. Contoh 2.
() -> ""
Juga pilihan yang menarik. Ia tidak menerima apa-apa dan mengembalikan rentetan kosong ( returndiabaikan sebagai tidak perlu). Sama, tetapi dengan return:
() -> {
    return "";
}
Contoh 3. Hello world menggunakan lambdas
() -> System.out.println("Hello world!")
Tidak menerima apa-apa, tidak mengembalikan apa-apa (kami tidak boleh meletakkan returnsebelum panggilan System.out.println(), kerana jenis pulangan dalam kaedah println() — void), hanya memaparkan inskripsi pada skrin. Sesuai untuk melaksanakan antara muka Runnable. Contoh yang sama adalah 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 juga boleh menyimpan ungkapan lambda sebagai objek jenis Runnable, dan kemudian menyerahkannya kepada pembina 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 dengan lebih dekat pada saat menyimpan ungkapan lambda ke dalam pembolehubah. Antara muka Runnablememberitahu kita bahawa objeknya mesti mempunyai kaedah public void run(). Mengikut antara muka, kaedah larian tidak menerima apa-apa sebagai parameter. Dan ia tidak mengembalikan apa-apa (void). Oleh itu, apabila menulis dengan cara ini, objek akan dicipta dengan beberapa kaedah yang tidak menerima atau mengembalikan apa-apa. Yang agak konsisten dengan kaedah run()dalam antara muka Runnable. Itulah sebabnya kami dapat meletakkan ungkapan lambda ini ke dalam pembolehubah seperti Runnable. Contoh 4
() -> 42
Sekali lagi, ia tidak menerima apa-apa, tetapi mengembalikan nombor 42. Ungkapan lambda ini boleh diletakkan dalam pembolehubah jenis Callable, kerana antara muka ini hanya mentakrifkan satu kaedah, yang kelihatan seperti ini:
V call(),
di manakah Vjenis nilai pulangan (dalam kes kami int). Sehubungan itu, kita boleh menyimpan ungkapan lambda seperti 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 ungkapan lambda tanpa parameter dan jenis pulangannya void(kerana tiada return). Contoh 6
x -> x
Di sini kita mengambil sesuatu menjadi pembolehubah хdan mengembalikannya. Sila ambil perhatian bahawa jika hanya satu parameter diterima, maka kurungan di sekelilingnya tidak perlu ditulis. Sama, tetapi dengan kurungan:
(x) -> x
Dan inilah pilihan dengan pilihan yang jelas return:
x -> {
    return x;
}
Atau seperti ini, dengan kurungan dan return:
(x) -> {
    return x;
}
Atau dengan petunjuk jenis yang jelas (dan, dengan itu, dengan kurungan):
(int x) -> x
Contoh 7
x -> ++x
Kami menerimanya хdan mengembalikannya, tetapi untuk 1lebih. Anda juga boleh menulis semula seperti ini:
x -> x + 1
Dalam kedua-dua kes, kami tidak menunjukkan tanda kurungan di sekeliling parameter, badan kaedah dan perkataan return, kerana ini tidak perlu. Pilihan dengan kurungan dan pulangan diterangkan dalam contoh 6. Contoh 8
(x, y) -> x % y
Kami menerima sebahagian хdan у, mengembalikan baki bahagian xdengan y. Tanda kurung di sekitar parameter sudah diperlukan di sini. Ia adalah pilihan hanya apabila terdapat hanya satu parameter. Seperti ini dengan petunjuk 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, rentetan dengan nama dan umur integer. Dalam kaedah itu sendiri, kami menetapkan nama dan umur yang diluluskan kepada Kucing. Memandangkan pembolehubah catkami ialah jenis rujukan, objek Cat di luar ungkapan lambda akan berubah (ia akan menerima nama dan umur yang diluluskan di dalam). Versi yang lebih rumit yang menggunakan lambda yang 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 +
                '}';
    }
}
Keputusan: Cat{name='null', age=0} Cat{name='Murzik', age=3} Seperti yang anda lihat, pada mulanya objek Cat mempunyai satu keadaan, tetapi selepas menggunakan ungkapan lambda, keadaan berubah . Ungkapan Lambda berfungsi dengan baik dengan generik. Dan jika kita perlu mencipta class Dog, sebagai contoh, itu juga akan melaksanakan WithNameAndAge, maka dalam kaedah itu main()kita boleh melakukan operasi yang sama dengan Dog, tanpa mengubah ungkapan lambda itu sendiri sama sekali. Tugasan 3 . Tulis antara muka berfungsi dengan kaedah yang mengambil nombor dan mengembalikan nilai Boolean. Tulis pelaksanaan antara muka sedemikian dalam bentuk ungkapan lambda yang kembali truejika nombor yang diluluskan boleh dibahagi dengan 13 tanpa baki.Tugas 4 . Tulis antara muka berfungsi dengan kaedah yang mengambil dua rentetan dan mengembalikan rentetan yang sama. Tulis pelaksanaan antara muka sedemikian dalam bentuk lambda yang mengembalikan rentetan terpanjang. Tugasan 5 . Tulis antara muka berfungsi dengan kaedah yang menerima tiga nombor pecahan: a, b, cdan mengembalikan nombor pecahan yang sama. Tulis pelaksanaan antara muka sedemikian dalam bentuk ungkapan lambda yang mengembalikan diskriminasi. Siapa terlupa, D = b^2 - 4ac . Tugasan 6 . Menggunakan antara muka berfungsi daripada tugasan 5, tulis ungkapan lambda yang mengembalikan hasil operasi a * b^c. Popular tentang ungkapan lambda di Jawa. Dengan contoh dan tugasan. Bahagian 2.
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION