Monday, March 19, 2018

15. Ekspresi Lambda Bagian Empat



Referensi Konstruktor
Sama dengan cara ketika Anda menciptakan referensi metode, Anda bisa juga menciptakan referensi ke konstruktor. Berikut adalah bentuk umum dari sintaksis yang akan Anda gunakan:

namakelas::new

Referensi ini dapat ditugaskan kepada sembarang referensi antarmuka fungsional yang mendefinsikan sebuah metode yang kompatibel dengan konstruktor. Berikut adalah sebuah contoh sederhana:

// Mendemonstrasikan sebuah referensi konstruktor.

// FungsiKu adalah sebuah antarmuka fungsional yang memiliki
// metode yang menghasilkan sebuah referensi KelasKu.
interface FungsiKu {
   KelasKu fungsi(int n);
}

class KelasKu {
   private int nil;

   // Konstruktor ini mengambil sebuah argumen.
   KelasKu(int v) { nil = v; }

   // Ini adalalah konstruktor default.
   KelasKu() { nil = 0; }

   // ...

   int getNil() { return nil; };
}

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

      // Menciptakan sebuah referensi ke konstruktor KelasKu
      // Karena fungsi() yang ada di dalam KelasKu mengambil
      // satu argumen, new mereferensi ke konstruktor
      // terparameterisasi di dalam KelasKu,
      // bukan ke konstruktor default.
      FungsiKu KonstKelasKu = KelasKu::new;
 
      // Menciptakan sebuah objek dari KelasKu melalui referensi konstruktor.
      KelasKu mc = KonstKelasKu.fungsi(100);
 
      // Menggunakan objek dari KelasKu yang baru diciptakan.
      System.out.println("nil di dalam mc = " + mc.getNil( ));
 }
}

Jika dijalankan, program di atas menghasilkan keluaran berikut:

nil di dalam mc = 100

Pada program, perhatikan bahwa metode fungsi() dari FungsiKu menghasilkan sebuah referensi bertipe KelasKu dan memiliki sebuah parameter int. Selanjutnya, perhatikan bahwa KelasKu mendefinisikan dua konstruktor. Konstruktor pertama memiliki sebuah parameter bertipe int. Yang kedua merupakan konstruktor default, yaitu konstruktor tanpa parameter. Sekarang, perhatikan baris berikut:

FungsiKu KonstKelasKu = KelasKu::new;

Di sini, ekspresi KelasKu::new menciptakan sebuah referensi konstruktor ke sebuah konstruktor KelasKu. Pada kasus ini, karena metode fungsi() dari FungsiKu mengambil sebuah parameter int, maka konstruktor yang direferensi adalah KelasKu(int v) karena itu yang cocok. Selain itu perhatikan bahwa referensi ke konstruktor ini ditugaskan kepada sebuah referensi FungsiKu, dengan nama KonstKelasKu. Setelah statemen ini dieksekusi, KonsKelasKu dapat dipakai untuk menciptakan sebuah objek dari KelasKu, seperti pada baris berikut:

KelasKu mc = KonstKelasKu.fungsi(100);

Intinya, KonstKelasKu telah menjadi cara lain untuk memanggil KelasKu(int v).

Referensi konstruktor ke kelas generik diciptakan dengan cara sama. Satu-satunya perbedaan adalah bahwa argumen dapat ditetapkan. Ini sama seperti ketika menggunakan sebuah kelas generik untuk menciptakan sebuah metode referensi: hanya perlu menetapkan argumen setelah nama kelas. Program berikut mengilustrasikannya dengan memodifikasi contoh sebelumnya sehingga FungsiKu dan KelasKu keduanya generik.

// Mendemonstrasikan sebuah referensi konstruktor dengan kelas generik.

// FungsiKu sekarang adalah antarmuka fungsional generik.
interface FungsiKu<T> {
   KelasKu<T> fungsi(T n);
}

class KelasKu<T> {
   private T nil;

   // Konstruktor ini mengambil sebuah argumen.
   KelasKu(T v) { nil = v; }

   // Ini adalalah konstruktor default.
   KelasKu() { nil = null; }

   // ...

   T getNil() { return nil; };
}

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

      // Menciptakan sebuah referensi ke konstruktor KelasKu<T>.
      FungsiKu<Integer> konstKelasKu = KelasKu<Integer>::new;
   
      // Menciptakan sebuah objek dari KelasKu<T> melalui referensi konstruktor itu.
      KelasKu<Integer> mc = konstKelasKu.fungsi(100);
   
      // Menggunakan objek dari KelasKu<T> yang baru diciptakan.
      System.out.println("nil di dalam mc = " + mc.getNil( ));
 }
}

Program ini menghasilkan keluaran yang sama seperti versi sebelumnya. Perbedaannya adalah bahwa sekarang keduanya FungsiKu dan KelasKu adalah generik. Jadi, runtun yang menciptakan sebuah referensi konstruktor dapat mencantumkan argumen tipe data, seperti ditunjukkan di sini:

FungsiKu<Integer> konstKelasKu = KelasKu<Integer>::new;

Karena argumen tipe data Integer telah ditetapkan ketika konstKelasKu diciptakan, ia dapat dipakai untuk menciptakan objek KelasKu<Integer>, seperti ditunjukkan pada baris selanjutnya:

KelasKu<Integer> mc = konstKelasKu.fungsi(100);

Meskipun contoh-contoh sebelumnya mendemonstrasikan mekanisme penggunaan referensi konstruktor, tidak ada programer yang menggunakan referensi konstruktor seperti yang ditunjukkan karena tidak ada untungnya. Selain itu, dengan memiliki dua nama untuk konstruktor yang sama, hal itu bisa jadi membingungkan. Namun, untuk memberikan rasa praktisme, program berikut menggunakan sebuah metode static, dengan nama pembangunKelasKu(), yang merupakan pembangun untuk menciptakan objek-objek dengan tipe objek FungsiKu. Metode ini dapat dipakai untuk menciptakan sembarang objek yang memiliki sebuah konstruktor yang kompatibel dengan parameter pertamanya.

// Mengimplementasikan sebuah kelas pembangun sederhana
// menggunakan sebuah referensi konstruktor.

interface FungsiKu<R, T> {
   R fungsi(T n);
}

// Sebuah kelas generik sederhana.
class KelasKu<T> {
   private T nil;

   // Sebuah konstruktor yang mengambil satu argumen.
   KelasKu(T v) { nil = v; }

   // Konstruktor default. Konstruktor ini tidak
   // digunakan pada program ini.
   KelasKu() { nil = null; }

   //...

   T getNil() { return nil; };
}

//Sebuah kelas tak-generik sederhana.
class KelasKu2 {
   String str;

// Sebuah konstruktor yang mengambil satu argumen.
   KelasKu2(String s) { str = s; }

   // Konstruktor default. Konstruktor ini tidak
   // digunakan pada program ini.
   KelasKu2() { str = ""; }

   //...

   String getNil() { return str; };
}

class DemoRefKonstruktor3 {
   // Sebuah metode pembangun untuk objek-objek kelas.
   // Kelas harus memiliki sebuah konstruktor yang 
   // mengambil satu parameter dengan tipe data T.
   // R menetapkan tipe data objek yang sedang diciptakan.
   static <R,T> R pembangunKelasKu(FungsiKu<R, T> kons, T v) {
      return kons.fungsi(v);
   }
 
   public static void main(String args[])
   {
      // Menciptakan sebuah referensi ke konstruktor KelasKu.
      // Pada kasus ini, new mereferensi ke konnstruktor
      // yang mengambil satu argumen.
      FungsiKu<KelasKu<Double>, Double> konstKelasKu = KelasKu<Double>::new;
 
      // Menciptakan sebuah objek dari KelasKu menggunakan
      // metode pembangun.
      KelasKu<Double> mc = pembangunKelasKu(konstKelasKu, 100.1);
 
      // Menggunakan objek dari KelasKu yang baru diciptakan.
      System.out.println("nil di dalam mc = " + mc.getNil( ));
 
      // Sekarang, ciptakan kelas berbeda menggunakan pembangunKelasKu().
      FungsiKu<KelasKu2, String> konstKelasKu2 = KelasKu2::new;
 
      // Menciptakan sebuah objek dari KelasKu2 menggunakan
      // metode pembangun.
      KelasKu2 mc2 = pembangunKelasKu(konstKelasKu2, "Lambda");
 
      // Menggunakan objek dari KelasKu2 yang baru diciptakan.
      System.out.println("str di dalam mc2 = " + mc2.getNil( ));
   }
}

Ketika dijalankan, program di atas menghasilkan keluaran berikut:

nil di dalam mc = 100.1
str di dalam mc2 = Lambda

Seperti yang dapat Anda lihat, pembangunKelasKu() digunakan untuk menciptakan objek-objek dengan tipe KelasKu<Double>. Meskipun kedua kelas berbeda, misalnya KelasKu merupakan kelas generik dan KelasKu2 tidak generik, keduanya dapat diciptakan oleh pembangunKelasKu() karena keduanya memiliki konstruktor yang kompatibel dengan fungsi() di dalam FungsiKu(). Ini bisa dilakukan karena pembangunKelasKu() menerima konstruktor untuk objek yang dibangunnya. Anda bisa bereksperimen dengan program ini, dengan mencoba kelas-kelas yang Anda ciptakan. Anda juga bisa menciptakan objek-objek dengan tipe data berbeda dari objek KelasKu. Seperti yang Anda lihat, pembangunKelasKu() dapat menciptakan sembarang objek dengan kelas yang memiliki sebuah konstruktor yang kompatibel dengan fungsi() di dalam FungsiKu(). Meskipun ini adalah contoh sederhana, ia menunjukkan kekuatan yang diberikan referensi konstruktor pada Java.

Sebelum melanjutkan, penting untuk menyebutkan bentuk kedua dari sintaksis referensi konstruktor yang digunakan untuk array. Untuk menciptakan sebuah referensi konstruktor bagi sebuah array, gunakan konstruksi ini:

tipe[]::new

Di sini, tipe menetapkan tipe data objek yang sedang diciptakan. Sebagai contoh, dengan mengasumsikan bentuk dari KelasKu seperti ditunjukkan pada contoh referensi konstruktor (DemoRefKonstruktor) dan jika diberikan antarmuka PenciptaArrayKu seperti ditunjukkan di sini:

interface PenciptaArrayKu<T> {
   T fungsi(int n);
}

Maka kode berikut menciptakan sebuah array dua-elemen yang berisi objek-objek KelasKu dan memberikan nilai awal kepada tiap elemen array:

PenciptaArrayKu<KelasKu[]> konstArrayKu = KelasKu[]::new;
KelasKu[] a = konstArrayKu.fungsi(2);
a[0] = new KelasKu(1);
a[1] = new KelasKu(2);


Antarmuka Fungsional Pustaka
Sampai saat ini, contoh-contoh pada bab ini telah mendefinisikan antarmuka-antarmuka fungsional sendiri sehingga konsep fundamental di balik ekspresi lambda dan antarmuka fungsional secara jelas diilustrasikan. Namun, pada banyak kasus, Anda tidak perlu mendefinisikan antarmuka fungsional sendiri karena JDK 8 menambahkan sebuah paket baru dengan nama java.util.function yang menyediakannya. Lihat tabel berikut:

Antarmuka
Tujuan
UnaryOperator<T>
Menerapkan operasi unary terhadap sebuah objek dengan tipe T dan kemudian memberikan hasil dengan tipe data T juga. Metodenya dinamakan dengan apply().
BinaryOperator<t>
Menerapkan operasi biner terhadap dua objek dengan tipe T dan kemudian memberikan hasil dengan tipe data T juga. Metodenya dinamakan dengan apply().
Consumer<T>
Menerapkan operasi pada sebuah objek bertipe T. Metodenya dinamakan dengan accept().
Supplier<T>
Menghasilkan sebuah objek bertipe T. Metodenya dinamakan dengan get().
Function<T,R>
Menerapkan operasi terhadap sebuah objek bertipe T dan memberikan hasil dengan tipe data R. Metodenya dinamakan dengan apply().
Predicate<T>
Menentukan apakah sebuah objek bertipe T memenuhi kekangan tertentu. Menghasilkan sebuah nilai boolean yang mengindikasikan keluaran. Metodenya dinamakan dengan test().

Program berikut menunjukkan bagaimana antarmuka Function digunakan dengan memanfaatkannya pada contoh sebelumnya DemoLambdaBlok yang mendemonstrasikan lambda blok dengan mengimplementasikan contoh faktorial. Contoh tersebut menciptakan antarmuka fungsional sendiri dengan nama FungsiNumerik, tetapi antarmuka pustaka Function juga dapat digunakan, seperti versi program berikut:

// Menggunakan antarmuka fungsional pustaka Function.

// Mengimpor antarmuka Function.
import java.util.function.Function;

public class DemoAntarmukaFunction {
   public static void main(String args[])
   {
      // Lambda blok ini menghitung faktorial dari sebuah nilai int
      // Kali ini, Function sebagai antarmuka fungsional.
      Function<Integer, 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.apply(3));
      System.out.println("Faktorial dari 5 = " + faktorial.apply(5));
   }
}

Ketika dijalankan, program ini menghasilkan keluaran yang sama dengan versi program sebelumnya.



No comments: