Lambda İfadeleri
Lambda İfadeleri (Lambda Expressions) fonksiyonel programlama tekniğinin uygulanabilmesi için gereken bir araçtır. Fonksiyonel programlama genel olarak fonksiyonlar yazarak programalama yöntemidir. Lambda ifadeleri Java 8 ile birlikte dile eklenmiştir. Dile eklenmesiyle ileride ele alınacak olan Stream gibi konularda da oldukça kolay programlama yapılabilmesini sağlar. Genel olarak anonim sınıflara benzetilebilir. Şüphesiz kendine özgü bir çok özellik de barıdırmaktadır. Lambda ifadeleri bir çok programlama dilinde kullanılmaktadır. Java'da lambda ifadeleri 8(sekiz) farklı biçimde kullanılabilir:
( ) → ifade
( ) → {….}
<parametre ismi> → ifade
<parametre ismi> → {….}
(<parametre listesi>) → ifade
(<parametre listesi>) → {….}
(<tür parametre, tur parametre, ..>) → ifade
(<tür parametre, tur parametre, ..>) → {….}
Bir lambda ifadesi içerisinde yalnızca bir tane abstract metot olan interface referanslarına atanabilir. Java 8 ile birlikte bu tarz arayüzlere fonksiyonel arayüzler (functional interfaces) denilmektedir. Aslında lambda ifadesi ile fonksiyonel arayüzün tek olan abstract metodu override edilmiş olur. Böylelikle arayüz referansı kullanılarak çağırma yapıldığında lambda ifadesi ile bildirilmiş olan metot çağrılır.
Örneğin:
package csd;
public class App {
public static void main(String [] args)
{
IX ix = val -> System.out.println(val);
ix.foo(20);
ix = a -> System.out.println(a * a);
ix.foo(30);
}
}
interface IX {
void foo(int val);
}
Lambda ifadelerinde → atomunun sol tarafında atanacak arayüz referansına ilişkin abstract metodun parametrik yapısına uygun bir ifade yazılır. Yani metodun kaç tane parametresi varsa o kadar değişken ismi yazılır. Eğer yerine geçecek metodun parametresi yoksa bu durumda 1. ve 2. biçimlerdeki gibi kullanılır. Örneğin:
package csd;
public class App {
public static void main(String [] args)
{
IX ix = () -> System.out.println("foo");
ix.foo();
}
}
interface IX {
void foo();
}
Burada foo()
metodunun yerine geçen bir metot lambda ifadesi ile bildirilmiştir. Birden fazla parametreye sahip olan metotlar için parametre listesi parantezler içerisinde belirtilmelidir. Örneğin:
public class App {
public static void main(String [] args)
{
IOperations op = (a, b) -> a + b;
System.out.println(op.op(10, 20));
op = (a, b) -> a * b;
System.out.println(op.op(10, 20));
}
}
interface IOperations {
int op(int a, int b);
}
Burada iki parametreli bir metot için Lambda ifadeleri yazılmıştır. Lambda ifadeleri yazılırken parametrelerin türleri belirtilmeyebilir. Bu durumda derleyici arayüzün abstract metoduna bakarak türleri tespit eder (type inference). BU aşamadan sonra Lambda ifadesindeki → dan sonra bu parametreler tespit edilmiş türler olarak kullanılmalıdır. O türlere uymayan kullanım error oluşturur. Lambda ifadelerinin → sonraki küme parantezsiz kullanımlarında tek bir ifade yaılmalıdır. Birden fazla ifade yazılması geçersizdir. Örneğin:
public class App {
public static void main(String [] args)
{
IOperations op = (a, b) -> System.out.println(a + b);a + b; // error:
System.out.println(op.op(10, 20));
}
}
interface IOperations {
int op(int a, int b);
}
Lambda ifadelerinin bloksuz kullanımlarında eğer yerine geçecek metodun geri dönüş değeri yazılan ifadenin ürettiği değer geri dönüş olarak kabul edilir. Programcının ayrıca return anahtar sözcüğünü yazması geçersizdir. Şüphesiz bu durumda programcının geri dönüş değerine uygun bir ifade yazması gerekir. Programcı geri dönüş değeri olmayacak ya da o dönüş değerine göre geçersiz bir ifade yazarsa error oluşur. Örneğin:
public class App {
public static void main(String [] args)
{
IOperations op = (a, b) -> System.out.println(a + b); //error:
System.out.println(op.op(10, 20));
}
}
interface IOperations {
int op(int a, int b);
}
Örneğin:
public class App {
public static void main(String [] args)
{
IOperations op = (a, b) -> (int)Math.pow(a, b);
System.out.println(op.op(2, 3));
}
}
interface IOperations {
int op(int a, int b);
}
Yukarıdaki kodun anonim sınıf kullanılarak yapılışı:
public class App {
public static void main(String [] args)
{
IOperations op = new IOperations() {
@Override
public int op(int a, int b)
{
return (int)Math.pow(a, b);
}
};
System.out.println(op.op(2, 3));
}
}
interface IOperations {
int op(int a, int b);
}
Her lambda ifadesi birebir aynı bile olsa farklı ifadelerdir. Onlardan elde edilen referanslar da farklıdır. Örneğin:
public class App {
public static void main(String [] args)
{
IOperations op1 = (a, b) -> a * b;
IOperations op2 = (a, b) -> a * b;
System.out.println(op1 == op2);
}
}
interface IOperations {
int op(int a, int b);
}
Lambda ifadesinin küme parantezleri ile kullanımı artık normal bir metot gövdesi yazmakla aynı anlamdadır. Örneğin yerine geçecek metodun bir geri dönüş değeri varsa bu durumda akışın her noktasında return edilmelidir:
public class App {
public static void main(String [] args)
{
IOperations op = (a, b) -> {
System.out.println(a * b);
return a * b;
};
System.out.println(op.op(10, 20));
}
}
interface IOperations {
int op(int a, int b);
}
Örneğin aşağıdaki ifade geçersizdir:
public class App {
public static void main(String [] args)
{
IX ix = (a, b) -> a + b; //error
}
}
interface IX {
void foo(int a, int b);
}
Buradaki Lambda ifadesinde geri dönüş olmayan bir metot geri dönüş değeri varmış yazılmıştır. Dolayısıyla error oluşur.
Sınıf Çalışması: Comparator
arayüzünü kullanarak Integer
türden elemanlar tutan bir liste tarzı collection'ı büyükten küçüğe sıralayınız. Burada Comparator arayüzü için Lambda
ifadesi kullanınız.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class App {
public static void main(String [] args)
{
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; ++i)
list.add(i + 1);
Collections.sort(list, (a, b) -> b - a);
System.out.println(list);
}
}
Derleme zamanında lambda ifadesindeki türün belirlenmesi o türe ilişkin metotları da çağırmamıza olanak sağlar. Örneğin:
public class App {
public static void main(String [] args)
{
IX ix = s -> s.toUpperCase();
System.out.println(ix.foo("ankara"));
}
}
interface IX {
String foo(String str);
}
Burada lambda ifadesindeki parametrenin türünün String olacağı yerine geçeceği metottan tespit edilebilmektedir.
Sınıf Çalışması: Klavyeden exit girilene kadar alınan isimleri, liste tarzı bir collection'a ekleyiniz. Bu collection içerisinde sözlük sırasına sondan başa doğru sıralayan programı yazınız.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import csd.utils.Keyboard;
public class App {
public static void main(String [] args)
{
Keyboard kb = new Keyboard();
System.out.println("İsimleri girmeye başlayınız");
List<String> names = new ArrayList<>();
for(;;) {
String name = kb.getLine();
if (name.equals("exit"))
break;
names.add(name);
}
Collections.sort(names, (s1, s2) -> s2.compareTo(s1));
System.out.println(names);
}
}
Sınıf Çalışması: Aşağıdaki Product sınıfını kullanarak bir liste tarzı collection oluşturunuz. Bu collection içerisinde son kullanma tarihi en geç tarihte olan ürünü bulan programı yazınız.
class Product {
//private fields
private int m_id;
private String m_name;
private LocalDate m_expiryDate;
//Constructors
public Product(int id, String name, int day, int mon, int year)
{
this.setId(id);
this.setName(name);
this.setExpiryDate(day, mon, year);
}
//getters
public int getId() { return m_id; }
public String getName() { return m_name; }
public LocalDate getExpiryDate() { return m_expiryDate; }
//Setters
public void setId(int id)
{
m_id = id;
}
public void setName(String name)
{
if (name == null || name.equals(""))
throw new IllegalArgumentException("Argument error in name");
m_name = name;
}
public void setExpiryDate(int day, int mon, int year)
{
m_expiryDate = LocalDate.of(year, mon, day);
}
//Override methods
@Override
public String toString()
{
return String.format("[%d]-%s-[%s.%d.%d]",
m_id, m_name, m_expiryDate.getDayOfMonth(), m_expiryDate.getMonthValue(), m_expiryDate.getYear());
}
}
package csd;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class App {
public static void main(String [] args)
{
List<Product> products = new ArrayList<>();
products.add(new Product(1, "Yoğurt", 12, 2, 2016));
products.add(new Product(2, "Peynir", 31, 1, 2016));
products.add(new Product(3, "Konserve", 31, 12, 2017));
products.add(new Product(4, "Balık", 10, 2, 2016));
Product p = Collections.max(products, (p1, p2) -> p1.getExpiryDate().compareTo(p2.getExpiryDate()));
System.out.println(p);
}
}
Lambda İfadelerinin Değişkenleri Yakalaması
Lambda ifadesinde bir değişken isminin kullanılması kavramına yakalama (capture) denilmektedir. Lambda ifadesinden önce bildirilen yerel değişken ile lambda ifadesindeki paramtre değişkenleri aynı isimde olamaz.
public class App {
public static void main(String [] args)
{
int a;
a = 10;
IX ix = (a) -> Math.sqrt(a); //error:
System.out.println(ix.foo(2));
}
}
interface IX {
double foo(double a);
}
Lambda ifadesi içerisinde kullanılan parametre değişkenleri yalnızca o ifade içerisinde kullanılabilir:
public class App {
public static void main(String [] args)
{
IX ix = a -> Math.sqrt(a); //error:
a = 30; //error
System.out.println(ix.foo(2));
}
}
interface IX {
double foo(double a);
}
Bir lambda ifadesi içerisinde ifadeden önce bildirilen yerel değişkenler kullanılabilir:
public class App {
public static void main(String [] args)
{
int val;
val = 10;
IX ix = a -> Math.sqrt(a) * val;
System.out.println(ix.foo(2));
}
}
interface IX {
double foo(double a);
}
Bir yerel değişkene lambda ifadesi içerisinde atama yapılamaz. Örneğin:
public class App {
public static void main(String [] args)
{
int val;
val = 10;
IX ix = a -> {
val *= 2; //error
return Math.sqrt(a) * val;
};
System.out.println(ix.foo(2));
}
}
interface IX {
double foo(double a);
}
Bir yerel değişkene faaliyet alanı içerisinde birden fazla kez değer atanabilmesi için, o yerel değişkenin hiç bir lambda ifadesinde kullanılmaması gerekir:
public class App {
public static void main(String [] args)
{
int val;
val = 10;
IX ix = a -> {
return Math.sqrt(a) * val;
};
val = 20;
System.out.println(ix.foo(2));
}
}
interface IX {
double foo(double a);
}
Ya da tersine, bir yerel değişken lambda ifadesi içerisinde kullanılacaksa, faaliyet alanı içerisinde o değişken toplamda bir kez değer alabilir:
public class App {
public static void main(String [] args)
{
int val;
val = 10;
val = 20;
IX ix = a -> {
return Math.sqrt(a) * val;
};
System.out.println(ix.foo(2));
}
}
interface IX {
double foo(double a);
}
Yerel bir değişken faaliyet alanı içerisinde bir lambda ifadesinde kullanılmışsa final olarak bildirilmemiş olsa bile “final etkili” (effectively final) kabul edilir ve atama işlemine kapatılır. Bu duruma ingilizce “closure” da denilmektedir.
Anahtar Notlar: Aslında aynı durum Java 8 den itibaren anonim sınıflar için de geçerlidir. Java 8 öncesinde anonim sınıflar içerisinde yerel bir değişkenin yakalanması için final olarak bildirilmesi gerekir:
public class App {
public static void main(String [] args)
{
int val; //Java 8 öncesinde final bildirilmeliydi
val = 10;
IX ix = new IX() {
@Override
public double foo(double a)
{
return Math.sqrt(a) * val;
}
};
System.out.println(ix.foo(2));
}
}
interface IX {
double foo(double a);
}
Lambda ifadelerinde ayrıca, yazılmış oldukları metotların ait oldukları sınıfların veri elemanları da doğrudan kullanılabilir. Örneğin:
public class App {
public static void main(String [] args)
{
Sample s = new Sample(10);
System.out.println(s.bar(20));
s.setVal(3);
System.out.println(s.bar(20));
}
}
interface IX {
double foo(double a);
}
class Sample {
private int m_val;
public Sample(int val)
{
m_val = val;
}
public void setVal(int val)
{
m_val = val;
}
public double bar(double a)
{
IX ix = val -> Math.pow(val, 2) * m_val;
return ix.foo(a);
}
}
Lambda ifadeleri içerisinde kullanılan final olmayan sınıf veri elemanları kaptatılmaz. Yani, lambda ifadesi de dahil istenilen yerde değişiklik yapılabilir:
public class App {
public static void main(String [] args)
{
Sample s = new Sample(10);
System.out.println(s.bar(20));
System.out.println(s.getVal());
}
}
interface IX {
double foo(double a);
}
class Sample {
private int m_val;
public Sample(int val)
{
m_val = val;
}
public int getVal() {return m_val;}
public void setVal(int val)
{
m_val = val;
}
public double bar(double a)
{
IX ix = val -> {
m_val *= 2;
return Math.pow(val, 2) * m_val;
};
return ix.foo(a);
}
}