Java бастапқыда толығымен an objectіге бағытталған тіл болып табылады. Қарапайым типтерді қоспағанда, Java-дағы барлық нәрсе an object болып табылады. Тіпті массивтер де an objectілер болып табылады. Әрбір сыныптың даналары нысандар болып табылады. Функцияны бөлек анықтаудың бірде-бір мүмкіндігі жоқ (сыныптан тыс – шамамен аударма ). Әдісті дәлел ретінде беру немесе басқа әдістің нәтижесі ретінде әдіс денесін қайтару мүмкіндігі жоқ. Дәл солай. Бірақ бұл Java 8-ге дейін болды. Бұрынғы жақсы 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 };
@FunctionalInterface
Java тілінің спецификациясына сәйкес 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 өрнектері арқылы да жасауға болады. Төменде ActionListener
UI компонентіне оқиға өңдегішін қосудың екі жолы берілген:
// 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қа қалай құрастыратынын сипаттады
GO TO FULL VERSION