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 2

Diterbitkan dalam kumpulan
Untuk siapa artikel ini?
  • Bagi mereka yang membaca bahagian pertama 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.

Akses kepada pembolehubah luaran

Adakah kod ini akan dikompilasi dengan kelas tanpa nama?
int counter = 0;
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter++;
    }
};
Tidak. Pembolehubah countermestilah final. Atau tidak semestinya final, tetapi dalam apa jua keadaan ia tidak boleh mengubah nilainya. Prinsip yang sama digunakan dalam ungkapan lambda. Mereka mempunyai akses kepada semua pembolehubah yang "kelihatan" kepada mereka dari tempat di mana ia diisytiharkan. Tetapi lambda tidak boleh mengubahnya (tetapkan nilai baharu). Benar, terdapat pilihan untuk memintas had ini dalam kelas tanpa nama. Ia cukup hanya untuk mencipta pembolehubah jenis rujukan dan menukar keadaan dalaman objek. Dalam kes ini, pembolehubah itu sendiri akan menunjuk ke objek yang sama, dan dalam kes ini anda boleh menunjukkannya dengan selamat sebagai final.
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Di sini pembolehubah kami counteradalah rujukan kepada objek jenis AtomicInteger. Dan untuk menukar keadaan objek ini, kaedah digunakan incrementAndGet(). Nilai pembolehubah itu sendiri tidak berubah semasa program berjalan dan sentiasa menunjuk ke objek yang sama, yang membolehkan kami mengisytiharkan pembolehubah serta-merta dengan kata kunci final. Contoh yang sama, tetapi dengan ungkapan lambda:
int counter = 0;
Runnable r = () -> counter++;
Ia tidak menyusun atas sebab yang sama seperti pilihan dengan kelas tanpa nama: counteria tidak sepatutnya berubah semasa program sedang berjalan. Tetapi seperti ini - semuanya baik-baik saja:
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = () -> counter.incrementAndGet();
Ini juga terpakai kepada kaedah panggilan. Dari dalam ungkapan lambda, anda bukan sahaja boleh mengakses semua pembolehubah "kelihatan", tetapi juga memanggil kaedah yang anda ada akses.
public class Main {
    public static void main(String[] args) {
        Runnable runnable = () -> staticMethod();
        new Thread(runnable).start();
    }

    private static void staticMethod() {
        System.out.println("Я - метод staticMethod(), и меня только-что кто-то вызвал!");
    }
}
Walaupun kaedah staticMethod()itu peribadi, ia "boleh diakses" untuk dipanggil di dalam kaedah main(), jadi ia juga boleh diakses untuk memanggil dari dalam lambda yang dibuat dalam kaedah main.

Detik pelaksanaan kod ungkapan lambda

Soalan ini mungkin kelihatan terlalu mudah kepada anda, tetapi patut ditanya: bilakah kod di dalam ungkapan lambda akan dilaksanakan? Pada saat penciptaan? Atau pada masa bila (masih tidak diketahui di mana) ia akan dipanggil? Ia agak mudah untuk menyemak.
System.out.println("Запуск программы");

// много всякого разного codeа
// ...

System.out.println("Перед объявлением лямбды");

Runnable runnable = () -> System.out.println("Я - лямбда!");

System.out.println("После объявления лямбды");

// много всякого другого codeа
// ...

System.out.println("Перед передачей лямбды в тред");
new Thread(runnable).start();
Output pada paparan:
Запуск программы
Перед объявлением лямбды
После объявления лямбды
Перед передачей лямбды в тред
Я - лямбда!
Ia boleh dilihat bahawa kod ungkapan lambda telah dilaksanakan pada penghujungnya, selepas benang dicipta dan hanya apabila proses pelaksanaan program mencapai pelaksanaan sebenar kaedah run(). Dan tidak sama sekali pada masa pengumumannya. Dengan mengisytiharkan ungkapan lambda, kami hanya mencipta objek jenis Runnabledan menerangkan gelagat kaedahnya run(). Kaedah itu sendiri telah dilancarkan tidak lama kemudian.

Rujukan Kaedah?

Tidak berkaitan secara langsung dengan lambdas itu sendiri, tetapi saya fikir adalah logik untuk mengatakan beberapa perkataan mengenainya dalam artikel ini. Katakan kita mempunyai ungkapan lambda yang tidak melakukan sesuatu yang istimewa, tetapi hanya memanggil beberapa kaedah.
x -> System.out.println(x)
Mereka menghulurkan sesuatu kepadanya х, dan ia hanya memanggilnya System.out.println()dan melepasinya di sana х. Dalam kes ini, kami boleh menggantikannya dengan pautan kepada kaedah yang kami perlukan. seperti ini:
System.out::println
Ya, tanpa tanda kurung di penghujungnya! Contoh yang lebih lengkap:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(x -> System.out.println(x));
Pada baris terakhir kami menggunakan kaedah forEach()yang menerima objek antara muka Consumer. Ini sekali lagi merupakan antara muka berfungsi dengan hanya satu kaedah void accept(T t). Sehubungan itu, kami menulis ungkapan lambda yang mengambil satu parameter (memandangkan ia ditaip dalam antara muka itu sendiri, kami tidak menunjukkan jenis parameter, tetapi menunjukkan bahawa ia akan dipanggil х). Dalam badan ungkapan lambda kami menulis kod yang akan dilaksanakan apabila kaedah dipanggil accept(). Di sini kita hanya memaparkan pada skrin apa yang ada dalam pembolehubah х. Kaedah itu sendiri forEach()melalui semua elemen koleksi, memanggil Consumerkaedah objek antara muka yang dihantar kepadanya (lambda kami) accept(), di mana ia melepasi setiap elemen daripada koleksi. Seperti yang telah saya katakan, ini adalah lambda -expression (hanya memanggil kaedah lain) kita boleh gantikan dengan rujukan kepada kaedah yang kita perlukan. Kemudian kod kita akan kelihatan seperti ini:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(System.out::println);
Perkara utama ialah parameter yang diterima kaedah (println()dan accept()). Memandangkan kaedah println()boleh menerima apa-apa sahaja (ia terbeban untuk semua primitif dan untuk sebarang objek), bukannya ungkapan lambda, kita boleh menghantar forEach()hanya rujukan kepada kaedah println(). Kemudian forEach()ia akan mengambil setiap elemen koleksi dan menghantarnya terus kepada kaedah println(). Bagi mereka yang pertama kali menghadapi perkara ini, sila ambil perhatian Sila ambil perhatian bahawa kami tidak memanggil kaedah tersebut System.out.println()(dengan titik antara perkataan dan dengan kurungan di hujungnya), sebaliknya kami memberikan rujukan kepada kaedah ini sendiri.
strings.forEach(System.out.println());
kami akan mempunyai ralat penyusunan. Kerana sebelum panggilan forEach(), Java akan melihat bahawa ia sedang dipanggil System.out.println(), ia akan memahami bahawa ia sedang dikembalikan voiddan akan cuba voidmenghantar ini kepada forEach()objek jenis yang sedang menunggu di sana Consumer.

Sintaks untuk menggunakan Rujukan Kaedah

Ia agak mudah:
  1. Menghantar rujukan kepada kaedah statikNameКласса:: NameСтатическогоМетода?

    public class Main {
        public static void main(String[] args) {
            List<String> strings = new LinkedList<>();
            strings.add("Mother");
            strings.add("soap");
            strings.add("frame");
    
            strings.forEach(Main::staticMethod);
        }
    
        private static void staticMethod(String s) {
            // do something
        }
    }
  2. Menghantar rujukan kepada kaedah bukan statik menggunakan objek sedia adaNameПеременнойСОбъектом:: method name

    public class Main {
        public static void main(String[] args) {
            List<String> strings = new LinkedList<>();
            strings.add("Mother");
            strings.add("soap");
            strings.add("frame");
    
            Main instance = new Main();
            strings.forEach(instance::nonStaticMethod);
        }
    
        private void nonStaticMethod(String s) {
            // do something
        }
    }
  3. Kami memberikan rujukan kepada kaedah bukan statik menggunakan kelas di mana kaedah sedemikian dilaksanakanNameКласса:: method name

    public class Main {
        public static void main(String[] args) {
            List<User> users = new LinkedList<>();
            users.add(new User("Vasya"));
            users.add(new User("Коля"));
            users.add(new User("Петя"));
    
            users.forEach(User::print);
        }
    
        private static class User {
            private String name;
    
            private User(String name) {
                this.name = name;
            }
    
            private void print() {
                System.out.println(name);
            }
        }
    }
  4. Menghantar pautan kepada pembina NameКласса::new
    Menggunakan pautan kaedah adalah sangat mudah apabila terdapat kaedah siap sedia yang anda berpuas hati sepenuhnya, dan anda ingin menggunakannya sebagai panggilan balik. Dalam kes ini, bukannya menulis ungkapan lambda dengan kod kaedah itu, atau ungkapan lambda di mana kita hanya memanggil kaedah ini, kita hanya menyampaikan rujukan kepadanya. Itu sahaja.

Perbezaan menarik antara kelas tanpa nama dan ungkapan lambda

Dalam kelas tanpa nama, kata kunci thismenghala ke objek kelas tanpa nama itu. Dan jika kita menggunakannya thisdi dalam lambda, kita akan mendapat akses kepada objek kelas pembingkaian. Di mana kita sebenarnya menulis ungkapan ini. Ini berlaku kerana ungkapan lambda, apabila disusun, menjadi kaedah peribadi kelas tempat ia ditulis. Saya tidak akan mengesyorkan menggunakan "ciri" ini, kerana ia mempunyai kesan sampingan, yang bercanggah dengan prinsip pengaturcaraan berfungsi. Tetapi pendekatan ini agak konsisten dengan OOP. ;)

Dari mana saya dapat maklumat atau apa lagi untuk dibaca

Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION