CodeGym /Blog Java /rawak /Penjelasan tentang ungkapan lambda di Jawa. Dengan contoh...
John Squirrels
Tahap
San Francisco

Penjelasan tentang ungkapan lambda di Jawa. Dengan contoh dan tugasan. Bahagian 2

Diterbitkan dalam kumpulan
Untuk siapa artikel ini?
  • Ia untuk orang yang membaca bahagian pertama artikel ini;
  • Ia adalah untuk orang yang berpendapat mereka sudah mengetahui Java Core dengan baik, tetapi tidak tahu tentang ungkapan lambda di Jawa. Atau mungkin mereka pernah mendengar sesuatu tentang ungkapan lambda, tetapi butirannya kurang.
  • Ia adalah untuk orang yang mempunyai pemahaman tertentu tentang ungkapan lambda, tetapi masih takut dengannya dan tidak biasa menggunakannya.
Jika anda tidak sesuai dengan salah satu daripada kategori ini, anda mungkin mendapati artikel ini membosankan, cacat, atau biasanya bukan secawan teh anda. Dalam kes ini, jangan ragu untuk beralih kepada perkara lain atau, jika anda mahir dalam subjek, sila berikan cadangan dalam ulasan tentang cara saya boleh menambah baik atau menambah artikel. Penjelasan tentang ungkapan lambda di Jawa.  Dengan contoh dan tugasan.  Bahagian 2 - 1Bahan tersebut tidak mendakwa mempunyai sebarang nilai akademik, apatah lagi kebaharuan. Sebaliknya: Saya akan cuba menerangkan perkara yang rumit (bagi sesetengah orang) semudah mungkin. Permintaan untuk menerangkan API Strim telah memberi inspirasi kepada saya untuk menulis ini. Saya memikirkannya dan memutuskan bahawa beberapa contoh strim saya tidak dapat difahami tanpa pemahaman tentang ungkapan lambda. Jadi kita akan mulakan dengan ungkapan lambda.

Akses kepada pembolehubah luaran

Adakah kod ini disusun dengan kelas tanpa nama?

int counter = 0;
Runnable r = new Runnable() { 

    @Override 
    public void run() { 
        counter++;
    }
};
Tidak. counter Pembolehubah mestilah final. Atau jika tidak final, maka sekurang-kurangnya ia tidak boleh mengubah nilainya. Prinsip yang sama berlaku dalam ungkapan lambda. Mereka boleh mengakses semua pembolehubah yang mereka boleh "lihat" dari tempat mereka diisytiharkan. Tetapi lambda tidak boleh mengubahnya (berikan nilai baharu kepada mereka). Walau bagaimanapun, terdapat cara untuk memintas sekatan ini dalam kelas tanpa nama. Hanya buat pembolehubah rujukan dan tukar keadaan dalaman objek. Dengan berbuat demikian, pembolehubah itu sendiri tidak berubah (menunjuk ke objek yang sama) dan boleh ditanda 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 AtomicIntegerobjek. Dan incrementAndGet()kaedah digunakan untuk menukar keadaan objek ini. Nilai pembolehubah itu sendiri tidak berubah semasa program sedang berjalan. Ia sentiasa menunjuk ke objek yang sama, yang membolehkan kami mengisytiharkan pembolehubah dengan kata kunci akhir. Berikut adalah contoh yang sama, tetapi dengan ungkapan lambda:

int counter = 0;
Runnable r = () -> counter++;
Ini tidak akan dikompil atas sebab yang sama seperti versi dengan kelas tanpa nama:  countertidak boleh berubah semasa program sedang berjalan. Tetapi semuanya baik-baik saja jika kita melakukannya seperti ini:

final AtomicInteger counter = new AtomicInteger(0); 
Runnable r = () -> counter.incrementAndGet();
Ini juga terpakai pada kaedah panggilan. Dalam ungkapan lambda, anda bukan sahaja boleh mengakses semua pembolehubah "kelihatan", tetapi juga memanggil sebarang kaedah yang boleh diakses.

public class Main { 

    public static void main(String[] args) {
        Runnable runnable = () -> staticMethod();
        new Thread(runnable).start();
    } 

    private static void staticMethod() { 

        System.out.println("I'm staticMethod(), and someone just called me!");
    }
}
Walaupun staticMethod()bersifat peribadi, ia boleh diakses di dalam main()kaedah, jadi ia juga boleh dipanggil dari dalam lambda yang dibuat dalam mainkaedah tersebut.

Bilakah ungkapan lambda dilaksanakan?

Anda mungkin mendapati soalan berikut terlalu mudah, tetapi anda harus bertanya sama sahaja: bilakah kod di dalam ungkapan lambda akan dilaksanakan? Bilakah ia dicipta? Atau apabila disebut (yang belum diketahui)? Ini agak mudah untuk diperiksa.

System.out.println("Program start"); 

// All sorts of code here
// ...

System.out.println("Before lambda declaration");

Runnable runnable = () -> System.out.println("I'm a lambda!");

System.out.println("After lambda declaration"); 

// All sorts of other code here
// ...

System.out.println("Before passing the lambda to the thread");
new Thread(runnable).start(); 
Output skrin:

Program start
Before lambda declaration
After lambda declaration
Before passing the lambda to the thread
I'm a lambda!
Anda boleh melihat bahawa ungkapan lambda telah dilaksanakan pada penghujungnya, selepas benang dibuat dan hanya apabila pelaksanaan program mencapai kaedah run(). Sudah tentu tidak apabila ia diisytiharkan. Dengan mengisytiharkan ungkapan lambda, kami hanya mencipta Runnableobjek dan menerangkan cara run()kaedahnya bertindak. Kaedah itu sendiri dilaksanakan lebih lama kemudian.

Rujukan kaedah?

Rujukan kaedah tidak berkaitan secara langsung dengan lambda, tetapi saya rasa masuk akal untuk menyatakan beberapa perkataan tentangnya dalam artikel ini. Katakan kita mempunyai ungkapan lambda yang tidak melakukan sesuatu yang istimewa, tetapi hanya memanggil kaedah.

x -> System.out.println(x)
Ia menerima beberapa xdan hanya panggilan System.out.println(), melalui x. Dalam kes ini, kita boleh menggantikannya dengan rujukan kepada kaedah yang dikehendaki. seperti ini:

System.out::println
Betul — tiada kurungan pada penghujungnya! Berikut ialah contoh yang lebih lengkap:

List<String> strings = new LinkedList<>(); 

strings.add("Dota"); 
strings.add("GTA5"); 
strings.add("Halo"); 

strings.forEach(x -> System.out.println(x));
Dalam baris terakhir, kami menggunakan forEach()kaedah, yang mengambil objek yang melaksanakan Consumerantara muka. Sekali lagi, ini adalah antara muka berfungsi, hanya mempunyai satu void accept(T t)kaedah. Sehubungan itu, kami menulis ungkapan lambda yang mempunyai satu parameter (kerana ia ditaip dalam antara muka itu sendiri, kami tidak menyatakan jenis parameter; kami hanya menunjukkan bahawa kami akan memanggilnya x). Dalam badan ungkapan lambda, kami menulis kod yang akan dilaksanakan apabila kaedah accept()dipanggil. Di sini kita hanya memaparkan apa yang berakhir dalam xpembolehubah. Kaedah yang sama ini forEach()berulang melalui semua elemen dalam koleksi dan memanggil accept()kaedah pada pelaksanaanConsumerantara muka (lambda kami), menghantar setiap item dalam koleksi. Seperti yang saya katakan, kita boleh menggantikan ungkapan lambda sedemikian (yang hanya mengkelaskan kaedah yang berbeza) dengan merujuk kepada kaedah yang dikehendaki. Kemudian kod kami akan kelihatan seperti ini:

List<String> strings = new LinkedList<>(); 

strings.add("Dota"); 
strings.add("GTA5"); 
strings.add("Halo");

strings.forEach(System.out::println);
Perkara utama ialah parameter println()dan accept()kaedah sepadan. Oleh kerana kaedah itu println()boleh menerima apa-apa sahaja (ia terlebih beban untuk semua jenis primitif dan semua objek), bukannya ungkapan lambda, kita hanya boleh menghantar rujukan kepada kaedah println()kepada forEach(). Kemudian forEach()akan mengambil setiap elemen dalam koleksi dan menghantarnya terus ke println()kaedah. Bagi sesiapa yang menghadapi perkara ini buat kali pertama, sila ambil perhatian bahawa kami tidak memanggil System.out.println()(dengan titik antara perkataan dan dengan kurungan di hujungnya). Sebaliknya, kami memberikan rujukan kepada kaedah ini. Jika kita menulis ini

strings.forEach(System.out.println());
kami akan mempunyai ralat penyusunan. Sebelum panggilan ke forEach(), Java melihat bahawa System.out.println()sedang dipanggil, jadi ia memahami bahawa nilai pulangan adalah voiddan akan cuba dihantar voidke forEach(), yang sebaliknya mengharapkan Consumerobjek.

Sintaks untuk rujukan kaedah

Ia agak mudah:
  1. Kami memberikan rujukan kepada kaedah statik seperti ini:ClassName::staticMethodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<String> strings = new LinkedList<>(); 
            strings.add("Dota"); 
            strings.add("GTA5"); 
            strings.add("Halo"); 
    
            strings.forEach(Main::staticMethod); 
        } 
    
        private static void staticMethod(String s) { 
    
            // Do something 
        } 
    }
    
  2. Kami menghantar rujukan kepada kaedah bukan statik menggunakan objek sedia ada, seperti ini:objectName::instanceMethodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<String> strings = new LinkedList<>();
            strings.add("Dota"); 
            strings.add("GTA5"); 
            strings.add("Halo"); 
    
            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 yang melaksanakannya seperti berikut:ClassName::methodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<User> users = new LinkedList<>(); 
            users.add (new User("John")); 
            users.add(new User("Paul")); 
            users.add(new User("George")); 
    
            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. Kami menyampaikan rujukan kepada pembina seperti ini:ClassName::new

    Rujukan kaedah sangat mudah apabila anda sudah mempunyai kaedah yang akan berfungsi dengan sempurna sebagai panggilan balik. Dalam kes ini, daripada menulis ungkapan lambda yang mengandungi kod kaedah atau menulis ungkapan lambda yang hanya memanggil kaedah, kami hanya menyampaikan rujukan kepadanya. Dan itu sahaja.

Perbezaan menarik antara kelas tanpa nama dan ungkapan lambda

Dalam kelas tanpa nama, thiskata kunci menghala ke objek kelas tanpa nama. Tetapi jika kita menggunakan ini di dalam lambda, kita mendapat akses kepada objek kelas yang mengandungi. Yang mana kita sebenarnya menulis ungkapan lambda. Ini berlaku kerana ungkapan lambda dikompilasi ke dalam kaedah persendirian kelas yang ditulis. Saya tidak mengesyorkan menggunakan "ciri" ini kerana ia mempunyai kesan sampingan dan bercanggah dengan prinsip pengaturcaraan berfungsi. Walau bagaimanapun, pendekatan ini konsisten sepenuhnya dengan OOP. ;)

Di manakah saya mendapat maklumat saya dan apa lagi yang perlu anda baca?

Dan, sudah tentu, saya menemui banyak perkara di Google :)
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION