Tuesday, March 27, 2018

9. Paket dan Antarmuka Bagian Dua



Antarmuka
Dengan menggunakan katakunci interface, Anda dapat mengabstraksi (memisahkan) sebuah antarmuka kelas dari implementasinya. Jadi, dengan melakukannya, Anda bisa menetapkan apa saja yang bisa dilakukan oleh sebuah kelas, tetapi bukan bagaimana kelas itu melakukannya. Antarmuka secara sintaksis sama dengan kelas, tetapi ia tidak memiliki variabel kelas. Sebagai aturan umum, metode-metode yang dideklarasikan di dalam antarmuka tidak memiliki definisi atau memiliki tubuh yang kosong. Pada prakteknya, ini berarti bahwa Anda dapat mendefinisikan antarmuka yang tidak mengasumsikan bagaimana ia diimplementasikan. Setelah antarmuka didefinisikan, kelas apapun dapat mengimplementasikannya. Selain itu, satu kelas dapat mengimplementasikan antarmuka sebanyak yang diperlukan.

Untuk mengimplementasikan sebuah antarmuka, sebuah kelas perlu menyediakan definisi dari semua metode yang dideklarasikan oleh antarmuka. Namun, setiap kelas bebas untuk menentukan detil implementasinya sendiri. 


Mendefinisikan Antarmuka
Sebuah antarmuka didefinisikan mirip dengan kelas. Berikut adalah bentuk umum dari sebuah antarmuka:

Sebuah antarmuka didefinisikan mirip dengan kelas. Berikut adalah bentuk umum dari sebuah antarmuka:

akses interface nama {
   tipe-nilai-balik nama-metode1(daftar-param);
   tipe-nilai-balik nama-metode2(daftar-param);

   tipe-data final nama-var1 = nilai;
   tipe-data final nama-var2 = nilai;

   //...

   tipe-nilai-balik nama-metodeN(daftar-param);
   tipe-data final nama-varN = nilai;
}

Ketika tidak ada pemodifikasi akses yang dicantumkan, maka akses default akan diberikan dan antarmuka tersebut hanya tersedia bagi anggota-anggota lain pada paket yang sama. Ketika antarmuka dideklarasikan sebagai public, antarmuka itu dapat dipakai oleh semua kode lain. Pada kasus ini, antarmuka tersebut harus merupakan satu-satunya antarmuka public yang dideklarasikan pada sebuah file, dan file tersebut harus memiliki nama sama dengan antarmuka.

Pada bentuk umum dari antarmuka di atas, nama adalah nama dari antarmuka, dan dapat berupa sembarang pengenal yang valid. Perhatikan bahwa metode yang dideklarasikan di dalam antarmuka tidak memiliki tubuh dan diakhiri dengan semicolon (titik-koma) setelah daftar parameter metode. Semua metode tersebut pada dasarnya adalah metode abstrak. Setiap kelas yang mengimplementasikan sebuah antarmuka harus menyediakan definisi bagi semua metode dari antarmuka tersebut.

Sebelum melanjutnya, ada hal penting lain yang perlu diperhatikan. JDK 8 menambahkan sebuah fitur pada interface yang bisa meningkatkan kapabilitas dari antarmuka. Sebelum JDK 8, sebuah antarmuka tidak bisa mendefinisikan implementasi apapun. Inilah yang ditunjukkan pada bentuk umum dari antarmuka yang telah diberikan di atas. JDK 8 mengubah hal ini. Mulai sejak JDK 8, Anda diperbolehkan untuk menambahkan implementasi default pada metode antarmuka. Jadi, sekarang Anda bisa menetapkan watak default untuk metode antarmuka. Namun, metode default ini merupakan fitur dengan kegunaan khusus yang akan dibahas nanti pada bab ini. Anda sekarang akan mulai belajar bentuk tradisional dari antarmuka.
Seperti yang ditunjukkan pada bentuk umum dari antarmuka, variabel dapat dideklarasikan di dalam deklarasi antarmuka. Variabel itu secara implisit adalah final dan static, yang berarti bahwa ia tidak bisa diubah dengan mengimplementasikan kelas. Variabel itu juga harus diinisialisasi atau diberikan nilai awal. Semua metode dan variabel di dalam sebuah antarmuka secara implisit dideklarasikan public.

Berikut adalah sebuah contoh definisi antarmuka. Contoh ini mendeklarasikan sebuah antarmuka sederhana yang memuat satu metode dengan nama panggilbalik() yang mengambil satu parameter integer:

interface Panggilbalik {
   void panggilbalik(int param);
}


Mengimplementasikan Antarmuka
Setelah antarmuka didefinisikan, satu atau lebih kelas dapat mengimplementasikan antarmuka itu. Untuk mengimplementasikan sebuah antarmuka, Anda mencantumkan klausa implements pada definisi kelas, dan kemudian menciptakan metode-metode yang diperlukan oleh antarmuka tersebut. Bentuk umum dari sebuah kelas yang mencantumkan klausa implements tampak seperti ini:

class nama-kelas [extends superkelas] [implements antarmuka [,antarmuka]] {
   // tubuh kelas
}

Jika sebuah kelas mengimplementasikan lebih dari satu antarmuka, antarmuka-antarmuka tersebut harus dipisahkan dengan koma. Jika sebuah kelas mengimplementasikan dua antarmuka yang mendeklarasikan metode yang sama, maka metode yang sama tersebut akan digunakan oleh klien dari kedua antarmuka. Metode yang mengimplementasikan sebuah antarmuka harus dideklarasikan public. Di samping itu, sidik tipe data dari metode pengimplementasi harus cocok atau sama persis dengan sidik tipe yang ditetapkan pada definisi antarmuka.

Berikut adalah sebuah kelas contoh yang mengimplementasikan antarmuka Panggilbalik yang ditunjukkan sebelumnya:

class Klien implements Panggilbalik {
   // Mengimplementasikan antarmuka Panggilbalik
   public void panggilbalik(int p) {
      System.out.println("panggilbalik() dipanggil dengan " + p);
   }
}

Adalah hal umum bagi kelas yang mengimplementasikan antarmuka untuk mendefinisikan metode-metodenya sendiri. Misalnya, versi berikut dari kelas Klien mengimplementasikan panggilbalik() dan menambah metode bukanMetAntarmuka():

class Klien implements Panggilbalik {
   // Mengimplementasikan antarmuka Panggilbalik
   public void panggilbalik(int p) {
      System.out.println("panggilbalik() dipanggil dengan " + p);
   }

   void bukanMetAntarmuka() {
      System.out.println("Kelas yang mengimplementasikan antarmuka " +
         "bisa juga memiliki anggota lain.");
   }
}


Mengakses Implementasi Melalui Referensi Antarmuka
Anda dapat mendeklarasikan variabel sebagai referensi objek yang menggunakan tipe antarmuka, bukan hanya tipe kelas. Setiap objek dari kelas yang mengimplementasikan antarmuka dapat direferensi oleh variabel. Ketika Anda memanggil sebuah metode melalui referensi seperti ini, versi mana yang akan dipanggil didasarkan pada objek aktual dari antamuka yang direferensi. Ini merupakan fitur kunci dari antarmuka. Metode yang akan dieksekusi akan dicari secara dinamis saat program dijalankan.

Contoh berikut memanggil metode panggilbalik() melalui sebuah variabel referensi antarmuka:

public class UjiAntarmuka {
   public static void main(String args[]) {
      Panggilbalik c = new Klien();
      c.panggilbalik(42);
   }
}

Jika dijalankan, program di atas akan menghasilkan keluaran berikut:

panggilbalik() dipanggil dengan 42

Perhatikan bahwa c dideklarasikan sebagai bagian dari tipe antarmuka Panggilbalik, tetapi ia diberikan sebuah objek dari kelas Klien. Meskipun c dapat dipakai untuk mengakses metode panggilbalik(), ia  tidak bisa mengakses anggota-anggota lain dari kelas Klien. Sebuah variabel referensi antarmuka hanya mengenal metode-metode yang dideklarasikan oleh deklarasi antarmukanya. Jadi, c tidak bisa dipakai untuk mengakses metode bukanMetAntarmuka() karena ia didefinisikan oleh Klien tetapi tidak oleh Panggilbalik.

Sementara contoh sebelumnya menunjukkan bagaimana sebuah variabel referensi antarmuka dapat mengakses objek kelas pengimplementasi, hal itu tidak mendemonstrasikan kekuatan polimorfik dari referensi tersebut. Untuk menunjukkannya, pertama-tama diciptakan implementasi kedua dari Panggilbalik, yang ditunjukkan di sini:

// Implementasi lain dari antarmuka Panggilbalik
public class KlienLain implements Panggilbalik{
   // Mengimplementasikan antarmuka Panggilbalik
   public void panggilbalik(int p) {
      System.out.println("Versi lain dari panggilbalik");
      System.out.println("p dikuadrat menghasilkan " + (p*p));
   }
}

Sekarang, lakukan pengujian seperti ini:


public class UjiAntarmuka2 {
   public static void main(String args[]) {
      Panggilbalik c = new Klien();
      KlienLain ob = new KlienLain();
  
      c.panggilbalik(42);
      c = ob; // c sekarang mereferensi ke objek KlienLain
      c.panggilbalik(42);
   }
}

Jika dijalankan, program di atas akan menghasilkan keluaran berikut:

panggilbalik() dipanggil dengan 42
Versi lain dari panggilbalik
p dikuadrat menghasilkan 1764

Seperti yang dapat dilihat, versi panggilbalik() yang dipanggil ditentukan oleh tipe objek yang direferensi oleh c pada saat program dijalankan. Ini merupakan contoh sederhana, selanjutnya Anda akan melihat contoh lain yang lebih praktis.


Implementasi Parsial
Jika sebuah kelas mencantumkan intermuka tetapi tidak sepenuhnya mengimplementasikan metode-metode yang disyaratkan oleh antarmuka, maka kelas itu harus dideklarasikan abstract. Sebagai contoh:

abstract class TakLengkap implements Panggilbalik {
   int a, b;

   void tampil() {
      System.out.println(a + " " + b);
   }
   //...
}

Di sini, kelas TakLengkap tidak mengimplementasikan metode panggilbalik() dan harus dideklarasikan abstract. Semua kelas yang mewarisi TakLengkap harus mengimplementasikan panggilbalik() atau dideklarasikan abstract jika tidak melakukannya.


Antarmuka Bersarang
Sebuah antarmuka dapat dideklarasikan sebagai anggota dari sebuah kelas atau antarmuka lain. Antarmuka semacam itu dinamakan dengan antarmuka anggota atau antarmuka bersarang.

Sebuah antarmuka bersarang dapat dideklarasikan public, private, atau protected. Ini berbeda dari antarmuka level-atas, yang harus dideklarasikan public atau menggunakan level akses default, seperti yang telah dijelaskan sebelumnya. Ketika sebuah antarmuka bersarang dipakai di luar skop pembungkusnya, antarmuka itu harus menggunakan kelas atau antarmuka yang membungkusnya.

Berikut adalah sebuah contoh yang mendemonstrasikan antarmuka bersarang:

// Kelas ini memuat sebuah antarmuka anggota.
class A {
   // ini adalah antarmuka bersarang
   public interface AMBersarang {
      boolean apaTidakNegatif(int x);
   }
}

// B mengimplementasikan antarmuka bersarang.
class B implements A.AMBersarang {
   public boolean apaTidakNegatif(int x) {
      return x < 0 ? false: true;
   }
}

public class AntarmukaBersarang {
   public static void main(String args[]) {
      // menggunakan referensi antarmuka bersarang
      A.AMBersarang nif = new B();
  
      if(nif.apaTidakNegatif(10))
         System.out.println("10 bukan bilangan negatif");
  
      if(nif.apaTidakNegatif(-12))
         System.out.println("ini tidak akan ditampilkan");
   }
}

Perhatikan bahwa kelas A mendefinisikan sebuah antarmuka anggota dengan nama AMBersarang dan ia dideklarasikan public. Selanjutnya, kelas B mengimplementasikan antarmuka bersarang tersebut dengan menetapkan:

implements A.AMBersarang

Perhatikan bahwa nama kelas pembungkus atau pengapit antarmuka bersarang itu juga harus disebutkan. Di dalam main(), referensi A.AMBersarang yang dinamakan nif diciptakan, dan sebuah referensi ke objek B ditugaskan kepadanya. Karena B mengimplementasikan A.AMBersarang, maka hal itu menjadi legal.


Menerapkan Antarmuka
Untuk memahami kekuatan dari antarmuka, Anda akan melihat satu contoh yang lebih praktis. Pada bab-bab sebelumnya, Anda telah mengembangkan sebuah kelas dengan nama Tumpukan yang mengimplementasikan sebuah tumpukan berukuran tetap. Namun, ada sejumlah cara dalam mengimplementasikan tumpukan. Misalnya, tumpukan dapat berukuran tetap atau dinamis. Tumpukan dapat pula dimuat di dalam sebuah array, senarai-berantai, pohon biner, dan seterusnya.

Tak peduli bagaimana tumpukan diimplementasikan, antarmuka untuk tumpukan tetap sama. Yaitu, metode push() dan pop() mendefinisikan antarmuka terhadap tumpukan secara independen dari detil implementasinya. Karena antarmuka terhadap sebuah tumpukan terpisah dari implementasinya, adalah mudah untuk mendefinisikan antarmuka tumpukan. Lihat dua contoh berikut.

Pertama, berikut adalah antarmuka yang mendefinisikan sebuah tumpukan integer. Tempatkan kode ini ke dalam sebuah file dengan nama TumpukanInt.java. Antarmuka ini akan digunakan oleh kedua implementasi tumpukan.

// Mendefinisikan sebuah antarmuka tumpukan integer
public interface TumpukanInt {
   void push(int item); // menyimpan sebuah item
   int pop(); // mengambil sebuah item
}

Program berikut menciptakan sebuah kelas dengan nama TumpukanTetap yang mengimplementasikan versi panjang-tetap dari sebuah tumpukan integer:

// Sebuah implementasi dari TumpukanInt yang menggunakan
// penyimpanan tetap.
public class TumpukanTetap implements TumpukanInt{
   private int tumpuk[];
   private int nitem;
 
   // mengalokasikan dan menginisialisasi tumpukan
   TumpukanTetap(int ukuran) {
      tumpuk = new int[ukuran];
      nitem = -1;
   }
 
   // Menempatkan sebuah item ke atas tumpukan
   public void push(int item) {
      if(nitem==tumpuk.length-1) // menggunakan anggota length
         System.out.println("Tumpukan penuh.");
      else
         tumpuk[++nitem] = item;
   }
 
   // Mengambil sebuah item dari tumpukan
   public int pop() {
      if(nitem < 0) {
         System.out.println("Tumpukan kosong.");
         return 0;
      }
      else
         return tumpuk[nitem--];
   }
}

Berikut adalah kode sumber untuk menguji tumpukan ukuran tetap tersebut:

//Menguji tumpukan ukuran tetap
public class UjiAntarmuka {
   public static void main(String args[]) {
      TumpukanTetap tumpukanku1 = new TumpukanTetap(5);
      TumpukanTetap tumpukanku2 = new TumpukanTetap(8);
  
      // menempatkan sejumlah nilai ke atas tumpukan
      for(int i=0; i<5; i++) tumpukanku1.push(i);
      for(int i=0; i<8; i++) tumpukanku2.push(i);
      
      // mengambil sejumlah nilai dari tumpukan
      System.out.println("Tumpukan pada tumpukanku1:");
      for(int i=0; i<5; i++)
         System.out.println(tumpukanku1.pop());
  
      System.out.println("Tumpukan pada tumpukanku2:");
      for(int i=0; i<8; i++)
         System.out.println(tumpukanku2.pop());
   }
}

Jika dijalankan, program di atas akan menghasilkan keluaran berikut:

Tumpukan pada tumpukanku1:
4
3
2
1
0
Tumpukan pada tumpukanku2:
7
6
5
4
3
2
1
0

Berikut adalah implementasi lain dari TumpukanInt yang menciptakan tumpukan dinamis menggunakan definisi antarmuka yang sama. Pada implementasi ini, setiap tumpukan diciptakan dengan sebuah nilai awal. Jika panjang awal ini tidak cukup, maka ukuran tumpukan akan ditambah. Setiap kali ruang baru diperlukan, ukuran tumpukan digandakan.

// Mengimplementasikan sebuah tumpukan dinamis
public class TumpukanDinamis implements TumpukanInt{
   private int tumpuk[];
   private int nitem;
 
   // Mengalokasikan dan menginisialisasi tumpukan
   TumpukanDinamis(int ukuran) {
      tumpuk = new int[ukuran];
      nitem = -1;
   }
 
   // Menempatkan sebuah item ke atas tumpukan
   public void push(int item) {
      // jika tumpukan penuh,
      // alokasikan tumpukan yang lebih besar
      if(nitem==tumpuk.length-1) {
         int temp[] = new int[tumpuk.length * 2]; // ukuran ganda
         for(int i=0; i<tumpuk.length; i++) temp[i] = tumpuk[i];
 
         tumpuk = temp;
         tumpuk[++nitem] = item;
      }
      else
         tumpuk[++nitem] = item;
   }
 
   // Mengambil sebuah item dari tumpukan
   public int pop() {
      if(nitem < 0) {
         System.out.println("Tumpukan kosong.");
         return 0;
      }
      else
         return tumpuk[nitem--];
   }
}

Berikut adalah kode sumber untuk menguji tumpukan ukuran dinamis tersebut:

//Menguji tumpukan ukuran tetap
public class UjiAntarmuka2 {
   public static void main(String args[]) {
      TumpukanDinamis tumpukanku1 = new TumpukanDinamis(5);
      TumpukanDinamis tumpukanku2 = new TumpukanDinamis(8);
  
      // menempatkan sejumlah nilai ke atas tumpukan
      for(int i=0; i<12; i++) tumpukanku1.push(i);
      for(int i=0; i<20; i++) tumpukanku2.push(i);
      
      // mengambil sejumlah nilai dari tumpukan
      System.out.println("Tumpukan pada tumpukanku1:");
      for(int i=0; i<12; i++)
         System.out.println(tumpukanku1.pop());
  
      System.out.println("Tumpukan pada tumpukanku2:");
      for(int i=0; i<20; i++)
         System.out.println(tumpukanku2.pop());
   }
}

Kelas berikut menggunakan baik implementasi TumpukanTetap maupun TumpukanDinamis. Kelas ini melakukannya melalui referensi antarmuka. Ini berarti bahwa pemanggilan terhadap push() dan pop() dilakukan saat program dijalankan bukan pada saat kompilasi.

/* Menciptakan sebuah variabel antarmuka dan
 * mengakses tumpukan melaluinya.
 */

public class UjiAntarmuka3 {
   public static void main(String args[]) {
      TumpukanInt tumpukanku; // menciptakan sebuah variabel ref antarmuka
      TumpukanDinamis ds = new TumpukanDinamis(5);
      TumpukanTetap fs = new TumpukanTetap(8);
  
      tumpukanku = ds; // memuat tumpukan dinamis
      // menempatkan sejumlah nilai ke atas tumpukan
      for(int i=0; i<12; i++) tumpukanku.push(i);

      tumpukanku = fs; // memuat tumpukan tetap
      for(int i=0; i<8; i++) tumpukanku.push(i);
  
      tumpukanku = ds;
      System.out.println("Nilai-nilai pada tumpukan ukuran-dinamis:");
      for(int i=0; i<12; i++)
         System.out.println(tumpukanku.pop());
  
      tumpukanku = fs;
      System.out.println("Nilai-nilai pada tumpukan ukuran-tetap:");
      for(int i=0; i<8; i++)
         System.out.println(tumpukanku.pop());
   }
}

Jika dijalankan, program di atas akan menghasilkan keluaran berikut:

Nilai-nilai pada tumpukan ukuran-dinamis:
11
10
9
8
7
6
5
4
3
2
1
0
Nilai-nilai pada tumpukan ukuran-tetap:
7
6
5
4
3
2
1
0

Pada program ini, tumpukanku adalah sebuah referensi ke antarmuka TumpukanInt. Jadi, ketika ia menunjuk ke ds, ia menggunakan versi push() dan pop() yang didefinisikan oleh implementasi TumpukanDinamis. Ketika referensi itu menunjuk ke fs, ia menggunakan versi push() dan pop() yang didefinisikan oleh implementasi TumpukanTetap. Seperti dijelaskan, penentuan ini dilakukan saat program dijalankan. Akses terhadap sejumlah implementasi dari antarmuka melalui variabel referensi antarmuka merupakan sebuah kekuatan tangguh, yang dalam Java dikenal dengan polimorfisme.


Variabel Pada Antarmuka
Anda bisa menggunakan antarmuka untuk mengimpor konstanta-konstanta berbagi-pakai kepada sejumlah kelas hanya dengan mendeklarasikan sebuah antarmuka yang memuat variabel-variabel yang diinisialisasi dengan nilai-nilai yang diinginkan. Jika sebuah antarmuka tidak memiliki metode apapun, maka setiap kelas yang mencantumkannya tidak perlu mengimplementasikan apapun. Hal itu berarti kelas tersebut hanya mengimpor bidang-bidang konstanta ke dalam ruang nama kelas sebagai variabel-variabel final.

Contoh berikut menggunakan teknik ini untuk mengimplementasikan sistem pembuatan keputusan sederhana:

// Mendemonstrasikan variabel antarmuka
import java.util.Random;

interface KonstantaBersama {
   int TIDAK = 0;
   int YA = 1;
   int MUNGKIN = 2;
   int NANTI = 3;
   int SEGERA = 4;
   int TIDAK_PERNAH = 5;
}

class Pertanyaan implements KonstantaBersama {
   Random rand = new Random();

   int tanya() {
      int prob = (int) (100 * rand.nextDouble());

      if (prob < 30)
      return TIDAK; // 30%
      else if (prob < 60)
         return YA; // 30%
      else if (prob < 75)
         return NANTI; // 15%
      else if (prob < 98)
         return SEGERA; // 13%
      else
         return TIDAK_PERNAH; // 2%
   }
}

public class VariabelAntarmuka implements KonstantaBersama {
   static void jawab(int hasil) {
      switch(hasil) {
         case TIDAK:
            System.out.println("Tidak");
            break;
         case YA:
            System.out.println("Ya");
            break;
         case MUNGKIN:
            System.out.println("Mungkin");
            break;
         case NANTI:
            System.out.println("Nanti");
            break;
         case SEGERA:
            System.out.println("Segera");
            break;
         case TIDAK_PERNAH:
            System.out.println("Tidak Pernah");
            break;
      }
   }
  
   public static void main(String args[]) {
      Pertanyaan q = new Pertanyaan();
  
      jawab(q.tanya());
      jawab(q.tanya());
      jawab(q.tanya());
      jawab(q.tanya());
   }
}

Perhatikan bahwa program ini menggunakan salah satu kelas standar Java: Random. Kelas ini membangkitkan bilangan-bilangan semi-acak. Kelas ini memuat sejumlah metode yang bisa dipakai untuk menghasilkan bilangan-bilangan semi-acak yang diperlukan program Anda. Pada contoh ini, metode nextDouble() dipakai, yang menghasilkan nilai-nilai acak dalam rentang 0.0 sampai 1.0.

Pada contoh ini, dua kelas, Pertanyaan dan VariabelAntarmuka, keduanya mengimplementasikan antarmuka KonstantaBersama dimana TIDAK, YA, MUNGKIN, SEGERA, NANTI, dan TIDAK_PERNAH didefinisikan. Di dalam tiap kelas, kode mereferensi konstanta-konstanta ini dengan cara seolah-olah setiap kelas mendefinisikan atau mewarisinya secara langsung. Berikut adalah contoh keluaran dari program ini:

Ya
Nanti
Nanti
Segera














No comments: