Chapter Polymorphism memperlihatkan bagaimana satu variabel bertipe parent dapat menampung objek dari berbagai subclass. Namun ada situasi di mana parent tersebut sebenarnya tidak punya wujud konkret yang masuk akal: sebuah “Bentuk” pada umumnya tidak punya satu rumus luas yang berlaku untuk semua bangun. Kalau kita tetap menginstansiasi parent seperti itu, method-nya hanya berisi nilai palsu seperti return 0. Abstract class hadir untuk mengatasi masalah ini: class yang boleh memuat method tanpa body dan dilarang diinstansiasi sehingga berperan sebagai cetak biru parsial. Pada Chapter ini kita akan mendeklarasikan abstract class, memahami peran constructor di dalamnya, dan membandingkannya dengan interface.

Mengapa Membutuhkan Class Abstrak #

Bayangkan class Bentuk yang dimaksudkan sebagai parent bagi Lingkaran, Persegi, dan Segitiga. Setiap bangun pasti bisa menghitung luas dan keliling, tetapi rumusnya berbeda untuk tiap bangun. Jika Bentuk berupa class biasa, kita terpaksa mengisi luas() dengan nilai seperti 0 atau melempar exception — dua-duanya menutup kesalahan desain, bukan memperbaikinya.

Abstract class menyelesaikan dua kebutuhan sekaligus. Pertama, class tersebut tetap bisa memuat field, constructor, dan method konkret yang dipakai bersama oleh semua subclass. Kedua, kata kunci abstract pada sebuah method menyatakan bahwa subclass-lah yang wajib menyediakan implementasi. Compiler javac kemudian menjaga kontrak ini: jika sebuah subclass lupa mengimplementasikan method abstrak, proses kompilasi subclass tersebut gagal.

Mendeklarasikan Abstract Class #

Kita mulai dengan class abstrak Bentuk untuk domain bangun datar. Perhatikan bahwa luas() dan keliling() hanya berupa deklarasi tanpa body, sementara deskripsi() adalah method konkret yang langsung memakai keduanya:

java Bentuk.java
// Class abstrak: cetak biru parsial yang tidak bisa diinstansiasi langsung.
abstract class Bentuk {
    // Field dan constructor tetap boleh ada: dipakai oleh subclass lewat super().
    protected final String nama;

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

    // Method abstrak: hanya deklarasi, tanpa body.
    // Setiap subclass wajib menyediakan implementasinya.
    public abstract double luas();

    public abstract double keliling();

    // Method konkret: perilaku bersama yang diwariskan apa adanya.
    public String deskripsi() {
        return nama + " dengan luas " + String.format("%.2f", luas());
    }
}

Selanjutnya, dua subclass mengisi implementasi yang belum ada. Method abstrak luas() dan keliling() diubah menjadi method konkret dengan @Override, lalu kita jalankan semuanya lewat sebuah array bertipe Bentuk:

class Lingkaran extends Bentuk {
    private final double radius;

    public Lingkaran(double radius) {
        super("Lingkaran");
        this.radius = radius;
    }

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

    @Override
    public double keliling() {
        return 2 * Math.PI * radius;
    }
}

class Persegi extends Bentuk {
    private final double sisi;

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

    @Override
    public double luas() {
        return sisi * sisi;
    }

    @Override
    public double keliling() {
        return 4 * sisi;
    }
}

public class BangunDatarAbstrak {
    public static void main(String[] args) {
        // Variabel bertipe Bentuk boleh menampung objek subclass-nya.
        Bentuk[] daftar = { new Lingkaran(7), new Persegi(5) };

        for (Bentuk b : daftar) {
            System.out.println(b.deskripsi());
            System.out.printf("  keliling: %.2f%n", b.keliling());
        }
    }
}

Output yang Dihasilkan:

text
Lingkaran dengan luas 153.94
  keliling: 43.98
Persegi dengan luas 25.00
  keliling: 20.00

Mari kita bedah bagian-bagian pentingnya:

  1. Baris 1 (abstract class Bentuk {): modifier abstract pada class memberi dua efek — class ini tidak bisa diinstansiasi, dan class ini boleh memiliki method abstrak.
  2. Baris 4-7 (protected Bentuk(String nama)): constructor pada class abstrak tetap sah. Constructor tersebut dipanggil oleh subclass melalui super("Lingkaran").
  3. Baris 11-17 (public abstract double luas();): deklarasi method tanpa body diakhiri titik koma. Method ini adalah kontrak yang harus dipenuhi setiap subclass.
  4. Baris 20-22 (public String deskripsi()): method konkret boleh memanggil method abstrak. Saat runtime, JVM menjalankan versi luas() milik objek aktual melalui dynamic dispatch.
  5. Bentuk[] daftar = { ... }: polimorfisme tetap bekerja penuh; kita mengelola berbagai bangun lewat satu tipe parent.

Class Abstrak Tidak Dapat Diinstansiasi #

Apa yang terjadi kalau kita tetap memaksa membuat objek dari class abstrak? Compiler menolaknya sebelum program sempat berjalan. Berkas berikut sengaja berisi upaya instansiasi yang salah:

public class InstansiasiGagal {
    public static void main(String[] args) {
        // Baris ini sengaja salah: class abstrak tidak bisa diinstansiasi.
        Bentuk b = new Bentuk("Segitiga");
        System.out.println(b.deskripsi());
    }
}

Saat dikompilasi, javac mengeluarkan pesan error berikut (dipotong pada bagian intinya):

text
error: Bentuk is abstract; cannot be instantiated

Pesan ini penting dikenali karena compiler memakainya untuk menjaga desain kita. Ingat pula bahwa method abstrak hanya boleh hidup di dalam class abstrak — mendeklarasikan abstract double luas(); di class biasa juga memicu compile error.

Constructor Tetap Ada untuk Subclass #

Class abstrak tidak bisa diinstansiasi, tetapi justru boleh punya constructor. Constructor tersebut bertugas menginisialisasi field warisan (nama) agar subclass tidak mengulang logika yang sama di setiap constructor-nya. Pada contoh di atas, Lingkaran dan Persegi cukup memanggil super(...) satu baris; inisialisasi terpusat di Bentuk. Tanpa constructor ini, setiap subclass harus mengisi nama sendiri dan risiko field terlewat menjadi besar.

Abstract Class vs Interface #

Abstract class dan interface sama-sama mendefinisikan kontrak; pembedaan praktisnya sebagai berikut:

AspekAbstract classInterface
Bentuk kontrakParsial: field, constructor, method konkret + abstrakPerilaku: method abstrak, default, dan static
Jumlah per classSatu saja (extends)Banyak sekaligus (implements)
FieldBebas, bisa berupa state instanceHanya konstanta public static final
Kapan dipilihSubclass berbagi state dan kode bersamaClass beragam berbagi perilaku yang sama

Aturan praktisnya: jika Lingkaran, Persegi, dan Segitiga memang satu keluarga yang berbagi field serta method konkret, abstract class cocok. Jika yang dibutuhkan hanya janji perilaku — misalnya “bisa didaur ulang” atau “bisa dicetak” — interface lebih pas; Chapter berikutnya membahasnya secara khusus.

Dengan abstract class, hierarki bangun datar kita kini punya parent yang jujur: Bentuk menyatakan kontrak luas dan keliling tanpa memalsukan implementasi. Namun Java juga menawarkan cara lain mendefinisikan kontrak murni tanpa state sama sekali — interface — yang mendukung implementasi ganda pada satu class. Chapter Interfaces akan mengupasnya tuntas.

Berkas Sumber

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

BangunDatarAbstrak.java

Unduh berkas
java
class Lingkaran extends Bentuk {
    private final double radius;

    public Lingkaran(double radius) {
        super("Lingkaran");
        this.radius = radius;
    }

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

    @Override
    public double keliling() {
        return 2 * Math.PI * radius;
    }
}

class Persegi extends Bentuk {
    private final double sisi;

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

    @Override
    public double luas() {
        return sisi * sisi;
    }

    @Override
    public double keliling() {
        return 4 * sisi;
    }
}

public class BangunDatarAbstrak {
    public static void main(String[] args) {
        // Variabel bertipe Bentuk boleh menampung objek subclass-nya.
        Bentuk[] daftar = { new Lingkaran(7), new Persegi(5) };

        for (Bentuk b : daftar) {
            System.out.println(b.deskripsi());
            System.out.printf("  keliling: %.2f%n", b.keliling());
        }
    }
}

Bentuk.java

Unduh berkas
java
// Class abstrak: cetak biru parsial yang tidak bisa diinstansiasi langsung.
abstract class Bentuk {
    // Field dan constructor tetap boleh ada: dipakai oleh subclass lewat super().
    protected final String nama;

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

    // Method abstrak: hanya deklarasi, tanpa body.
    // Setiap subclass wajib menyediakan implementasinya.
    public abstract double luas();

    public abstract double keliling();

    // Method konkret: perilaku bersama yang diwariskan apa adanya.
    public String deskripsi() {
        return nama + " dengan luas " + String.format("%.2f", luas());
    }
}

InstansiasiGagal.java

Unduh berkas
java
public class InstansiasiGagal {
    public static void main(String[] args) {
        // Baris ini sengaja salah: class abstrak tidak bisa diinstansiasi.
        Bentuk b = new Bentuk("Segitiga");
        System.out.println(b.deskripsi());
    }
}