Interface

Mendeklarasikan kontrak murni dengan interface dan memanfaatkan polimorfisme lewat tipe kontraknya.

Daftar isi

Interface adalah tipe yang hanya menetapkan kontrak metode tanpa menyimpan keadaan, sehingga kelas mana pun yang menyediakan isinya dapat dipakai secara bergantian di tempat kontrak itu dituntut. Topik ini memandu deklarasi interface pertama, penerapannya di beberapa kelas yang tidak memiliki hubungan pewarisan, serta pemakaian tipe interface untuk variabel, parameter, dan elemen array. Setelah contoh dijalankan, pergantian implementasi cukup dilakukan di satu titik, yaitu saat objek dibuat, sementara seluruh kode pemakainya tetap utuh.

Mendeklarasikan interface#

Interface dideklarasikan dengan kata kunci interface, dan metodenya ditulis tanpa isi seperti metode abstrak, tetapi tanpa kata kunci abstract. Java memperlakukan metode di dalam interface sebagai public abstract secara implisit, sehingga setiap kelas yang mengimplementasikannya wajib menyediakan isinya. Kelas mengikat dirinya pada kontrak lewat kata kunci implements, dan satu kelas boleh mengimplementasikan lebih dari satu interface. Kode berikut menetapkan kontrak pengukuran bernama Pengukur beserta dua kelas sensor yang tidak saling berhubungan.

java DemoSensor.java
public class DemoSensor {

    public static void main(String[] args) {
        SensorSuhu suhu = new SensorSuhu("Gudang A", 27.6);
        SensorKelembapan lembap = new SensorKelembapan("Gudang A", 64.0);

        System.out.println(suhu.nama() + ": " + suhu.baca());
        System.out.println(lembap.nama() + ": " + lembap.baca());
    }
}

interface Pengukur {
    String nama();

    double baca();
}

class SensorSuhu implements Pengukur {
    String lokasi;
    double celsius;

    SensorSuhu(String lokasi, double celsius) {
        this.lokasi = lokasi;
        this.celsius = celsius;
    }

    public String nama() {
        return "sensor suhu " + lokasi;
    }

    public double baca() {
        return celsius;
    }
}

class SensorKelembapan implements Pengukur {
    String lokasi;
    double persen;

    SensorKelembapan(String lokasi, double persen) {
        this.lokasi = lokasi;
        this.persen = persen;
    }

    public String nama() {
        return "sensor kelembapan " + lokasi;
    }

    public double baca() {
        return persen;
    }
}
text Keluaran
sensor suhu Gudang A: 27.6
sensor kelembapan Gudang A: 64.0

Menjalankan programnya dari terminal cukup dengan satu perintah berikut.

bash
# berada di folder tempat DemoSensor.java disimpan
java DemoSensor.java
text Keluaran
sensor suhu Gudang A: 27.6
sensor kelembapan Gudang A: 64.0

Kelas SensorSuhu dan SensorKelembapan tidak punya hubungan apa pun selain menepati kontrak yang sama. Perhatikan bahwa implementasi nama() dan baca() pada keduanya selalu diberi public. Metode interface secara implisit bersifat publik, sehingga implementasi dengan akses lebih sempit ditolak kompiler dengan pesan attempting to assign weaker access privileges; was public. Penulisan public secara eksplisit memang lazim ditinggalkan di dalam interface, tetapi wajib dituliskan pada saat metode diisi di kelasnya.

Polimorfisme lewat tipe kontrak#

Tipe interface berperan seperti tipe induk pada polimorfisme, yaitu variabel, parameter, dan elemen array bertipe kontrak boleh menampung objek dari kelas pengimplementasi mana pun. Dynamic dispatch yang berlaku pada kelas juga bekerja sama persis di sini, yaitu metode yang berjalan dipilih menurut tipe objeknya saat program berjalan, bukan menurut tipe variabelnya. Program berikut menyusun laporan dari tiga sensor berbeda jenis tanpa satu pun percabangan yang memeriksa jenisnya.

java LaporanPengukuran.java
public class LaporanPengukuran {

    public static void main(String[] args) {
        Pengukur[] deret = {
            new SensorSuhu("Kandang", 29.4),
            new SensorKelembapan("Kandang", 71.5),
            new SensorSuhu("Kantor", 24.8)
        };

        for (Pengukur p : deret) {
            System.out.println(p.nama() + " membaca " + p.baca());
        }
        System.out.printf("Rata-rata: %.2f%n", rataRata(deret));
    }

    static double rataRata(Pengukur[] pengukur) {
        double total = 0;
        for (Pengukur p : pengukur) {
            total = total + p.baca();
        }
        return total / pengukur.length;
    }
}

interface Pengukur {
    String nama();

    double baca();
}

class SensorSuhu implements Pengukur {
    String lokasi;
    double celsius;

    SensorSuhu(String lokasi, double celsius) {
        this.lokasi = lokasi;
        this.celsius = celsius;
    }

    public String nama() {
        return "sensor suhu " + lokasi;
    }

    public double baca() {
        return celsius;
    }
}

class SensorKelembapan implements Pengukur {
    String lokasi;
    double persen;

    SensorKelembapan(String lokasi, double persen) {
        this.lokasi = lokasi;
        this.persen = persen;
    }

    public String nama() {
        return "sensor kelembapan " + lokasi;
    }

    public double baca() {
        return persen;
    }
}
text Keluaran
sensor suhu Kandang membaca 29.4
sensor kelembapan Kandang membaca 71.5
sensor suhu Kantor membaca 24.8
Rata-rata: 41.90

Metode rataRata hanya bergantung pada janji baca() milik Pengukur, sehingga menambah jenis sensor baru, misalnya sensor tekanan udara, tidak menyentuh metode itu sama sekali. Cukup kelas baru yang dideklarasikan implements Pengukur, dan seluruh kode pemakai langsung mengenalinya. Hubungan kontrak dengan pengimplementasinya digambarkan berikut ini.

Tipe kontrak Pengukur diikuti dua kelas pengimplementasinyainterface Pengukurnama()baca()class SensorSuhulokasi, celsiusclass SensorKelembapanlokasi, persenimplementsimplements
Tipe kontrak Pengukur diikuti dua kelas yang tidak berhubungan pewarisan

Banyak interface pada satu kelas#

Satu kelas boleh mengimplementasikan banyak interface sekaligus, dan di titik ini interface melampaui kelas abstrak yang hanya dapat diwarisi satu. Kemampuan-kemampuan kecil yang menyeberang hierarki kelas, misalnya kemampuan dinyalakan atau dijadwalkan, paling alami dimodelkan sebagai kontrak terpisah, lalu dilekatkan ke kelas yang memang membutuhkannya. Contoh berikut memberi dua kemampuan kepada satu kelas PompaAir.

java UjiBanyakInterface.java
public class UjiBanyakInterface {

    public static void main(String[] args) {
        PompaAir pompa = new PompaAir();

        nyalakan(pompa);
        pompa.mati();
        cetakJadwal(pompa, 30);
    }

    static void nyalakan(Saklar saklar) {
        saklar.nyala();
    }

    static void cetakJadwal(Terjadwal terjadwal, int menit) {
        System.out.println("Menyala tiap " + menit + " menit, durasi "
                + terjadwal.durasiDetik() + " detik.");
    }
}

interface Saklar {
    void nyala();

    void mati();
}

interface Terjadwal {
    int durasiDetik();
}

class PompaAir implements Saklar, Terjadwal {
    public void nyala() {
        System.out.println("Pompa air menyala.");
    }

    public void mati() {
        System.out.println("Pompa air berhenti.");
    }

    public int durasiDetik() {
        return 120;
    }
}
text Keluaran
Pompa air menyala.
Pompa air berhenti.
Menyala tiap 30 menit, durasi 120 detik.

Objek pompa dapat dikirim ke metode nyalakan karena memenuhi kontrak Saklar, dan dapat dikirim ke cetakJadwal karena memenuhi kontrak Terjadwal, tanpa kedua metode itu perlu saling mengenal. Pewarisan ganda pada interface diperbolehkan karena kontraknya tidak membawa field instance, sehingga tidak ada konflik keadaan yang harus diwaspadai. Konflik baru mungkin muncul bila kedua interface sama-sama membawa metode dengan isi, dan aturan penyelesaiannya dibahas pada topik metode default dan static.

Memilih interface atau kelas abstrak#

Keduanya sama-sama mendefinisikan kontrak, sehingga pilihannya jatuh pada apa yang ikut diwariskan. Interface dipilih bila yang dibagi hanya kemampuan tanpa keadaan, terutama bila kemampuan itu harus dilekatkan pada kelas-kelas yang tidak seharusnya satu hierarki. Kelas abstrak dipilih bila turunannya berbagi field dan sebagian implementasi, sebagaimana kelas Bentuk yang membawa nama bersama bagi semua bentuknya. Banyak tipe inti di pustaka standar Java berbentuk interface, misalnya List untuk daftar, Map untuk pasangan kunci-nilai, Comparable untuk pembandingan, dan Runnable untuk tugas yang dijalankan thread. Semuanya menegaskan pola yang sama, yaitu pemakai berhenti pada tipe kontraknya sedangkan detail implementasinya bebas berganti.

Kesalahan umum#

  • Melupakan public pada implementasi metode interface. Metode di interface implisit publik, sehingga implementasi tanpa public ditolak kompiler dengan pesan attempting to assign weaker access privileges. Beri public pada setiap metode yang mengisi kontrak.
  • Mencoba membuat objek dari interface. Pernyataan new Pengukur() ditolak dengan pesan Pengukur is abstract; cannot be instantiated, karena interface tidak punya konstruktor dan tidak menyimpan keadaan. Yang dibuat adalah objek kelas pengimplementasinya.
  • Menyisakan satu metode kontrak yang belum diisi. Kelas yang belum melengkapi seluruh metode abstrak ditolak dengan pesan is not abstract and does not override abstract method. Lengkapi metodenya, atau nyatakan kelas itu abstract juga.
  • Mengira variabel bertipe interface mengenal metode kelasnya. Variabel Pengukur p hanya dapat memanggil nama() dan baca(), kendati objeknya sebenarnya SensorSuhu. Metode khas kelasnya baru dapat dipanggil setelah downcast, sebagaimana pada polimorfisme kelas.

Ringkasan#

  • Interface menetapkan kontrak metode tanpa keadaan, dan kelas menepatinya lewat implements dengan mengisi seluruh metodenya sebagai public.
  • Variabel, parameter, dan elemen array bertipe interface menerima objek kelas pengimplementasi mana pun, sehingga dynamic dispatch bekerja seperti pada tipe induk.
  • Satu kelas boleh mengimplementasikan banyak interface karena kontraknya tidak membawa field instance, sehingga tidak ada konflik keadaan.
  • Interface dipilih untuk kemampuan yang menyeberang hierarki kelas, sedangkan kelas abstrak dipilih bila keadaan dan kode bersama ikut diwariskan.

Referensi#