CodeGym /Java Blogu /Rastgele /Java'daki lambda ifadelerinin açıklaması. Örnekler ve gör...
John Squirrels
Seviye
San Francisco

Java'daki lambda ifadelerinin açıklaması. Örnekler ve görevler ile. Bölüm 2

grupta yayınlandı
Bu makale kimin için?
  • Bu yazının ilk bölümünü okuyanlar için ;
  • Java Core'u zaten iyi bildiğini düşünen, ancak Java'daki lambda ifadeleri hakkında hiçbir fikri olmayan kişiler içindir. Ya da belki lambda ifadeleri hakkında bir şeyler duymuşlardır, ancak ayrıntılar eksiktir.
  • Lambda ifadeleri konusunda belirli bir anlayışa sahip olan, ancak yine de bunlardan yılan ve bunları kullanmaya alışkın olmayan insanlar içindir.
Bu kategorilerden birine uymuyorsanız, bu makaleyi sıkıcı, kusurlu veya genel olarak size göre olmayabilir. Bu durumda, başka şeylere geçmekten çekinmeyin veya konuyla ilgili bilginiz varsa, lütfen yorumlarda makaleyi nasıl geliştirebileceğim veya tamamlayabileceğim konusunda önerilerde bulunun. Java'daki lambda ifadelerinin açıklaması.  Örnekler ve görevler ile.  Bölüm 2 - 1Materyal, yenilik bir yana, herhangi bir akademik değere sahip olduğunu iddia etmemektedir. Tam tersine: Karmaşık olan şeyleri (bazı insanlar için) olabildiğince basit bir şekilde açıklamaya çalışacağım. Stream API'yi açıklamaya yönelik bir istek, bunu yazmam için bana ilham verdi. Bunun üzerine düşündüm ve lambda ifadelerini anlamadan bazı akış örneklerimin anlaşılmaz olacağına karar verdim. Lambda ifadeleriyle başlayacağız.

Harici değişkenlere erişim

Bu kod anonim bir sınıfla mı derleniyor?

int counter = 0;
Runnable r = new Runnable() { 

    @Override 
    public void run() { 
        counter++;
    }
};
Hayır. counter Değişken olmalıdır final. Veya değilse final, en azından değerini değiştiremez. Aynı prensip lambda ifadelerinde de geçerlidir. Bildirildikleri yerden "görebildikleri" tüm değişkenlere erişebilirler. Ancak bir lambda onları değiştirmemelidir (onlara yeni bir değer atayın). Ancak, anonim sınıflarda bu kısıtlamayı atlamanın bir yolu vardır. Basitçe bir referans değişkeni oluşturun ve nesnenin dahili durumunu değiştirin. Bunu yaparken, değişkenin kendisi değişmez (aynı nesneyi işaret eder) ve güvenle olarak işaretlenebilir final.

final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() { 

    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Burada değişkenimiz counterbir nesneye referanstır AtomicInteger. Ve incrementAndGet()yöntem, bu nesnenin durumunu değiştirmek için kullanılır. Program çalışırken değişkenin kendisinin değeri değişmez. Her zaman aynı nesneyi işaret eder, bu da değişkeni final anahtar sözcüğüyle bildirmemize olanak tanır. İşte aynı örnekler, ancak lambda ifadeleriyle:

int counter = 0;
Runnable r = () -> counter++;
Bu, anonim bir sınıfa sahip sürümle aynı nedenle derlenmeyecektir:  counterprogram çalışırken değişmemelidir. Ama böyle yaparsak her şey yolunda:

final AtomicInteger counter = new AtomicInteger(0); 
Runnable r = () -> counter.incrementAndGet();
Bu aynı zamanda arama yöntemleri için de geçerlidir. Lambda ifadelerinde, yalnızca tüm "görünür" değişkenlere erişmekle kalmaz, aynı zamanda erişilebilir herhangi bir yöntemi de çağırabilirsiniz.

public class Main { 

    public static void main(String[] args) {
        Runnable runnable = () -> staticMethod();
        new Thread(runnable).start();
    } 

    private static void staticMethod() { 

        System.out.println("I'm staticMethod(), and someone just called me!");
    }
}
Özel olmasına rağmen staticMethod(), yöntem içinden erişilebilir main(), bu nedenle yöntemde oluşturulan bir lambda içinden de çağrılabilir main.

Bir lambda ifadesi ne zaman yürütülür?

Aşağıdaki soruyu çok basit bulabilirsiniz, ancak yine de sormalısınız: lambda ifadesinin içindeki kod ne zaman çalıştırılacak? Ne zaman oluşturulur? Ya da ne zaman çağrıldığı (ki henüz bilinmiyor)? Bunu kontrol etmek oldukça kolaydır.

System.out.println("Program start"); 

// All sorts of code here
// ...

System.out.println("Before lambda declaration");

Runnable runnable = () -> System.out.println("I'm a lambda!");

System.out.println("After lambda declaration"); 

// All sorts of other code here
// ...

System.out.println("Before passing the lambda to the thread");
new Thread(runnable).start(); 
Ekran çıkışı:

Program start
Before lambda declaration
After lambda declaration
Before passing the lambda to the thread
I'm a lambda!
Lambda ifadesinin en sonda, thread oluşturulduktan sonra ve sadece programın çalışması method'a ulaştığında çalıştırıldığını görebilirsiniz run(). Açıklandığında kesinlikle değil. RunnableBir lambda ifadesi bildirerek, yalnızca bir nesne yarattık ve run()yönteminin nasıl davrandığını açıkladık. Yöntemin kendisi çok sonra yürütülür.

Yöntem referansları?

Yöntem referansları doğrudan lambdalarla ilgili değildir, ancak bu makalede onlar hakkında birkaç söz söylemenin mantıklı olduğunu düşünüyorum. Özel bir şey yapmayan, sadece bir yöntem çağıran bir lambda ifademiz olduğunu varsayalım.

x -> System.out.println(x)
xBazı ve sadece çağrıları alır System.out.println(), geçer x. Bu durumda, istenen yönteme bir referansla değiştirebiliriz. Bunun gibi:

System.out::println
Bu doğru - sonunda parantez yok! İşte daha eksiksiz bir örnek:

List<String> strings = new LinkedList<>(); 

strings.add("Dota"); 
strings.add("GTA5"); 
strings.add("Halo"); 

strings.forEach(x -> System.out.println(x));
Son satırda, forEach()arayüzü gerçekleyen bir nesneyi alan yöntemi kullanıyoruz Consumer. Yine, bu, yalnızca bir void accept(T t)yöntemi olan işlevsel bir arayüzdür. Buna göre tek parametreli bir lambda ifadesi yazıyoruz (arayüzün kendisinde yazıldığı için parametre tipini belirtmiyoruz; sadece onu çağıracağımızı belirtiyoruz x). Lambda ifadesinin gövdesine ise method çağrıldığında çalışacak kodu yazıyoruz accept(). Burada sadece değişkende neyin sona erdiğini gösteriyoruz x. Bu aynı forEach()yöntem, koleksiyondaki tüm öğeleri yineler ve yöntemi accept(),Consumerarayüz (bizim lambda), koleksiyondaki her öğeye geçiyor. Dediğim gibi, böyle bir lambda ifadesini (basitçe farklı bir yöntemi sınıflandıran) istenen yönteme referansla değiştirebiliriz. O zaman kodumuz şöyle görünecek:

List<String> strings = new LinkedList<>(); 

strings.add("Dota"); 
strings.add("GTA5"); 
strings.add("Halo");

strings.forEach(System.out::println);
println()Ana şey, ve yöntemlerin parametrelerinin accept()eşleşmesidir. Yöntem her şeyi kabul edebildiğinden (tüm ilkel türleri ve tüm nesneler için aşırı yüklenmiştir), lambda ifadeleri yerine, yönteme println()bir referansı basitçe iletebiliriz . Ardından koleksiyondaki her bir öğeyi alıp doğrudan yönteme iletir . Bununla ilk kez karşılaşan herkes, lütfen arama yapmadığımızı unutmayın (kelimeler arasında noktalar ve sonunda parantezler). Bunun yerine, bu yönteme bir başvuru geçiyoruz. bunu yazarsak println()forEach()forEach()println()System.out.println()

strings.forEach(System.out.println());
derleme hatası alırız. to çağrısından önce forEach(), Java bunun System.out.println()çağrıldığını görür, böylece dönüş değerinin olduğunu anlar ve bunun yerine bir nesne bekleyen to öğesine voidgeçmeye çalışır . voidforEach()Consumer

Yöntem referansları için sözdizimi

Oldukça basit:
  1. Bunun gibi statik bir yönteme bir referans iletiyoruz:ClassName::staticMethodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<String> strings = new LinkedList<>(); 
            strings.add("Dota"); 
            strings.add("GTA5"); 
            strings.add("Halo"); 
    
            strings.forEach(Main::staticMethod); 
        } 
    
        private static void staticMethod(String s) { 
    
            // Do something 
        } 
    }
    
  2. Şunun gibi varolan bir nesneyi kullanarak statik olmayan bir yönteme bir referans iletiyoruz:objectName::instanceMethodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<String> strings = new LinkedList<>();
            strings.add("Dota"); 
            strings.add("GTA5"); 
            strings.add("Halo"); 
    
            Main instance = new Main(); 
            strings.forEach(instance::nonStaticMethod); 
        } 
    
        private void nonStaticMethod(String s) { 
    
            // Do something 
        } 
    }
    
  3. Aşağıdaki gibi uygulayan sınıfı kullanarak statik olmayan bir yönteme bir referans iletiyoruz:ClassName::methodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<User> users = new LinkedList<>(); 
            users.add (new User("John")); 
            users.add(new User("Paul")); 
            users.add(new User("George")); 
    
            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. Şunun gibi bir yapıcıya bir referans iletiyoruz:ClassName::new

    Geri arama olarak mükemmel bir şekilde çalışacak bir yönteminiz olduğunda, yöntem referansları çok kullanışlıdır. Bu durumda, yöntemin kodunu içeren bir lambda ifadesi yazmak veya yalnızca yöntemi çağıran bir lambda ifadesi yazmak yerine, ona bir referans iletiyoruz. Ve bu kadar.

Anonim sınıflar ve lambda ifadeleri arasında ilginç bir ayrım

Anonim bir sınıfta, thisanahtar kelime anonim sınıfın bir nesnesine işaret eder. Ancak bunu bir lambda içinde kullanırsak, içeren sınıfın nesnesine erişim kazanırız. Aslında lambda ifadesini yazdığımız yer. Bunun nedeni, lambda ifadelerinin yazıldıkları sınıfın özel bir yönteminde derlenmesidir. Bu "özelliği" kullanmanızı tavsiye etmem, çünkü bir yan etkisi vardır ve bu, işlevsel programlama ilkeleriyle çelişir. Bununla birlikte, bu yaklaşım tamamen OOP ile tutarlıdır. ;)

Bilgilerimi nereden aldım ve başka ne okumalısınız?

Ve tabii ki Google'da bir ton şey buldum :)
Yorumlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION