JavaRush /Java Blogu /Random-AZ /Java-da lambda ifadələri haqqında məşhurdur. Nümunələr və...
Стас Пасинков
Səviyyə
Киев

Java-da lambda ifadələri haqqında məşhurdur. Nümunələr və tapşırıqlarla. 2-ci hissə

Qrupda dərc edilmişdir
Bu məqalə kimin üçündür?
  • Bu yazının birinci hissəsini oxuyanlar üçün ;

  • Java Core-u artıq yaxşı bildiyini düşünən, lakin Java-da lambda ifadələri haqqında heç bir fikri olmayanlar üçün. Və ya, bəlkə də, siz artıq lambdalar haqqında bir şey eşitmisiniz, lakin təfərrüatlar olmadan.

  • lambda ifadələri haqqında bir qədər anlayışı olan, lakin hələ də onlardan istifadə etməkdən qorxan və qeyri-adi olanlar üçün.

Xarici dəyişənlərə giriş

Bu kod anonim siniflə tərtib ediləcəkmi?
int counter = 0;
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter++;
    }
};
Yox. Dəyişən counterolmalıdır final. Və ya mütləq deyil final, lakin heç bir halda dəyərini dəyişə bilməz. Eyni prinsip lambda ifadələrində də istifadə olunur. Onların elan edildiyi yerdən onlara "görünən" bütün dəyişənlərə giriş imkanı var. Lakin lambda onları dəyişdirməməlidir (yeni dəyər təyin edin). Düzdür, anonim siniflərdə bu məhdudiyyəti keçmək variantı var. Sadəcə olaraq istinad tipli dəyişən yaratmaq və obyektin daxili vəziyyətini dəyişmək kifayətdir. Bu halda, dəyişənin özü eyni obyekti göstərəcək və bu halda siz onu təhlükəsiz olaraq kimi göstərə bilərsiniz final.
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Burada dəyişənimiz countertipli obyektə istinaddır AtomicInteger. Və bu obyektin vəziyyətini dəyişdirmək üçün metoddan istifadə olunur incrementAndGet(). Proqram işləyərkən dəyişənin özü dəyişmir və həmişə eyni obyektə işarə edir ki, bu da bizə dəyişəni açar sözlə dərhal elan etməyə imkan verir final. Eyni nümunələr, lakin lambda ifadələri ilə:
int counter = 0;
Runnable r = () -> counter++;
O, anonim sinifli seçimlə eyni səbəbdən tərtib edilmir: counterproqram işləyərkən dəyişməməlidir. Ancaq belə - hər şey yaxşıdır:
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = () -> counter.incrementAndGet();
Bu, zəng üsullarına da aiddir. Lambda ifadəsinin içərisindən siz yalnız bütün "görünən" dəyişənlərə daxil ola bilməzsiniz, həm də çıxışınız olan metodları çağıra bilərsiniz.
public class Main {
    public static void main(String[] args) {
        Runnable runnable = () -> staticMethod();
        new Thread(runnable).start();
    }

    private static void staticMethod() {
        System.out.println("Я - метод staticMethod(), и меня только-что кто-то вызвал!");
    }
}
Metod staticMethod()özəl olsa da, onu metod daxilində çağırmaq “əlçatandır” main()və ona görə də metodda yaradılmış lambda daxilindən zəng etmək mümkündür main.

Lambda ifadə kodunun icra anı

Bu sual sizə çox sadə görünə bilər, amma soruşmağa dəyər: lambda ifadəsinin daxilindəki kod nə vaxt yerinə yetiriləcək? Yaradılış anında? Yoxsa nə vaxt (hələlik məlum deyil) hara çağırılacaq? Yoxlamaq olduqca asandır.
System.out.println("Запуск программы");

// много всякого разного codeа
// ...

System.out.println("Перед объявлением лямбды");

Runnable runnable = () -> System.out.println("Я - лямбда!");

System.out.println("После объявления лямбды");

// много всякого другого codeа
// ...

System.out.println("Перед передачей лямбды в тред");
new Thread(runnable).start();
Ekranda çıxış:
Запуск программы
Перед объявлением лямбды
После объявления лямбды
Перед передачей лямбды в тред
Я - лямбда!
Görünür ki, lambda ifadə kodu ən sonunda, ip yaradıldıqdan sonra və yalnız proqramın icra prosesi metodun faktiki icrasına çatdıqda yerinə yetirilmişdir run(). Və elan edildiyi vaxtda heç də yox. Lambda ifadəsini elan etməklə biz yalnız tipli obyekt yaratdıq Runnablevə onun metodunun davranışını təsvir etdik run(). Metodun özü çox sonra istifadəyə verildi.

Metod Referansları?

Lambdaların özləri ilə birbaşa əlaqəli deyil, amma bu məqalədə bu barədə bir neçə söz söyləməyin məntiqli olacağını düşünürəm. Tutaq ki, bizdə xüsusi bir şey etməyən, sadəcə olaraq hansısa metodu çağıran lambda ifadəsi var.
x -> System.out.println(x)
Ona bir şey verdilər хvə o, sadəcə onu çağırıb System.out.println()oradan ötürdü х. Bu halda biz onu bizə lazım olan metodun linki ilə əvəz edə bilərik. Bunun kimi:
System.out::println
Bəli, sonunda mötərizə olmadan! Daha dolğun nümunə:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(x -> System.out.println(x));
Son sətirdə forEach()interfeys obyektini qəbul edən metoddan istifadə edirik Consumer. Bu yenə yalnız bir metodu olan funksional interfeysdir void accept(T t). Müvafiq olaraq, biz bir parametr qəbul edən lambda ifadəsini yazırıq (interfeysdə yazıldığı üçün parametrin tipini göstərmirik, lakin onun çağırılacağını bildiririk х). Lambda ifadəsinin gövdəsinə kodu yazırıq. metod çağırıldıqda yerinə yetiriləcək.Burada accept()biz sadəcə olaraq ekranda dəyişəndə ​​nə olduğunu göstəririk.Metodun хözü forEach()kolleksiyanın bütün elementlərindən keçir, Consumerona ötürülən interfeys obyektinin metodunu çağırır (bizim lambda) accept(), kolleksiyadan hər bir elementi keçdiyi yer. Artıq dediyim kimi, bu, lambda ifadəsidir (sadəcə başqa metodu çağırırıq) biz lazım olan metoda istinadla əvəz edə bilərik.Sonra kodumuz belə görünəcək:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(System.out::println);
Əsas odur ki, üsulların qəbul edilmiş parametrləri (println()accept()). Metod println()hər hansı bir şeyi qəbul edə bildiyi üçün (bütün primitivlər və istənilən obyektlər üçün həddən artıq yüklənmişdir) lambda ifadəsi əvəzinə biz forEach()sadəcə metoda istinadla ötürə bilərik.Sonra o, kolleksiyanın hər bir elementini götürəcək və birbaşa ona ötürür println(). forEach()Bu üsulla ilk dəfə qarşılaşanlar üçün qeyd edək ki, biz metodu (sözlər arasında nöqtələr və sonunda mötərizə ilə) println()adlandırmırıq , əksinə bu metodun özünə istinad edirik.System.out.println()
strings.forEach(System.out.println());
kompilyasiya xətamız olacaq. Çünki zəngdən əvvəl forEach()Java onun çağırıldığını görəcək System.out.println(), geri qaytarıldığını anlayacaq və bunu orada gözləyən tipli obyektə ötürməyə voidçalışacaq . voidforEach()Consumer

Metod Referanslarından istifadə sintaksisi

Bu olduqca sadədir:
  1. Statik metoda istinadın ötürülməsiNameКласса:: NameСтатическогоМетода?

    public class Main {
        public static void main(String[] args) {
            List<String> strings = new LinkedList<>();
            strings.add("Mother");
            strings.add("soap");
            strings.add("frame");
    
            strings.forEach(Main::staticMethod);
        }
    
        private static void staticMethod(String s) {
            // do something
        }
    }
  2. Mövcud obyektdən istifadə edərək qeyri-statik metoda istinadın ötürülməsiNameПеременнойСОбъектом:: method name

    public class Main {
        public static void main(String[] args) {
            List<String> strings = new LinkedList<>();
            strings.add("Mother");
            strings.add("soap");
            strings.add("frame");
    
            Main instance = new Main();
            strings.forEach(instance::nonStaticMethod);
        }
    
        private void nonStaticMethod(String s) {
            // do something
        }
    }
  3. Belə bir metodun tətbiq olunduğu sinifdən istifadə edərək, qeyri-statik metoda istinad veririkNameКласса:: method name

    public class Main {
        public static void main(String[] args) {
            List<User> users = new LinkedList<>();
            users.add(new User("Vasya"));
            users.add(new User("Коля"));
            users.add(new User("Петя"));
    
            users.forEach(User::print);
        }
    
        private static class User {
            private String name;
    
            private User(String name) {
                this.name = name;
            }
    
            private void print() {
                System.out.println(name);
            }
        }
    }
  4. Konstruktora keçidin NameКласса::new
    ötürülməsi Metod bağlantılarından istifadə etmək, sizi tamamilə qane edən hazır metod olduqda və onu geri çağırış kimi istifadə etmək istədiyiniz zaman çox rahatdır. Bu halda, həmin metodun kodu ilə lambda ifadəsini və ya sadəcə olaraq bu metodu adlandırdığımız lambda ifadəsini yazmaq əvəzinə, sadəcə olaraq ona istinad ötürürük. Hamısı budur.

Anonim sinif və lambda ifadəsi arasındakı maraqlı fərq

Anonim sinifdə açar söz thishəmin anonim sinfin obyektinə işarə edir. Əgər biz onu thislambda daxilində istifadə etsək, çərçivə sinfinin obyektinə giriş əldə edəcəyik. Bu ifadəni əslində harada yazdıq. Bu ona görə baş verir ki, lambda ifadələri tərtib edildikdə yazıldıqları sinfin özəl metoduna çevrilir. Bu "xüsusiyyətdən" istifadə etməyi məsləhət görməzdim, çünki onun funksional proqramlaşdırma prinsiplərinə zidd olan yan təsiri var. Lakin bu yanaşma OOP ilə olduqca uyğundur. ;)

Məlumatı haradan almışam və ya başqa nə oxumalıyam

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION