Sunday, March 18, 2018

15. Ekspresi Lambda Bagian 2



Ekspresi Lambda Blok
Tubuh dari ekspresi lambda yang ditunjukkan pada contoh-contoh sebelumnya hanya memiliki satu ekspresi. Pada tubuh ekspresi lambda semacam ini, kode di sebelah kanan dari operator lambda harus berupa satu ekspresi. Tentu, banyak situasi dimana Anda memerlukan lebih dari satu ekspresi. Untuk menangani kasus semacam itu, Java mendukung jenis kedua dari ekspresi lambda yang di dalamnya kode di sebelah kanan operator lambda memuat blok kode yang dapat memiliki lebih dari satu statemen. Jenis tubuh lambda semacam ini dikenal pula dengan tubuh blok. Ekspresi lambda yang memiliki tubuh blok seringkali dinamakan pula dengan lambda blok. 

Ekspresi lambda blok memperluas jenis-jenis operasi yang dapat ditangani karena tubuh lambda dapat memuat banyak statemen. Misalnya, blok tersebut dapat Anda pakai untuk mendeklarasikan variabel, menggunakan loop, memanfaatkan if dan switch, menciptakan blok bersarang, dan seterusnya. Lambda blok gampang diciptakan. Anda hanya perlu mengapit tubuh blok dengan kurung kurawal.

Disamping dibolehkannya banyak statemen, lambda blok bisa dipakai seperti ekspresi lambda dengan satu statemen yang telah dijelaskan. Satu-satunya perbedaan adalah bahwa Anda perlu secara eksplisit menggunakan sebuah statemen return untuk menghasilkan nilai balik. Ini diperlukan karena tubuh lambda blok tidak merepresentasikan ekspresi tunggal.

Berikut adalah sebuah contoh yang menggunakan lambda blok untuk menghitung dan menghasilkan nilai balik berupa faktorial dari sebuah nilai int:

// Sebuah lambda blok yang menghitung faktorial dari nilai int.
interface FungsiNumerik {
   int fungsi(int n);
}

public class DemoLambdaBlok {
   public static void main(String args[])
   {
      // Lambda blok ini menghitung faktorial dari nilai int.
      FungsiNumerik faktorial = (n) -> {
         int hasil = 1;
 
         for(int i=1; i <= n; i++)
          hasil = i * hasil;
            return hasil;
      };
 
      System.out.println("Faktorial dari 3 = " + faktorial.fungsi(3));
      System.out.println("Faktorial dari 5 = " + faktorial.fungsi(5));
   }
}

Jika dijalankan, program ini menghasilkan keluaran berikut:

Faktorial dari 3 = 6
Faktorial dari 5 = 120

Pada program tersebut, perhatikan bahwa lambda blok mendeklarasikan sebuah variabel dengan nama hasil, menggunakan loop for, dan memiliki statemen return. Ini legal dilakukan di dalam tubuh lambda blok. Pada intinya, tubuh dari lambda blok mirip dengan tubuh metode biasa.

Contoh lain dari lambda blok ditunjukkan pada program berikut, yang membalikkan urutan karakter pada sebuah string:

// Lambda blok yang membalikkan urutan karakter pada sebuah string.
interface FungsiString {
   String fungsi(String n);
}

public class DemoLambdaBlok2 {
   public static void main(String args[])
   {
      // Lambda blok ini membalikkan urutan karakter pada sebuah string.
   FungsiString balik = (str) -> {
         String hasil = "";
         int i;
 
         for(i = str.length()-1; i >= 0; i--)
          hasil += str.charAt(i);
            return hasil;
      };
 
      System.out.println("Lambda dibalik menjadi: " +
        balik.fungsi("Lambda"));
 
      System.out.println("Ekspresi dibalik menjadi: " +
        balik.fungsi("Ekspresi"));
   }
}

Jika dijalankan, program ini menghasilkan keluaran berikut:

Lambda dibalik menjadi: adbmaL
Ekspresi dibalik menjadi: iserpskE

Pada contoh ini, antarmuka fungsional FungsiNumerik mendeklarasikan metode fungsi(). Metode ini mengambil satu parameter bertipe data String dan memiliki nilai balik bertipe data String pula. Jadi, pada ekspresi lambda balik, tipe data dari str, disimpulkan oleh kompilator sebagai tipe data String. Perhatikan bahwa metode charAt() dipanggil pada str.


Antarmuka Fungsional Generik
Ekspresi lambda itu sendiri tidak bisa menetapkan tipe data parameter. Jadi, ekspresi lambda tidak bisa dijadikan generik. Namun, antarmuka fungsional yang berkaitan dengan ekspresi lambda dapat dijadikan generik. Pada kasus ini, tipe data target dari ekspresi lambda ditentukan oleh tipe data argumen yang ditetapkan ketika referensi antarmuka fungsional dideklarasikan.

Untuk memahami nilai dari antarmuka fungsional generik, perhatikan berikut. Dua contoh sebelumnya menggunakan antarmuka fungsional yang berbeda, satu dengan nama FungsiNumerik dan yang lain dengan nama FungsiString. Namun, keduanya mendefinisikan sebuah metode dengan nama fungsi() yang mengambil satu parameter dan memberikan nilai balik. Pada kasus pertama, tipe data parameternya dan tipe data nilai baliknya adalah int. Pada kasus kedua, tipe data parameternya dan tipe data nilai baliknya adalah String. Jadi, satu-satunya perbedaan dari kedua metode tersebut adalah tipe datanya. Daripada harus memiliki dua antarmuka fungsional dengan metode yang hanya berbeda tipe data, Anda dimungkinkan untuk mendeklarasikan satu antarmuka generik yang dapat dipakai untuk menangani kedua kasus tersebut. Program berikut menunjukkan pendekatan ini:

// Menggunakan antarmuka fungsional generik dengan ekspresi lambda

// Antarmuka fungsional generik
interface SebuahFungsi<T> {
   T fungsi(T t);
}

public class DemoAntarmukaFungsionalGenerik {
   public static void main(String args[])
   {
      // Menggunakan versi String dari SebuahFungsi.
    SebuahFungsi<String> balik = (str) -> {
         String hasil = "";
         int i;
 
         for(i = str.length()-1; i >= 0; i--)
          hasil += str.charAt(i);
            return hasil;
      };
 
      System.out.println("Lambda dibalik menjadi: " +
        balik.fungsi("Lambda"));
 
      System.out.println("Ekspresi dibalik menjadi: " +
        balik.fungsi("Ekspresi"));
      
      // Menggunakan versi int dari SebuahFungsi.
      SebuahFungsi<Integer> faktorial = (n) -> {
         int hasil = 1;
 
         for(int i=1; i <= n; i++)
          hasil = i * hasil;
            return hasil;
      };
 
      System.out.println("Faktorial dari 3 = " + faktorial.fungsi(3));
      System.out.println("Faktorial dari 5 = " + faktorial.fungsi(5));
   }
}

Jika dijalankan, program ini menghasilkan keluaran berikut:

Lambda dibalik menjadi: adbmaL
Ekspresi dibalik menjadi: iserpskE
Faktorial dari 3 = 6
Faktorial dari 5 = 120

Di sini, T menetapkan baik tipe data nilai balik dan tipe data parameter dari fungsi(). Ini berarti bahwa ia kompatibel dengan sembarang ekspresi lambda yang mengambil satu parameter dan yang menghasil sebuah nilai balik dengan tipe data sama.

Antarmuka SebuahFungsi dipakai untuk menyediakan referensi ke dua jenis lambda berbeda. Ekspresi lambda pertama menggunakan tipe data String. Yang kedua menggunakan tipe data Integer. Jadi, antarmuka fungsional yang sama dapat dipakai untuk ekspresi lambda balik dan ekspresi lambda faktorial.


Ekspresi Lambda dan Eksepsi
Ekspresi lambda bisa melemparkan eksepsi. Jika hal itu terjadi, maka eksepsi tersebut harus kompatibel dengan eksepsi-eksepsi yang tercantum pada klause throws dari metode abstrak pada antarmuka fungsional. Berikut adalah sebuah contoh yang mengilustrasikan proses ini. Program menghitung rerata dari sebuah array yang memuat nilai-nilai double. Jika array dengan panjang nol dilewatkan, ia akan melemparkan eksepsi EksepsiArrayKosong. Seperti yang ditunjukkan, eksepsi ini dicantumkan pada klausa throws dari fungsi fungsi() yang dideklarasikan di dalam antarmuka fungsional FungsiArrayNumerikDouble.

// Melemparkan sebuah eksepsi dari ekspresi lambda.

interface FungsiArrayNumerikDouble {
   double fungsi(double[] n) throws EksepsiArrayKosong;
}

class EksepsiArrayKosong extends Exception {
 EksepsiArrayKosong() {
      super("Array Kosong");
   }
}

public class DemoEksepsiLambda {
   public static void main(String args[]) throws EksepsiArrayKosong
   {
      double[] arrayNilai = { 1.0, 2.0, 3.0, 4.0 };
 
      // Lambda blok ini menghitung rerata dari array double.
      FungsiArrayNumerikDouble rerata = (n) -> {
         double jum = 0;
 
         if(n.length == 0)
            throw new EksepsiArrayKosong();
 
         for(int i=0; i < n.length; i++)
            jum += n[i];
            return jum / n.length;
      };
 
      System.out.println("Rerata array = " + rerata.fungsi(arrayNilai));
 
      // Ini menyebabkan eksepsi dilemparkan.
      System.out.println("Rerata array = " + rerata.fungsi(new double[0]));
   }
}

Pemanggilan pertama terhadap rerata.fungsi() menghasilkan nilai 2. Pemanggilan kedua, yang melewatkan sebuah array dengan panjang nol, menyebabkan eksepsi EksepsiArrayKosong dilemparkan. Ingat, pencantuman klausa throws di dalam fungsi() ini diperlukan. Tanpanya, program tidak akan bisa dikompilasi karena ekspresi lambda tidak lagi kompatibel dengan fungsi().

Contoh ini mendemonstrasikan hal penting lain tentang ekspresi lambda. Perhatikan bahwa parameter yang ditetapkan oleh fungsi() pada antarmuka fungsional FungsiArrayNumerikDouble adalah sebuah array. Namun, parameter pada ekspresi lambda adalah n, bukan n[ ]. Ingat, tipe data dari parameter ekspresi lambda akan disimpulkan oleh kompilator dari konteks target. Pada kasus ini, konteks target adalah double[], jadi tipe data n adalah double[]. Anda juga bisa (meskipun tidak diperlukan) menetapkannya menjadi double[] n.


Ekspresi Lambda dan Penangkapan Variabel
Variabel-variabel yang didefinisikan oleh skop pengapit dari sebuah ekspresi lambda dapat diakses hanya di dalam ekspresi lambda. Misalnya, ekspresi lambda dapat menggunakan sebuah variabel objek atau variabel static yang didefinisikan oleh kelas pembungkusnya. Ekspresi lambda juga dapat mengakses this (baik secara implisit maupun eksplisit), yang mereferensi objek pemanggil dari kelas pembungkus ekspresi lambda. Jadi, ekspresi lambda dapat memperoleh atau menetapkan nilai dari sebuah variabel objek atau variabel static, dan memanggil metode yang didefinisikan oleh kelas pembungkusnya.

Namun, ketika sebuah ekspresi lambda menggunakan variabel lokal dari skop pembungkusnya, kejadian spesial yang dinamakan dengan penangkapan variabel terjadi. Pada kasus ini, ekspresi lambda hanya bisa menggunakan variabel lokal yang dideklarasikan final, yaitu variabel yang tidak pernah bisa diubah setelah pertama kali ditugasi nilai. Anda juga tidak perlu secara eksplisit membubuhi final pada variabel tersebut.

Adalah penting untuk memahami bahwa sebuah variabel lokal dari skop penutup tidak dapat dimodifikasi oleh ekspresi lambda. Program berikut mengilustrasikannya:

// Sebuah contoh penangkapan variabel lokal dari skop pembungkus.

interface FungsiKu {
   int fungsi(int n);
}

public class PenangkapanVariabel {
   public static void main(String args[])
   {
      // Sebuah variabel lokal yang dapat ditangkap.
      int angka = 10;
 
      FungsiKu lambdaKu = (n) -> {
         // Menggunakan angka OK di sini. Tidak memodifikasi angka.
         int v = angka + n;
 
         // Namun, berikut adalah ilegal karena
         // mencoba memodifikasi nilai angka
         // angka++
 
         return v;
      };
 
      // Baris berikut juga akan menyebabkan error, karena
      // akan menghapus kondisi final dari angka.
      // angka = 9;
   }
}

Seperti yang diindikasikan oleh komentar, angka sebenarnya adalah variabel dengan deklarasi final, dan, oleh karena itu, dapat dipakai di dalam lambdaKu. Namun, jika angka akan dimodifikasi, baik di dalam lambda atau di luarnya, angka akan kehilangan status finalnya. Ini akan menyebabkan error, dan program tidak akan bisa dimodifikasi.



Referensi Metode
Ini merupakan fitur penting yang berkaitan dengan ekspresi lambda. Referensi metode menyediakan cara untuk mereferensi sebuah metode tanpa perlu mengeksekusinya. Ini berkaitan dengan ekspres lambda karena ia juga mensyaratkan konteks tipe data target yang memuat sebuah antarmuka fungsional kompatibel. Ketika dievaluasi, referensi metode juga menciptakan sebuah objek dari antarmuka fungsional.

Ada beberapa jenis referensi metode. Anda akan mulai mempelajari referensi metode yang mereferensi ke metode static.


Referensi Metode ke Metode static
Untuk menciptakan sebuah referensi metode static, Anda menggunakan sintaksis umum berikut:

NamaKelas::namaMetode

Perhatikan bahwa nama kelas dipisahkan dari nama metode menggunakan dua buah titik-dua (kolon). Simbol :: ini merupakan separator baru yang telah ditambahkan pada Java sejak JDK 8. Referensi metode ini dapat dipakai di mana saja dengan syarat kompatibilitas dengan tipe data targetnya.

Program berikut mendemonstrasikan sebuah referensi metode static:

// Mendemonstrasikan sebuah referensi metode ke metode static.

// Antarmuka fungsional untuk operasi-operasi string.
interface FungsiString {
   String fungsi(String n);
}

// Kelas ini mendefinisikan sebuah metode static dengan nama balikString().
class OpStringKu {
   // Sebuah metode static yang membalikkan urutan string.
   static String balikString(String str) {
      String hasil = "";
      int i;

      for(i = str.length()-1; i >= 0; i--)
       hasil += str.charAt(i);

      return hasil;
   }
}

public class DemoReferensiMetode {
   // Metode ini memiliki sebuah antarmuka fungsional sebagai
   // tipe data dari parameternya. Jadi, ia dapat
   // menerima sembarang objek dari antarmuka tersebut,
   // termasuk sebuah referensi metode.
   static String stringOp(FungsiString sf, String s) {
      return sf.fungsi(s);
   }
 
   public static void main(String args[])
   {
      String strMasukan = "Lambda menambah kekuatan";
      String strKeluaran;
 
      // Di sini, sebuah ref metode ke balikString
      // dilewatkan kepada stringOp().
      strKeluaran = stringOp(OpStringKu::balikString, strMasukan);
      
      System.out.println("String semula: " + strMasukan);
      System.out.println("String jika dibalik: " + strKeluaran);
   }
}

Ketika dijalankan, program ini akan menghasilkan keluaran berikut:

String semula: Lambda menambah kekuatan
String jika dibalik: nataukek habmanem adbmaL

Pada program, perhatikan khusus pada baris ini:

strKeluaran = stringOp(OpStringKu::balikString, strMasukan);

Di sini, sebuah referensi ke metode static, balikString(), yang dideklarasikan di dalam OpStringKu, dilewatkan sebagai argumen pertama kepada stringOp(). Ini dapat dilakukan karena balikString kompatibel dengan antarmuka fungsional FungsiString. Jadi, ekspresi OpStringKu::balikString dievaluasi menghasilkan sebuah referensi ke sebuah objek dimana di dalamnya balikString menyediakan implementasi dari fungsi() pada FungsiString.


Selanjutnya  >>>

No comments: