JavaRush /Java блогу /Random-KY /Мисалдар менен Lambda туюнтмалары

Мисалдар менен Lambda туюнтмалары

Группада жарыяланган
Java алгач толугу менен an objectиге багытталган тил болуп саналат. Примитивдик типтерден башкасы, Javaда бардыгы an object болуп саналат. Жада калса массивдер an object болуп саналат. Ар бир класстын инстанциялары an object болуп саналат. Функцияны өзүнчө аныктоонун бир да мүмкүнчүлүгү жок (класстан тышкары - болжол менен котормо. ). Методду аргумент катары өткөрүү же башка методдун натыйжасы катары методдун денесин кайтаруу үчүн эч кандай жол жок. Ушундай. Бирок бул Java 8ге чейин болгон. Ламбда туюнтмалары мисалдар менен - ​​1Эски жакшы Свингдин күндөрүнөн бери кандайдыр бир функцияларды кандайдыр бир методго өткөрүү зарыл болгондо анонимдүү класстарды жазуу керек болчу. Мисалы, окуяны иштеткичти кошуу ушундай болду:
someObject.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {

                //Event listener implementation goes here...

            }
        });
Бул жерде биз чычкан окуясынын угуучусуна кандайдыр бир code кошкубуз келет. Биз анонимдүү классты аныктап MouseAdapter, андан дароо an object түздүк. Ошентип, биз кошумча функцияларды addMouseListener. Кыскасы, Java тorнде жөнөкөй ыкманы (функцияны) аргументтер аркылуу өткөрүү оңой эмес. Бул чектөө Java 8 иштеп чыгуучуларын тил спецификациясына Lambda туюнтмалары сыяктуу функцияны кошууга мажбур кылды.

Эмне үчүн Java Lambda туюнтмалары керек?

Башынан бери Java тor Аннотациялар, Генериктер ж.б. сыяктуу нерселерди эске албаганда көп өнүгө элек. Биринчиден, Java дайыма an objectиге багытталган бойдон калган. JavaScript сыяктуу функционалдык тилдер менен иштегенден кийин, Java кандайча an objectиге багытталган жана катуу терилгенин түшүнсө болот. Функциялар Java тorнде талап кылынbyte. Өз алдынча, аларды Java дүйнөсүндө табууга болбойт. Функционалдык программалоо тилдеринде функциялар биринчи орунга чыгат. Алар өз алдынча бар. Сиз аларды өзгөрмөлөргө дайындап, аргумент аркылуу башка функцияларга өткөрө аласыз. JavaScript функционалдык программалоо тилдеринин эң мыкты үлгүлөрүнүн бири. Сиз Интернеттен JavaScriptтин функционалдык тил катары артыкчылыктарын чагылдырган жакшы макалаларды таба аласыз. Функционалдык тилдерде Closure сыяктуу күчтүү куралдар бар, алар тиркемелерди жазуунун салттуу ыкмаларына караганда бир катар артыкчылыктарды берет. Жабылуу - бул ага тиркелген чөйрөсү бар функция - функциянын бардык локалдык эмес өзгөрмөлөрүнө шилтемелерди сактаган table. Javaда жабууларды Lambda туюнтмалары аркылуу симуляциялоого болот. Албетте, жабуулар менен Lambda туюнтмаларынын ортосунда майда эмес айырмачылыктар бар, бирок ламбда туюнтмалары жабууга жакшы альтернатива болуп саналат. Стив Йегге өзүнүн мыскылдуу жана күлкүлүү блогунда Java дүйнөсү зат атоочтор менен кантип тыгыз байланышта экенин сүрөттөйт (an objectтер, an objectтер - болжол менен котормо. ). Эгер сиз анын блогун окуй элек болсоңуз, мен аны сунуштайм. Ал Lambda туюнтмаларынын Javaга эмне үчүн кошулганын күлкүлүү жана кызыктуу түрдө сүрөттөйт. Lambda туюнтмалары Javaга көптөн бери жок болуп келген функцияларды алып келет. Ламбда туюнтмалары тилге an objectтер сыяктуу эле функцияларды алып келет. Бул 100% туура эмес болсо да, сиз Lambda туюнтмалары жабылбаганы менен окшош мүмкүнчүлүктөрдү камсыз кылаарын көрө аласыз. Функционалдык тилде ламбда туюнтмалары функция болуп саналат; бирок Java-да ламбда туюнтмалары an objectтер аркылуу көрсөтүлөт жана функционалдык интерфейс деп аталган белгилүү бир an object түрү менен байланыштырылышы керек. Кийинки биз анын эмне экенин карап чыгабыз. Марио Фусконун "Эмне үчүн бизге Lambda Expression in Java керек" деген макаласында эмне үчүн бардык заманбап тилдер жабуу мүмкүнчүлүктөрүн талап кылышы керектиги кеңири сүрөттөлөт.

Ламбда туюнтмаларына киришүү

Lambda туюнтмалары анонимдүү функциялар (Java үчүн 100% туура аныктама болушу мүмкүн эмес, бирок бул кандайдыр бир тактык алып келет). Жөнөкөй сөз менен айтканда, бул декларациясыз ыкма, б.а. кирүү өзгөрткүчтөрү жок, маанини жана атын кайтарып берет. Кыскасы, алар ыкманы жазууга жана аны дароо колдонууга мүмкүнчүлүк берет. Бул бир жолку ыкмасы чакыруу учурда өзгөчө пайдалуу, анткени класс түзбөстөн методду жарыялоо жана жазуу убактысын кыскартат. Javaдагы Lambda туюнтмаларында адатта төмөнкү синтаксис бар (аргументы) -> (тело). Мисалы:
(арг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 };

Ламбда туюнтмаларынын структурасы

Ламбда туюнтмаларынын түзүлүшүн изилдеп көрөлү:
  • Lambda туюнтмаларында 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 тorнде, Marker интерфейстери жарыялоо ыкмалары же талаалары жок интерфейстер. Башкача айтканда, токен интерфейстери бош интерфейстер. Ошо сыяктуу эле, Функционалдык интерфейстер бир гана абстракттуу методу бар интерфейстер. java.lang.Runnableфункционалдык интерфейстин мисалы болуп саналат. Ал бир гана ыкманы жарыялайт void run(). Интерфейс дагы бар ActionListener- ошондой эле функционалдуу. Мурда функционалдык интерфейсти ишке ашырган an objectтерди түзүү үчүн анонимдүү класстарды колдонууга туура келген. Lambda туюнтмалары менен баары жөнөкөй болуп калды. Ар бир ламбда туюнтмасы кандайдыр бир функционалдык интерфейске кыйыр түрдө байланышы мүмкүн. Мисалы, сиз Runnableтөмөнкү мисалда көрсөтүлгөндөй, интерфейске шилтеме түзө аласыз:
Runnable r = () -> System.out.println("hello world");
Функционалдык интерфейсти көрсөтпөгөндө, конversionнын мындай түрү ар дайым кыйыр түрдө жүзөгө ашырылат:
new Thread(
    () -> System.out.println("hello world")
).start();
Жогорудагы мисалда, компилятор класс конструкторунан Runnableинтерфейстин ишке ашырылышы катары автоматтык түрдө lambda туюнтмасын түзөт 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 };
Java тorнин спецификациясына ылайык Java 8де кошулган annotation @FunctionalInterfaceжарыяланган интерфейстин иштешин текшерет. Мындан тышкары, 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 туюнтмаларын аргумент катары кабыл алууга жөндөмдүү.

Ламбда туюнтмаларынын мисалдары

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анонимдүү класстын an objectисин билдирет, ал эми lambda туюнтмасында ' this' lambda туюнтмасы колдонулган класстын an objectисин билдирет. Дагы бир айырмачылык - аларды түзүү жолу. Java lambda туюнтмаларын түзөт жана аларды класстык методдорго айландырат private. Бул динамикалык ыкма менен байланыштыруу үчүн Java 7де киргизилген invokedynamic инструкциясын колдонот. Тал Вайсс өзүнүн блогунда Java ламбда туюнтмаларын bytecodeго кантип түзөрүн айтып берди

Корутунду

Марк Рейнхольд (Oracle башкы архитектору) Lambda туюнтмаларын программалоо моделиндеги эң олуттуу өзгөрүү деп атады - генериктерге караганда дагы маанилүү. Ал туура болсо керек, анткени... алар Java программисттерине бардыгы күткөн функционалдык программалоо тorнин мүмкүнчүлүктөрүн берет. Виртуалдык кеңейтүү ыкмалары сыяктуу инновациялар менен бирге, Lambda туюнтмалары сизге абдан сапаттуу code жазууга мүмкүндүк берет. Мен бул макала сизге Java 8 капотунун астына карап берди деп үмүттөнөм. Ийгorк :)
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION