Kelas dan Metode Abstrak

Menetapkan kontrak parsial lewat kelas abstrak dan mengetahui kapan kelas biasa tak lagi memadai.

Daftar isi

Kelas abstrak (abstract class) adalah kelas yang diperuntukkan menjadi induk, bukan bahan pembuatan objek, dan boleh memuat metode abstrak yang isinya dititipkan kepada turunan. Kombinasi ini menghasilkan kontrak parsial, yaitu sebagian perilaku ditetapkan tanpa isi dan sebagian lainnya sudah disediakan induk. Topik ini menunjukkan kapan kelas biasa tak lagi memadai, cara mendeklarasikannya, dan kapan pilihan jatuh ke interface.

Kapan kelas biasa tak lagi memadai#

Kelas dasar yang masih konkret menyimpan godaan berbahaya, yaitu perilaku default yang tidak bermakna. Contoh pada topik polimorfisme memuat luas() yang mengembalikan 0 di kelas Bentuk, nilai yang hanya berfungsi membuat kode lolos kompilasi. Turunan yang lupa mengoverride tidak tertangkap kompiler, karena 0 terasa sah, dan hasil hitungnya merusak program secara diam-diam. Kelas seperti ini sebenarnya tidak pernah dijadikan objek secara waras, karena new Bentuk() tidak mewakili keadaan apa pun.

Situasi itu adalah sinyal desain, bukan kebetulan. Bila sebuah kelas hanya berdiri untuk diperluas, janji-janjinya sebaiknya ditegakkan pada turunan, bukan ditutupi nilai kosong. Java menyediakan mekanisme itu lewat kata kunci abstract, yang membuat metode tanpa isi wajib diimplementasikan turunan dan membuat kelasnya tidak dapat diinstansiasi. Kontrak menjadi ketat di sisi yang memang harus berbeda, sementara kode yang sama tetap ditulis sekali di induk.

Mendeklarasikan kelas dan metode abstrak#

Kata kunci abstract dipasang di depan kelas untuk melarang instansiasi, dan di depan metode untuk menyerahkan isinya kepada turunan. Metode abstrak diakhiri titik koma tanpa badan, dan setiap turunan konkret wajib mengimplementasikan seluruh metode abstrak yang diwarisinya. Di sisi lain, kelas abstrak tetap boleh memuat field, konstruktor, dan metode konkret, karena turunannya memang butuh fondasi itu.

java DemoKelasAbstrak.java
public class DemoKelasAbstrak {
    public static void main(String[] args) {
        Bentuk[] daftar = {
            new Lingkaran(7),
            new Persegi(5)
        };

        for (Bentuk b : daftar) {
            System.out.println(b.deskripsi());
        }
    }
}

abstract class Bentuk {
    String nama;

    Bentuk(String nama) {
        this.nama = nama;
    }

    abstract double luas();

    String deskripsi() {
        return nama + " seluas " + luas();
    }
}

class Lingkaran extends Bentuk {
    double jariJari;

    Lingkaran(double jariJari) {
        super("lingkaran");
        this.jariJari = jariJari;
    }

    @Override
    double luas() {
        return Math.PI * jariJari * jariJari;
    }
}

class Persegi extends Bentuk {
    double sisi;

    Persegi(double sisi) {
        super("persegi");
        this.sisi = sisi;
    }

    @Override
    double luas() {
        return sisi * sisi;
    }
}
text Keluaran
lingkaran seluas 153.93804002589985
persegi seluas 25.0

Metode luas() kini dinyatakan tanpa isi, sehingga Lingkaran dan Persegi wajib menyediakan rumusnya. Kelalaian itu tidak lagi tertutup nilai 0, melainkan berhenti di layar kompilasi dengan pesan class Lingkaran is not abstract and does not override abstract method luas() in Bentuk. Sementara itu, deskripsi() tetap ditulis sekali di induk dan memanggil luas() tanpa peduli rumus siapa yang berjalan. Dengan begitu, bagian yang sama kini berada di induk dan bagian yang berbeda berada di turunannya masing-masing.

Objek kelas abstrak tidak dapat dibuat#

Kelas abstrak melarang new secara langsung, dan larangan itu justru nilai jualnya. Tidak ada lagi objek setengah jadi yang berkeliling memakai metode yang seharusnya diimplementasikan turunan. Variabel bertipe kelas abstrak tetap sah dan justru lazim, karena yang disimpan adalah objek turunannya, sedangkan larangan hanya berlaku pada pembuatan objek dari kelas itu sendiri.

java Contoh: instansiasi kelas abstrak ditolak kompiler
// tidak-dijalankan
Bentuk b = new Bentuk("bentuk");
// error: Bentuk is abstract; cannot be instantiated

Turunan yang belum siap mengisi seluruh kontrak pun masih punya jalan, yaitu menyatakan dirinya abstrak juga dan menyisakan sebagian metode abstrak kepada turunan berikutnya. Rantai ini memungkinkan hierarki bertingkat, misalnya kelas menengah yang menyediakan sebagian perilaku umum namun masih menunggu detail dari turunan paling bawah.

Kontrak parsial dengan metode konkret di induk#

Kekuatan sebenarnya muncul ketika metode konkret di induk menyusun langkah-langkah yang tetap, sementara langkah yang berbeda tiap turunan dinyatakan abstrak. Pola ini dikenal sebagai pola template method, yaitu kerangka proses dipatok di induk dan detailnya diisi turunan. Contoh berikut mematok urutan ekspor laporan, yaitu header lalu isi, dengan isi diserahkan kepada tiap bentuk ekspor.

java UjiKontrakParsial.java
public class UjiKontrakParsial {
    public static void main(String[] args) {
        EksporTeks ekspor = new EksporTeks("Rata-rata ujian", 82.5);

        System.out.println(ekspor.ekspor());
    }
}

abstract class EksporLaporan {
    String judul;
    double nilai;

    EksporLaporan(String judul, double nilai) {
        this.judul = judul;
        this.nilai = nilai;
    }

    String ekspor() {
        return header() + "\n" + isi();
    }

    String header() {
        return "=== " + judul + " ===";
    }

    abstract String isi();
}

class EksporTeks extends EksporLaporan {
    EksporTeks(String judul, double nilai) {
        super(judul, nilai);
    }

    @Override
    String isi() {
        return "Nilai " + judul + ": " + nilai;
    }
}
text Keluaran
=== Rata-rata ujian ===
Nilai Rata-rata ujian: 82.5

Metode ekspor() konkret menjamin urutan kerjanya sama untuk semua turunan, sedangkan isi() abstrak menjamin setiap turunan menyediakan bagian yang berbeda. Bila kelak ada EksporCsv, satu metode isi() saja yang ditulis dan urutannya otomatis mengikuti induk. Kontrak semacam ini sulit ditegakkan dengan kelas biasa, karena tidak ada cara memaksa turunan menyediakan metode tertentu tanpa menyembunyikan default yang meragukan.

Kelas abstrak dan interface#

Kelas abstrak dan interface sama-sama mendefinisikan kontrak, dan perbedaannya terletak pada apa yang boleh dibawa. Kelas abstrak membawa keadaan, yaitu field dan konstruktor, serta dapat diwariskan hanya oleh satu kelas. Interface mengatur perilaku tanpa keadaan, dan satu kelas boleh mengimplementasikan banyak interface sekaligus.

AspekKelas abstrakInterface
Field instanceboleh, lengkap dengan konstruktorhanya konstanta
Warisansatu kelas induk sajabanyak interface sekaligus
Metode konkretboleh, untuk kode yang dibagiboleh, sebagai default
Paling pas untukberbagi keadaan dan kode antarturunankemampuan yang dilekatkan lintas kelas

Pilihan praktisnya sederhana. Bila turunan-turunannya berbagi data dan sebagian implementasi, kelas abstrak adalah tempatnya, sebagaimana Bentuk membawa nama dan deskripsi(). Bila yang dibagi hanya kemampuan tanpa keadaan, misalnya kemampuan dibandingkan atau dicetak, interface lebih ringan dan tidak mengunci satu-satunya jatah pewarisan kelas.

Kesalahan umum#

  • Mengira kelas abstrak tidak boleh punya konstruktor. Konstruktor tetap diperlukan, karena konstruktor turunan memanggilnya lewat super(...) untuk mengisi field induk. Yang dilarang adalah new terhadap kelas abstraknya sendiri.
  • Turunan tidak mengimplementasikan semua metode abstrak. Kompiler menolak dengan pesan is not abstract and does not override abstract method, dan perbaikannya dua arah, yaitu mengisi metodenya atau menyatakan turunan itu abstrak juga.
  • Memasang abstract pada metode static atau private. Keduanya tidak dapat dioverride, sehingga kontraknya mustahil dipenuhi turunan, dan kompiler menolaknya. Metode abstrak harus bisa dicapai dan diganti oleh turunan.
  • Membuat kelas abstrak untuk satu turunan saja. Abstraksi yang tidak pernah dibagi adalah beban tambahan tanpa manfaat. Tahan diri hingga turunan kedua benar-benar muncul dan bagian bersamanya terlihat.

Ringkasan#

  • Kelas abstrak tidak dapat diinstansiasi dan diperuntukkan menjadi induk yang membawa field, konstruktor, serta metode konkret.
  • Metode abstrak dinyatakan tanpa isi dan wajib diimplementasikan turunan konkret, sehingga kelalaian berhenti di kompilasi.
  • Pola template method mematok kerangka proses di induk sementara langkah yang berbeda diserahkan kepada turunan.
  • Kelas abstrak dipilih bila turunan berbagi keadaan, sedangkan interface dipilih untuk kemampuan tanpa keadaan yang menyeberang hierarki.

Referensi#