Java Lambda Expressions Nedir?
6 min read

Java Lambda Expressions Nedir?

Lambda Expressions, Java 8 ile gelen en yenilikçi ve kod yazmanızı kolaylaştıran özelliklerden biridir.
Java Lambda Expressions Nedir?
Photo by Clément Hélardot / Unsplash

Lambda expressions, Java SE 8'e dahil edilen Java'nın yeni ve önemli bir özelliğidir. Lambda expressions, işlevsel programlamayı kolaylaştırır ve geliştirmeyi çok basitleştirir. Koleksiyondan verilerin yinelenmesine, filtrelenmesine ve çıkarılmasına yardımcı olur.

Lambda expression, functional interface'in implementasyonunu sağlamak için kullanılır. Bizi birçok kod fazlalığından kurtarır. Lambda expression olması durumunda, implementasyonu sağlamak için methodu tekrar tanımlamamıza gerek yoktur.

Lambda expressions'lara giriş yapmadan önce ilk olarak functional interface'lerin ne olduğunu anlamamız gerekir.

Functional Interface Nedir?

Yalnızca bir abstract methodu olan interface'e fonksiyonel interface denir. Lambda expression da fonksiyonel interface implementasyonunu sağlar.

Örneğin, java.lang paketindeki Runnable arabirimi; functional interface'dir. Çünkü yalnızca bir yöntem, yani run() methodundan oluşturur.

import java.lang.FunctionalInterface;
@FunctionalInterface
public interface MyInterface{
    // the single abstract method
    double getValue();
}

Yukarıdaki örnekte, MyInterface arabiriminin yalnızca bir soyut getValue() yöntemi vardır. Bu nedenle, işlevsel bir arayüzdür.

Yukarıdaki örnekte, MyInterface arabiriminin yalnızca bir abstract getValue() yöntemi vardır. Bu nedenle, functional interface'dir.

Burada @FunctionalInterface annotation kullandık. Annotation, Java derleyicisini interface'in functional interface olduğunu belirtmede kullanılır.

Java 7'de, fonksiyonel interfaceler Single Abstract Methods veya SAM tipi olarak kabul edilmiştir. SAM'ler, Java 7'deki Anonymous Sınıflarla yaygın olarak uygulanır.

public class FunctionInterfaceTest {
    public static void main(String[] args) {

        // anonymous class
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("I just implemented the Runnable Functional Interface.");
            }
        }).start();
    }
}

Burada anonymous bir sınıfı bir metoda geçirebiliriz. Bu, Java 7'de daha az kodlu programların yazılmasına yardımcı olur. Ancak, sözdizimi hala zor ve çok fazla kod satırı gerekiyor.

Java 8, bir adım daha ileri giderek SAM'lerin gücünü genişletmiştir. Functional bir interface'in yalnızca bir yöntemi olduğunu bildiğimiz için, onu argüman olarak iletirken o yöntemin adını tanımlamaya gerek yoktur. Lambda ifadesi tam olarak bunu yapmamızı sağlar.

Lambda Expressions Giriş

Lambda expressions, esasen anonymous veya unnamed bir yöntemdir. Lambda expression'lar kendi başına yürütülmez. Bunun yerine, functional interface tarafından tanımlanan bir yöntemi implement etmek için kullanılır.

Java'da Lambda Expression Nasıl Tanımlanır?

Java'da lambda expression'ı şu şekilde tanımlayabiliriz.

(parameter list) -> {body}  
  1. Parameter list: Fonksiyonun parametrelerini burada tanımlıyoruz. Hiç parametre geçirmeden boşta olabilir.
  2. Arrow-token: Argüman listesi ile expression gövdesini birbirine bağlamak için kullanılır.
  3. Body: Expressionları ve statementları içerir.

Diyelim ki şöyle bir yöntemimiz var:

double getPiValue() {
    return 3.1415;
}

Bu yöntemi lambda ifadesini kullanarak şu şekilde yazabiliriz:

() -> 3.1415

Burada, methodun herhangi bir parametresi yoktur. Bu nedenle, operatörün sol tarafında boş bir parametre bulunur. Sağ taraf, lambda ifadesinin eylemini belirten lambda gövdesidir. Bu durumda 3.1415 değerini döndürür.

Lambda Body Çeşitleri

Java'da lambda body iki tiptir.

  • Tek bir expression'a sahip bir body

Bu tip lambda body, expression body olarak bilinir.

() -> System.out.println("Lambdas are great");
  • Bir kod bloğundan oluşan bir body

Bu tip lambda body, block body olarak bilinir. Blok gövdesi, lambda gövdesinin birden çok ifade içermesine izin verir. Bu ifadeler parantez içine alınır ve parantezlerden sonra noktalı virgül eklemeniz gerekir.

() -> {
    double pi = 3.1415;
    return pi;
};

Lambda Expression

Hadi şimdi Lambda expression kullanarak Pi değerini döndüren bir Java programı yazalım.

Daha önce belirtildiği gibi, bir lambda expression kendi başına yürütülmez. Bunun yerine, functional interface tarafından tanımlanan abstract yöntemin uygulamasını oluşturur.

Bu nedenle, önce bir functional interface tanımlamamız gerekiyor.

import java.lang.FunctionalInterface;

// this is functional interface
@FunctionalInterface
interface MyInterface{

    // abstract method
    double getPiValue();
}

public class Main {

    public static void main( String[] args ) {

    // declare a reference to MyInterface
    MyInterface ref;
    
    // lambda expression
    ref = () -> 3.1415;
    
    System.out.println("Value of Pi = " + ref.getPiValue());
    } 
}

Çıktısı:

Value of Pi = 3.1415

Yukarıdaki örnekte,

  • MyInterface adında functional interface oluşturduk. Bu interface getPiValue() adında abstract method içerir.
  • Main sınıfının içinde MyInterface'den bir referans tanımladık. Bir interface'in referansını declare edebileceğimizi ancak bir interface'den instance oluşturamayacağımızı unutmayın. Yani,
// hata verir
MyInterface ref = new myInterface();

// geçerli declare
MyInterface ref;
  • Daha sonra referansa bir lambda expression atadık.
ref = () -> 3.1415;
  • Son olarak, referans interface'ini kullanarak getPiValue() yöntemini çağırıyoruz.
System.out.println("Value of Pi = " + ref.getPiValue());

Parametrelerle Lambda Expressions Kullanımı

Şimdiye kadar herhangi bir parametre olmadan lambda expression oluşturduk. Ancak, methodlara benzer şekilde lambda expressionlar da parametrelere sahip olabilir. Örneğin;

(n) -> (n%2)==0

Burada parantez içindeki n değişkeni lambda expressiona iletilen bir parametredir. Lambda body parametreyi alır ve çift mi yoksa tek mi olduğunu kontrol eder.

Şimdi başka bir örneğe daha bakalım.

@FunctionalInterface
interface MyInterface {

    // abstract method
    String reverse(String n);
}

public class Main {

    public static void main( String[] args ) {

        // declare a reference to MyInterface
        // assign a lambda expression to the reference
        MyInterface ref = (str) -> {

            String result = "";
            for (int i = str.length()-1; i >= 0 ; i--)
                result += str.charAt(i);
            return result;
        };

        // call the method of the interface
        System.out.println("Lambda reversed = " + ref.reverse("Lambda"));
    }

}

Çıktısı:

Lambda reversed = adbmaL

Generic Functional Interface

Şimdiye kadar yalnızca bir tür değeri kabul eden functional interface kullandık. Örneğin;

@FunctionalInterface
interface MyInterface {
    String reverseString(String n);
}

Yukarıdaki functional interface yalnızca String kabul eder ve String döndürür. Ancak, herhangi bir veri tipinin kabul edilmesi için functional interface'i generic hale getirebiliriz. Generic konusuna hakim öncelikle Java Generics sayfasını ziyaret edebilirsiniz.

Java Generics (With Examples)
Java Generics allows us to create a single class/interface/method that can be used with different types of data. In this tutorial, we will learn about Java generics with the help of examples.

Şimdi bir örnek bakalım.

// GenericInterface.java
@FunctionalInterface
interface GenericInterface<T> {

    // generic method
    T func(T t);
}

// GenericLambda.java
public class Main {

    public static void main( String[] args ) {

        // declare a reference to GenericInterface
        // the GenericInterface operates on String data
        // assign a lambda expression to it
        GenericInterface<String> reverse = (str) -> {

            String result = "";
                for (int i = str.length()-1; i >= 0 ; i--)
            result += str.charAt(i);
            return result;
        };

        System.out.println("Lambda reversed = " + reverse.func("Lambda"));

        // declare another reference to GenericInterface
        // the GenericInterface operates on Integer data
        // assign a lambda expression to it
        GenericInterface<Integer> factorial = (n) -> {

            int result = 1;
                for (int i = 1; i <= n; i++)
            result = i * result;
            return result;
        };

        System.out.println("factorial of 5 = " + factorial.func(5));
    }
}

Çıktısı:

Lambda reversed = adbmaL
factorial of 5 = 120

Yukarıdaki örnekte func() adında generic bir method içeren GenericInterface isimli generic bir functional interface oluşturduk.

Burada, Main sınıfın içinde;

  • GenericInterface<String> reverse - interface'e bir referans oluşturur. Interface artık String tipi veri üzerinde çalışır.
  • GenericInterface<Integer> factorial - interface'e bir referans oluşturur. Interface, bu durumda, Integer tipi veri üzerinde çalışır.

Lambda Expression ve Stream API

Java geliştiricilerinin Lists gibi koleksiyonlarda search, filter, map, reduce veya manipulate gibi işlemleri gerçekleştirmesine olanak tanıyan yeni java.util.stream paketi JDK8'e eklenmiştir.

Örneğin, her dizenin ülke adı ve ülkenin yerinin bir kombinasyonu olduğu bir veri akışımız (bizim durumumuzda bir String List) var. Şimdi, bu veri akışını işleyebilir ve sadece Nepal'den gelen yerleri alabiliriz.

Bunun için Stream API ve Lambda expression'un birleşimi ile stream içerisinde toplu işlemler gerçekleştirebiliriz.

Hemen bir örnek görelim:

import java.util.ArrayList;
import java.util.List;

public class StreamMain {

    // create an object of list using ArrayList
    static List<String> places = new ArrayList<>();

    // preparing our data
    public static List getPlaces(){

        // add places and country to the list
        places.add("Nepal, Kathmandu");
        places.add("Nepal, Pokhara");
        places.add("India, Delhi");
        places.add("USA, New York");
        places.add("Africa, Nigeria");

        return places;
    }

    public static void main( String[] args ) {

        List<String> myPlaces = getPlaces();
        System.out.println("Places from Nepal:");
        
        // Filter places from Nepal
        myPlaces.stream()
                .filter((p) -> p.startsWith("Nepal"))
                .map((p) -> p.toUpperCase())
                .sorted()
                .forEach((p) -> System.out.println(p));
    }

}

Çıktısı:

Places from Nepal:
NEPAL, KATHMANDU
NEPAL, POKHARA

Yukarıdaki örnekte, expressiona dikkat edin.

myPlaces.stream()
        .filter((p) -> p.startsWith("Nepal"))
        .map((p) -> p.toUpperCase())
        .sorted()
        .forEach((p) -> System.out.println(p));

Burada, Stream API'nin filter(), map() ve forEach() gibi yöntemlerini kullanıyoruz. Bu yöntemler girdi olarak bir lambda expression alabilir.

Yukarıda öğrendiğimiz söz diziminden yola çıkarak kendi ifadelerimizi de tanımlayabiliriz. Bu, yukarıdaki örnekte gördüğümüz gibi kod satırlarını büyük ölçüde azaltmamızı sağlar.

Son örneğimizde Stream API konusunu bir sonraki dersimizde daha detaylı olarak inceleyeceğiz. Şimdilik iyi bloglar.