JavaRush /Java блогу /Random-KY /ArrayList.forEach ичиндеги ламбдалар жана метод шилтемеле...
Anonymous #2633326
Деңгээл

ArrayList.forEach ичиндеги ламбдалар жана метод шилтемелери - бул кантип иштейт

Группада жарыяланган
Java Syntax Zero квестиндеги lambda туюнтмаларына киришүү өзгөчө бир мисал менен башталат:
ArrayList<string> list = new ArrayList<>();
Collections.addAll(list, "Hello", "How", "дела?");

list.forEach( (s) -> System.out.println(s) );
Лекциянын авторлору ArrayList классынын стандарттуу forEach функциясын колдонуу менен ламбдаларды жана метод шилтемелерин талдайт. Жеке мага эмне болуп жатканын түшүнүү кыйынга турду, анткени бул функцияны ишке ашыруу, ошондой эле аны менен байланышкан интерфейс "капчыктын астында" бойдон калууда. Аргумент (лар) кайдан келип чыгат , println() функциясы кайдан өткөрүлөт - бул суроолорго өзүбүз жооп беришибиз керек. Бактыга жараша, IntelliJ IDEA менен биз ArrayList классынын ички түзүлүштөрүн оңой эле карап чыгып, бул кесмени башынан эле ача алабыз. Эгер сиз дагы эч нерсе түшүнбөсөңүз жана аны түшүнгүңүз келсе, мен сизге бул жагынан бир аз болсо да жардам берүүгө аракет кылам. Lambda туюнтмасы жана ArrayList.forEach - бул кантип иштейт Лекциядан биз ламбда туюнтмасы функционалдык интерфейстин ишке ашырылышы экенин билебиз . Башкача айтканда, биз бир функция менен интерфейсти жарыялайбыз жана бул функция эмне кылаарын сүрөттөө үчүн ламбданы колдонобуз. Бул үчүн: 1. Функционалдык интерфейсти түзүү; 2. Функционалдык интерфейске тиби туура келген өзгөрмө түзүңүз; 3. Бул өзгөрмөгө функциянын аткарылышын сүрөттөгөн ламбда туюнтмасын дайындаңыз; 4. Өзгөрмөлөргө жетүү менен функцияны чакырыңыз (балким, мен терминологияда одоно болуп жатам, бирок бул эң ачык жол). Мен Google'дан жөнөкөй мисал келтирейин, аны толук комментарийлер менен камсыз кылам (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
    }
}
Эми лекциядагы мисалга кайрылып көрөлү. Тизме жыйнагына String түрүндөгү бир нече элементтер кошулат . Андан кийин элементтер тизме an objectисинде чакырылган стандарттуу forEach функциясынын жардамы менен чыгарылат . Кандайдыр бир кызыктай s параметри бар ламбда туюнтмасы функцияга аргумент катары берилет .
ArrayList<string> list = new ArrayList<>();
Collections.addAll(list, "Hello", "How", "дела?");

list.forEach( (s) -> System.out.println(s) );
Бул жерде эмне болгонун дароо түшүнбөсөңүз, анда сиз жалгыз эмессиз. Бактыга жараша, IntelliJ IDEAда мыкты баскычтоп бар: Ctrl+Left_Mouse_Button . Эгерде биз forEach баскычынын үстүнө алып барып, бул айкалышты чыкылдатсак, стандарттуу ArrayList классынын баштапкы codeу ачылат, анда биз 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();
}
Киргизүү аргументи Керектөөчү түрүндөгү иш-аракет экенин көрөбүз . Курсорду Керектөөчү сөзүнүн үстүнө жылдырып, Ctrl+LMB сыйкырдуу айкалышын кайрадан басыңыз . Керектөөчү интерфейсинин сүрөттөмөсү ачылат . Эгерде биз андан демейки ишке ашырууну алып салсак (бул азыр биз үчүн маанилүү эмес), биз төмөнкү codeду көрөбүз:
public interface Consumer<t> {
   void accept(T t);
}
Ошентип. Бизде каалаган түрдөгү бир аргументти кабыл алган бир кабыл алуу функциясы бар Керектөөчү интерфейси бар. Бир гана функция бар болгондуктан, интерфейс функционалдуу болуп саналат жана анын аткарылышын ламбда туюнтмасы аркылуу жазууга болот. Биз буга чейин ArrayListтин forEach функциясы бар экенин көрдүк , ал Керектөөчү интерфейсин иш-аракет аргументи катары ишке ашырат . Мындан тышкары, forEach функциясында биз төмөнкү codeду табабыз:
for (int i = 0; modCount == expectedModCount && i < size; i++)
    action.accept(elementAt(es, i));
for цикли негизинен ArrayListтин бардык элементтери аркылуу кайталанат. Циклдин ичинде биз аракет an objectинин кабыл алуу функциясына чакырууну көрөбүз - биз операция.calculate кантип чакырганыбызды эстеңизби? Коллекциянын учурдагы элементи кабыл алуу функциясына өткөрүлөт . Эми биз акыры баштапкы ламбда туюнтмасына кайрылып, анын эмне кыларын түшүнө алабыз. Келгиле, бардык codeду бир үймөктө чогулталы:
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) );
Биздин ламбда туюнтмасы - Керектөөчү интерфейсинде сүрөттөлгөн кабыл алуу функциясынын ишке ашырылышы . Ламбданы колдонуп, кабыл алуу функциясы s аргументин алып , аны экранда көрсөтөөрүн белгиледик . Lambda туюнтмасы Керектөөчү интерфейсин ишке ашырууну сактаган аракет аргументи катары forEach функциясына өткөрүлүп берилди . Эми forEach функциясы биздин Керектөөчү интерфейсин ишке ашырууну төмөнкүдөй сызык менен чакыра алат:
action.accept(elementAt(es, i));
Ошентип, lambda туюнтмасындагы s киргизүү аргументи ArrayList коллекциясынын дагы бир элементи болуп саналат, ал биздин Керектөөчү интерфейсин ишке ашырууга өткөрүлүп берилет . Болгону: ArrayList.forEach ичиндеги ламбда туюнтмасынын логикасын талдап чыктык. ArrayList.forEach ичиндеги методго шилтеме - ал кантип иштейт? Лекциянын кийинки кадамы метод шилтемелерин кароо. Ырас, алар муну абдан таң калыштуу түрдө түшүнүшөт - лекцияны окугандан кийин, мен бул codeдун эмне экенин түшүнүүгө мүмкүнчүлүк болгон жок:
list.forEach( System.out::println );
Биринчиден, дагы бир аз теория. Метод шилтемеси - бул, болжол менен айтканда, башка функция тарабынан сүрөттөлгөн функционалдык интерфейстин ишке ашырылышы . Дагы, мен жөнөкөй бир мисал менен баштайм:
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
}
Лекциядагы мисалга кайрылып көрөлү:
list.forEach( System.out::println );
Эске сала кетейин, System.out - бул println функциясы бар PrintStream түрүндөгү an object . Келгиле, курсорду println үстүнө алып барып, Ctrl+LMB басыңыз :
public void println(String x) {
    if (getClass() == PrintStream.class) {
        writeln(String.valueOf(x));
    } else {
        synchronized (this) {
            print(x);
            newLine();
        }
    }
}
Эки негизги өзгөчөлүккө көңүл буралы: 1. println функциясы эч нерсе кайтарbyte (жараксыз). 2. println функциясы киргизүү катары бир аргумент алат. Сизге эч нерсени эске салбайбы?
public interface Consumer<t> {
   void accept(T t);
}
Туура - кабыл алуу функциясынын кол тамгасы println ыкмасы кол тамгасынын жалпы учуру ! Бул акыркы ыкмага шилтеме катары ийгorктүү колдонулушу мүмкүн экенин билдирет - башкача айтканда, println кабыл алуу функциясынын конкреттүү ишке ашырылышы болуп калат :
list.forEach( System.out::println );
Биз System.out an objectинин println функциясын forEach функциясына аргумент катары өткөрүп бердик . Принцип ламбдадагыдай эле: азыр forEach action.accept(elementAt(es, i)) чалуу аркылуу println функциясына коллекция элементин өткөрө алат . Чынында, муну азыр 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();
    }
Мен ламбдалар жана ыкма шилтемелер үчүн жаңы болгондор үчүн, жок дегенде, бир аз жагдайды түшүндүрүп бердим деп үмүттөнөм. Жыйынтыктап айтканда, мен Роберт Шилдттин атактуу "Java: Beginner's Guide" китебин сунуштайм - менин оюмча, анда ламбдалар жана функция шилтемелери абдан акылдуу сүрөттөлгөн.
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION