CodeGym /Java Blog /Acak /Penjelasan ekspresi lambda di Jawa. Dengan contoh dan tug...
John Squirrels
Level 41
San Francisco

Penjelasan ekspresi lambda di Jawa. Dengan contoh dan tugas. Bagian 2

Dipublikasikan di grup Acak
Untuk siapa artikel ini?
  • Ini untuk orang yang membaca bagian pertama artikel ini;
  • Ini untuk orang-orang yang merasa sudah mengenal Java Core dengan baik, tetapi tidak tahu tentang ekspresi lambda di Java. Atau mungkin mereka pernah mendengar sesuatu tentang ekspresi lambda, tetapi detailnya kurang.
  • Ini untuk orang-orang yang memiliki pemahaman tertentu tentang ekspresi lambda, tetapi masih takut dan tidak terbiasa menggunakannya.
Jika Anda tidak cocok dengan salah satu kategori ini, Anda mungkin menganggap artikel ini membosankan, cacat, atau secara umum tidak cocok untuk Anda. Dalam hal ini, jangan ragu untuk beralih ke hal lain atau, jika Anda berpengalaman dalam subjek ini, silakan berikan saran di komentar tentang bagaimana saya dapat meningkatkan atau menambah artikel. Penjelasan ekspresi lambda di Jawa.  Dengan contoh dan tugas.  Bagian 2 - 1Materi tersebut tidak mengklaim memiliki nilai akademis, apalagi kebaruan. Justru sebaliknya: Saya akan mencoba mendeskripsikan hal-hal yang kompleks (bagi sebagian orang) sesederhana mungkin. Permintaan untuk menjelaskan Stream API mengilhami saya untuk menulis ini. Saya memikirkannya dan memutuskan bahwa beberapa contoh aliran saya tidak akan dapat dipahami tanpa pemahaman tentang ekspresi lambda. Jadi kita akan mulai dengan ekspresi lambda.

Akses ke variabel eksternal

Apakah kode ini dikompilasi dengan kelas anonim?

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

    @Override 
    public void run() { 
        counter++;
    }
};
Tidak. counter Variabel harus final. Atau jika tidak final, maka setidaknya tidak dapat mengubah nilainya. Prinsip yang sama berlaku dalam ekspresi lambda. Mereka dapat mengakses semua variabel yang dapat mereka "lihat" dari tempat mereka dideklarasikan. Tetapi lambda tidak boleh mengubahnya (berikan nilai baru padanya). Namun, ada cara untuk melewati batasan ini di kelas anonim. Cukup buat variabel referensi dan ubah status internal objek. Dengan demikian, variabel itu sendiri tidak berubah (menunjuk ke objek yang sama) dan dapat dengan aman ditandai 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 suatu AtomicIntegerobjek. Dan incrementAndGet()metode ini digunakan untuk mengubah keadaan objek ini. Nilai variabel itu sendiri tidak berubah saat program sedang berjalan. Itu selalu menunjuk ke objek yang sama, yang memungkinkan kita mendeklarasikan variabel dengan kata kunci final. Berikut adalah contoh yang sama, tetapi dengan ekspresi lambda:

int counter = 0;
Runnable r = () -> counter++;
Ini tidak dapat dikompilasi karena alasan yang sama dengan versi dengan kelas anonim:  countertidak boleh berubah saat program sedang berjalan. Tapi semuanya baik-baik saja jika kita melakukannya seperti ini:

final AtomicInteger counter = new AtomicInteger(0); 
Runnable r = () -> counter.incrementAndGet();
Ini juga berlaku untuk metode pemanggilan. Dalam ekspresi lambda, Anda tidak hanya dapat mengakses semua variabel yang "terlihat", tetapi juga memanggil metode apa pun yang dapat 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!");
    }
}
Meskipun staticMethod()bersifat pribadi, ini dapat diakses di dalam main()metode, sehingga juga dapat dipanggil dari dalam lambda yang dibuat di dalam mainmetode.

Kapan ekspresi lambda dieksekusi?

Anda mungkin menemukan pertanyaan berikut terlalu sederhana, tetapi Anda harus menanyakannya dengan cara yang sama: kapan kode di dalam ekspresi lambda akan dieksekusi? Kapan itu dibuat? Atau kapan disebut (yang belum diketahui)? Ini cukup 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(); 
Keluaran layar:

Program start
Before lambda declaration
After lambda declaration
Before passing the lambda to the thread
I'm a lambda!
Anda dapat melihat bahwa ekspresi lambda dieksekusi di bagian paling akhir, setelah utas dibuat dan hanya ketika eksekusi program mencapai metode run(). Tentu saja tidak saat diumumkan. Dengan mendeklarasikan ekspresi lambda, kami hanya membuat Runnableobjek dan menjelaskan bagaimana run()metodenya berperilaku. Metode itu sendiri dieksekusi lama kemudian.

Referensi metode?

Referensi metode tidak terkait langsung dengan lambda, tetapi menurut saya masuk akal untuk mengatakan beberapa patah kata tentangnya di artikel ini. Misalkan kita memiliki ekspresi lambda yang tidak melakukan sesuatu yang istimewa, tetapi hanya memanggil metode.

x -> System.out.println(x)
Ia menerima beberapa xdan hanya panggilan System.out.println(), lewat x. Dalam hal ini, kita dapat menggantinya dengan referensi metode yang diinginkan. Seperti ini:

System.out::println
Itu benar — tidak ada tanda kurung di akhir! Berikut 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));
Di baris terakhir, kami menggunakan forEach()metode yang mengambil objek yang mengimplementasikan Consumerantarmuka. Sekali lagi, ini adalah antarmuka fungsional, hanya memiliki satu void accept(T t)metode. Oleh karena itu, kami menulis ekspresi lambda yang memiliki satu parameter (karena diketik di antarmuka itu sendiri, kami tidak menentukan tipe parameter; kami hanya menunjukkan bahwa kami akan memanggilnya x). Di badan ekspresi lambda, kami menulis kode yang akan dieksekusi saat metode accept()dipanggil. Di sini kami hanya menampilkan apa yang berakhir di xvariabel. Metode yang sama ini forEach()mengiterasi semua elemen dalam koleksi dan memanggil accept()metode pada implementasiConsumerantarmuka (lambda kami), meneruskan setiap item dalam koleksi. Seperti yang saya katakan, kita dapat mengganti ekspresi lambda (yang hanya mengklasifikasikan metode yang berbeda) dengan referensi ke metode yang diinginkan. Maka kode kita akan terlihat seperti ini:

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

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

strings.forEach(System.out::println);
Hal utama adalah bahwa parameter dari metode println()dan accept()cocok. Karena println()metode dapat menerima apa saja (itu kelebihan beban untuk semua tipe primitif dan semua objek), alih-alih ekspresi lambda, kita cukup meneruskan referensi ke metode println()ke forEach(). Kemudian forEach()akan mengambil setiap elemen dalam koleksi dan meneruskannya langsung ke println()metode. Bagi siapa pun yang mengalami ini untuk pertama kalinya, harap perhatikan bahwa kami tidak memanggil System.out.println()(dengan titik di antara kata dan dengan tanda kurung di akhir). Sebagai gantinya, kami memberikan referensi ke metode ini. Jika kita menulis ini

strings.forEach(System.out.println());
kita akan mengalami kesalahan kompilasi. Sebelum pemanggilan ke forEach(), Java melihat apa System.out.println()yang dipanggil, sehingga ia memahami bahwa nilai yang dikembalikan adalah voiddan akan mencoba meneruskan voidke forEach(), yang sebaliknya mengharapkan sebuah Consumerobjek.

Sintaks untuk referensi metode

Ini cukup sederhana:
  1. Kami meneruskan referensi ke metode statis 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 meneruskan referensi ke metode non-statis menggunakan objek yang sudah 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 meneruskan referensi ke metode non-statis menggunakan kelas yang mengimplementasikannya sebagai 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 meneruskan referensi ke konstruktor seperti ini:ClassName::new

    Referensi metode sangat nyaman ketika Anda sudah memiliki metode yang akan bekerja dengan sempurna sebagai panggilan balik. Dalam hal ini, alih-alih menulis ekspresi lambda yang berisi kode metode, atau menulis ekspresi lambda yang hanya memanggil metode, kami cukup meneruskan referensi ke metode tersebut. Dan itu saja.

Perbedaan yang menarik antara kelas anonim dan ekspresi lambda

Di kelas anonim, thiskata kunci menunjuk ke objek kelas anonim. Tetapi jika kita menggunakan ini di dalam lambda, kita mendapatkan akses ke objek dari kelas yang memuatnya. Di mana kami benar-benar menulis ekspresi lambda. Ini terjadi karena ekspresi lambda dikompilasi menjadi metode privat dari kelas tempat mereka ditulis. Saya tidak akan merekomendasikan penggunaan "fitur" ini, karena memiliki efek samping dan bertentangan dengan prinsip pemrograman fungsional. Yang mengatakan, pendekatan ini sepenuhnya konsisten dengan OOP. ;)

Dari mana saya mendapatkan informasi saya dan apa lagi yang harus Anda baca?

Dan, tentu saja, saya menemukan banyak hal di Google :)
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION