Sunday, March 18, 2018

15. Ekspresi Lambda



Selama evolusi Java, banyak fitur ditambahkan sejak rilis pertamanya. Ada dua yang perlu diberikan perhatian khusus, yang secara fundamental mengubah bagaimana kode Java dituliskan. Pertama adalah penambahan pemrograman generik, yang ditambahkan sejak JDK 5. Yang kedua adalah ekspresi lambda, yang merupakan pokok bahasan pada bab ini.

Ditambahkan sejak JDK 8, ekspresi lambda (dan semua fitur terkait) secara signifikan memperkuat pemrograman Java karena dua alasan utama. Pertama, adanya elemen-elemen sintaksis baru yang meningkatkan kekuatan bahasa Java. Kedua, penambahan ekspresi lambda mengakibatkan terciptanya kapabilitas-kapabilitas baru pada pustaka API.   Di antaranya adalah kemampuan untuk menangani pemrosesan parallel, khususnya yang berkaitan dengan operasi-operasi gaya for-each, dan API aliran baru, yang mendukung operasi-operasi pipeline pada data. Dengan adanya ekspresi lambda, hal ini menjadi katalis untuk fitur-fitur Java lainnya, termasuk metode default, yang dapat Anda pakai untuk mendefinisikan watak default bagi metode antarmuka, dan metode referensi.


Mengenalkan Ekspresi Lambda
Kunci dalam memahami implementasi Java atas ekspresi lambda adalah dua konstruksi. Konstruksi pertama adalah ekspresi lambda itu sendiri. Kedua adalah antarmuka fungsional. Anda akan mempelajarinya satu demi satu.

Ekspresi lambda, pada dasarnya, adalah sebuah metode tak-bernama atau anonym. Metode ini tidak bisa dieksekusi secara otonom. Ia, melainkan, dipakai untuk mengimplementasikan sebuah metode yang didefinisikan oleh sebuah antarmuka fungsional. Jadi, ekspresi lambda menghasilkan sebuah bentuk kelas anonym. 

Antarmuka fungsional adalah sebuah antarmuka yang memuat satu dan hanya satu metode abstrak. Normalnya, metode ini ini menetapkan tujuan dari antarmuka. Jadi, antarmuka fungsional umumnya merepresentasikan satu aksi atau tugas. Misalnya, antarmuka standar Runnable adalah sebuah antarmuka fungsional karena ia mendefinisikan hanya satu metode: run(). Oleh karena itu, run() mendefinisikan aksi atau tujuan dari Runnable. Jadi, antarmuka fungsional mendefinisikan tipe data target dari sebuah ekspresi lambda. Kunci penting di sini: ekspresi lambda hanya dapat dipakai pada sebuah konteks dimana tipe data targetnya ditetapkan. 


Dasar-Dasar Ekspresi Lambda
Ekspresi lambda menghasilkan elemen sintaksis baru dan operator baru ke dalam bahasa Java. Operator baru ini dikenal pula sebagai operator lambda atau operator anak-panah, ->. Ekspresi lambda dibagi menjadi dua bagian. Sisi kiri menetapkan semua parameter yang diperlukan oleh ekspresi lambda. (Jika tidak ada parameter yang diperlukan, daftar parameter kosong yang dipakai). Di sisi kanan adalah tubuh lambda, yang menetapkan aksi atau tujuan dari ekspresi lambda. 

Java mendefinisikan dua jenis tubuh lambda. Pertama memuat ekspresi tunggal, dan jenis lain memuat blok kode. Anda akan mulai belajar dari lamda dengan ekspresi tunggal. Lambda dengan tubuh blok kode akan didiskusikan nanti pada bab ini.

Pada titik ini, akan sangat membantu bila melihat beberapa contoh ekspresi lambda sebelum melanjutkan. Anda akan mempelajari jenis sederhana dari ekspresi lambda yang bisa Anda tuliskan. Ekspresi ini akan menghasilkan sebuah nilai konstan:


() -> 123.45

Ekspresi lambda ini tidak mengambil parameter apapun, jadi daftar parameter dibiarkan kosong. Ekspresi ini menghasilkan nilai konstan 123.45. Oleh karena itu, ini sama dengan metode berikut:

double metodeKu() { return 123.45;}

Tentu, metode yang didefinisikan oleh ekspresi lambda tidak memiliki nama.

Sedikit yang lebih menarik tentang ekspresi lambda adalah berikut:

() -> Math.random() * 100

Ekspresi lambda ini memperoleh sebuah nilai semi-acak dari metode Math.random(), mengalikannya dengan 100, dan menjadikan hasilnya sebagai nilai balik. Ekspresi ini juga tidak memerlukan parameter apapun.

Ketika sebuah ekspresi lambda memerlukan parameter, parameter itu dituliskan pada daftar parameter di sisi kiri dari operator lambda. Berikut adalah salah satu contohnya:

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

Ekspresi lambda ini menghasilkan true jika nilai dari parameter n adalah bilangan genap. Meskipun Anda dimungkinkan untuk secara eksplisit menetapkan tipe data parameter, pada kasus ini n, tetapi Anda seringkali tidak memerlukannya karena pada banyak kasus hal itu bisa disimpulkan sendiri oleh kompilator. Seperti metode yang memiliki nama, ekspresi lambda dapat memiliki sebanyak mungkin parameter yang diperlukan.


Antarmuka Fungsional
Seperti disebutkan sebelumnya, antarmuka fungsional adalah sebuah antarmuka yang hanya mendefinisikan satu metode abstrak.

Jika Anda telah memiliki pengalaman pemrograman sebelumnya, Anda mungkin akan berpikir bahwa semua metode antarmuka adalah abstrak secara implisit. Meskipun ini berlaku untuk sebelum JDK 8, situasinya telah berubah sekarang. Mula dari JDK 8, adalah dimungkinkan untuk menetapkan watak default untuk sebuah metode pada antarmuka. Ini dinamakan dengan metode default. Hari ini, metode antarmuka adalah abstrak hanya jia ia tidak menetapkan implementasi defaultnya. Karena metode antarmuka tak-default akan dipandang abstrak secara implisit, maka Anda tidak perlu menggunakan pemodifikasi abstract (meskipun Anda bisa menetapkannya, jika Anda suka).

Berikut adalah sebuah contoh antarmuka fungsional:

interface NilaiKu {
   double getNilai();
}

Pada kasus ini, metode getNilai() secara implisit adalah abstrak, dan ia merupakan satu-satunya metode yang didefinisikan oleh antarmuka NilaiKu. Jadi, NilaiKu adalah sebuah antarmuka fungsional, dan fungsinya didefinisikan oleh getNilai().

Seperti disebutkan sebelumnya, ekspresi lambda tidak bisa berdiri sendiri. Ia membentuk implementasi dari metode abstrak yang didefiniskan oleh antarmuka fungsional yang menetapkan tipe data targetnya. Hasilnya, ekspresi lambda hanya dapat dipakai pada konteks dimana tipe data target didefinisikan. Salah satu konteks ini diciptakan ketika ekspresi lambda ditugaskan kepada referensi antarmuka fungsional. Konteks tipe data target lain mencakup inisialisasi variabel, statemen return, dan argumen metode, dan lainnya.

Anda akan melihat sebuah contoh yang menunjukkan bagaimana sebuah ekspresi lambda dapat dipakai pada konteks penugasan. Pertama, sebuah referensi ke antarmuka fungsional NilaiKu dideklarasikan:

// Menciptakan sebuah referensi ke objek NilaiKu
NilaiKu nilKu;

Selanjutnya, sebuah ekspresi lambda ditugaskan kepada referensi antarmuka tersebut:

// Menggunakan sebuah lambda pada konteks penugasan
nilKu = () -> 123.45;

Ketika sebuah ekspresi lambda ditemukan pada konteks tipe data target, objek dari kelas itu secara otomatis diciptakan yang mengimplementasikan antarmuka fungsional, dengan ekspresi lambda mendefinisikan watak dari metode abstrak yang dideklarasikan oleh antarmuka fungsional. Ketika metode itu dipanggil melalui target, ekspresi lambda akan dieksekusi. Jadi, ekspresi lambda memberikan cara baru dalam mentransformasi sebuah segmen kode kepada sebuah objek.

Pada contoh tersebut, ekspresi lambda menjadi implementasi untuk metode getNilai(). Hasilnya, statemen berikut menghasilkan nilai 123.45:

// Memanggila getNilai(), yang diimplementasikan oleh ekspresi lambda
System.out.println(nilKu.getNilai());

Karena ekspresi lambda yang ditugaskan kepada nilKu menghasilkan nilai 123.45, nilai itulah yang diperoleh ketika getNilai() dipanggil.

Agar ekspresi lambda dapat dipakai pada konteks tipe data target, tipe data dari metode abstrak dan tipe data dari ekspresi lambda harus kompatibel. Misalnya, jika metode abstrak menetapkan dua parameter int, maka ekspresi lambda harus pula menetapkan dua parameter dengan keduanya secara eksplisit int atau secara implisit dapat disimpulkan int oleh kompilator. Umumnya, tipe data dan banyak parameter dari ekspresi lambda harus kompatibel dengan parameter-parameter metode; tipe nilai baliknya juga harus kompatibel; dan semua eksepsi yang dilempar oleh ekspresi lambda juga harus dapat diterima oleh metode.


Beberapa Contoh Ekspresi Lambda
Dengan konsep yang telah dijelaskan, Anda akan melihat beberapa contoh sederhana yang mengilustrasikan konsep dasar dari ekspresi lambda. Berikut adalah contoh pertamanya:

// Mendemonstrasikan sebuah ekspresi lambda sederhana.

// Antarmuka fungsional.
interface NilaiKu {
   double getNilai();
}

public class DemoLambda {
   public static void main(String args[])
   {
      NilaiKu nilKu; // mendeklarasikan sebuah referensi antarmuka
 
      /* Di sini, ekspresi lambda hanya merupakan sebuah ekspresi konstan.
       * Ketika ditugaskan kepada nilKu, sebuah objek kelas akan 
       * diciptakan dimana di dalamnya ekspresi lambda
       * mengimplementasikan metode getNilai() pada antarmuka NilaiKu.
       */
      nilKu = () -> 123.45;
      
 // Memanggil getNilai(), yang disediakan oleh ekspresi lambda
 System.out.println("Nilai tetap: " + nilKu.getNilai());
 
 // Di sini, ekspresi lambda lebih kompleks.
 nilKu = () -> Math.random() * 100;
 
 // Keduanya memanggil ekspresi lambda tersebut di atas.
 System.out.println("Nilai acak: " + nilKu.getNilai());
 System.out.println("Nilai acak lain: " + nilKu.getNilai());
 
 /* Ekspresi lambda harus kompatibel dengan metode
  * yang didefinisikan oleh antarmuka fungsional, Jadi, ini tidak valid:
  * nilKu = () -> "123.45"; // Error!
  */
 }
}

Jika dijalankan, program ini menghasilkan contoh keluaran berikut:

Nilai tetap: 123.45
Nilai acak: 79.77216071396708
Nilai acak lain: 63.69232623990111

Seperti disebutkan sebelumnya, ekspresi lambda harus kompatibel dengan metode abstrak yang diimplementasikan. Karena itu, jika baris komentar di baris akhir program dihapus, maka program tidak akan bisa dikompilasi karena nilai dari tipe data String tidak kompatibel dengan double, yang merupakan tipe data nilai balik dari getNilai().

Program selanjutnya menunjukkan penggunaan satu parameter pada ekspresi lambda:

// Mendemonstrasikan sebuah ekspresi lambda yang mengambil satu parameter

// Antarmuka fungsional lain.
interface UjiNumerik {
 boolean uji(int n);
}

public class DemoLambda2 {
   public static void main(String args[]) {
      // Ekspresi lambda yang menguji apakah sebuah nilai genap atau tidak.
    UjiNumerik apaGenap = (n) -> (n % 2)==0;
    
    if(apaGenap.uji(10)) System.out.println("10 adalah nilai genap");
    if(!apaGenap.uji(9)) System.out.println("9 bukan nilai genap");
    
    // Sekarang, menggunakan ekspresi lambda yang menguji apakah sebuah
    // nilai tak-negatif.
    UjiNumerik apaTakNegatif = (n) -> n >= 0;
    
    if(apaTakNegatif.uji(1)) System.out.println("1 adalah nilai tak-negatif");
    if(!apaTakNegatif.uji(-1)) System.out.println("-1 adalah nilai negatif");
 }
}

Jika dijalankan, program ini menghasilkan keluaran berikut:

10 adalah nilai genap
9 bukan nilai genap
1 adalah nilai tak-negatif
-1 adalah nilai negatif

Program ini menunjukkan fakta kunci tentang ekspresi lambda. Perhatikan khusus pada ekspresi lambda yang melakukan uji kegenapan:

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

Perhatikan bahwa tipe data dari n tidak ditetapkan. Pada kasus ini, tipe datanya disimpulkan sendiri oleh kompilator Java. Jadi, tipe datanya disimpulkan kompilator dari tipe data parameter metode uji() yang didefinisikan oleh antarmuka UjiNumerik, yaitu int. Anda juga bisa menetapkan secara eksplisit tipe data parameter pada ekspresi lambda. Misalnya, hal ini juga valid untuk dilakukan:

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

Di sini, n secara eksplisit ditetapkan int. Umumnya Anda tidak perlu melakukannya, tetapi pada beberapa keadaan Anda harus melakukannya.

Program ini mendemonstrasikan hal penting lain tentang ekspresi lambda: referensi antarmuka fungsional dapat dipakai untuk mengeksekusi sembarang ekspresi lambda yang kompatibel dengannya. Perhatikan bahwa program mendefinisikan dua ekspresi lambda yang kompatibel dengan metode uji() pada antarmuka fungsional UjiNumerik. Pertama, ekspresi lambda yang dinamakan dengan apaGenap, menentukan apakah sebuah nilai genap atau tidak. Kedua, ekspresi lambda dengan nama apaTakNegatif, memeriksa apakah sebuah nilai tak-negatif atau tidak. Pada kedua kasus ini, nilai dari parameter n akan diuji. Karena setiap ekspresi lambda harus kompatibel dengan uji(), masing-masing dieksekusi melalui referensi UjiNumerik.

Satu hal penting lain adalah ketika sebuah ekspresi lambda hanya memiliki satu parameter, Anda tidak harus mengapit parameter tersebut dengan kurung. Sebagai contoh, ini juga merupakan cara valid untuk menuliskan ekspresi lambda dalam program tersebut:

n -> (n % 2) == 0

Tetapi, agar konsisten, buku ini akan mengapit semua parameter ekspresi lambda dengan kurung, meskipun jika hanya memuat satu parameter.

Program selanjutnya mendemonstrasikan sebua ekspresi lambda yang mengambil dua parameter. Pada kasus ini, ekspresi lambda menguji jika salah satu nilai merupakan faktor dari nilai lainnya.

// Mendemonstrasikan sebuah ekspresi lambda dengan dua parameter.
interface UjiNumerik2 {
   boolean uji(int n, int d);
}

public class DemoLamda3 {
   public static void main(String args[])
   {
      // Ekspresi lambda ini menentukan apakah satu nilai
   // merupakan faktor dari nilai lain.
      UjiNumerik2 apaFaktor = (n, d) -> (n % d) == 0;
 
      if(apaFaktor.uji(10, 2))
         System.out.println("2 sebuah merupakan faktor dari 10");
 
      if(!apaFaktor.uji(10, 3))
         System.out.println("3 bukan faktor dari 10");
 }
}

Jika dijalankan, program ini menghasilkan keluaran berikut:

2 sebuah merupakan faktor dari 10
3 bukan faktor dari 10

Pada program ini, antarmuka fungsional UjiNumerik2 mendefinisikan metode uji():

boolean uji(int n, int d);

Pada versi ini, uji() mengambil dua parameter. Jadi, agar ekspresi lambda bisa kompatibel dengan uji(), ekspresi tersebut harus juga memiliki dua parameter. Perhatikan bagaimana keduanya digunakan:

(n, d) -> (n % d) == 0

Kedua parameter, n dan d, ditetapkan pada daftar parameter, yang dipisahkan dengan koma.

Berikut adalah hal penting tentang parameter jamak pada sebuah ekspresi lambda: Jika Anda perlu secara eksplisit mendefinisikan tipe data dari sebuah parameter, maka semua parameter juga harus dideklarasikan tipe datanya secara eksplisit. Misalnya:

(int n, int d) -> (n % d) == 0

Tetapi, ini adalah sebuah kesalahan:

(int n, d) -> (n % d) == 0


Selanjutnya  >>>


No comments: