JavaRush /Java блогы /Random-KK /Java тіліндегі ламбда өрнектері туралы танымал. Мысалдар ...
Стас Пасинков
Деңгей
Киев

Java тіліндегі ламбда өрнектері туралы танымал. Мысалдар мен тапсырмалар арқылы. 2-бөлім

Топта жарияланған
Бұл мақала кімге арналған?
  • Осы мақаланың бірінші бөлігін оқығандар үшін ;

  • Java Core-ді жақсы білемін деп ойлайтындар үшін, бірақ Java тіліндегі лямбда өрнектері туралы түсінігі жоқ. Немесе, мүмкін, сіз ламбдалар туралы бірдеңе естіген шығарсыз, бірақ егжей-тегжейсіз.

  • лямбда өрнектері туралы біраз түсінігі бар, бірақ әлі де қорқатын және оларды пайдалану әдеттен тыс адамдар үшін.

Сыртқы айнымалыларға қол жеткізу

Бұл code анонимді сыныппен құрастырыла ма?
int counter = 0;
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter++;
    }
};
Жоқ. Айнымалы counterболуы керек final. Немесе міндетті емес final, бірақ кез келген жағдайда оның мәнін өзгерте алмайды. Дәл осындай принцип ламбда өрнектерінде қолданылады. Олар жарияланған жерден өздеріне «көрінетін» барлық айнымалыларға қол жеткізе алады. Бірақ ламбда оларды өзгертпеуі керек (жаңа мән тағайындайды). Рас, анонимді сабақтарда бұл шектеуді айналып өту мүмкіндігі бар. Анықтамалық түрдегі айнымалыны жасау және an objectінің ішкі күйін өзгерту жеткілікті. Бұл жағдайда айнымалының өзі сол нысанды көрсетеді және бұл жағдайда оны ретінде қауіпсіз түрде көрсетуге болады final.
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Мұндағы айнымалы counterтүрдегі нысанға сілтеме болып табылады AtomicInteger. Бұл нысанның күйін өзгерту үшін әдіс қолданылады 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()жеке болса да, оны әдіс ішінде шақыруға «қолжетімді» main(), сондықтан әдісте жасалған лямбда ішінен шақыруға да қол жетімді main.

Ламбда өрнек codeының орындалу сәті

Бұл сұрақ сізге тым қарапайым болып көрінуі мүмкін, бірақ бұл сұрақ қоюға тұрарлық: лямбда өрнегі ішіндегі 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(). Және ол хабарландыру кезінде мүлде емес. Ламбда өрнегін жариялау арқылы біз тек түрдегі нысанды жасадық 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оған берілген интерфейс нысанының әдісін шақырады (біздің лямбда) 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()кез келген нәрсені қабылдай алатындықтан (ол барлық примитивтер және кез келген нысандар үшін шамадан тыс жүктеледі) лямбда өрнегі орнына forEach()әдіске сілтемені ғана бере аламыз.Одан println()кейін forEach()ол жинақтың әрбір элементін қабылдайды және оны тікелей жібереді. Бұл әдісті println()бірінші рет кездестіретіндер үшін ескеріңіз, біз әдісті шақырмаймыз System.out.println()(сөздер арасында нүктелер және соңында жақшалармен), керісінше біз осы әдістің өзіне сілтеме береміз.
strings.forEach(System.out.println());
бізде компиляция қатесі болады. Өйткені қоңырау алдында forEach()Java оның шақырылып жатқанын көреді System.out.println(), оның қайтарылып жатқанын түсінеді және оны күтіп тұрған нысанға беруге 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. Бар нысанды пайдаланып, статикалық емес әдіске сілтеме беру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сол анонимдік сыныптың нысанына нұсқайды. Ал егер біз оны thisлямбда ішінде қолдансақ, фреймдік класстың an objectісіне қол жеткіземіз. Бұл өрнекті біз қай жерде жаздық. Бұл лямбда өрнектері құрастырылған кезде олар жазылған сыныптың жеке әдісіне айналатындықтан орын алады. Мен бұл «мүмкіндікті» пайдалануды ұсынбаймын, өйткені оның функционалдық бағдарламалау принциптеріне қайшы келетін жанама әсері бар. Бірақ бұл тәсіл OOP-ке әбден сәйкес келеді. ;)

Ақпаратты қайдан алдым немесе тағы не оқу керек

Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION