JavaRush /Java блогы /Random-KK /Ламбда өрнектері мысалдармен

Ламбда өрнектері мысалдармен

Топта жарияланған
Java бастапқыда толығымен an objectіге бағытталған тіл болып табылады. Қарапайым типтерді қоспағанда, Java-дағы барлық нәрсе an object болып табылады. Тіпті массивтер де an objectілер болып табылады. Әрбір сыныптың даналары нысандар болып табылады. Функцияны бөлек анықтаудың бірде-бір мүмкіндігі жоқ (сыныптан тыс – шамамен аударма ). Әдісті дәлел ретінде беру немесе басқа әдістің нәтижесі ретінде әдіс денесін қайтару мүмкіндігі жоқ. Дәл солай. Бірақ бұл Java 8-ге дейін болды. Ламбда өрнектері мысалдармен - 1Бұрынғы жақсы Swing күндерінен бастап кейбір функционалдылықты қандай да бір әдіске беру қажет болғанда анонимді сыныптарды жазу қажет болды. Мысалы, оқиға өңдеушісін қосу келесідей болды:
someObject.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {

                //Event listener implementation goes here...

            }
        });
Мұнда біз тінтуір оқиғасын тыңдаушыға кейбір code қосқымыз келеді. Біз анонимді классты анықтадық MouseAdapterжәне одан бірден an object жасадық. Осылайша, біз қосымша функционалдылықты addMouseListener. Қысқасы, Java тіліндегі қарапайым әдісті (функционалдылықты) аргументтер арқылы беру оңай емес. Бұл шектеу Java 8 әзірлеушілерін тіл сипаттамасына Lambda өрнектері сияқты мүмкіндікті қосуға мәжбүр етті.

Неліктен Java-ға Lambda өрнектері қажет?

Басынан бастап Java тілі Аннотациялар, Generics және т.б. сияқты нәрселерден басқа көп дамымаған. Біріншіден, Java әрқашан an objectіге бағытталған болып қала берді. JavaScript сияқты функционалдық тілдермен жұмыс істегеннен кейін Java-ның қатаң түрде нысанға бағытталғанын және қатты терілетінін түсінуге болады. Java тілінде функциялар қажет емес. Өздігінен олар Java әлемінде табылмайды. Функционалды бағдарламалау тілдерінде функциялар бірінші орынға шығады. Олар өз бетінше бар. Оларды айнымалыларға тағайындауға және оларды аргументтер арқылы басқа функцияларға беруге болады. JavaScript - функционалды бағдарламалау тілдерінің ең жақсы үлгілерінің бірі. Сіз Интернеттен JavaScript-тің функционалды тіл ретіндегі артықшылықтарын егжей-тегжейлі сипаттайтын жақсы мақалаларды таба аласыз. Функционалды тілдерде Closure сияқты қуатты құралдар бар, олар қосымшаларды жазудың дәстүрлі әдістеріне қарағанда бірқатар артықшылықтар береді. Жабу - оған бекітілген ортасы бар функция - функцияның барлық жергілікті емес айнымалыларына сілтемелерді сақтайтын кесте. Java тілінде жабуларды Lambda өрнектері арқылы модельдеуге болады. Әрине, жабулар мен Lambda өрнектерінің арасында кішігірім емес айырмашылықтар бар, бірақ ламбда өрнектері жабуларға жақсы балама болып табылады. Стив Йегге өзінің мысқылды және күлкілі блогында Java әлемінің зат есімдермен (an objectілер, нысандар - шамамен аударылған ) қалай байланыстырылғанын сипаттайды . Егер сіз оның блогын оқымаған болсаңыз, мен оны ұсынамын. Ол Lambda өрнектерінің Java-ға қосылуының нақты себебін күлкілі және қызықты түрде сипаттайды. Lambda өрнектері Java-ға ұзақ уақыт бойы жетіспейтін функционалдылықты береді. Lambda өрнектері нысандар сияқты тілге функционалдылықты әкеледі. Бұл 100% дұрыс болмаса да, Lambda өрнектері жабылмаса да, ұқсас мүмкіндіктерді қамтамасыз ететінін көруге болады. Функционалды тілде лямбда өрнектері функция болып табылады; бірақ Java тілінде лямбда өрнектері нысандар арқылы көрсетіледі және функционалды интерфейс деп аталатын белгілі бір нысан түрімен байланысты болуы керек. Әрі қарай біз оның не екенін қарастырамыз. Марио Фусконың «Бізге Java-дағы Lambda өрнектері не үшін қажет» мақаласында барлық заманауи тілдерге жабу мүмкіндіктері неліктен қажет екендігі егжей-тегжейлі сипатталған.

Ламбда өрнектеріне кіріспе

Lambda өрнектері анонимді функциялар болып табылады (Java үшін 100% дұрыс анықтама болмауы мүмкін, бірақ ол біршама айқындық береді). Қарапайым тілмен айтқанда, бұл декларациясыз әдіс, яғни. қатынас модификаторлары жоқ, мән мен атауды қайтарады. Қысқасы, олар әдісті жазуға және оны дереу пайдалануға мүмкіндік береді. Бұл бір реттік әдісті шақыру жағдайында әсіресе пайдалы, өйткені сыныпты жасамай-ақ әдісті жариялауға және жазуға кететін уақытты қысқартады. Java тіліндегі ламбда өрнектері әдетте келесі синтаксиске ие (аргументы) -> (тело). Мысалы:
(арг1, арг2...) -> { тело }

(тип1 арг1, тип2 арг2...) -> { тело }
Төменде нақты Lambda өрнектерінің кейбір мысалдары берілген:
(int a, int b) -> {  return a + b; }

() -> System.out.println("Hello World");

(String s) -> { System.out.println(s); }

() -> 42

() -> { return 3.1415 };

Ламбда өрнектерінің құрылымы

Ламбда өрнектерінің құрылымын зерттейік:
  • Ламбда өрнектерінде 0 немесе одан да көп кіріс параметрлері болуы мүмкін.
  • Параметрлердің түрін анық көрсетуге болады немесе контекстен алуға болады. Мысалы ( int a) былай жазылуы мүмкін ( a)
  • Параметрлер жақшаға алынып, үтірмен бөлінген. Мысалы ( a, b) немесе ( int a, int b) немесе ( String a, int b, float c)
  • Параметрлер болмаса, бос жақшаларды пайдалану керек. Мысалы() -> 42
  • Бір ғана параметр болғанда, түрі анық көрсетілмесе, жақшаларды алып тастауға болады. Мысалы:a -> return a*a
  • Lambda өрнегі денесінде 0 немесе одан да көп өрнек болуы мүмкін.
  • Егер дене бір мәлімдемеден тұрса, ол бұйра жақшаға алынбауы мүмкін және қайтарылатын мән кілт сөзсіз көрсетілуі мүмкін return.
  • Әйтпесе, бұйра жақшалар қажет (code блогы) және қайтару мәні кілт сөзді пайдаланып соңында көрсетілуі керек return(әйтпесе қайтару түрі болады void).

Функционалдық интерфейс дегеніміз не

Java тілінде Marker интерфейстері - бұл әдістерді немесе өрістерді жариялаусыз интерфейстер. Басқаша айтқанда, маркер интерфейстері бос интерфейстер болып табылады. Сол сияқты, Функционалдық интерфейстер – онда жарияланған бір ғана дерексіз әдісі бар интерфейстер. java.lang.Runnableфункционалдық интерфейстің мысалы болып табылады. Ол тек бір әдісті жариялайды void run(). Сондай-ақ интерфейс бар ActionListener- сонымен қатар функционалды. Бұрын біз функционалды интерфейсті жүзеге асыратын an objectілерді жасау үшін анонимді класстарды қолдануға тура келді. Lambda өрнектерімен бәрі оңайырақ болды. Әрбір ламбда өрнегі кейбір функционалды интерфейске жанама түрде байланыстырылуы мүмкін. RunnableМысалы, келесі мысалда көрсетілгендей интерфейске сілтеме жасай аласыз :
Runnable r = () -> System.out.println("hello world");
Функционалдық интерфейсті көрсетпеген кезде түрлендірудің бұл түрі әрқашан жасырын түрде орындалады:
new Thread(
    () -> System.out.println("hello world")
).start();
Жоғарыдағы мысалда компилятор Runnableкласс конструкторынан интерфейсті іске асыру ретінде автоматты түрде лямбда өрнегін жасайды Thread: public Thread(Runnable r) { }. Мұнда лямбда өрнектерінің және сәйкес функционалды интерфейстердің кейбір мысалдары берілген:
Consumer<Integer> c = (int x) -> { System.out.println(x) };

BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);

Predicate<String> p = (String s) -> { s == null };
@FunctionalInterfaceJava тілінің спецификациясына сәйкес Java 8 жүйесінде қосылған annotation жарияланған интерфейстің жұмыс істейтінін тексереді. Сонымен қатар, Java 8 Lambda өрнектерімен пайдалануға арналған бірқатар дайын функционалды интерфейстерді қамтиды. @FunctionalInterfaceегер жарияланған интерфейс жұмыс істемесе, компиляция қатесін жібереді. Төменде функционалды интерфейсті анықтау мысалы берілген:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
Анықтамадан көрініп тұрғандай, функционалдық интерфейсте бір ғана дерексіз әдіс болуы мүмкін. Басқа дерексіз әдіс қосуға әрекеттенсеңіз, компиляция қатесін аласыз. Мысалы:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

    public void doSomeMoreWork();

}
Қате
Unexpected @FunctionalInterface annotation
    @FunctionalInterface ^ WorkerInterface is not a functional interface multiple
    non-overriding abstract methods found in interface WorkerInterface 1 error
После определения функционального интерфейса, мы можем его использовать и получать все преимущества Lambda-выражений. Пример:// defining a functional interface
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
public class WorkerInterfaceTest {

    public static void execute(WorkerInterface worker) {
        worker.doSomeWork();
    }

    public static void main(String [] args) {

      // calling the doSomeWork method via an anonymous class
      // (classic)
      execute(new WorkerInterface() {
            @Override
            public void doSomeWork() {
               System.out.println("Worker called via an anonymous class");
            }
        });

      // calling the doSomeWork method via Lambda expressions
      // (Java 8 new)
      execute( () -> System.out.println("Worker called via Lambda") );
    }

}
Қорытынды:
Worker вызван через анонимный класс
Worker вызван через Lambda
Мұнда біз өзіміздің функционалды интерфейсімізді анықтадық және лямбда өрнегін қолдандық. Бұл әдіс execute()лямбда өрнектерін аргумент ретінде қабылдауға қабілетті.

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

Lambda өрнектерін түсінудің ең жақсы жолы - бірнеше мысалдарды қарау: Ағынды Threadекі жолмен инициализациялауға болады:
// Old way:
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from thread");
    }
}).start();
// New way:
new Thread(
    () -> System.out.println("Hello from thread")
).start();
Java 8-де оқиғаларды басқаруды Lambda өрнектері арқылы да жасауға болады. Төменде ActionListenerUI компонентіне оқиға өңдегішін қосудың екі жолы берілген:
// Old way:
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button pressed. Old way!");
    }
});
// New way:
button.addActionListener( (e) -> {
        System.out.println("Button pressed. Lambda!");
});
Берілген массивтің барлық элементтерін көрсетудің қарапайым мысалы. Ламбда өрнегін пайдаланудың бірнеше жолы бар екенін ескеріңіз. Төменде біз көрсеткі синтаксисін пайдаланып лямбда өрнегін әдеттегі жолмен жасаймыз, сонымен қатар (::)Java 8 жүйесінде қалыпты әдісті лямбда өрнегін түрлендіретін қос қос нүкте операторын қолданамыз:
// Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
    System.out.println(n);
}
// New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
// New way using double colon operator ::
list.forEach(System.out::println);
Келесі мысалда Predicateсынақты жасау және сол сынақтан өткен элементтерді басып шығару үшін функционалды интерфейсті қолданамыз. Осылайша сіз лямбда өрнектеріне логиканы қойып, соған негізделген әрекеттерді жасай аласыз.
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Main {

    public static void main(String [] a)  {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

        System.out.print("Outputs all numbers: ");
        evaluate(list, (n)->true);

        System.out.print("Does not output any number: ");
        evaluate(list, (n)->false);

        System.out.print("Output even numbers: ");
        evaluate(list, (n)-> n%2 == 0 );

        System.out.print("Output odd numbers: ");
        evaluate(list, (n)-> n%2 == 1 );

        System.out.print("Output numbers greater than 5: ");
        evaluate(list, (n)-> n > 5 );

    }

    public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
        for(Integer n: list)  {
            if(predicate.test(n)) {
                System.out.print(n + " ");
            }
        }
        System.out.println();
    }

}
Қорытынды:
Выводит все числа: 1 2 3 4 5 6 7
Не выводит ни одного числа:
Вывод четных чисел: 2 4 6
Вывод нечетных чисел: 1 3 5 7
Вывод чисел больше 5: 6 7
Lambda өрнектерімен жұмыс жасау арқылы тізімнің әрбір элементінің шаршысын көрсетуге болады. stream()Біз әдеттегі тізімді ағынға түрлендіру әдісін қолданып жатқанымызға назар аударыңыз . Java 8 тамаша сыныпты ұсынады Stream( java.util.stream.Stream). Онда лямбда өрнектерін қолдануға болатын көптеген пайдалы әдістер бар. Ламбда өрнегін ағындағы барлық элементтерге қолданатын x -> x*xәдіске береміз . map()Осыдан кейін біз forEachтізімнің барлық элементтерін басып шығару үшін қолданамыз.
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
    int x = n * n;
    System.out.println(x);
}
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);
Тізімді ескере отырып, тізімнің барлық элементтерінің квадраттарының қосындысын басып шығару керек. Lambda өрнектері codeтың бір жолын жазу арқылы бұған қол жеткізуге мүмкіндік береді. Бұл мысалда конволюция (азайту) әдісі қолданылады reduce(). map()Біз әрбір элементті квадраттау әдісін қолданамыз , содан кейін reduce()барлық элементтерді бір санға жию әдісін қолданамыз.
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = 0;
for(Integer n : list) {
    int x = n * n;
    sum = sum + x;
}
System.out.println(sum);
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
System.out.println(sum);

Lambda өрнектері мен анонимді сыныптар арасындағы айырмашылық

Негізгі айырмашылық - кілт сөзді пайдалану this. Анонимді сыныптар үшін ' ' кілт сөзі thisанонимдік сыныптың нысанын білдіреді, ал лямбда өрнегінде ' this' лямбда өрнегі қолданылатын сыныптың нысанын білдіреді. Тағы бір айырмашылығы - оларды құрастыру тәсілі. Java лямбда өрнектерін құрастырады және оларды класс әдістеріне түрлендіреді private. Бұл динамикалық әдісті байланыстыру үшін Java 7 жүйесінде енгізілген invokedynamic нұсқаулығын пайдаланады . Тал Вайсс өз блогында Java лямбда өрнектерін byte codeқа қалай құрастыратынын сипаттады

Қорытынды

Марк Рейнхольд (Oracle бас сәулетшісі) Lambda өрнектерін бағдарламалау үлгісіндегі ең маңызды өзгеріс деп атады, тіпті генериктерден де маңыздырақ. Ол дұрыс айту керек, өйткені... олар Java бағдарламашыларына барлығы күткен функционалды бағдарламалау тілінің мүмкіндіктерін береді. Виртуалды кеңейту әдістері сияқты инновациялармен қатар, Lambda өрнектері өте жоғары сапалы code жазуға мүмкіндік береді. Бұл мақала сізге Java 8 нұсқасын ашты деп үміттенемін. Сәттілік :)
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION