CodeGym /Kurslar /JAVA 25 SELF /Lambda ifadələrinə giriş

Lambda ifadələrinə giriş

JAVA 25 SELF
Səviyyə , Dərs
Mövcuddur

1. Tanışlıq

Qısaca desək: lambda ifadəsi — ayrıca sinif və ya anonim sinif elan etmədən, “yerindəcə” funksional interfeysin reallaşdırmasını tez yaratmağın bir yoludur. Bu, adı olmayan mini-metod kimidir; onu arqument kimi ötürmək və ya dəyişəndə saxlamaq olar.

Java uzun müddət “klassik” OOP dili idi. Əgər davranış parçasını ötürmək istəyirdinizsə, məsələn, düyməyə basıldıqda nə etmək lazım olduğunu, anonim siniflər yazmalı idiniz:

button.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick() {
        System.out.println("Düymə basıldı!");
    }
});

Java 8 ilə lambda sehrləri yarandı:

button.setOnClickListener(() -> System.out.println("Düymə basıldı!"));

Burada () -> System.out.println("Düymə basıldı!") — bu elə lambda ifadəsidir.

Rəsmi olaraq: lambda ifadəsi — funksional interfeysin yeganə abstrakt metodunun reallaşdırılmasının yığcam yazılış formasıdır.

Niyə bu vacibdir?

  • Davranışı dəyər kimi ötürmək (məsələn, siyahının hər elementi ilə nə etmək).
  • Yığcam və oxunaqlı kod yazmaq.
  • Java-nın müasir API-lərindən istifadə: Stream API, hadisələrin emalı, asinxron tapşırıqlar və daha çoxu.

2. Lambda ifadələrinin sintaksisi

Ümumi forma

(parametrlər) -> ifadə
// və ya
(parametrlər) -> { kod bloku }

Müxtəlif interfeyslər üçün nümunələr

1. Parametrsiz (məsələn, Runnable):

Runnable r = () -> System.out.println("Salam, lambda!");
r.run();

2. Bir parametr (məsələn, Consumer):

Consumer<String> printer = s -> System.out.println("Siz ötürdünüz: " + s);
printer.accept("Java");

Parametr yalnız bir dənədirsə və tipini çıxarmaq mümkündürsə, mötərizələri buraxmaq olar.
String tipi — ora ötürdüyümüz dəyişənin (s) tipidir.

3. Bir neçə parametr (məsələn, Comparator):

Comparator<Integer> cmp = (a, b) -> a - b;
System.out.println(cmp.compare(10, 5)); // 5

Integer tipi — ora ötürdüyümüz dəyişənlərin (a, b) tipidir.

4. Çoxsətirli bədən (açıq qaytarma varsa süslü mötərizə və return gərəkdir):

Function<Integer, Integer> square = x -> {
    int result = x * x;
    return result;
};
System.out.println(square.apply(6)); // 36

Birinci Integer — funksiyanın nəticə tipidir, ikinci Integer — parametrin tipidir.

Qısaltmalar və yığcamlıq

  • Əgər gövdə tək ifadədən ibarətdirsə, mötərizələri və return-u buraxmaq olar.
  • Parametr yoxdursa — boş mötərizə yazırıq: () -> ...
  • Bir parametr varsa — mötərizəsiz ola bilər: x -> ...
  • Bir neçə parametr olduqda — mötərizə lazımdır: (a, b) -> ...

Cədvəl-xülasə

Eyni ideyanın lambda və anonim siniflə necə yazıldığına baxaq:

İnterfeys Lambda ifadəsi (nümunə) Anonim sinifin ekvivalenti
Runnable
() -> System.out.println("Hi")
new Runnable() { public void run() { System.out.println("Hi"); } }
Consumer<String>
s -> System.out.println(s)
new Consumer<String>() { public void accept(String s) { System.out.println(s); } }
Comparator<Integer>
(a, b) -> a - b
new Comparator<Integer>() { public int compare(Integer a, Integer b) { return a - b; } }

Öz interfeysimizlə nümunə

Tutaq ki, bizdə funksional interfeys var:

@FunctionalInterface
interface Operation {
    int apply(int a, int b);
}

Əvvəllər belə həyata keçirilirdi:

Operation sum = new Operation() {
    @Override
    public void apply(int a, int b) {
        return a + b;
    }
};

İndi — müasir yanaşma:

Operation sum = (a, b) -> a + b;
System.out.println(sum.apply(3, 5)); // 8

Standart interfeyslər üçün nümunələr

Runnable:

Runnable hello = () -> System.out.println("Hello from thread!");
new Thread(hello).start();

Comparator:

List<String> list = Arrays.asList("alma", "banan", "kivi");
list.sort((a, b) -> a.length() - b.length());
System.out.println(list);

Function:

Function<String, Integer> parse = s -> Integer.parseInt(s);
System.out.println(parse.apply("123")); // 123

3. Görünürlük sahəsi və dəyişənlərin tutulması

Xarici kontekstdən olan dəyişənlər (effectively final)

Lambda ifadələri əhatə edən metoddan dəyişənlərdən istifadə edə bilər. Amma bir qayda var: bu cür dəyişənlər effectively final olmalıdır — yəni ya açıq şəkildə final kimi elan edilir, ya da sadəcə ilkinləşmədən sonra dəyişdirilmir.

Nümunə:

String prefix = "Nəticə: ";
Function<Integer, String> f = x -> prefix + (x * 2);
// prefix burada 'dondurulur' — bundan sonra onu dəyişmək olmaz
System.out.println(f.apply(5)); // Nəticə: 10

Əgər prefix-i lambda daxilində istifadə etdikdən sonra dəyişdirməyə cəhd etsəniz, kompilyator səhv verəcək.

Niyə belədir?
Lambda ifadəsi onun elan olunduğu metoddan çıxdıqdan sonra da çağırıla bilər. “Sehrli” buglardan qaçmaq üçün Java yalnız dəyişməyən dəyişənlərin istifadəsinə icazə verir.

Anonim siniflərdən fərqi

Anonim siniflərdə də eyni qayda keçərlidir: xarici metoddan götürülən dəyişənlər final/effectively final olmalıdır. Amma görünürlük sahəsi ilə bağlı nüanslar var: anonim sinifin içində this həmin anonim sinifə istinad edir, lambda-da isə — xarici sinifə.

public class Demo {
    public void test() {
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println(this); // Çap edəcək: Demo$1 (anonim sinif)
            }
        };

        Runnable r2 = () -> System.out.println(this); // Çap edəcək: Demo (xarici sinif)

        r1.run();
        r2.run();
    }
}

Lambda və sinif sahələri

Lambda ifadəsi xarici sinifin sahələrinə məhdudiyyətsiz müraciət edə bilər:

public class Counter {
    private int base = 10;

    public void printSum(int x) {
        Function<Integer, Integer> sum = y -> base + y + x;
        System.out.println(sum.apply(5));
    }
}

Burada base — dəyişilə bilər (bu, sinif sahəsidir).
x — effectively final olmalıdır.

4. Təcrübə: bir neçə lambda ifadəsi yazaq

Nümunə: ədədlər siyahısının filtrasiyası

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

nums.stream()
    .filter(n -> n % 2 == 0)
    .forEach(n -> System.out.println("Cüt: " + n));

Stream API haqqında daha ətraflı 30-cu səviyyədə öyrənəcəksiniz :P

Nümunə: sətirin çevrilməsi funksiyası

Function<String, String> capitalize = s -> s.toUpperCase();
System.out.println(capitalize.apply("java")); // JAVA

Nümunə: öz funksional interfeysiniz

@FunctionalInterface
interface StringTransformer {
    String transform(String s);
}

StringTransformer exclaim = s -> s + "!";
System.out.println(exclaim.transform("Salam")); // Salam!

Nümunə: xarici kontekstdən dəyişənlərin istifadəsi

int factor = 2;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> System.out.println(n * factor));
// Bundan sonra factor-u dəyişmək olmaz!

5. Lambda ifadələri ilə işləyərkən tipik səhvlər

Səhv №1: lambdanın tutduğu dəyişəni dəyişməyə cəhd.
Bu cür kod kompilyasiya olunmayacaq — dəyişən effectively final olmalıdır:

int sum = 0;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> sum += n); // Səhv: sum final deyil!

Əgər dəyərləri toplamaq lazımdırsa — massivdən və ya wrapper obyektdən istifadə edin.

Səhv №2: this-in görünürlük sahəsi ilə çaşqınlıq.
Lambda daxilində this xarici sinifə istinad edir, lambda-ya yox (anonim sinifdən fərqli olaraq).

Səhv №3: çoxsətirli lambda ifadəsində süslü mötərizələri və return-u unutmaq.
Əgər lambda gövdəsi tək ifadə deyiləcəksə, mötərizələr və return tələb olunur:

Function<Integer, Integer> square = x -> {
    int y = x * x;
    return y;
};

Səhv №4: lambda ifadəsinin tipini səhv təyin etmək.
Lambda ifadəsi həmişə funksional interfeysi reallaşdırır. Sadəcə belə yazmaq olmaz:

var f = x -> x + 1; // Səhv! Hansı interfeys tipi olduğu məlum deyil.

Tipi açıq şəkildə göstərmək lazımdır:

Function<Integer, Integer> f = x -> x + 1;
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION