15-bo‘lim

Lambda va funksional interfeyslar

Lambda ifodalar, funksional interfeys tushunchasi, Function va Consumer oilasi, metod havolalari, yopilmalar va effektiv final qoidasi.

🕑 13 daqiqa o‘qish 📄 723 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Lambda'gacha
  2. Funksional interfeys
  3. Tayyor funksional interfeyslar
  4. Lambda yozilish shakllari
  5. Metod havolalari
  6. Yopilmalar va effektiv final
  7. Funksiyalarni birlashtirish
  8. Xulosa

Lambda - nomsiz funksiyaning qisqa yozuvi. Java 8 dan boshlab u tilning eng ko'p ishlatiladigan vositalaridan biriga aylandi.

Lambda'gacha #

Java 8 gacha "kodni parametr sifatida uzatish" uchun anonim sinf yozilardi:

Java
import java.util.*;

public class Eski {
    public static void main(String[] args) {
        List<String> shaharlar = new ArrayList<>(
                List.of("Samarqand", "Chust", "Namangan"));

        shaharlar.sort(new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                return Integer.compare(a.length(), b.length());
            }
        });

        System.out.println(shaharlar);
    }
}
Natija
[Chust, Namangan, Samarqand]

Bitta amal uchun olti qator. Lambda bilan:

Java
import java.util.*;

public class Yangi {
    public static void main(String[] args) {
        List<String> shaharlar = new ArrayList<>(
                List.of("Samarqand", "Chust", "Namangan"));

        shaharlar.sort((a, b) -> Integer.compare(a.length(), b.length()));
        System.out.println("Uzunlik bo'yicha: " + shaharlar);

        shaharlar.sort(Comparator.comparing(String::length).reversed());
        System.out.println("Teskari:          " + shaharlar);

        shaharlar.sort(Comparator.naturalOrder());
        System.out.println("Alifbo bo'yicha:  " + shaharlar);
    }
}
Natija
Uzunlik bo'yicha: [Chust, Namangan, Samarqand]
Teskari:          [Samarqand, Namangan, Chust]
Alifbo bo'yicha:  [Chust, Namangan, Samarqand]

Funksional interfeys #

Lambda faqat bitta abstrakt metodi bor interfeys uchun yoziladi. Bunday interfeysga funksional interfeys deyiladi.

Java
public class Funksional {
    public static void main(String[] args) {
        Hisoblovchi qoshish = (a, b) -> a + b;
        Hisoblovchi kopaytirish = (a, b) -> a * b;
        Hisoblovchi kattasi = (a, b) -> a > b ? a : b;

        System.out.println("Qo'shish:     " + hisobla(7, 5, qoshish));
        System.out.println("Ko'paytirish: " + hisobla(7, 5, kopaytirish));
        System.out.println("Kattasi:      " + hisobla(7, 5, kattasi));
    }

    static int hisobla(int a, int b, Hisoblovchi amal) {
        return amal.bajar(a, b);
    }
}

@FunctionalInterface
interface Hisoblovchi {
    int bajar(int a, int b);
}
Natija
Qo'shish:     12
Ko'paytirish: 35
Kattasi:      7
@FunctionalInterface izohini yozing

U majburiy emas, lekin foydali: agar interfeysga ikkinchi abstrakt metod qo'shilsa, kompilyator darhol xato beradi:

Natija
error: Unexpected @FunctionalInterface annotation
  multiple non-overriding abstract methods found

Usiz esa xato faqat lambda yozilgan joyda, ancha tushunarsiz shaklda chiqadi.

default va static metodlar hisobga olinmaydi - ular qancha bo'lsa ham interfeys funksional bo'lib qolaveradi.

Tayyor funksional interfeyslar #

Amalda o'z interfeysingizni yozish kamdan-kam kerak - java.util.function paketida tayyorlari bor:

Java
import java.util.function.*;

public class Tayyor {
    public static void main(String[] args) {
        Function<Integer, Integer> kvadrat = son -> son * son;
        Predicate<String> uzunmi = matn -> matn.length() > 5;
        Consumer<String> yozuvchi =
                matn -> System.out.println("[LOG] " + matn);
        Supplier<String> manba = () -> "yangi qiymat";
        BiFunction<Integer, Integer, Integer> qoshish = (a, b) -> a + b;
        UnaryOperator<String> katta = String::toUpperCase;

        System.out.println("Function:  " + kvadrat.apply(7));
        System.out.println("Predicate: " + uzunmi.test("Namangan"));
        yozuvchi.accept("xabar");
        System.out.println("Supplier:  " + manba.get());
        System.out.println("BiFunction:" + qoshish.apply(3, 4));
        System.out.println("Unary:     " + katta.apply("salom"));
    }
}
Natija
Function:  49
Predicate: true
[LOG] xabar
Supplier:  yangi qiymat
BiFunction:7
Unary:     SALOM
InterfeysOladiQaytaradiMetod
Function<T,R>TRapply
BiFunction<T,U,R>T, URapply
Predicate<T>Tbooleantest
Consumer<T>Thech narsaaccept
Supplier<T>hech narsaTget
UnaryOperator<T>TTapply
BinaryOperator<T>T, TTapply
Primitivlar uchun alohida versiyalar

Function<Integer, Integer> ishlatsangiz, har chaqiruvda avtoqutilash bo'ladi: intIntegerint.

Million element uchun bu sezilarli sekinlashtiradi. Shuning uchun primitivlar uchun alohida interfeyslar bor:

UmumiyPrimitiv versiyasi
Function<Integer, Integer>IntUnaryOperator
Predicate<Integer>IntPredicate
Supplier<Integer>IntSupplier
Function<Integer, R>IntFunction<R>

Kichik ro'yxatlarda farq sezilmaydi. Katta hajmda esa ularni ishlating.

Lambda yozilish shakllari #

Java
import java.util.function.*;

public class Shakllar {
    public static void main(String[] args) {
        Function<Integer, Integer> a = son -> son * 2;
        Function<Integer, Integer> b = (son) -> son * 2;
        Function<Integer, Integer> c = (Integer son) -> son * 2;
        Function<Integer, Integer> d = son -> {
            int natija = son * 2;
            return natija;
        };

        Supplier<String> parametrsiz = () -> "hech narsa olmaydi";
        BiFunction<Integer, Integer, Integer> ikkita = (x, y) -> x + y;

        System.out.println(a.apply(5) + ", " + b.apply(5)
                + ", " + c.apply(5) + ", " + d.apply(5));
        System.out.println(parametrsiz.get());
        System.out.println(ikkita.apply(3, 4));
    }
}
Natija
10, 10, 10, 10
hech narsa olmaydi
7
ShaklQachon
son -> ifodaBitta parametr, bitta ifoda - eng ko'p ishlatiladi
(a, b) -> ifodaBir nechta parametr
() -> ifodaParametrsiz
son -> { ... }Bir nechta ko'rsatma, return majburiy

Metod havolalari #

Lambda faqat mavjud metodni chaqirsa, uni qisqaroq yozish mumkin:

Java
import java.util.*;
import java.util.function.*;

public class Havolalar {
    public static void main(String[] args) {
        List<String> shaharlar = new ArrayList<>(
                List.of("namangan", "andijon", "samarqand"));

        shaharlar.forEach(System.out::println);

        Function<String, Integer> uzunlik = String::length;
        System.out.println("Uzunlik: " + uzunlik.apply("Namangan"));

        Supplier<ArrayList<String>> yaratuvchi = ArrayList::new;
        System.out.println("Yangi ro'yxat: " + yaratuvchi.get());

        Function<String, String> katta = String::toUpperCase;
        System.out.println("Katta: " + katta.apply("salom"));

        BiFunction<String, String, Boolean> boshlanadimi = String::startsWith;
        System.out.println("Boshlanadimi: "
                + boshlanadimi.apply("Namangan", "Nam"));
    }
}
Natija
namangan
andijon
samarqand
Uzunlik: 8
Yangi ro'yxat: []
Katta: SALOM
Boshlanadimi: true
ShaklMa'nosiLambda ekvivalenti
Sinf::statikMetodStatik metodx -> Sinf.statikMetod(x)
obyekt::metodAniq obyekt metodix -> obyekt.metod(x)
Sinf::metodBirinchi argument obyekt bo'ladix -> x.metod()
Sinf::newKonstruktor() -> new Sinf()
Uchinchi shakl boshida chalkash

String::length ni shunday o'qing: "berilgan satrning length() metodini chaqir".

Ya'ni lambda parametri obyektga aylanadi, argumentga emas.

String::startsWith esa ikki argument oladi: birinchisi obyekt, ikkinchisi metod argumenti. Shuning uchun u BiFunction ga mos keladi.

Yopilmalar va effektiv final #

Java
import java.util.function.Supplier;

public class Yopilma {
    public static void main(String[] args) {
        String salom = "Assalomu alaykum";
        Supplier<String> xabar = () -> salom + ", dunyo!";

        System.out.println(xabar.get());
        System.out.println("Ikkinchi marta: " + xabar.get());
    }
}
Natija
Assalomu alaykum, dunyo!
Ikkinchi marta: Assalomu alaykum, dunyo!
Lambda faqat o'zgarmaydigan o'zgaruvchini ushlaydi

Java'da lambda ichida ishlatilgan lokal o'zgaruvchi final yoki effektiv final bo'lishi shart - ya'ni u yaratilgandan keyin o'zgarmasligi kerak.

Java
int hisoblagich = 0;
Runnable r = () -> System.out.println(hisoblagich);
hisoblagich++;                  // XATO
Natija
error: local variables referenced from a lambda expression
must be final or effectively final

Sabab: lambda o'zgaruvchining nusxasini oladi. Agar asl o'zgaruvchi keyin o'zgarsa, lambda eski qiymatni ko'rardi - bu chalkashlik tug'diradi. Java buni umuman taqiqlagan.

Yechim: o'zgaruvchan holat kerak bo'lsa, maydondan yoki bir elementli massivdan foydalaning:

Java
public class Aylanma {
    public static void main(String[] args) {
        int[] hisoblagich = {0};

        Runnable oshir = () -> hisoblagich[0]++;
        oshir.run();
        oshir.run();
        oshir.run();

        System.out.println("Hisoblagich: " + hisoblagich[0]);
    }
}
Natija
Hisoblagich: 3

Massivning o'zi o'zgarmadi (havola bir xil qoldi), faqat uning ichidagi qiymat o'zgardi - shuning uchun bu ruxsat etiladi.

Diqqat: bu usul ko'p oqimli kodda xavfli. U faqat bitta oqimda ishlaydi.

Funksiyalarni birlashtirish #

Java
import java.util.function.*;

public class Birlashtirish {
    public static void main(String[] args) {
        Function<Integer, Integer> ikkiBarobar = son -> son * 2;
        Function<Integer, Integer> birQoshish = son -> son + 1;

        System.out.println("andThen: "
                + ikkiBarobar.andThen(birQoshish).apply(5));
        System.out.println("compose: "
                + ikkiBarobar.compose(birQoshish).apply(5));

        Predicate<String> uzun = m -> m.length() > 5;
        Predicate<String> nBilan = m -> m.startsWith("N");

        System.out.println("and: " + uzun.and(nBilan).test("Namangan"));
        System.out.println("or:  " + uzun.or(nBilan).test("Nur"));
        System.out.println("negate: " + uzun.negate().test("Nur"));
    }
}
Natija
andThen: 11
compose: 12
and: true
or:  true
negate: true

andThen - avval o'zi, keyin keyingisi. compose - teskarisi. Shuning uchun 5 * 2 + 1 = 11, lekin (5 + 1) * 2 = 12.

Amaliy topshiriq
  1. Anonim sinf bilan Comparator yozing, keyin uni lambda'ga aylantiring.
  2. O'z funksional interfeysingizni yozing va @FunctionalInterface qo'ying.
  3. Unga ikkinchi abstrakt metod qo'shib, xatoni ko'ring.
  4. Function, Predicate, Consumer va Supplier ni sinab ko'ring.
  5. Lambda'ning to'rt xil shaklini yozing.
  6. String::length va String::toUpperCase metod havolalarini ishlating.
  7. ArrayList::new bilan Supplier yarating.
  8. Lambda ichida o'zgaruvchini o'zgartirishga urinib, xatoni ko'ring.
  9. Bir elementli massiv bilan hisoblagich yozing.
  10. andThen va compose natijalarini solishtiring.

Xulosa #

  • Lambda - bitta abstrakt metodi bor interfeys uchun yoziladi.
  • @FunctionalInterface izohi xatoni erta topadi.
  • Asosiy tayyor interfeyslar: Function, Predicate, Consumer, Supplier.
  • Katta hajmda primitiv versiyalarni ishlating - avtoqutilash qimmat.
  • Metod havolasi (String::length) lambda'ning qisqa shakli.
  • Sinf::metod shaklida lambda parametri obyektga aylanadi.
  • Lambda ichidagi lokal o'zgaruvchi effektiv final bo'lishi shart.
  • andThen va compose funksiyalarni zanjirga bog'laydi.

Keyingi bo'limda Stream API ni o'rganamiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.