JavaRush /Java Blog /Random-TL /Lambdas at mga sanggunian ng pamamaraan sa ArrayList.forE...

Lambdas at mga sanggunian ng pamamaraan sa ArrayList.forEach - kung paano ito gumagana

Nai-publish sa grupo
Ang pagpapakilala sa mga lambda expression sa Java Syntax Zero quest ay nagsisimula sa isang napaka-espesipikong halimbawa:
ArrayList<string> list = new ArrayList<>();
Collections.addAll(list, "Hello", "How", "дела?");

list.forEach( (s) -> System.out.println(s) );
Ang mga may-akda ng lecture ay nag-parse ng mga lambdas at mga sanggunian ng pamamaraan gamit ang pamantayan para sa Bawat function ng klase ng ArrayList. Sa personal, nahirapan akong maunawaan ang kahulugan ng kung ano ang nangyayari, dahil ang pagpapatupad ng function na ito, pati na rin ang interface na nauugnay dito, ay nananatiling "sa ilalim ng hood". Kung saan nagmula ang (mga) argumento , kung saan ipinasa ang println() function ay mga tanong na kailangan nating sagutin sa ating sarili. Sa kabutihang palad, gamit ang IntelliJ IDEA, madali nating matingnan ang mga panloob ng klase ng ArrayList at mapawi ang pansit na ito sa simula pa lang. Kung wala ka ring naiintindihan at gusto mong malaman ito, susubukan kong tulungan ka dito kahit kaunti. Lambda expression at ArrayList.forEach - kung paano ito gumagana Mula sa lecture alam na natin na ang isang lambda expression ay isang pagpapatupad ng isang functional na interface . Iyon ay, ipinapahayag namin ang isang interface na may isang solong function, at gumagamit ng lambda upang ilarawan kung ano ang ginagawa ng function na ito. Upang gawin ito kailangan mo: 1. Lumikha ng isang functional na interface; 2. Gumawa ng variable na ang uri ay tumutugma sa functional interface; 3. Italaga ang variable na ito ng lambda expression na naglalarawan sa pagpapatupad ng function; 4. Tumawag sa isang function sa pamamagitan ng pag-access sa isang variable (marahil ako ay hindi maganda sa terminolohiya, ngunit ito ang pinakamalinaw na paraan). Magbibigay ako ng isang simpleng halimbawa mula sa Google, na binibigyan ito ng mga detalyadong komento (salamat sa mga may-akda ng site 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
    }
}
Ngayon bumalik tayo sa halimbawa mula sa panayam. Maraming elemento ng uri ng String ang idinagdag sa koleksyon ng listahan . Ang mga elemento ay pagkatapos ay retrieved gamit ang standard forEach function , na kung saan ay tinatawag na sa listahan object . Ang isang lambda expression na may ilang kakaibang parameter s ay ipinasa bilang argumento sa function .
ArrayList<string> list = new ArrayList<>();
Collections.addAll(list, "Hello", "How", "дела?");

list.forEach( (s) -> System.out.println(s) );
Kung hindi mo agad naiintindihan ang nangyari dito, hindi ka nag-iisa. Sa kabutihang-palad, ang IntelliJ IDEA ay may mahusay na keyboard shortcut: Ctrl+Left_Mouse_Button . Kung mag-hover tayo sa forEach at i-click ang kumbinasyong ito, magbubukas ang source code ng karaniwang ArrayList class, kung saan makikita natin ang pagpapatupad ng forEach na paraan :
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();
}
Nakita namin na ang input argument ay aksyon ng uri ng Consumer . Ilipat natin ang cursor sa salitang Consumer at pindutin muli ang magic combination na Ctrl+LMB . Magbubukas ang isang paglalarawan ng interface ng Consumer . Kung aalisin namin ang default na pagpapatupad mula dito (hindi ito mahalaga sa amin ngayon), makikita namin ang sumusunod na code:
public interface Consumer<t> {
   void accept(T t);
}
Kaya. Mayroon kaming interface ng Consumer na may iisang accept function na tumatanggap ng isang argument ng anumang uri. Dahil mayroon lamang isang function, kung gayon ang interface ay gumagana, at ang pagpapatupad nito ay maaaring isulat sa pamamagitan ng isang lambda expression. Nakita na namin na ang ArrayList ay mayroong forEach function na kumukuha ng pagpapatupad ng interface ng Consumer bilang argumento ng aksyon . Bilang karagdagan, sa forEach function na makikita natin ang sumusunod na code:
for (int i = 0; modCount == expectedModCount && i < size; i++)
    action.accept(elementAt(es, i));
Ang para sa loop ay mahalagang umuulit sa lahat ng mga elemento ng isang ArrayList. Sa loob ng loop nakikita namin ang isang tawag sa accept function ng action object - tandaan kung paano namin tinatawag na operation.calculate? Ang kasalukuyang elemento ng koleksyon ay ipinasa sa accept function . Ngayon ay maaari na tayong bumalik sa orihinal na expression ng lambda at maunawaan kung ano ang ginagawa nito. Kolektahin natin ang lahat ng code sa isang pile:
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) );
Ang aming lambda expression ay isang pagpapatupad ng accept function na inilarawan sa interface ng Consumer . Gamit ang isang lambda, tinukoy namin na ang accept function ay tumatagal ng argument s at ipinapakita ito sa screen. Ang lambda expression ay ipinasa sa forEach function bilang action argument nito , na nag-iimbak ng pagpapatupad ng interface ng Consumer . Ngayon ang forEach function ay maaaring tumawag sa aming pagpapatupad ng Consumer interface na may linyang tulad nito:
action.accept(elementAt(es, i));
Kaya, ang input argument s sa lambda expression ay isa pang elemento ng ArrayList collection , na ipinapasa sa aming pagpapatupad ng Consumer interface . Iyon lang: sinuri namin ang logic ng lambda expression sa ArrayList.forEach. Sanggunian sa isang pamamaraan sa ArrayList.forEach - paano ito gumagana? Ang susunod na hakbang sa panayam ay tingnan ang mga sanggunian ng pamamaraan. Totoo, naiintindihan nila ito sa isang kakaibang paraan - pagkatapos basahin ang lektura, wala akong pagkakataon na maunawaan kung ano ang ginagawa ng code na ito:
list.forEach( System.out::println );
Una, isang maliit na teorya muli. Ang isang sanggunian ng pamamaraan ay, halos halos nagsasalita, isang pagpapatupad ng isang functional na interface na inilarawan ng isa pang function . Muli, magsisimula ako sa isang simpleng halimbawa:
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
}
Bumalik tayo sa halimbawa mula sa lecture:
list.forEach( System.out::println );
Hayaan mong ipaalala ko sa iyo na ang System.out ay isang object ng uri ng PrintStream na mayroong println function . Mag-hover tayo sa println at i-click ang Ctrl+LMB :
public void println(String x) {
    if (getClass() == PrintStream.class) {
        writeln(String.valueOf(x));
    } else {
        synchronized (this) {
            print(x);
            newLine();
        }
    }
}
Pansinin natin ang dalawang pangunahing tampok: 1. Ang println function ay hindi nagbabalik ng anuman (walang bisa). 2. Ang println function ay tumatanggap ng isang argumento bilang input. Hindi nagpapaalala sa iyo ng kahit ano?
public interface Consumer<t> {
   void accept(T t);
}
Iyan ay tama - ang accept function signature ay isang mas pangkalahatang kaso ng println method signature ! Nangangahulugan ito na ang huli ay maaaring matagumpay na magamit bilang isang sanggunian sa isang pamamaraan - iyon ay, ang println ay nagiging isang tiyak na pagpapatupad ng accept function :
list.forEach( System.out::println );
Ipinasa namin ang function na println ng System.out object bilang argumento sa forEach function . Ang prinsipyo ay kapareho ng sa lambda: ngayon forEach ay maaaring magpasa ng isang elemento ng koleksyon sa println function sa pamamagitan ng isang action.accept(elementAt(es, i)) call . Sa katunayan, maaari na itong basahin bilang 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();
    }
Umaasa ako na nilinaw ko ang sitwasyon kahit kaunti para sa mga bago sa lambdas at mga sanggunian ng pamamaraan. Sa konklusyon, inirerekumenda ko ang sikat na aklat na "Java: A Beginner's Guide" ni Robert Schildt - sa palagay ko, ang mga lambdas at mga sanggunian sa pag-andar ay inilarawan nang may katuturan dito.
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION