JavaRush /Java блогу /Random-KY /Java тилиндеги ламбда туюнтмалары боюнча популярдуу. Миса...
Стас Пасинков
Деңгээл
Киев

Java тилиндеги ламбда туюнтмалары боюнча популярдуу. Мисалдар жана тапшырмалар менен. 2 бөлүк

Группада жарыяланган
Бул макала кимге арналган?
  • Бул макаланын биринчи бөлүгүн окугандар үчүн ;

  • Java Coreду жакшы билем деп ойлогондор үчүн, бирок Javaдагы ламбда туюнтмалары жөнүндө эч кандай түшүнүгү жок. Же, балким, сиз буга чейин ламбдалар жөнүндө бир нерсе уккандырсыз, бирок чоо-жайы жок.

  • лямбда туюнтмаларын түшүнгөн, бирок дагы эле коркуп, аларды колдонуудан адаттан тыш болгондор үчүн.

Тышкы өзгөрмөлөргө жетүү

Бул code анонимдүү класс менен түзүлөбү?
int counter = 0;
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter++;
    }
};
Жок. өзгөрмө counterболушу керек final. Же сөзсүз түрдө эмес final, бирок кандай болгон күндө да анын маанисин өзгөртө алbyte. Ушул эле принцип ламбда туюнтмаларында колдонулат. Алар жарыяланган жерден аларга "көрүнүүчү" бардык өзгөрмөлөргө жетүү мүмкүнчүлүгүнө ээ. Бирок ламбда аларды өзгөртпөшү керек (жаңы маанини дайындоо). Ырас, анонимдүү класстарда бул чектөөнү айланып өтүү мүмкүнчүлүгү бар. Болгону шилтеме түрүндөгү өзгөрмө түзүү жана an objectтин ички абалын өзгөртүү жетиштүү. Бул учурда, өзгөрмө өзү бир эле an objectти көрсөтөт жана бул учурда сиз аны final.
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Бул жерде биздин өзгөрмө counterтиптеги an objectке шилтеме болуп саналат AtomicInteger. Жана бул an objectтин абалын өзгөртүү үчүн, ыкмасы колдонулат incrementAndGet(). Өзгөрмөнүн мааниси программа иштеп жатканда өзгөрбөйт жана ар дайым бир эле an objectти көрсөтүп турат, бул бизге өзгөрмөнү дароо ачкыч сөз менен жарыялоого мүмкүндүк берет final. Ошол эле мисалдар, бирок ламбда туюнтмалары менен:
int counter = 0;
Runnable r = () -> counter++;
Ал анонимдүү класстагы вариант сыяктуу эле себеп менен түзүлбөйт: counterал программа иштеп жатканда өзгөрбөшү керек. Бирок бул сыяктуу - баары жакшы:
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = () -> counter.incrementAndGet();
Бул чакыруу ыкмаларына да тиешелүү. Ламбда туюнтмасынын ичинен сиз бардык "көрүнүүчү" өзгөрмөлөргө гана кирбестен, сиз кире алган ыкмаларды чакыра аласыз.
public class Main {
    public static void main(String[] args) {
        Runnable runnable = () -> staticMethod();
        new Thread(runnable).start();
    }

    private static void staticMethod() {
        System.out.println("Я - метод staticMethod(), и меня только-что кто-то вызвал!");
    }
}
Метод staticMethod()купуя болсо да, аны методдун ичинде чакыруу "жеткorктүү" main(), ошондуктан методдо түзүлгөн ламбданын ичинен чакырууга да жеткorктүү main.

Ламбда экспрессиясынын codeун ишке ашыруу учуру

Бул суроо сиз үчүн өтө жөнөкөй сезorши мүмкүн, бирок мунун бардыгын сураш керек: Ламбда туюнтмасынын ичиндеги code качан аткарылат? Жаратылыш учурда? Же качан (азырынча белгисиз) каякта аталат? Аны текшерүү оңой.
System.out.println("Запуск программы");

// много всякого разного codeа
// ...

System.out.println("Перед объявлением лямбды");

Runnable runnable = () -> System.out.println("Я - лямбда!");

System.out.println("После объявления лямбды");

// много всякого другого codeа
// ...

System.out.println("Перед передачей лямбды в тред");
new Thread(runnable).start();
Дисплейдеги чыгаруу:
Запуск программы
Перед объявлением лямбды
После объявления лямбды
Перед передачей лямбды в тред
Я - лямбда!
Ламбда экспрессия codeу эң аягында, жип түзүлгөндөн кийин жана программаны аткаруу процесси методдун иш жүзүндө аткарылышына жеткенде гана аткарылганын көрүүгө болот run(). Ал эми аны жарыялоо учурунда такыр эмес. Ламбда туюнтмасын жарыялоо менен биз типтеги an objectти гана түздүк Runnableжана анын методунун жүрүм-турумун сүрөттөп бердик run(). Методдун өзү кийинчерээк ишке киргизилген.

Метод шилтемелери?

Ламбдалардын өздөрүнө түздөн-түз тиешеси жок, бирок бул макалада бул жөнүндө бир нече сөз айтуу логикага туура келет деп ойлойм. Бизде өзгөчө эч нерсе жасабаган, бирок жөн гана кандайдыр бир ыкманы чакырган ламбда туюнтмасы бар дейли.
x -> System.out.println(x)
Алар ага бир нерсе беришти х, ал жөн эле аны чакырып System.out.println(), ошол жерден өтүп кетти х. Бул учурда, биз аны керектүү ыкмага шилтеме менен алмаштыра алабыз. Бул сыяктуу:
System.out::println
Ооба, аягында кашаасыз! Толук мисал:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(x -> System.out.println(x));
Акыркы сапта forEach()интерфейс an objectин кабыл алган ыкманы колдонобуз Consumer. Бул дагы бир гана методу бар функционалдык интерфейс void accept(T t). Демек, биз бир параметрди кабыл алган ламбда туюнтмасын жазабыз (ал интерфейстин өзүндө терилгендиктен, биз параметрдин түрүн көрсөтпөйбүз, бирок анын . деп аталаарын көрсөтөбүз х). Lambda туюнтмасынын корпусуна codeду жазабыз. Бул ыкма чакырылганда аткарылат.Бул accept()жерде биз жөн гана экранда өзгөрмөдө эмне бар экенин көрсөтөбүз х. Метод өзү forEach()коллекциянын бардык элементтерин аралап өтөт, Consumerага берилген интерфейс an objectинин ыкмасын чакырат (биздин ламбда) accept(), ал коллекциянын ар бир элементин кайсы жерден өткөрөт.Мен жогоруда айткандай, бул lambda -экспрессиясы (жөн гана башка ыкманы чакырат) биз керектүү методго шилтеме менен алмаштырсак болот.Андан кийин биздин code төмөнкүдөй болот:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(System.out::println);
Негизгиси ыкмалардын кабыл алынган параметрлери (println()жана accept()). Метод println()каалаган нерсени кабыл ала алгандыктан (ал бардык примитивдер жана ар кандай an objectтер үчүн ашыкча жүктөлгөн), lambda туюнтмасынын ордуна, биз forEach()жөн гана методго шилтеме аркылуу өтө алабыз.Андан println()кийин forEach()ал коллекциянын ар бир элементин алып, аны түз өткөрүп берет. Бул ыкманы println()биринчи жолу көрүп жаткандар үчүн эске алыңыз, биз ыкманы чакырбайбыз System.out.println()(сөздөрдүн ортосундагы чекиттер жана аягында кашаалар менен), тескерисинче, биз бул ыкманын өзүнө шилтеме беребиз.
strings.forEach(System.out.println());
бизде компиляция катасы болот. Анткени чалуудан мурун forEach()Java анын чакырып жатканын көрүп System.out.println(), анын кайтарылып жатканын түшүнөт жана аны ошол жерде күтүп турган типтеги an objectке өткөрүүгө voidаракет кылат . voidforEach()Consumer

Метод Шилтемелерин колдонуу синтаксиси

Бул абдан жөнөкөй:
  1. Статикалык ыкмага шилтеме берүүNameКласса:: NameСтатическогоМетода?

    public class Main {
        public static void main(String[] args) {
            List<String> strings = new LinkedList<>();
            strings.add("Mother");
            strings.add("soap");
            strings.add("frame");
    
            strings.forEach(Main::staticMethod);
        }
    
        private static void staticMethod(String s) {
            // do something
        }
    }
  2. Учурдагы an objectти колдонуу менен статикалык эмес методго шилтеме берүүNameПеременнойСОбъектом:: method name

    public class Main {
        public static void main(String[] args) {
            List<String> strings = new LinkedList<>();
            strings.add("Mother");
            strings.add("soap");
            strings.add("frame");
    
            Main instance = new Main();
            strings.forEach(instance::nonStaticMethod);
        }
    
        private void nonStaticMethod(String s) {
            // do something
        }
    }
  3. Биз статикалык эмес методго шилтемени мындай ыкма ишке ашырылган классты колдонуп өткөрүп беребизNameКласса:: method name

    public class Main {
        public static void main(String[] args) {
            List<User> users = new LinkedList<>();
            users.add(new User("Vasya"));
            users.add(new User("Коля"));
            users.add(new User("Петя"));
    
            users.forEach(User::print);
        }
    
        private static class User {
            private String name;
    
            private User(String name) {
                this.name = name;
            }
    
            private void print() {
                System.out.println(name);
            }
        }
    }
  4. Конструкторго шилтемени өткөрүү NameКласса::new
    Метод шилтемелерин колдонуу сизди толук канааттандырган даяр метод болгондо абдан ыңгайлуу жана аны кайра чалуу катары колдонгуңуз келет. Бул учурда, ошол ыкманын codeу менен ламбда туюнтмасын жазуунун ордуна, же биз бул ыкманы жөн эле чакырган ламбда туюнтмасын жазуунун ордуна, биз ага шилтемени өткөрүп беребиз. Болду.

Анонимдүү класс менен лямбда туюнтмасынын ортосундагы кызыктуу айырма

Анонимдүү класста ачкыч сөз thisошол анонимдүү класстын an objectисин көрсөтөт. Жана эгерде биз аны thisламбданын ичинде колдонсок, анда биз фреймдер классынын an objectисине кире алабыз. Бул сөздү биз кайда жазганбыз. Бул лямбда туюнтмалары компиляцияланганда, алар жазылган класстын жеке ыкмасы болуп калгандыктан болот. Мен бул "функцияны" колдонууну сунуш кылбайм, анткени анын функционалдык программалоо принциптерине карама-каршы келген терс таасири бар. Бирок бул ыкма OOP менен абдан шайкеш келет. ;)

Маалыматты кайдан алдым же дагы эмнени окуйм

Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION