JavaRush /Java blogi /Random-UZ /ArrayList.forEach-dagi lambdalar va usul havolalari - bu ...

ArrayList.forEach-dagi lambdalar va usul havolalari - bu qanday ishlaydi

Guruhda nashr etilgan
Java Syntax Zero kvestidagi lambda iboralariga kirish juda aniq misol bilan boshlanadi:
ArrayList<string> list = new ArrayList<>();
Collections.addAll(list, "Hello", "How", "дела?");

list.forEach( (s) -> System.out.println(s) );
Ma'ruza mualliflari ArrayList sinfining standart forEach funksiyasidan foydalangan holda lambdalar va metodlarga havolalarni tahlil qiladilar. Shaxsan men nima sodir bo'layotganini tushunish qiyin edi, chunki ushbu funktsiyani amalga oshirish, shuningdek, u bilan bog'liq interfeys "kaput ostida" qolmoqda. Argument (lar) qayerdan keladi , println() funksiyasi qayerdan uzatiladi , bu savollarga o'zimiz javob berishimiz kerak bo'ladi. Yaxshiyamki, IntelliJ IDEA yordamida biz ArrayList sinfining ichki qismlarini osongina ko'rib chiqishimiz va bu noodleni boshidanoq ochishimiz mumkin. Agar siz ham hech narsani tushunmasangiz va buni tushunishni istasangiz, men sizga bu borada ozgina bo'lsa ham yordam berishga harakat qilaman. Lambda ifodasi va ArrayList.forEach - bu qanday ishlaydi Ma'ruzadan biz allaqachon bilamizki, lambda ifodasi funktsional interfeysning amalga oshirilishidir . Ya'ni, biz bitta funktsiyaga ega interfeysni e'lon qilamiz va bu funksiya nima qilishini tasvirlash uchun lambdadan foydalanamiz. Buning uchun sizga kerak: 1. Funktsional interfeys yaratish; 2. Turi funksional interfeysga mos keladigan o‘zgaruvchini yarating; 3. Ushbu o'zgaruvchiga funksiyaning bajarilishini tavsiflovchi lambda ifodasini belgilang; 4. O'zgaruvchiga kirish orqali funktsiyani chaqiring (ehtimol, men terminologiyada qo'pol gapiryapman, lekin bu eng aniq yo'l). Men Google-dan oddiy misol keltiraman, uni batafsil sharhlar bilan ta'minlayman (metanit.com sayti mualliflariga rahmat):
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
    }
}
Endi ma'ruzadagi misolga qaytaylik. Ro'yxat to'plamiga String tipidagi bir nechta elementlar qo'shiladi . Keyin elementlar ro'yxat ob'ektida chaqiriladigan standart forEach funktsiyasi yordamida olinadi . Ba'zi g'alati parametr s bo'lgan lambda ifodasi funktsiyaga argument sifatida uzatiladi .
ArrayList<string> list = new ArrayList<>();
Collections.addAll(list, "Hello", "How", "дела?");

list.forEach( (s) -> System.out.println(s) );
Agar siz bu erda nima bo'lganini darhol tushunmagan bo'lsangiz, unda siz yolg'iz emassiz. Yaxshiyamki, IntelliJ IDEA ajoyib klaviatura yorlig'iga ega: Ctrl+Left_Mouse_Button . Agar kursorni forEach ustiga olib borsak va ushbu kombinatsiyani bossak, standart ArrayList sinfining manba kodi ochiladi, unda biz forEach usulining amalga oshirilishini ko'ramiz :
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();
}
Biz kirish argumenti Consumer tipidagi harakat ekanligini ko'ramiz . Keling, kursorni Consumer so'zi ustiga olib boramiz va Ctrl+LMB sehrli birikmasini yana bir marta bosing . Iste'molchi interfeysi tavsifi ochiladi . Agar biz undan standart dasturni olib tashlasak (hozir bu biz uchun muhim emas), biz quyidagi kodni ko'ramiz:
public interface Consumer<t> {
   void accept(T t);
}
Shunday qilib. Bizda har qanday turdagi bitta argumentni qabul qiladigan yagona qabul qilish funksiyasi bilan iste'molchi interfeysi mavjud. Faqat bitta funktsiya mavjud bo'lganligi sababli, interfeys funktsionaldir va uni amalga oshirish lambda ifodasi orqali yozilishi mumkin. ArrayList-da iste'molchi interfeysini amal argumenti sifatida amalga oshirishni oladigan forEach funksiyasi borligini allaqachon ko'rdik . Bundan tashqari, forEach funksiyasida biz quyidagi kodni topamiz:
for (int i = 0; modCount == expectedModCount && i < size; i++)
    action.accept(elementAt(es, i));
For tsikli asosan ArrayList ning barcha elementlarini takrorlaydi. Loop ichida biz harakat ob'ektining qabul qilish funksiyasiga qo'ng'iroqni ko'ramiz - biz qanday qilib operatsiya.hisoblash deb ataganimizni eslaysizmi? To'plamning joriy elementi qabul qilish funktsiyasiga o'tkaziladi . Endi biz nihoyat asl lambda ifodasiga qaytishimiz va u nima qilishini tushunishimiz mumkin. Keling, barcha kodlarni bitta qoziqda to'playmiz:
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) );
Bizning lambda ifodasi iste'molchi interfeysida tasvirlangan qabul qilish funktsiyasining amalga oshirilishidir . Lambda yordamida biz qabul qilish funksiyasi s argumentini olishini va uni ekranda ko'rsatishini aniqladik. Lambda ifodasi forEach funksiyasiga uning harakat argumenti sifatida uzatildi , bu Iste'molchi interfeysini amalga oshirishni saqlaydi . Endi forEach funksiyasi bizning Consumer interfeysini amalga oshirishimizni quyidagi qator bilan chaqirishi mumkin:
action.accept(elementAt(es, i));
Shunday qilib, lambda ifodasidagi s kiritish argumenti ArrayList to'plamining yana bir elementi bo'lib, u bizning iste'molchi interfeysini amalga oshirishimizga o'tkaziladi . Hammasi shu: biz ArrayList.forEach da lambda ifodasi mantiqini tahlil qildik. ArrayList.forEach-dagi usulga havola - u qanday ishlaydi? Ma'ruzaning keyingi bosqichi metodik havolalarni ko'rib chiqishdir. To'g'ri, ular buni juda g'alati tarzda tushunishadi - ma'ruzani o'qib chiqqandan so'ng, men ushbu kod nima qilishini tushunish imkoniyatiga ega emas edim:
list.forEach( System.out::println );
Birinchidan, yana bir oz nazariya. Usul ma'lumotnomasi , qo'pol qilib aytganda, boshqa funktsiya tomonidan tasvirlangan funktsional interfeysni amalga oshirishdir . Yana oddiy misol bilan boshlayman:
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
}
Keling, ma'ruzadagi misolga qaytaylik:
list.forEach( System.out::println );
Eslatib o'taman, System.out bu println funksiyasiga ega bo'lgan PrintStream tipidagi ob'ektdir . Keling, printerni println ustiga olib boramiz va Ctrl+LMB tugmalarini bosing :
public void println(String x) {
    if (getClass() == PrintStream.class) {
        writeln(String.valueOf(x));
    } else {
        synchronized (this) {
            print(x);
            newLine();
        }
    }
}
Ikkita asosiy xususiyatga e'tibor qaratamiz: 1. Println funksiyasi hech narsani qaytarmaydi (void). 2. Println funksiyasi kirish sifatida bitta argument oladi. Sizga hech narsani eslatmayaptimi?
public interface Consumer<t> {
   void accept(T t);
}
To'g'ri - qabul qilish funktsiyasi imzosi println usuli imzosining umumiy holatidir ! Bu shuni anglatadiki, ikkinchisi usulga havola sifatida muvaffaqiyatli ishlatilishi mumkin, ya'ni println qabul qilish funktsiyasining o'ziga xos amalga oshirilishiga aylanadi :
list.forEach( System.out::println );
Biz System.out obyektining println funksiyasini forEach funksiyasiga argument sifatida topshirdik . Printsip lambda bilan bir xil: endi forEach kolleksiya elementini println funksiyasiga action.accept(elementAt(es, i)) chaqiruvi orqali uzatishi mumkin . Aslida, buni endi System.out.println(elementAt(es, i)) sifatida o'qish mumkin .
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();
    }
Umid qilamanki, men lambdalar va usullarga murojaat qilish uchun yangi bo'lganlar uchun vaziyatni hech bo'lmaganda biroz aniqlab berdim. Xulosa qilib, men Robert Shildtning mashhur "Java: Yangi boshlanuvchilar uchun qo'llanma" kitobini tavsiya qilaman - mening fikrimcha, unda lambdalar va funktsiyalarga havolalar juda oqilona tasvirlangan.
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION