Abstract class membantu kita membuat parent dengan kontrak parsial, tetapi Java hanya mengizinkan satu extends per class. Bagaimana jika sebuah class perlu mengikuti beberapa kontrak sekaligus — misalnya buku yang sekaligus “dapat dipinjam” dan “punya identitas”? Jawabannya adalah interface: tipe referensi berisi kontrak perilaku murni tanpa state instance. Sebuah class boleh implements banyak interface sekaligus, sehingga interface menjadi alat utama untuk mendeskripsikan kemampuan yang bisa dimiliki class dari kategori mana pun. Pada Chapter ini kita akan mendeklarasikan interface, mengimplementasikannya pada class, dan memahami aturan implisit yang menyertainya.

Interface sebagai Kontrak #

Interface dideklarasikan dengan kata kunci interface dan diimplementasikan dengan kata kunci implements. Konsep dasarnya: interface menjanjikan perilaku — daftar method yang setiap class implementor wajib sediakan — tanpa memaksakan bagaimana perilaku tersebut diwujudkan. Sebuah Peminjaman hanya menuntut “bisa dipinjam” dan “bisa dikembalikan”; detail cara class Buku atau class DVD melakukannya bebas ditentukan masing-masing class.

Ada dua aturan implisit yang perlu diingat. Pertama, semua method yang dideklarasikan tanpa body di dalam interface secara otomatis bersifat public dan abstract — kita tidak perlu (dan tidak boleh) menulis kedua modifier itu. Kedua, setiap field di dalam interface secara otomatis menjadi konstanta public static final. Interface memang tidak mengenal state instance sama sekali; interface hanya memuat kontrak dan konstanta.

Mengimplementasikan Banyak Interface #

Kita mulai dengan dua kontrak untuk domain perpustakaan. Interface Peminjaman memuat dua method dan satu konstanta, sedangkan interface Identitas menuntut method id():

java Peminjaman.java
// Interface: kontrak perilaku tanpa state instance.
interface Peminjaman {
    // Field interface secara implisit public static final (konstanta).
    int LAMA_PINJAM_HARI = 14;

    // Method interface secara implisit public dan abstrak.
    void pinjam();

    void kembali();
}
java Identitas.java
// Interface kedua: setiap class implementor wajib punya penanda unik.
interface Identitas {
    String id();
}

Selanjutnya, class Buku mengimplementasikan kedua interface sekaligus melalui satu daftar setelah implements. Class tersebut menyediakan seluruh method kontrak dengan @Override, lalu program utama mengakses objek yang sama lewat tiga sudut pandang berbeda:

class Buku implements Peminjaman, Identitas {
    private final String judul;
    private final String isbn;
    private boolean dipinjam;

    public Buku(String judul, String isbn) {
        this.judul = judul;
        this.isbn = isbn;
        this.dipinjam = false;
    }

    @Override
    public void pinjam() {
        dipinjam = true;
        System.out.println(judul + " dipinjam, batas " + LAMA_PINJAM_HARI + " hari");
    }

    @Override
    public void kembali() {
        dipinjam = false;
        System.out.println(judul + " dikembalikan");
    }

    @Override
    public String id() {
        return isbn;
    }

    public boolean sedangDipinjam() {
        return dipinjam;
    }
}

public class PerpustakaanMini {
    public static void main(String[] args) {
        Buku buku = new Buku("Java Dasar", "978-602-1234-01-5");

        // Variabel bertipe interface hanya mengenal anggota kontraknya.
        Peminjaman pinjaman = buku;
        Identitas kartu = buku;

        pinjaman.pinjam();
        System.out.println("ID: " + kartu.id());
        pinjaman.kembali();

        System.out.println("Status dipinjam: " + buku.sedangDipinjam());
    }
}

Output yang Dihasilkan:

text
Java Dasar dipinjam, batas 14 hari
ID: 978-602-1234-01-5
Java Dasar dikembalikan
Status dipinjam: false

Mari kita bedah bagian-bagian pentingnya:

  1. Baris 1 (class Buku implements Peminjaman, Identitas): satu class mengikuti dua kontrak sekaligus, dipisah koma. Java membatasi extends class menjadi satu, tetapi jumlah interface tidak dibatasi.
  2. Baris 9-11 (@Override public void pinjam()): implementasi method interface wajib public. Compiler javac menolak implementasi dengan akses lebih sempit dan mengeluarkan pesan attempting to assign weaker access privileges; was public.
  3. Baris 10 (LAMA_PINJAM_HARI): konstanta dipakai langsung di dalam class karena field interface bersifat static — nilainya milik interface, bukan milik objek.
  4. Peminjaman pinjaman = buku; dan Identitas kartu = buku;: satu objek Buku dapat ditampung variabel bertipe interface mana pun yang class-nya implementasikan. Variabel pinjaman hanya bisa memanggil pinjam() dan kembali(); method id() tidak terlihat dari variabel tersebut.
  5. buku.sedangDipinjam(): method di luar kontrak hanya bisa dipanggil lewat variabel bertipe Buku.

Perilaku pada poin 4 adalah bentuk polimorfisme lewat interface — dan inilah fondasi bagi banyak API Java: List, Comparable, dan Runnable semuanya interface.

Field Interface Selalu Konstanta #

Karena field interface implisit static final, nilainya tidak bisa diubah setelah kompilasi. Berkas berikut mencoba melanggar aturan itu:

public class UbahKonstantaGagal {
    public static void main(String[] args) {
        // Baris ini sengaja salah: LAMA_PINJAM_HARI adalah konstanta final.
        Peminjaman.LAMA_PINJAM_HARI = 7;
        System.out.println("Batas baru: " + Peminjaman.LAMA_PINJAM_HARI);
    }
}

Compiler javac menolak program tersebut dengan pesan:

text
error: cannot assign a value to static final variable LAMA_PINJAM_HARI

Anotasi Override Menjaga Kontrak #

Anotasi @Override pada contoh di atas bukan hiasan. Anotasi tersebut memerintahkan compiler memeriksa bahwa method benar-benar mengimplementasikan method dari supertype. Jika kita salah mengetik nama method — misalnya pinjamKembali() alih-alih kembali() — compiler mengeluarkan pesan method does not override or implement a method from a supertype. Tanpa @Override, method yang salah nama akan diperlakukan sebagai method baru biasa, kontrak tidak terpenuhi, dan class menjadi abstrak tanpa sengaja. Kebiasaan menulis @Override setiap kali mengimplementasikan method interface adalah cara termurah mencegah bug ketik.

Interface ternyata tidak berhenti pada method abstrak murni. Sejak Java 8, interface boleh memuat method dengan body berupa default dan static — fitur yang lahir dari kebutuhan memperluas interface lama tanpa merusak class implementor yang sudah ada. Chapter Default dan Static Interface Methods akan membahas keduanya beserta jebakan konflik diamond-nya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

Identitas.java

Unduh berkas
java
// Interface kedua: setiap class implementor wajib punya penanda unik.
interface Identitas {
    String id();
}

Peminjaman.java

Unduh berkas
java
// Interface: kontrak perilaku tanpa state instance.
interface Peminjaman {
    // Field interface secara implisit public static final (konstanta).
    int LAMA_PINJAM_HARI = 14;

    // Method interface secara implisit public dan abstrak.
    void pinjam();

    void kembali();
}

PerpustakaanMini.java

Unduh berkas
java
class Buku implements Peminjaman, Identitas {
    private final String judul;
    private final String isbn;
    private boolean dipinjam;

    public Buku(String judul, String isbn) {
        this.judul = judul;
        this.isbn = isbn;
        this.dipinjam = false;
    }

    @Override
    public void pinjam() {
        dipinjam = true;
        System.out.println(judul + " dipinjam, batas " + LAMA_PINJAM_HARI + " hari");
    }

    @Override
    public void kembali() {
        dipinjam = false;
        System.out.println(judul + " dikembalikan");
    }

    @Override
    public String id() {
        return isbn;
    }

    public boolean sedangDipinjam() {
        return dipinjam;
    }
}

public class PerpustakaanMini {
    public static void main(String[] args) {
        Buku buku = new Buku("Java Dasar", "978-602-1234-01-5");

        // Variabel bertipe interface hanya mengenal anggota kontraknya.
        Peminjaman pinjaman = buku;
        Identitas kartu = buku;

        pinjaman.pinjam();
        System.out.println("ID: " + kartu.id());
        pinjaman.kembali();

        System.out.println("Status dipinjam: " + buku.sedangDipinjam());
    }
}

UbahKonstantaGagal.java

Unduh berkas
java
public class UbahKonstantaGagal {
    public static void main(String[] args) {
        // Baris ini sengaja salah: LAMA_PINJAM_HARI adalah konstanta final.
        Peminjaman.LAMA_PINJAM_HARI = 7;
        System.out.println("Batas baru: " + Peminjaman.LAMA_PINJAM_HARI);
    }
}