JavaRush /Java Blog /Random-ID /Lambdas dan referensi metode di ArrayList.forEach - cara ...

Lambdas dan referensi metode di ArrayList.forEach - cara kerjanya

Dipublikasikan di grup Random-ID
Pengenalan ekspresi lambda dalam pencarian Java Syntax Zero dimulai dengan contoh yang sangat spesifik:
ArrayList<string> list = new ArrayList<>();
Collections.addAll(list, "Hello", "How", "дела?");

list.forEach( (s) -> System.out.println(s) );
Penulis kuliah mengurai lambda dan referensi metode menggunakan fungsi forEach standar dari kelas ArrayList. Secara pribadi, saya merasa sulit untuk memahami arti dari apa yang terjadi, karena penerapan fungsi ini, serta antarmuka yang terkait dengannya, tetap “tersembunyi”. Dari mana argumen berasal , dari mana fungsi println() dilewatkan adalah pertanyaan yang harus kita jawab sendiri. Untungnya, dengan IntelliJ IDEA, kita dapat dengan mudah melihat internal kelas ArrayList dan menguraikan mie ini dari awal. Jika Anda juga tidak memahami apa pun dan ingin mengetahuinya, saya akan mencoba membantu Anda setidaknya sedikit. Ekspresi Lambda dan ArrayList.forEach - cara kerjanya Dari kuliah kita telah mengetahui bahwa ekspresi lambda adalah implementasi antarmuka fungsional . Artinya, kita mendeklarasikan antarmuka dengan satu fungsi tunggal, dan menggunakan lambda untuk menjelaskan fungsi fungsi ini. Untuk melakukan ini, Anda perlu: 1. Membuat antarmuka fungsional; 2. Buat variabel yang tipenya sesuai dengan antarmuka fungsional; 3. Tetapkan variabel ini ekspresi lambda yang menjelaskan implementasi fungsi; 4. Panggil fungsi dengan mengakses variabel (mungkin saya terlalu kasar dalam terminologi, tapi ini cara yang paling jelas). Saya akan memberikan contoh sederhana dari Google, memberikannya dengan komentar rinci (terima kasih kepada penulis situs metanit.com):
interface Operationable {
    int calculate(int x, int y);
    // Единственная функция в интерфейсе — значит, это функциональный интерфейс,
    // который можно реализовать с помощью лямбды
}

public class LambdaApp {

    public static void main(String[] args) {

        // Создаём переменную operation типа Operationable (так называется наш функциональный интерфейс)
        Operationable operation;
        // Прописываем реализацию функции calculate с помощью лямбды, на вход подаём x и y, на выходе возвращаем их сумму
        operation = (x,y)->x+y;

        // Теперь мы можем обратиться к функции calculate через переменную operation
        int result = operation.calculate(10, 20);
        System.out.println(result); //30
    }
}
Sekarang mari kita kembali ke contoh dari kuliah tersebut. Beberapa elemen bertipe String ditambahkan ke koleksi daftar . Elemen-elemen tersebut kemudian diambil menggunakan fungsi forEach standar , yang dipanggil pada objek daftar . Ekspresi lambda dengan beberapa parameter aneh diteruskan sebagai argumen ke fungsi .
ArrayList<string> list = new ArrayList<>();
Collections.addAll(list, "Hello", "How", "дела?");

list.forEach( (s) -> System.out.println(s) );
Jika Anda tidak segera memahami apa yang terjadi di sini, Anda tidak sendirian. Untungnya, IntelliJ IDEA memiliki pintasan keyboard yang bagus: Ctrl+Left_Mouse_Button . Jika kita mengarahkan kursor ke forEach dan mengklik kombinasi ini, kode sumber kelas ArrayList standar akan terbuka, di mana kita akan melihat implementasi metode forEach :
public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    final Object[] es = elementData;
    final int size = this.size;
    for (int i = 0; modCount == expectedModCount && i < size; i++)
        action.accept(elementAt(es, i));
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}
Kita melihat bahwa argumen masukan adalah tindakan bertipe Consumer . Mari kita gerakkan kursor ke atas kata Konsumen dan tekan kombinasi ajaib Ctrl+LMB lagi . Deskripsi antarmuka Konsumen akan terbuka . Jika kita menghapus implementasi default darinya (tidak penting bagi kita sekarang), kita akan melihat kode berikut:
public interface Consumer<t> {
   void accept(T t);
}
Jadi. Kami memiliki antarmuka Konsumen dengan fungsi penerimaan tunggal yang menerima satu argumen jenis apa pun. Karena hanya ada satu fungsi, maka antarmuka berfungsi, dan implementasinya dapat ditulis melalui ekspresi lambda. Kita telah melihat bahwa ArrayList memiliki fungsi forEach yang mengambil implementasi antarmuka Konsumen sebagai argumen tindakan . Selain itu, dalam fungsi forEach kita menemukan kode berikut:
for (int i = 0; modCount == expectedModCount && i < size; i++)
    action.accept(elementAt(es, i));
Perulangan for pada dasarnya mengulangi semua elemen ArrayList. Di dalam loop kita melihat panggilan ke fungsi terima dari objek tindakan - ingat bagaimana kita memanggil operasi.hitung? Elemen koleksi saat ini diteruskan ke fungsi terima . Sekarang kita akhirnya bisa kembali ke ekspresi lambda asli dan memahami fungsinya. Mari kumpulkan semua kode dalam satu tumpukan:
public interface Consumer<t> {
   void accept(T t); // Функция, которую мы реализуем лямбда-выражением
}

public void forEach(Consumer<? super E> action) // В action хранится an object Consumer, в котором функция accept реализована нашей лямбдой {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    final Object[] es = elementData;
    final int size = this.size;
    for (int i = 0; modCount == expectedModCount && i < size; i++)
        action.accept(elementAt(es, i)); // Вызываем нашу реализацию функции accept интерфейса Consumer для каждого element коллекции
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

//...

list.forEach( (s) -> System.out.println(s) );
Ekspresi lambda kami adalah implementasi dari fungsi terima yang dijelaskan di antarmuka Konsumen . Dengan menggunakan lambda, kami menetapkan bahwa fungsi terima mengambil argumen dan menampilkannya di layar. Ekspresi lambda diteruskan ke fungsi forEach sebagai argumen tindakannya , yang menyimpan implementasi antarmuka Konsumen . Sekarang fungsi forEach dapat memanggil implementasi antarmuka Konsumen dengan baris seperti ini:
action.accept(elementAt(es, i));
Jadi, argumen masukan dalam ekspresi lambda adalah elemen lain dari koleksi ArrayList , yang diteruskan ke implementasi antarmuka Konsumen kami . Itu saja: kami telah menganalisis logika ekspresi lambda di ArrayList.forEach. Referensi ke metode di ArrayList.forEach - bagaimana cara kerjanya? Langkah selanjutnya dalam perkuliahan adalah melihat referensi metode. Benar, mereka memahaminya dengan cara yang sangat aneh - setelah membaca ceramah, saya tidak memiliki kesempatan untuk memahami apa fungsi kode ini:
list.forEach( System.out::println );
Pertama, sedikit teori lagi. Referensi metode , secara kasar, merupakan implementasi antarmuka fungsional yang dijelaskan oleh fungsi lain . Sekali lagi, saya akan mulai dengan contoh sederhana:
public interface Operationable {
    int calculate(int x, int y);
    // Единственная функция в интерфейсе — значит, это функциональный интерфейс
}

public static class Calculator {
    // Создадим статический класс Calculator и пропишем в нём метод methodReference.
    // Именно он будет реализовывать функцию calculate из интерфейса Operationable.
    public static int methodReference(int x, int y) {
        return x+y;
    }
}

public static void main(String[] args) {
    // Создаём переменную operation типа Operationable (так называется наш функциональный интерфейс)
    Operationable operation;
    // Теперь реализацией интерфейса будет не лямбда-выражение, а метод methodReference из нашего класса Calculator
    operation = Calculator::methodReference;

    // Теперь мы можем обратиться к функции интерфейса через переменную operation
    int result = operation.calculate(10, 20);
    System.out.println(result); //30
}
Mari kita kembali ke contoh kuliah:
list.forEach( System.out::println );
Izinkan saya mengingatkan Anda bahwa System.out adalah objek bertipe PrintStream yang memiliki fungsi println . Mari arahkan kursor ke println dan klik Ctrl+LMB :
public void println(String x) {
    if (getClass() == PrintStream.class) {
        writeln(String.valueOf(x));
    } else {
        synchronized (this) {
            print(x);
            newLine();
        }
    }
}
Mari kita perhatikan dua fitur utama: 1. Fungsi println tidak mengembalikan apa pun (batal). 2. Fungsi println menerima satu argumen sebagai masukan. Tidak mengingatkanmu pada apa pun?
public interface Consumer<t> {
   void accept(T t);
}
Itu benar - tanda tangan fungsi terima adalah kasus yang lebih umum dari tanda tangan metode println ! Ini berarti bahwa metode terakhir dapat berhasil digunakan sebagai referensi ke suatu metode - yaitu, println menjadi implementasi spesifik dari fungsi terima :
list.forEach( System.out::println );
Kita meneruskan fungsi println objek System.out sebagai argumen ke fungsi forEach . Prinsipnya sama dengan lambda: sekarang forEach dapat meneruskan elemen koleksi ke fungsi println melalui panggilan action.accept(elementAt(es, i)) . Faktanya, ini sekarang dapat dibaca sebagai System.out.println(elementAt(es, i)) .
public void forEach(Consumer<? super E> action) // В action хранится an object Consumer, в котором функция accept реализована методом println {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        final Object[] es = elementData;
        final int size = this.size;
        for (int i = 0; modCount == expectedModCount && i < size; i++)
            action.accept(elementAt(es, i)); // Функция accept теперь реализована методом System.out.println!
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
Saya harap saya telah memperjelas situasinya setidaknya sedikit bagi mereka yang baru mengenal lambda dan referensi metode. Sebagai kesimpulan, saya merekomendasikan buku terkenal "Java: A Beginner's Guide" oleh Robert Schildt - menurut pendapat saya, lambda dan referensi fungsi dijelaskan dengan cukup masuk akal di dalamnya.
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION