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 2

Dipublikasikan di grup Random-ID
Untuk siapa artikel ini?
  • Bagi yang membaca bagian pertama 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.

Akses ke variabel eksternal

Apakah kode ini akan dikompilasi dengan kelas anonim?
int counter = 0;
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter++;
    }
};
TIDAK. Variabelnya counterharus final. Atau belum tentu final, tetapi bagaimanapun juga nilainya tidak dapat diubah. Prinsip yang sama digunakan dalam ekspresi lambda. Mereka memiliki akses ke semua variabel yang "terlihat" oleh mereka dari tempat mereka dideklarasikan. Tetapi lambda tidak boleh mengubahnya (memberi nilai baru). Benar, ada opsi untuk melewati batasan ini di kelas anonim. Cukup dengan membuat variabel tipe referensi dan mengubah keadaan internal objek. Dalam hal ini, variabel itu sendiri akan menunjuk ke objek yang sama, dan dalam hal ini Anda dapat dengan aman menunjukkannya sebagai final.
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Di sini variabel kita counteradalah referensi ke objek bertipe AtomicInteger. Dan untuk mengubah keadaan objek ini digunakan metode incrementAndGet(). Nilai dari variabel itu sendiri tidak berubah ketika program sedang berjalan dan selalu menunjuk ke objek yang sama, sehingga memungkinkan kita untuk mendeklarasikan suatu variabel secara langsung dengan kata kunci final. Contoh yang sama, tetapi dengan ekspresi lambda:
int counter = 0;
Runnable r = () -> counter++;
Itu tidak dikompilasi karena alasan yang sama seperti opsi dengan kelas anonim: counteritu tidak boleh berubah saat program sedang berjalan. Tapi seperti ini - semuanya baik-baik saja:
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = () -> counter.incrementAndGet();
Hal ini juga berlaku untuk metode pemanggilan. Dari dalam ekspresi lambda, Anda tidak hanya dapat mengakses semua variabel “terlihat”, tetapi juga memanggil metode yang dapat Anda 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(), и меня только-что кто-то вызвал!");
    }
}
Meskipun metode ini staticMethod()bersifat pribadi, namun “dapat diakses” untuk dipanggil di dalam metode main(), sehingga juga dapat diakses untuk dipanggil dari dalam lambda yang dibuat dalam metode tersebut main.

Momen eksekusi kode ekspresi lambda

Pertanyaan ini mungkin tampak terlalu sederhana bagi Anda, tetapi patut ditanyakan: kapan kode di dalam ekspresi lambda akan dieksekusi? Pada saat penciptaan? Atau pada saat (masih belum diketahui di mana) akan dipanggil? Cara pengecekannya cukup mudah.
System.out.println("Запуск программы");

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

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

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

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

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

System.out.println("Перед передачей лямбды в тред");
new Thread(runnable).start();
Keluaran yang dipajang:
Запуск программы
Перед объявлением лямбды
После объявления лямбды
Перед передачей лямбды в тред
Я - лямбда!
Terlihat bahwa kode ekspresi lambda dieksekusi di bagian paling akhir, setelah thread dibuat dan hanya ketika proses eksekusi program mencapai eksekusi metode yang sebenarnya run(). Dan tidak sama sekali pada saat pengumumannya. Dengan mendeklarasikan ekspresi lambda, kami hanya membuat objek bertipe tersebut Runnabledan mendeskripsikan perilaku metodenya run(). Metodenya sendiri diluncurkan jauh kemudian.

Referensi Metode?

Tidak terkait langsung dengan lambda itu sendiri, tapi menurut saya masuk akal untuk mengatakan beberapa patah kata tentangnya di artikel ini. Katakanlah kita memiliki ekspresi lambda yang tidak melakukan sesuatu yang istimewa, namun hanya memanggil beberapa metode.
x -> System.out.println(x)
Mereka menyerahkan sesuatu padanya х, dan benda itu memanggilnya System.out.println()dan melewatinya di sana х. Dalam hal ini, kita bisa menggantinya dengan link ke metode yang kita butuhkan. Seperti ini:
System.out::println
Ya, tanpa tanda kurung di akhir! Contoh 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 kita menggunakan metode forEach()yang menerima objek antarmuka Consumer. Ini sekali lagi merupakan antarmuka fungsional dengan hanya satu metode void accept(T t). Oleh karena itu, kami menulis ekspresi lambda yang mengambil satu parameter (karena diketik di antarmuka itu sendiri, kami tidak menunjukkan jenis parameter, tetapi menunjukkan bahwa itu akan dipanggil х). Di badan ekspresi lambda kami menulis kode yang akan dieksekusi ketika metode dipanggil accept(). Di sini kita hanya menampilkan di layar apa yang ada dalam variabel х. Metode itu sendiri forEach()melewati semua elemen koleksi, memanggil Consumermetode objek antarmuka yang diteruskan ke sana (lambda kami) accept(), di mana ia meneruskan setiap elemen dari koleksi. Seperti yang sudah saya katakan, ini adalah ekspresi lambda (hanya memanggil metode lain) yang dapat kita ganti dengan referensi ke metode yang kita perlukan. Kemudian kode kita akan terlihat seperti ini:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(System.out::println);
Hal utama adalah parameter metode yang diterima (println()dan accept()). Karena metode ini println()dapat menerima apa pun (itu kelebihan beban untuk semua primitif dan objek apa pun), alih-alih ekspresi lambda, kita dapat meneruskan forEach()hanya referensi ke metode tersebut println(). Kemudian forEach()ia akan mengambil setiap elemen dari koleksi dan meneruskannya langsung ke metodenya println()Bagi mereka yang mengalami hal ini untuk pertama kalinya, harap dicatat Harap dicatat bahwa kami tidak memanggil metode tersebut System.out.println()(dengan titik di antara kata-kata dan dengan tanda kurung di akhir), melainkan kami meneruskan referensi ke metode ini sendiri.
strings.forEach(System.out.println());
kita akan mengalami kesalahan kompilasi. Karena sebelum pemanggilan forEach(), Java akan melihat bahwa ia sedang dipanggil System.out.println(), ia akan memahami bahwa ia sedang dikembalikan voiddan akan mencoba voidmeneruskannya ke forEach()objek bertipe yang menunggu di sana Consumer.

Sintaks untuk menggunakan Referensi Metode

Ini cukup sederhana:
  1. Melewati referensi ke metode statisNameКласса:: 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. Melewati referensi ke metode non-statis menggunakan objek yang sudah 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 meneruskan referensi ke metode non-statis menggunakan kelas tempat metode tersebut diterapkanNameКласса:: 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. Melewati tautan ke konstruktor NameКласса::new
    Menggunakan tautan metode sangat mudah bila ada metode siap pakai yang Anda benar-benar puas, dan Anda ingin menggunakannya sebagai panggilan balik. Dalam hal ini, alih-alih menulis ekspresi lambda dengan kode metode tersebut, atau ekspresi lambda yang mana kita cukup memanggil metode ini, kita cukup meneruskan referensi ke metode tersebut. Itu saja.

Perbedaan menarik antara kelas anonim dan ekspresi lambda

Dalam kelas anonim, kata kunci thismenunjuk ke objek kelas anonim tersebut. Dan jika kita menggunakannya thisdi dalam lambda, kita akan mendapatkan akses ke objek kelas framing. Di mana sebenarnya kami menulis ungkapan ini. Hal ini terjadi karena ekspresi lambda, ketika dikompilasi, menjadi metode privat dari kelas tempat ekspresi tersebut ditulis. Saya tidak akan merekomendasikan penggunaan “fitur” ini karena memiliki efek samping yang bertentangan dengan prinsip pemrograman fungsional. Namun pendekatan ini cukup konsisten dengan OOP. ;)

Dari mana saya mendapatkan informasinya atau apa lagi yang perlu dibaca

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