Umut Çakmak

Umut Çakmak

Java - Lambda ve Functional Interfaces

Java - Lambda ve Functional Interfaces

Umut Cakmak
·Aug 6, 2022·

Subscribe to my newsletter and never miss my upcoming articles

Table of contents

Arkadaşlar Merhaba,

Java'da Lambda ifadeleri ve functional interfaceslere göz atacağız.Umarım faydalı olur.

Fonksiyonel Programlama Nedir ?

Java özünde nesne yönelimli bir dildir.Java 8'de dil, başka bir stil kullanarak kod yazma yeteneğini ekledi. Fonksiyonel programlama, daha bildirimsel(declaratively) olarak kod yazmanın bir yoludur.

Nesnelerin durumuyla uğraşmak yerine ne yapmak istediğinizi belirtirsiniz. Döngülerden çok ifadelere odaklanırsınız.

Fonksiyonel programlama, kod yazmak için lambda ifadeleri kullanır.

Bir lambda ifadesi, etrafta dolaşan bir kod bloğudur. Bir lambda ifadesini isimsiz bir method olarak düşünebilirsiniz. Tam teşekküllü methodlar gibi parametreleri ve gövdesi var ama gerçek bir method gibi bir adı yoktur.

Java’da lambda ifadesi (lambda expressions), tek bir soyut metoda sahip bir interface gerçekleştiren ifadedir.

public class Person {

    private String name;
    private boolean canRun;
    private boolean canWalk;

    public Person(String name, boolean canRun, boolean canWalk) {
        this.name = name;
        this.canRun = canRun;
        this.canWalk = canWalk;
    }

    public boolean isCanRun() {
        return canRun;
    }

    public boolean isCanWalk() {
        return canWalk;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", canRun=" + canRun +
                ", canWalk=" + canWalk +
                '}';
    }
}
public interface CheckTrait {
    boolean test(Person p);
}
public class Check implements CheckTrait {

    @Override
    public boolean test(Person p) {
        return p.isCanRun();
    }

}

Java, lambda ifadelerinin ne anlama geldiğini çözerken bağlama (expressions) güvenir.

Bu lambda'yı print() methodunun ikinci parametresi olarak geçiyoruz.

Bu method, ikinci parametre olarak bir CheckTrait bekler. Bunun yerine bir ambda geçirdiğimiz için Java, lambda'mızı bu interface ile eşleştirmeye çalışır.

boolean test(Person p);

Bu interface'nin methodu bir Person aldığından, bu, lambda parametresinin bir Person olması gerektiği anlamına gelir. Ve bu interface'in methodu bir boolean döndürdüğünden, lambda'nın bir boolean değer döndürdüğünü biliyoruz.

p ->  p.isCanRun()

p adıyla belirtilen tek bir parametre ve parametreyi gövdeyi ayırmak için ok operatörü.

Tek bir methodu çağıran ve bu methodun sonucunu döndüren bir gövde.

(Person p) -> { return p.isCanRun(); }

p adıyla belirtilen ve türünü belirten tek bir parametre Person sınıfı

Parametreyi ve gövdeyi ayırmak için ok operatörü

Noktalı virgül ve return ifadesi dahil olmak üzere bir veya daha fazla kod satırına sahip bir gövde.

Parantezler, yalnızca tek bir parametre varsa ve türü açıkça belirtilmemişse atlanabilir.

Java, parantez kullanılmadığında return yazmanızı veya noktalı virgül kullanmanızı gerektirmez.

Birden fazla ifade var ya da return kullanıyorsa blok gereklidir.

s -> {} geçerli bir lambdadır. İfadenin sağ tarafında kod yoksa noktalı virgül veya return ifadesine ihtiyacınız yoktur.

public static void main(String[] args) {

      List<Person> personArrayList = new ArrayList<Person>();

        personArrayList.add(new Person("Umut", false, true));
        personArrayList.add(new Person("Ahmet", true, false));
        personArrayList.add(new Person("Hasan", true, false));
        personArrayList.add(new Person("GÖkhan", false, true));

        // pass class that does check

        print(personArrayList, p -> p.isCanRun());
        print(personArrayList, p -> p.isCanWalk());
        print(personArrayList, p -> !p.isCanRun());

}

private static void print(List<Person> personList, CheckTrait checker) {

        for (Person person : personList) {
            // the general check
            if (checker.test(person))
                System.out.println(person + " ");

        }
        System.out.println();
}

Geçerli Lambda İfadeleri

() -> true 0
a -> a.startsWith("test")
(String a) -> a.startsWith("test")
(a, b) -> a.startsWith("test")
(String a, String b) -> a.startsWith("test")

Geçersiz Lambda İfadeleri

a, b -> a.startsWith("test") Missing parentheses
a -> { a.startsWith("test"); } Missing return
 a -> { return a.startsWith("test") } Missing semicolon

Functional Interfaces

Lambda'lar, yalnızca bir soyut methodu olan interfaceler ile çalışır. Bunlara functional interface denir.

Single Abstract Method (SAM) kuralı olarak da bilinir.

Java, functional interfacelerin tamamında olmasa da bazılarında @FunctionalInterface annotation'ı sağlar.

Ancak, annotation'ı görmemeniz, bunun fonksiyonel bir interface olmadığı anlamına gelmez. Tam olarak bir soyut methoda sahip olmanın, onu annotation ile değil , fonksiyonel bir interface yapan şey olduğunu unutmayın.

Predicate

Geçilen argüman ile ilgili bir durumu test eder.

public interface Predicate<T> {
   boolean test(T t); 
}

Aslında önceki örnekte oluşturduğumuz functional interfacesimizden çağırdığımız metod yerine burada bu işlemi java'nın bizim için sağladı Predicate ile yapmış olduk.

public static void main(String[] args) {

    List<Person> personList = new ArrayList<>();
    personList.add(new Person("Umut" , true , false));

  printPerson(personList , p -> p.isCanRun());
}

private static void printPerson(List<Person> personList , Predicate<Person> personPredicate){
        for (Person person : personList){
            if (personPredicate.test(person))
                System.out.println("Person : " + person + " ");
        }
        System.out.println();
}

Consumer

Setter metodu gibi çalışır, geçilen argümanı alıp işler ama geriye bir şey döndürmez.

void accept(T t)
Consumer<String> consumer = x -> System.out.println(x);
    print(consumer, "Selam Canım :)");


private static void print(Consumer<String> consumer, String value) {
        consumer.accept(value);
      // accept() methodu çağrıldığında, lambda  çalışır ve değeri yazdırır.
}

Supplier

Getter ya da factory metodu gibi çalışır, bir değer üretip geri döndürür.

T get()
public static void main(String[] args) {


Supplier<Integer> number = () -> 32;
Supplier<Integer> random = () -> new Random().nextInt();

System.out.println(returnNumber(number));
}

private static int returnNumber(Supplier<Integer> supplier) {
        return supplier.get();
}

Comparator

Birden çok kıyaslama söz konusuysa ve eğer sıralanacak nesneler Comparable interfacesini kullanmıyorsa bu durumlarda java.util.Comparator interfacesi ,nesnelerin sıralamasında kullanılabilir.

Comparator Lambadaların javaya eklenmesinden önce mevcuttu. Bu nedenle java.util paketindedir.

Foknsiyonel bir interfacedir dolayısıyla üzerindeki, iki tane, kıyaslanacak nesneleri argüman olarak alan tek bir soyut metot vardır.

int compare(T o1, T o2)

natural sort order düzenini kullanır.İlk sayı daha büyükse, pozitif bir sayı döndürür.

Comparator 4-1'İ çıkarır ve 3 döndürür. Bu pozitif bir sayıdır, yani ilk sayı daha büyüktür ve artan düzende sıralama yapıyoruz.

Comparator<Integer> ints = (i1, i2) -> i1 - i2; 
System.out.println("comparator ints : " + ints.compare(4,1));

// 3

Bu karşılaştırıcıların ikisi de aslında aynı şeyi yapar: azalan düzende sıralama.

İlk örnekte, compareTo() çağrısı "geriye doğru"dur ve onu azalan yapar.

İkinci örnekte, çağrı varsayılan sırayı kullanır; ancak sonuca negatif bir işaret uygular ve bu da onu tersine çevirir.

Comparator<String> strings = (s1, s2) -> s2.compareTo(s1);
Comparator<String> moreStrings = (s1, s2) -> s1.compareTo(s2);

Lambda'larda Değişkenler ile Çalışmak

Değişkenler lambdalara göre üç yerde görünebilir: parametre listesi, lambda gövdesi içinde bildirilen local değişkenler ve lambda gövdesinden başvurulan değişkenlerdir.

Aşağıdaki lambda parametrelerinin türü String'dir. Bu durumda, lambda, String alan bir Predicate'e atanır.

Predicate<String> p = x -> true;
Predicate<String> p1 = (var x) -> true;
Predicate<String> p2 = (String x) -> true;

Lamb'da Body'lerde Local Variable Kullanımı

Bir lambda gövdesinin tek bir ifade olması en yaygın olanıdır, ancak bir blok tanımlayabiliriz.Bu blok, yerel değişken bildirimleri dahil olmak üzere normal bir Java bloğunda geçerli olan her şeye sahip olabilir.

Aşağıdaki örnekte c local variable'dır.

(a, b) -> { int c = 0; return 5;}

Bir scope'da daha önce bildirimiş aynı ada sahip değişkenkenle tekrar değişken tanımlanamaz.

a değişkeni tekrar ediyor.

(a, b) -> { int a = 0; return 5;} // DOES NOT COMPILE

Diğer bir örnekte method parametresi olarak tanımlanan a değişkeni tekrar kullanılamaz.

b değişkenide tekrar tanımlanarak syntax error hatasına neden olur.

public void variables(int a) {
        int b = 1;
       Predicate<Integer> p1 = a -> {  
       int b = 0; 
       int c = 0;
           return b == c; }
        } // burada ; olması gerekiyor !

}
Doğru Kullanımı
public void variables1(int a) {
    int b = 1;
    Predicate<Integer> p1 = d -> {
      int g = 0;
      int c = 0;
      return b == c; };
}

Lambda Body'sinde Referans Değişkeni Kullanımı

Metot parametreleri ve local variables, eğer etkin bir şekilde final iseler,referans alınmasına izin verilir.

Effectively final bir değişkenin değerinin, açıkça final olarak işaretlenip işaretlenmediğine bakılmaksızın, bildirildikten sonra değişmediği anlamına gelir.

Bir değişkenin effectively final olup olmadığından emin değilseniz, final anahtar kelimesini ekleyin. Kod hala derlenecekse, değişken effectively final'dır.

public void caw (final String name) {
        final String volume = "loudly";
        //name = "Caty";
        color = "black";

        Consumer<String> consumer = s -> System.out.println(name + "says" + volume + " that se is" + color);
}

Bu, lambda'nın belirli koşullar altında bir instance variable'a, method parametresine veya yerel değişkenler'e erişebileceğini gösterir.

Instance variables (ve sınıf değişkenlerine) her zaman izin verilir.

Metot parametreleri ve yerel değişkenler, etkin bir şekilde final oldukları takdirde referans alınmasına izin verilir.

Final olmayan bir değişkene değer atamak sorun değil.Ancak, lambda onu kullanmaya çalıştığında, bir sorunumuz var.

Değişken artık effectively final değildir, nedenle lambda'nın değişkeni kullanmasına izin verilmez. name değişkeni final olmadığı içim lambdanın değişkeni kullanmasına izin verilmez.

public void caw(String name) {
        String volume = "loudly";

        //name = "Caty"; // burada ayarlandığı zaman effectively final değildir.lambda body'de hata verecektir.
        color = "black"; // class variable

    Consumer<String> consumer = s -> System.out.println(name + "says" + volume + " that se is" + color);
}

Lambda APIs

removeIf()

List ve Set, Predicate alan bir removeIf() methodu kullanılabilir.

List<String> lions = new ArrayList<>();
lions.add("old lion");
lions.add("young lion");
lions.add("child lion");

lions.removeIf( l -> l.charAt(0) != 'c'); // c harfi ile başlamayanları sil.

// [child lion]

removeIf() metodu Set ile'de aynı şekilde çalışır.

sort()

Listeyi alfabetik olarak sıralıyoruz.

Set veya Map üzerinde herhangi bir sort() methodu yoktur. Bu türlerin hiçbirinde indeksleme yoktur, bu yüzden onları sıralamak mantıklı olmaz.

lions.sort((l1,l2) -> l1.compareTo(l2));

forEach()

forEach()'i bir Set veya Map ile kullanabiliriz.Set Liste ile aynı şekilde çalışır.

lions.forEach(l -> System.out.println(l));
Set<String> lions1  = Set.of("old lion", "child lion", "young lion");
lions.forEach(l -> System.out.println(l));

Map Kullanımı

Map<String, Integer> lions3 = new HashMap<>();
lions3.put("child lion" , 1);
lions3.put("old lion" ,7);
lions3.put("young lion" , 4);

lions3.keySet().forEach(l -> System.out.println("keySet Lion : " + l));
lions3.values().forEach(l -> System.out.println("values Lion : " + l));

keySet Lion : young lion
keySet Lion : old lion
keySet Lion : child lion

values Lion : 4
values Lion : 7
values Lion : 1

lions 3 {young lion=4, old lion=7, child lion=1}

BiConsumer

İki argümanlı setter metodu gibi çalışır.

Map sınıfındaki forEach methodu bir BiConsumer alır.

Map ile key , value geçilerek de kullanılabilir.

Map<String, Integer> lions4 = new HashMap<>();
lions4.put("child lion" , 1);
lions4.put("old lion" ,7);
lions4.put("young lion" , 4);
lions4.put("lioness" , 5);

lions4.forEach((k,v) -> System.out.println("Lions " +  k + " " + v));