Pilar OOP di Java: Encapsulation, Inheritance, Polymorphism, dan Abstraction

Hai, teman-teman! Pagi ini pemilik Warteg Canggih datang ke meja kasir membawa tiga catatan, dan tanpa dia sadari, ketiganya adalah pelajaran pilar OOP di Java. Pertama, ada baris kode yang tidak sengaja menulis esTeh.harga = -3000, dan kasir versi 12 menerimanya begitu saja. Kedua, dia mau menjual Paket Hemat (nasi, tempe orek, es teh) yang di daftar menu ikut menampilkan isi paketnya. Ketiga, dia mau ada diskon khusus jam sepi, tapi tanpa harus membongkar method diskon setiap kali aturannya berganti.

Tiga permintaan ini kelihatannya tidak berhubungan, tapi ternyata dijawab oleh empat prinsip yang sama. Di artikel Class dan Object kita sudah membuat class Menu dan Kasir. Sekarang saatnya class-class itu dipakai dengan benar.

Sampul seri belajar Java Studi Kasus Warteg Canggih bagian 13: Pilar OOP di Java, dengan ilustrasi struk kasir warteg
Seri Warteg Canggih bagian 13: kasir belajar menjaga harga, menjual Paket Hemat, dan menukar aturan diskon

TL;DR: OOP di Java biasa dirangkum menjadi empat pilar. Encapsulation menyembunyikan field dengan private dan membukanya lewat method, sehingga harga tidak bisa diubah sembarangan. Inheritance (extends) membuat class turunan, seperti MenuPaket yang mewarisi Menu. Polymorphism membuat satu perintah yang sama, misalnya tampilkan(), berperilaku sesuai jenis object-nya. Abstraction (interface) membuat kasir cukup tahu "apa" yang dikerjakan aturan diskon, bukan "bagaimana" caranya.

Kartu istilah Polymorphism atau polimorfisme: Kemampuan satu perintah yang sama berperilaku berbeda sesuai jenis object yang menerimanya
Perintah tampilkan() yang sama, tapi MenuPaket ikut menampilkan isi paketnya

Yang Sudah Kita Pelajari

Artikel ini memakai bekal dari beberapa artikel sebelumnya:

Tujuan Artikel Ini

Di sini kamu akan belajar:

  • Menjaga harga menu dengan encapsulation, sehingga harga minus ditolak.
  • Membuat MenuPaket sebagai turunan Menu dengan extends dan super.
  • Memakai polymorphism lewat override tampilkan(), dan membedakannya dengan overload.
  • Membuat aturan diskon yang bisa ditukar dengan interface, dan memilih antara abstract class dan interface.
  • Membangun kasir versi 13 yang memakai keempat pilar sekaligus.
  • Mengenali error yang sering muncul saat belajar OOP di Java.

Kenalan Dulu: Apa Itu Pilar OOP di Java?

Berdasarkan pelajaran Object-Oriented Programming Concepts dari Oracle, konsep OOP di Java terdiri dari object, class, inheritance, interface, dan package. Pilar OOP di Java adalah cara populer merangkum konsep itu menjadi empat prinsip: encapsulation, inheritance, polymorphism, dan abstraction. Jumlahnya bukan aturan baku, tapi versi empat pilar inilah yang paling sering muncul di materi belajar maupun wawancara kerja. Keempatnya bukan fitur yang dipakai terpisah, tapi cara menyusun class supaya setiap class punya tanggung jawab yang jelas, mudah ditambah, dan susah dirusak dari luar.

Daftar Oracle dan empat pilar ini tidak bertentangan. Class adalah tempat encapsulation dipasang, dan interface adalah alat utama untuk abstraction. Supaya tidak jadi hafalan, kita pasangkan setiap pilar dengan catatan pemilik warteg tadi:

Pilar Inti idenya Masalah warteg yang diselesaikan
Encapsulation Data disembunyikan, hanya bisa diubah lewat method yang punya aturan Harga es teh tidak bisa diisi minus
Inheritance Class baru mewarisi isi class lain Paket Hemat tidak perlu menulis ulang nama dan harga
Polymorphism Perintah sama, hasil menyesuaikan jenis object Paket Hemat tampil lengkap dengan isinya
Abstraction Yang dipakai cukup "apa"-nya, "bagaimana"-nya disembunyikan Aturan diskon bisa ditukar tanpa mengubah kasir

Coba bayangin warteg itu sendiri. Pembeli tidak boleh masuk dapur dan mengubah resep (encapsulation). Sambal goreng ati adalah turunan dari resep sambal dasar (inheritance). "Tolong siapkan pesanan" dikerjakan beda oleh tukang masak dan tukang bikin es teh (polymorphism). Kasir cukup tahu "ini struknya diskon sekian", tidak perlu tahu rumus promonya (abstraction).

Encapsulation di Java: Harga Tidak Bisa Diubah Sembarangan

Encapsulation adalah cara menyembunyikan field di dalam class dengan private, lalu membukanya hanya lewat method. Data tetap bisa dibaca lewat getter, tapi perubahan wajib lewat method yang memeriksa aturannya dulu. Dengan begitu, nilai yang tidak masuk akal tidak bisa masuk sejak awal.

Di kasir versi 12, field Menu bisa diubah dari mana saja. Baris seperti ini lolos compile dan diam-diam merusak struk:

Menu esTeh = new Menu("Es teh", 3000);
esTeh.harga = -3000;   // es teh sekarang dibayar oleh warteg

Masalahnya bukan di baris itu saja. Selama field bisa disentuh langsung, siapa pun yang menulis kode kasir bisa membuat kesalahan yang sama, dan tidak ada satu tempat pun yang menjaga aturannya. Seperti dapur warteg tanpa pintu: niatnya tidak ada yang mengacak resep, tapi tidak ada juga yang mencegah.

Jadi kita pasang pintunya. Field dibuat private, nama dibuat final karena memang tidak pernah berubah, dan harga hanya bisa diganti lewat satu method:

class Menu {
    private final String nama;
    private int harga;

    Menu(String nama, int harga) {
        this.nama = nama;
        this.harga = harga;
    }

    String getNama() {
        return nama;
    }

    int getHarga() {
        return harga;
    }

    // satu-satunya pintu untuk mengubah harga, dan pintu ini punya aturan
    boolean ubahHarga(int hargaBaru) {
        if (hargaBaru <= 0) {
            return false;
        }
        harga = hargaBaru;
        return true;
    }

    // hitungHarga dan tampilkan sama seperti versi 12
}

Sekarang baris esTeh.harga = -3000 bahkan tidak bisa di-compile:

Coba.java:4: error: harga has private access in Menu
        esTeh.harga = -3000;
             ^

Perubahan harga yang sah tetap bisa, asal lewat pintunya:

Menu esTeh = new Menu("Es teh", 3000);
System.out.println(esTeh.ubahHarga(-3000));   // false, ditolak
System.out.println(esTeh.ubahHarga(3500));    // true
System.out.println(esTeh.getHarga());         // 3500

ubahHarga mengembalikan false supaya pemanggilnya tahu perubahannya ditolak. Nanti di artikel Custom Exception, penolakan seperti ini akan dibuat lebih tegas dengan exception. Selain private, Java punya tingkat akses lain (public, protected, dan tanpa modifier) yang dijelaskan di tutorial Controlling Access dari Oracle. Kalau mau membaca contoh lain, saya pernah menulis konsep enkapsulasi pada OOP Java dan modifier dalam Java di sufyan97.com.

Inheritance di Java: Paket Hemat Adalah Menu Juga

Inheritance adalah cara membuat class baru yang mewarisi field dan method class lain dengan kata kunci extends. Class yang diwarisi disebut superclass, dan class barunya disebut subclass. Subclass otomatis punya semua yang dimiliki superclass, lalu tinggal menambahkan yang berbeda.

Permintaan kedua pemilik warteg adalah Paket Hemat: nasi putih, tempe orek, dan es teh seharga Rp10.000. Cara paling cepat adalah membuat class baru dari nol, lengkap dengan nama, harga, getNama(), hitungHarga(), dan seterusnya. Tapi itu berarti menyalin hampir seluruh Menu, dan setiap kali Menu diperbaiki, salinannya harus ikut diperbaiki.

Padahal Paket Hemat memang sebuah menu, hanya saja punya isi. Hubungan "adalah" seperti ini yang cocok dengan inheritance. Analoginya seperti resep turunan di dapur: sambal goreng ati tidak menulis ulang cara membuat sambal dasar, cukup "pakai sambal dasar, lalu tambah ati".

class MenuPaket extends Menu {
    private final String[] isi;

    MenuPaket(String nama, int harga, String[] isi) {
        super(nama, harga);                       // nama dan harga tetap diurus oleh Menu
        this.isi = isi;
    }
}

Hanya dengan baris itu, MenuPaket sudah bisa getNama(), getHarga(), ubahHarga(), dan hitungHarga(), padahal tidak satu pun ditulis di dalamnya. Penjelasan lengkap soal pewarisan ada di tutorial Inheritance dari Oracle, dan contoh lain bisa dilihat di contoh coding inheritance pada Java.

Constructor Turunan Wajib Memanggil super

Perhatikan baris super(nama, harga). Field nama dan harga milik Menu bersifat private, jadi MenuPaket sendiri pun tidak bisa mengisinya langsung. Yang bisa mengisinya hanya constructor Menu, dan super(...) adalah cara memanggil constructor itu.

Kalau baris itu lupa ditulis, Java mencoba memanggil super() tanpa argumen secara otomatis. Masalahnya, Menu tidak punya constructor tanpa parameter, sama seperti jebakan default constructor di artikel Class dan Object:

MenuPaket.java:4: error: constructor Menu in class Menu cannot be applied to given types;
    MenuPaket(String nama, int harga, String[] isi) {
                                                    ^
  required: String,int
  found:    no arguments

Aturan lainnya: di JDK 21 yang dipakai seri ini, super(...) harus jadi perintah pertama di dalam constructor. Logikanya sederhana, bagian "menu"-nya harus jadi dulu sebelum bagian "paket"-nya ditambahkan. Detailnya ada di tutorial Using the Keyword super dari Oracle. Sejak JDK 25, aturan ini sedikit dilonggarkan lewat JEP 513: boleh ada perintah sebelum super(...), misalnya untuk memeriksa argumen, asal tidak memakai object yang sedang dibuat.

Polymorphism di Java: Satu Perintah, Hasilnya Menyesuaikan

Polymorphism adalah kemampuan object dari class berbeda untuk merespons perintah yang sama dengan caranya masing-masing. Di Java, caranya adalah override: subclass menulis ulang method milik superclass. Saat method itu dipanggil, Java menjalankan versi milik object yang sebenarnya, bukan versi milik tipe variabelnya.

Paket Hemat sudah bisa dibuat, tapi tampilannya masih sama dengan menu biasa: 6. Paket Hemat (Rp10000). Pembeli tidak tahu isinya apa. Kita butuh tampilkan() versi khusus paket:

class MenuPaket extends Menu {
    // field dan constructor sama seperti di atas

    @Override
    void tampilkan(int nomor) {
        super.tampilkan(nomor);
        System.out.println("   isi: " + String.join(", ", isi));
    }
}

super.tampilkan(nomor) memanggil versi Menu untuk mencetak baris nama dan harga, lalu baris berikutnya menambahkan isi paket. Jadi kita tidak menulis ulang tampilan dasarnya, cukup menambah.

Sekarang bagian yang paling menarik. Array Menu[] boleh berisi object MenuPaket, karena paket adalah menu juga:

Menu[] daftarMenu = {
    new Menu("Nasi putih", 5000),
    // ... menu lain ...
    new MenuPaket("Paket Hemat", 10000, new String[] {"Nasi putih", "Tempe orek", "Es teh"})
};

for (int i = 0; i < daftarMenu.length; i++) {
    daftarMenu[i].tampilkan(i + 1);
}

Hasilnya:

1. Nasi putih (Rp5000)
2. Tempe orek (Rp4000)
3. Es teh (Rp3000)
4. Telur balado (Rp6000)
5. Sayur asem (Rp4000)
6. Paket Hemat (Rp10000)
   isi: Nasi putih, Tempe orek, Es teh

Loop-nya tidak berubah satu huruf pun. Tidak ada if yang mengecek "ini paket atau bukan". Setiap object tahu cara menampilkan dirinya sendiri, dan itulah polymorphism. Kalau besok ada MenuPrasmanan dengan tampilan lain, loop ini tetap tidak perlu disentuh. Penjelasan resminya ada di tutorial Polymorphism dari Oracle.

Anotasi @Override sebenarnya opsional, tapi sangat dianjurkan. Kalau nama method-nya salah ketik, misalnya tampilkn, tanpa @Override Java menganggapnya method baru dan diam saja, sehingga tampilan paket tidak pernah muncul. Dengan @Override, compiler langsung protes:

MenuPaket.java:11: error: method does not override or implement a method from a supertype
    @Override
    ^

Override vs Overload

Dua istilah ini sering tertukar karena namanya mirip. Di artikel Method, kita sudah memakai overloading: dua cetakGaris dengan parameter berbeda di class yang sama.

Overload Override
Letak Di class yang sama Di subclass
Nama method Sama Sama
Parameter Harus berbeda Harus sama persis
Dipilih kapan Saat compile, dari argumen yang dikirim Saat program berjalan, dari jenis object
Contoh di kasir cetakGaris() dan cetakGaris(int) tampilkan(int) di Menu dan MenuPaket

Aturan lengkapnya ada di tutorial Overriding and Hiding Methods dari Oracle, dan perbandingan lain dengan contoh berbeda bisa dibaca di konsep override dan overload pada OOP Java.

Abstraction di Java: Aturan Diskon yang Bisa Ditukar

Abstraction adalah cara menyembunyikan detail "bagaimana" di balik kontrak "apa". Di Java, kontrak itu paling sering ditulis sebagai interface: daftar method yang wajib ada, tanpa isinya. Class yang implements interface itu yang menentukan cara kerjanya, dan pemakainya cukup mengenal interface-nya saja.

Sekarang permintaan ketiga. Aturan diskon kasir masih aturan lama sejak artikel Percabangan: belanja minimal Rp10.000 dapat potongan Rp1.000, minimal Rp20.000 dapat Rp2.000. Pemilik warteg ingin di jam sepi semua belanja dipotong 10 persen.

Cara paling langsung adalah menambah if di hitungDiskon: kalau jam sepi, pakai rumus ini, kalau tidak, pakai rumus itu. Begitu ada promo ketiga, if-nya bertambah lagi, dan Kasir harus diedit setiap kali promo berganti. Padahal Kasir sebenarnya cuma butuh satu hal: "berikan subtotal, dapatkan potongannya".

Coba bayangin kasir warteg sungguhan. Dia tidak hafal rumus semua promo. Dia cukup melihat kartu promo yang sedang dipajang hari ini, lalu memotong sesuai kartu itu. Kartunya bisa ditukar kapan saja, kasirnya tetap orang yang sama. Kartu promo itu adalah interface:

interface AturanDiskon {
    int hitung(int subtotal);
}

Interface ini tidak menghitung apa pun. Ia hanya berjanji: siapa pun yang mengaku AturanDiskon pasti punya method hitung. Isinya ditulis oleh class yang implements:

class DiskonBertingkat implements AturanDiskon {
    @Override
    public int hitung(int subtotal) {
        if (subtotal >= 20000) {
            return 2000;
        }
        if (subtotal >= 10000) {
            return 1000;
        }
        return 0;
    }
}

class DiskonJamSepi implements AturanDiskon {
    @Override
    public int hitung(int subtotal) {
        return subtotal / 10;
    }
}

Keduanya bisa dipakai lewat variabel bertipe AturanDiskon, dan hasilnya mengikuti object mana yang sedang dipegang:

AturanDiskon biasa = new DiskonBertingkat();
AturanDiskon jamSepi = new DiskonJamSepi();
System.out.println(biasa.hitung(16000));     // 1000
System.out.println(jamSepi.hitung(16000));   // 1600

Ada dua hal yang perlu diperhatikan. Method di interface otomatis bersifat public, jadi implementasinya juga wajib ditulis public. Lalu, interface tidak bisa dibuat object-nya dengan new, karena isinya memang belum ada. Kedua aturan ini dijelaskan di tutorial Interfaces dari Oracle, dan saya juga pernah membahas konsep interface pada OOP Java dengan contoh lain.

Kalau diperhatikan, kita sebenarnya sudah memakai pola ini sejak artikel Stack dan Queue. Di sana kita menulis Deque<String> tumpukan = new ArrayDeque<>(): tipe variabelnya interface Deque, object-nya class ArrayDeque. Sekarang alasannya jelas. Kode yang memakai tumpukan cukup tahu "ini sebuah Deque", sehingga class di belakangnya bisa diganti tanpa mengubah kode lain.

Abstract Class atau Interface?

Selain interface, Java punya abstract class: class yang ditandai abstract, boleh punya method tanpa isi, dan juga tidak bisa dibuat object-nya. Keduanya sama-sama alat abstraction, tapi cocok untuk kebutuhan yang berbeda:

Abstract class Interface
Kata kunci extends implements
Boleh punya field biasa Ya Tidak, hanya konstanta
Boleh punya constructor Ya Tidak
Jumlah yang bisa diwarisi satu class Hanya satu Boleh banyak
Cocok untuk Keluarga class yang berbagi data dan kode Kemampuan yang bisa dimiliki class dari keluarga mana pun

Untuk aturan diskon, interface lebih pas. Aturan diskon tidak berbagi data apa pun, yang mereka punya bersama hanya kemampuan "menghitung potongan". Kalau suatu hari ada beberapa jenis menu yang berbagi field dan perilaku tapi tidak boleh ada menu "polosan", di situ abstract class lebih cocok. Perbandingan lengkapnya ada di tutorial Abstract Methods and Classes dari Oracle, dan versi lain bisa dibaca di konsep abstraksi pada OOP Java.

Kasir Warteg Canggih Versi 13 dengan Pilar OOP di Java

Keempat pilar sekarang dipasang di kasir. Programnya bertambah empat file baru:

13-pilar-oop-di-java/
├── WartegCanggih.java     (main: menyiapkan menu dan aturan diskon)
├── Menu.java              (encapsulation: field private, getter, ubahHarga)
├── MenuPaket.java         (inheritance dan polymorphism: extends Menu, override tampilkan)
├── AturanDiskon.java      (abstraction: interface)
├── DiskonBertingkat.java  (aturan diskon lama)
├── DiskonJamSepi.java     (aturan diskon jam sepi)
├── ItemPesanan.java       (field private dan getter)
└── Kasir.java             (memakai AturanDiskon, field private)

Isi main sekarang juga menentukan aturan diskon mana yang dipakai hari ini:

public class WartegCanggih {
    public static void main(String[] args) {
        Menu[] daftarMenu = {
            new Menu("Nasi putih", 5000),
            new Menu("Tempe orek", 4000),
            new Menu("Es teh", 3000),
            new Menu("Telur balado", 6000),
            new Menu("Sayur asem", 4000),
            new MenuPaket("Paket Hemat", 10000, new String[] {"Nasi putih", "Tempe orek", "Es teh"})
        };

        // ganti jadi new DiskonJamSepi() saat jam sepi, Kasir tidak perlu diubah sama sekali
        AturanDiskon aturanDiskon = new DiskonBertingkat();

        Scanner input = new Scanner(System.in);
        Kasir kasir = new Kasir("Warteg Canggih", daftarMenu, aturanDiskon, input);
        kasir.buka();
        input.close();
    }
}

Di Kasir, perubahannya ada di bagian atas class dan satu baris perhitungan diskon:

class Kasir {
    private final String namaWarteg;
    private final Menu[] daftarMenu;                // bisa berisi Menu biasa maupun MenuPaket
    private final AturanDiskon aturanDiskon;        // aturan apa pun, asal implements AturanDiskon
    private final Scanner input;
    private final int[] porsiHarian;
    private int omzet;
    private int jumlahPembeli;
    private final Queue<String> antrian = new ArrayDeque<>();

    Kasir(String namaWarteg, Menu[] daftarMenu, AturanDiskon aturanDiskon, Scanner input) {
        this.namaWarteg = namaWarteg;
        this.daftarMenu = daftarMenu;
        this.aturanDiskon = aturanDiskon;
        this.input = input;
        this.porsiHarian = new int[daftarMenu.length];
    }

    // ... di dalam layaniAntrianBerikutnya():
    //     int diskon = aturanDiskon.hitung(subtotal);
}

Sisanya hampir sama dengan versi 12, kecuali setiap menu.nama berganti jadi menu.getNama(), karena field-nya sekarang private. Kode lengkap setiap file ada di GitHub (link di akhir artikel).

Contoh Satu Sesi

Budi memesan Paket Hemat dan telur balado. Siti memesan es teh dua gelas dan tempe orek, lalu membatalkan tempe oreknya:

Pilih: Melayani Budi.
Daftar menu:
1. Nasi putih (Rp5000)
2. Tempe orek (Rp4000)
3. Es teh (Rp3000)
4. Telur balado (Rp6000)
5. Sayur asem (Rp4000)
6. Paket Hemat (Rp10000)
   isi: Nasi putih, Tempe orek, Es teh
Nomor menu (0 = selesai, -1 = batalkan terakhir): Berapa porsi? Dicatat: Paket Hemat x1
Nomor menu (0 = selesai, -1 = batalkan terakhir): Berapa porsi? Dicatat: Telur balado x1
Nomor menu (0 = selesai, -1 = batalkan terakhir): 
Pesanan atas nama Budi
Telur balado   x1     Rp6000
Paket Hemat    x1    Rp10000
----------------------------
Subtotal             Rp16000
Diskon                Rp1000
TOTAL                Rp15000

Rekap di akhir hari:

REKAP HARI INI
----------------------------
Nasi putih           0 porsi
Tempe orek           0 porsi
Es teh               2 porsi
Telur balado         1 porsi
Sayur asem           0 porsi
Paket Hemat          1 porsi
----------------------------
Pembeli              2 orang
Omzet                Rp21000
Menu terlaris: Es teh

Kalau main diganti memakai new DiskonJamSepi(), diskon Budi otomatis jadi Rp1.600, dan tidak ada satu baris pun di Kasir yang diubah.

Apa Saja yang Berubah dari Versi 12

  • Harga tidak bisa lagi diubah langsung. Semua field Menu, ItemPesanan, dan Kasir sekarang private. Yang boleh diubah dari luar hanya harga, lewat ubahHarga.
  • Paket Hemat cukup satu class kecil. MenuPaket mewarisi semuanya dari Menu dan hanya menambah isi paket serta tampilannya.
  • Loop tampilkanMenu tidak berubah, padahal sekarang ada menu yang tampil dua baris.
  • static hitungDiskon hilang. Di artikel Class dan Object, method ini sengaja tetap static karena tidak memakai field. Sekarang perhitungannya pindah ke class aturan diskon, dan Kasir hanya memanggil aturanDiskon.hitung(subtotal).
  • Aturan diskon baru tidak lagi mengedit kode lama. Di latihan artikel Method, mengubah aturan diskon berarti mengedit hitungDiskon. Sekarang, aturan baru cukup jadi class baru.

Error yang Sering Muncul Saat Belajar OOP di Java

Lima error ini hampir pasti ditemui saat pertama kali memakai keempat pilar. Kabar baiknya, semuanya ketahuan saat compile, jadi tidak ada yang lolos sampai program berjalan.

Gejala Penyebab Perbaikan
harga has private access in Menu Field private diakses langsung dari class lain Pakai getter atau method seperti ubahHarga
constructor Menu in class Menu cannot be applied to given types di constructor subclass Constructor subclass tidak memanggil super(...) Tulis super(nama, harga); di baris pertama constructor
method does not override or implement a method from a supertype Nama atau parameter method yang diberi @Override tidak sama dengan milik superclass Samakan nama dan parameternya persis
AturanDiskon is abstract; cannot be instantiated Membuat object langsung dari interface atau abstract class Buat object dari class yang implements atau extends, misalnya new DiskonBertingkat()
attempting to assign weaker access privileges; was public Method implementasi interface tidak ditulis public Tambahkan public di depan method-nya

Error terakhir sering bikin bingung, karena di interface kita tidak menulis public sama sekali. Tapi justru karena method interface otomatis public, implementasinya tidak boleh lebih tertutup dari itu.

Penutup

Kalau dirangkum, keempat pilar ini sebenarnya menjawab satu pertanyaan yang sama: siapa yang bertanggung jawab atas apa. Menu bertanggung jawab menjaga harganya sendiri. MenuPaket bertanggung jawab atas tampilan paketnya. Aturan diskon bertanggung jawab atas rumusnya. Kasir cukup mengatur alurnya, tanpa harus tahu detail semua itu. Begitu pembagiannya jelas, permintaan baru dari pemilik warteg tidak lagi berarti membongkar kode lama.

Sebagai latihan, buat class DiskonMember yang memberi potongan 5 persen untuk semua belanja, ditambah Rp500 kalau subtotalnya minimal Rp15.000. Arah jawabannya: cukup satu class baru yang implements AturanDiskon, lalu ganti satu baris di main. Kalau kamu ternyata perlu mengedit Kasir, berarti ada yang belum pas, coba cek lagi bagian abstraction di atas.

Di artikel berikutnya, Collection di Java: ArrayList, LinkedList, HashMap, dan Map, daftar menu yang panjangnya masih tetap akan diganti dengan collection yang bisa bertambah saat kasir sedang buka. Di sana kamu akan sering melihat List<Menu> daftarMenu = new ArrayList<>(), dan sekarang kamu sudah tahu kenapa tipe variabelnya List, bukan ArrayList.

Daftar semua artikel seri ini ada di label Warteg Canggih Series. Dari keempat pilar tadi, mana yang menurutmu paling susah dipahami saat pertama kali belajar OOP? Kalau jawabanmu polymorphism, coba ceritakan di kolom komentar contoh apa yang akhirnya membuatmu paham.

Source code lengkap artikel ini bisa dilihat di GitHub: 13-pilar-oop-di-java

FAQ

Apa saja 4 pilar OOP di Java? Empat pilar OOP di Java yang paling sering diajarkan adalah encapsulation, inheritance, polymorphism, dan abstraction. Encapsulation menyembunyikan data di balik method, inheritance membuat class turunan dengan extends, polymorphism membuat perintah yang sama berperilaku sesuai jenis object, dan abstraction menyembunyikan detail cara kerja di balik interface atau abstract class.

Kenapa ada yang bilang OOP punya 5 prinsip? Karena jumlahnya bukan aturan baku. Tutorial resmi Oracle menyusun konsep OOP di Java menjadi lima: object, class, inheritance, interface, dan package. Ada juga yang sebenarnya merujuk ke SOLID, yaitu lima prinsip desain class yang dibangun di atas pilar OOP. Empat pilar, lima konsep Oracle, dan SOLID membahas hal yang sama dari sudut pandang berbeda.

Apa itu encapsulation di Java? Encapsulation adalah teknik menyembunyikan field dengan modifier private, lalu menyediakan method untuk membaca atau mengubahnya. Dengan begitu, setiap perubahan data wajib melewati method yang bisa memeriksa aturannya, misalnya menolak harga yang minus. Akses langsung ke field private dari class lain ditolak saat compile.

Apa bedanya override dan overload di Java? Overload adalah beberapa method dengan nama sama tapi parameter berbeda di class yang sama, dan pilihannya ditentukan saat compile dari argumen yang dikirim. Override adalah subclass menulis ulang method superclass dengan nama dan parameter yang sama persis, dan pilihannya ditentukan saat program berjalan dari jenis object-nya.

Apa perbedaan abstract class dan interface di Java? Abstract class boleh punya field biasa dan constructor, tapi satu class hanya bisa mewarisi satu abstract class lewat extends. Interface hanya berisi kontrak method (dan konstanta), tapi satu class boleh implements banyak interface sekaligus. Pakai abstract class untuk keluarga class yang berbagi data, dan interface untuk kemampuan yang bisa dimiliki class mana pun.

Kenapa harus memanggil super di constructor subclass? Field milik superclass hanya bisa diisi oleh constructor superclass, apalagi kalau field itu private. super(...) memanggil constructor tersebut dan wajib jadi perintah pertama di constructor subclass. Kalau tidak ditulis, Java memanggil super() tanpa argumen secara otomatis, dan compile gagal kalau superclass tidak punya constructor tanpa parameter.

Referensi

Posting Komentar untuk "Pilar OOP di Java: Encapsulation, Inheritance, Polymorphism, dan Abstraction"