Java'nın Veri Dünyasına Lambda İle Yolculuğu - I

Tahmin ediyorum ki, Java 8 ile gelen yenilikleri bir çok kişi benim gibi ilk duyduğunda ‘Bunlar da ne acaba?’ diye aklından geçirip, sonra ‘Aman canım, nasıl olsa ihtiyacımız olduğunda öğreniriz nasılsa’ diyerek geçiştirmiştir.  Java 5 ten bu yana en ciddi gelişmeyi içeren 8 güncellemesi aslında bundan daha fazlasını hak ediyor.

Son zamanlarda, büyük veri dünyasındaki dikkat çeken ve baş döndüren gelişmeler, Scala gibi yeni popüler dillerin ortaya çıkması aslında veri dünyasındaki gelişmeleri yakından takip edenler için çok şaşırtıcı değil. Zira, veri işlemenin programlama perspektifinden bakıldığında yıllardan beridir kendine özgü dilleri ve platform anlamında çözümleri zaten vardı. Bu gün olan ise artık problem ve çözümlerin kaçınılmaz olarak daha geniş bir kitleyi içinde barındırmaya başlamasıdır. Bu da kullanılan yöntemlerin, rekabetin getirdiği hayatta kalma içgüdüsü ile değişime ayak uydurma olarak da yorumlanabilecek şekilde, bu yönde evrilmesini gerektiriyor. Java 8 ile gelen yenilikler içinde bulunan lambda yaklaşımına bu gözle bakmak çok da yanlış olmasa gerek.

Fırsat buldukça yenilik ve değişiklikler ile ilgili çalışmalarımı paylaşacağım. İlk olarak lambda konusunu ele aldım ve bir kaç kaynağa ve resmi dokümantasyona göz atarak, faydalandığım kaynakları referans vererek sonuçta Java nın veri dünyasını şekillendiren Hadoop ve eko sistemindeki birçok yazılımın geliştirmesinin yapıldığı dil olarak, Scala ve Python gibi dillere meydanı bırakmayacağının ip uçlarını veren bir Türkçe metin hazırladım. Sizlerin de faydalanacağınızı umuyorum.

Java Terim ve Tanımlarına Türkçe Bir Bakış

Bu dokümanı oluşturmaktaki amacım, Java ile ilgili temel kavramlarla ilgili tanımları kendi dilimde ifade etme ihtiyacı duymamdır.

Bu şekilde bir çevirme işlemi, her ne kadar yıllarca ingilizce terimleri kullanmaya ve duymaya alışmış birinin kulağına çok hoş gelmese de, Türkçe bir karşılık aradığımızda izlememiz ve kabul etmemiz gereken bir yoldur.

*Bu dokümanı yazarken OBE’den faydalandım.

Anonim Dahili Sınıflar ne demektir ?

Anonymous Inner Class

Bazı durumlarda, sadece belli bir amaç için bir kereliğine kullanılacak olan sınıf tanımlama ve uygulama (implement) ihtiyacı oluşabilir. Java bu durumlar için  anonim sınıfların kullanımı yolunu sunmaktadır. Örneğin, standart Swing ve JavaFX uygulamalarında fare ve klavye kaynaklı etkinlikleri(events) çözümleyip gereğini yapacak onlarca etkinlik işleyiciye (event handler) ihtiyaç duyulmaktadır. Bu tür her etkinlik için ayrı bir işleyici sınıf geliştirmektense, aşağıdaki türde bir çözüm üretmek daha pratiktir.

16     JButton testButton = new JButton("Test Button");
17     testButton.addActionListener(new ActionListener(){
18     @Override
      public void actionPerformed(ActionEvent ae){
19         System.out.println("Click Detected by Anon Class");
20       }
21     });

Bu şekilde bir tanımlama yapmaz isek, kullanmak istediğimiz her bir etkinlik için bu etkinlik ile ilişkilendireceğimiz ActionListener arayüzünden (interface) türeyen, bir etkinlik işleyici sınıf yazmak zorunda kalırız. Ancak bunun yerine sadece ihtiyaç duyulan yerde bu sınıfı oluşturup kullanarak, kodumuzu daha basit ve okunabilir kılabiliriz. Elbette ki bu şekilde bir uygulamanın getirileri yanında götürüleri de vardır. Örnek olarak, bu çözüm pek şık bir kod görüntüsü vermiyor. Zira tek bir metot tanımı yapmak için bir sürü kod yazmak zorunda kalıyoruz.

Fonksiyonel Arayüzler ne demek ?

Functional Interfaces

Bunu bir örnekle açıklamayı tercih edeceğim. ActionListener arayüzünü tanımlayan koda bakacak olursak şöyle bir şey göreceğiz :

  1    package java.awt.event;
  2    import java.util.EventListener;
  3
  4    public interface ActionListener extends EventListener {
  5     
  6       public void actionPerformed(ActionEvent e);
  7
  8    }

ActionListener tek bir metot barındıran arayüzler için bir örnektir ve Java SE 8 ile birlikte bu kalıba (pattern) uyumlu arayüzlere “fonksiyonel arayüzler” denmektedir.

Not: Bu tip arayüzler daha önceki Java sürümlerinde “Tekil Soyut Metot (Single Abstract Method (SAM))” tipi olarak biliniyorlardı.

Fonksiyonel arayüzler ve dahili anonim sınıfları birlikte kullanmak, Java içinde çok yaygın bir kullanım kalıbıdır. EventListener sınıfının yanı sıra Runnable ve Comparator arayüzleri de bu tarz kullanım için güzel örneklerdir. Bu özellikleri nedeni ile iş Lambda ya geldiğinde fonksiyonel arayüzlerden faydalanmak en akıllıca tercih olarak görünmektedir. Bunu yapmak için bu kalıp daha da geliştirilmiş ve Lambda ifadelerini (Lambda Expressions) destekler hale getirilmiştir.

Lambda İfade Koddizimi nasıldır ?

Lambda Expression Syntax

Lambda ifadeleri aslında anonim dahili sınıfların hantallığını bir nebze olsun gidermeyi hedeflemektedir. Bunu da bir kaç satırdan oluşan bir kod parçasını, tek satırlık bir ifadeye dönüştürerek yapar. Tanımlamak gerekirse, “dikey problem” olarak görünen dahili sınıfların oluşturduğu bu  problem, böylece basit yatay çözümle halledilmiş olur.

Bir lambda ifadesi üç parçadan oluşur.

Değişken Listesi
Ok Sembolü
Gövde
(int x, int y)
->
x + y

Gövde dediğimiz işlem grubu, tek bir ifade ya da bir çok satırlık koddan oluşan bir ifadeler bloğu olabilir. Tek bir ifade düşünüldüğünde işlem basittir, hesaplama yapılır ve sonuç geri döndürülür. Ancak ikinci durum için işlem biraz daha karmaşıktır. Bütün bir blok bir metotmuş gibi çalıştırılır ve eğer bir geri dönüş değeri oluşmuşsa bu değer bu anonim metodu çağırana döner, eğer bir geri dönüş değeri oluşmuyor ise hiç bir şey dönmez. Ayrıca, break ve continue anahtar kelimeleri sadece döngüler içinde kullanılabilir, bunun dışında kullanılmazlar. Bir geri dönüş değeri beklendiği durumlarda her bir kontrol noktasında bir değer dönmeli ya da bir hata atılmalıdır.

Bir kaç örnek :

(int x, int y) -> x + y

() -> 42

(String s) -> { System.out.println(s); }

Birinci ve ikinci örnekler tek bir ifade formuna örnektirler. Birinci örnek tamsayı tipinde x ve y değişkenlerini alıp x + y ifadesinin sonucunu geri döner. İkinci örnek ise hiç bir değişken almadan yine ifade formu kullanarak 42 tamsayı değerini geri döner. Üçüncü örnek ise blok formuna bir örnektir ve bir karakter dizisini alıp bu diziyi konsola yazar ve bir geri dönüş değeri yoktur.

Şimdi gördüğümüz bu temel koddizim formları ışığında bir kaç örneğe göz atalım.

Koşabilen Lambda

Runnable Lambda
*Runnable bir anahtar kelime olduğundan burada koşabilen, çalışabilen anlamında olduğu belirtilip bundan sonra aslına sadık kalınarak kendisi kullanılacaktır.

Aşağıda lambda kullanarak Runnable nasıl yazılır bir bakalım.

6 public class RunnableTest {
7   public static void main(String[] args) {
8     
9     System.out.println("=== RunnableTest ===");
10     
11     // Anonymous Runnable
12     Runnable r1 = new Runnable(){
13       
14       @Override
15       public void run(){
16         System.out.println("Hello world one!");
17       }
18     };
19     
20     // Lambda Runnable
21     Runnable r2 = () -> System.out.println("Hello world two!");
22     
23     // Run em!
24     r1.run();
25     r2.run();
26     
27   }
28 }

Kodu incelediğimizde hem geleneksel hem de lambda formunda tanımı görüyoruz. Her iki kullanımda da parametre geçişi ve geri değer dönüşü yok. Gördüğümüz gibi lambda - ki burada blok formu söz konusu - sayesinde bir çok satırdan oluşan ifade tek bir satırlık ifadeye dönüşüyor.

Karşılaştırıcı Lambda
Comparator Lambda

*Comparator bir anahtar kelime olduğundan burada karşılaştırıcı, kıyaslayıcı anlamında olduğu belirtilip bundan sonra aslına sadık kalınarak kendisi kullanılacaktır.

Java’da Comparator sınıfı, yığınları (collections)  sıralamak maksadı ile kullanılmaktadır. Aşağıdaki örnekte Person nesnesinden oluşan bir yığını barındıran ArrayList’i surName alanını baz alarak sıralayacağız. Aşağıda Person nesnesinin kullandığı alanları göreceksiniz.

9 public class Person {
10    private String givenName;
11    private String surName;
12    private int age;
13    private Gender gender;
14    private String eMail;
15    private String phone;
16    private String address;
17 }   

Aşağıdaki kod ise hem anonim dahili sınıf, hem de iki adet lambda ifadesi kullanarak Comparator uygulaması yapmaktadır.

10 public class ComparatorTest {
11
12   public static void main(String[] args) {
13    
14     List<Person> personList = Person.createShortList();
15   
16     // Sort with Inner Class
17     Collections.sort(personList, new Comparator<Person>(){
18       public int compare(Person p1, Person p2){
19         return p1.getSurName().compareTo(p2.getSurName());
20       }
21     });
22     
23     System.out.println("=== Sorted Asc SurName ===");
24     for(Person p:personList){
25       p.printName();
26     }
27     
28     // Use Lambda instead
29     
30     // Print Asc
31     System.out.println("=== Sorted Asc SurName ===");
32     Collections.sort(personList, (Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName()));
33
34     for(Person p:personList){
35       p.printName();
36     }
37     
38     // Print Desc
39     System.out.println("=== Sorted Desc SurName ===");
40     Collections.sort(personList, (p1,  p2) -> p2.getSurName().compareTo(p1.getSurName()));
41
42     for(Person p:personList){
43       p.printName();
44     }
45     
46   }
47 }

Burada da görüldüğü üzere, 17-21 satır arasındaki kod tek bir basit ifadeye dönüştürülüp 32. satırdaki gibi de yazılabilir.

Dikkatinizi çekti ise birinci lambda ifadesinde değişkenlerin tip tanımı da verilirken ikinci lambda ifadesinde (40. satır) tip tanımı verilmeyip sadece parametreler verilmiştir. Buradan da tip tanımın zorunlu olmadığını görürüz. Bunu lambdada var olan ve değişken tiplerini bağlam’a(context)  göre tespit eden “hedef nesne tip” desteği sayesinde açıklayabiliriz. Aslında yaptığımız iş, Comparator türündeki nesneyi ifade eden kod bütününü sort metoduna ikinci parametre olarak vermekten ibaret. Comparator nesnesi ise bir Generic olduğundan ve sort metoduna verilen birinci parametre olan List nesnesinin parametre tipi ile uyumlu olmak zorunda olduğundan, biz tanımlamasak dahi derleyici ikinci parametre olarak verilen Comparator nesnesinin parametre tipinin de birinci parametre olan List nesnesinin parametre tipi ile aynı olduğunu yani Person tipinde olduğunu bilir.

Dinleyici Lambda

Listener Lambda

*Listener bir anahtar kelime olduğundan burada dinleyen, dinleyici anlamında olduğu belirtilip bundan sonra aslına sadık kalınarak kendisi kullanılacaktır.

Aşağıdaki kod ise hem anonim dahili sınıf hem de lambda ifadesi kullanarak Listener uygulaması yapmaktadır.

13 public class ListenerTest {
14   public static void main(String[] args) {
15         
16     JButton testButton = new JButton("Test Button");
17     testButton.addActionListener(new ActionListener(){
18     @Override public void actionPerformed(ActionEvent ae){
19         System.out.println("Click Detected by Anon Class");
20       }
21     });
22     
23  testButton.addActionListener(e->System.out.println("Click Detected by Lambda Listener"));
24     
25     // Swing stuff
26     JFrame frame = new JFrame("Listener Test");
27     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
28     frame.add(testButton, BorderLayout.CENTER);
29     frame.pack();
30     frame.setVisible(true);
31     
32   }
33 }

Örnekte de görülebileceği gibi lambda ifadeleri metoda parametre olarak gönderiliyor. Hedef tip desteği aşağıdaki bağlamlarda da desteklenmektedir.

Değişken tanımlama - Variable declarations
Atamalar - Assignments
Dönüş ifadeleri - Return statements
Dizi hazırlayıcılar  - Array initializers
Metot ya da kurucu parametreleri - Method or constructor arguments
Lamda ifade gövdeleri - Lambda expression bodies
Şartlı ifadeler - Conditional expressions ?:
Tip Çevrim ifadeleri - Cast expressions
Kaynaklar - Resources

Comments

Popular posts from this blog

Automation of daily build process with TlosLite

Java Sürümleri ve Özellikleri Kılavuzu

Java 14'de neler var - 1