JavaRush /Blog Java /Random-MS /Lambdas dan rujukan kaedah dalam ArrayList.forEach - cara...

Lambdas dan rujukan kaedah dalam ArrayList.forEach - cara ia berfungsi

Diterbitkan dalam kumpulan
Pengenalan kepada ungkapan lambda dalam pencarian Java Syntax Zero bermula dengan contoh yang sangat khusus:
ArrayList<string> list = new ArrayList<>();
Collections.addAll(list, "Hello", "How", "дела?");

list.forEach( (s) -> System.out.println(s) );
Pengarang kuliah menghuraikan lambda dan rujukan kaedah menggunakan standard untukSetiap fungsi kelas ArrayList. Secara peribadi, saya mendapati sukar untuk memahami maksud apa yang berlaku, kerana pelaksanaan fungsi ini, serta antara muka yang berkaitan dengannya, kekal "di bawah tudung". Di mana hujah (s) berasal , di mana fungsi println() diluluskan adalah soalan yang perlu kita jawab sendiri. Nasib baik, dengan IntelliJ IDEA, kita boleh melihat bahagian dalaman kelas ArrayList dengan mudah dan melepaskan mi ini dari awal lagi. Jika anda juga tidak memahami apa-apa dan ingin memikirkannya, saya akan cuba membantu anda dengan ini sekurang-kurangnya sedikit. Ekspresi Lambda dan ArrayList.forEach - cara ia berfungsi Daripada kuliah kita sudah tahu bahawa ungkapan lambda ialah pelaksanaan antara muka berfungsi . Iaitu, kami mengisytiharkan antara muka dengan satu fungsi tunggal dan menggunakan lambda untuk menerangkan fungsi ini. Untuk melakukan ini, anda perlu: 1. Buat antara muka berfungsi; 2. Buat pembolehubah yang jenisnya sepadan dengan antara muka berfungsi; 3. Berikan pembolehubah ini ungkapan lambda yang menerangkan pelaksanaan fungsi; 4. Panggil fungsi dengan mengakses pembolehubah (mungkin saya kasar dalam istilah, tetapi ini adalah cara yang paling jelas). Saya akan memberikan contoh mudah daripada Google, memberikannya dengan ulasan terperinci (terima kasih kepada pengarang tapak menit.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 kepada contoh dari kuliah. Beberapa elemen jenis String ditambahkan pada koleksi senarai . Elemen-elemen itu kemudiannya diambil menggunakan fungsi standard forEach , yang dipanggil pada objek senarai . Ungkapan lambda dengan beberapa parameter pelik s diluluskan sebagai hujah kepada 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 berlaku di sini, maka anda tidak bersendirian. Nasib baik, IntelliJ IDEA mempunyai pintasan papan kekunci yang hebat: Ctrl+Left_Mouse_Button . Jika kita menuding pada forEach dan mengklik gabungan ini, kod sumber kelas ArrayList standard akan dibuka, di mana kita akan melihat pelaksanaan kaedah 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();
}
Kami melihat bahawa hujah input ialah tindakan jenis Consumer . Mari alihkan kursor ke atas perkataan Consumer dan tekan gabungan ajaib Ctrl+LMB sekali lagi . Penerangan mengenai antara muka Pengguna akan dibuka . Jika kami mengalih keluar pelaksanaan lalai daripadanya (ia tidak penting kepada kami sekarang), kami akan melihat kod berikut:
public interface Consumer<t> {
   void accept(T t);
}
Jadi. Kami mempunyai antara muka Pengguna dengan fungsi terima tunggal yang menerima satu hujah dari sebarang jenis. Oleh kerana hanya terdapat satu fungsi, maka antara muka berfungsi, dan pelaksanaannya boleh ditulis melalui ungkapan lambda. Kami telah melihat bahawa ArrayList mempunyai fungsi forEach yang mengambil pelaksanaan antara muka Pengguna sebagai hujah tindakan . Di samping itu, dalam fungsi forEach kita dapati kod berikut:
for (int i = 0; modCount == expectedModCount && i < size; i++)
    action.accept(elementAt(es, i));
Gelung for pada asasnya berulang melalui semua elemen ArrayList. Di dalam gelung kita melihat panggilan ke fungsi terima objek tindakan - ingat bagaimana kita memanggil operation.calculate? Elemen semasa koleksi dihantar ke fungsi terima . Kini kita akhirnya boleh kembali kepada ungkapan lambda asal dan memahami fungsinya. Mari kumpulkan semua kod dalam satu longgokan:
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) );
Ungkapan lambda kami ialah pelaksanaan fungsi terima yang diterangkan dalam antara muka Pengguna . Menggunakan lambda, kami menyatakan bahawa fungsi terima mengambil hujah s dan memaparkannya pada skrin. Ungkapan lambda telah dihantar ke fungsi forEach sebagai hujah tindakannya , yang menyimpan pelaksanaan antara muka Pengguna . Kini fungsi forEach boleh memanggil pelaksanaan antara muka Pengguna kami dengan baris seperti ini:
action.accept(elementAt(es, i));
Oleh itu, hujah input s dalam ungkapan lambda ialah satu lagi elemen koleksi ArrayList , yang dihantar kepada pelaksanaan antara muka Pengguna kami . Itu sahaja: kami telah menganalisis logik ungkapan lambda dalam ArrayList.forEach. Rujukan kepada kaedah dalam ArrayList.forEach - bagaimana ia berfungsi? Langkah seterusnya dalam kuliah ialah melihat rujukan kaedah. Benar, mereka memahaminya dengan cara yang sangat pelik - selepas membaca kuliah, saya tidak mempunyai peluang untuk memahami apa yang dilakukan oleh kod ini:
list.forEach( System.out::println );
Pertama, sedikit teori lagi. Rujukan kaedah adalah, secara kasarnya, pelaksanaan antara muka berfungsi yang diterangkan oleh fungsi lain . Sekali lagi, saya akan mulakan dengan contoh mudah:
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 kepada contoh dari kuliah:
list.forEach( System.out::println );
Biar saya ingatkan anda bahawa System.out ialah objek jenis PrintStream yang mempunyai fungsi println . Mari kita tuding pada 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 ciri utama: 1. Fungsi println tidak mengembalikan apa-apa (kosong). 2. Fungsi println menerima satu hujah sebagai input. Tidak mengingatkan anda tentang apa-apa?
public interface Consumer<t> {
   void accept(T t);
}
Betul - tandatangan fungsi terima ialah kes yang lebih umum bagi tandatangan kaedah println ! Ini bermakna bahawa yang terakhir boleh berjaya digunakan sebagai rujukan kepada kaedah - iaitu, println menjadi pelaksanaan khusus bagi fungsi terima :
list.forEach( System.out::println );
Kami lulus fungsi println objek System.out sebagai hujah kepada fungsi forEach . Prinsipnya adalah sama seperti lambda: kini forEach boleh menghantar elemen koleksi ke fungsi println melalui action.accept(elementAt(es, i)) call . Malah, ini kini boleh 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 menjelaskan keadaan sekurang-kurangnya untuk mereka yang baru mengenali lambdas dan rujukan kaedah. Sebagai kesimpulan, saya mengesyorkan buku terkenal "Java: A Beginner's Guide" oleh Robert Schildt - pada pendapat saya, rujukan lambdas dan fungsi diterangkan dengan agak masuk akal di dalamnya.
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION