Kelas #
Kelas adalah fondasi dari hampir semua kode Java yang kamu tulis. Tanpa memahami cara kelas bekerja, kamu akan kesulitan memahami mengapa Java dirancang seperti itu dan bagaimana potongan kode saling berinteraksi. Kelas bukan sekadar wadah untuk fungsi — ia adalah cetak biru yang mendefinisikan struktur data, perilaku, dan hubungan antar bagian program. Artikel ini membahas kelas dari konsep paling dasar hingga empat teknik implementasi utama: enkapsulasi, pewarisan, polimorfisme, dan abstraksi.
Konsep Dasar #
Sebelum melihat kode, ada empat konsep yang perlu kamu pahami. Keempatnya adalah blok bangunan yang membentuk sistem kelas di Java.
| Konsep | Peran | Analogi |
|---|---|---|
| Atribut | Data yang dimiliki objek | Spesifikasi mobil: merk, warna, kecepatan |
| Metode | Aksi yang bisa dilakukan objek | Tombol gas dan rem pada mobil |
| Konstruktor | Proses inisialisasi saat objek dibuat | Pabrik yang merakit mobil dari komponen awal |
| Objek | Realisasi nyata dari kelas | Satu unit mobil yang sudah jadi |
Hubungan antara keempat konsep ini bisa digambarkan sebagai berikut:
flowchart TD
A[Kelas — Cetak Biru] --> B[Konstruktor]
B --> C[Objek — Instance]
A --> D[Atribut]
A --> E[Metode]
D --> C
E --> C
C --> F[Objek 1]
C --> G[Objek 2]
C --> H[Objek N...]Satu kelas bisa menghasilkan banyak objek. Setiap objek punya salinan atributnya sendiri, tapi semua berbagi definisi metode yang sama dari kelas.
Deklarasi Kelas Sederhana #
Mari mulai dari kelas yang konkret — sebuah mobil. Kelas ini punya tiga atribut, satu konstruktor, dan tiga metode.
Atribut dan Konstruktor #
Atribut dideklarasikan di level kelas, di luar metode mana pun. Konstruktor adalah metode khusus yang namanya sama persis dengan nama kelas dan tidak punya return type.
public class Mobil {
// Atribut: data yang mendeskripsikan mobil
String merk;
String warna;
int kecepatan;
// Konstruktor: dijalankan saat objek baru dibuat
public Mobil(String merk, String warna, int kecepatan) {
this.merk = merk;
this.warna = warna;
this.kecepatan = kecepatan;
}
}
Perhatikan kata kunci this di dalam konstruktor. this.merk merujuk ke atribut milik objek, sedangkan merk (tanpa this) merujuk ke parameter konstruktor. Tanpa this, Java tidak bisa membedakan keduanya saat nama parameter sama dengan nama atribut.
Metode #
Metode dideklarasikan di dalam body kelas dan mendefinisikan apa yang bisa dilakukan objek. Metode bisa membaca dan mengubah atribut milik objek itu sendiri.
public class Mobil {
String merk;
String warna;
int kecepatan;
public Mobil(String merk, String warna, int kecepatan) {
this.merk = merk;
this.warna = warna;
this.kecepatan = kecepatan;
}
// Metode: aksi yang bisa dilakukan mobil
public void tambahKecepatan(int kenaikan) {
kecepatan += kenaikan;
System.out.println("Kecepatan sekarang: " + kecepatan + " km/h");
}
public void kurangiKecepatan(int pengurangan) {
kecepatan -= pengurangan;
System.out.println("Kecepatan sekarang: " + kecepatan + " km/h");
}
public void infoMobil() {
System.out.println("Merk: " + merk);
System.out.println("Warna: " + warna);
System.out.println("Kecepatan: " + kecepatan + " km/h");
}
}
Menginstansiasi Objek #
Kelas hanya definisi — ia tidak melakukan apa-apa sampai kamu membuat objek darinya. Proses membuat objek disebut instansiasi, dan dilakukan dengan kata kunci new.
Membuat Objek dan Memanggil Metode #
public class Main {
public static void main(String[] args) {
// Instansiasi: membuat objek baru dari kelas Mobil
Mobil mobilSaya = new Mobil("Toyota", "Merah", 0);
// Memanggil metode pada objek
mobilSaya.tambahKecepatan(50); // Output: Kecepatan sekarang: 50 km/h
mobilSaya.kurangiKecepatan(10); // Output: Kecepatan sekarang: 40 km/h
mobilSaya.infoMobil();
// Output:
// Merk: Toyota
// Warna: Merah
// Kecepatan: 40 km/h
}
}
Banyak Objek dari Satu Kelas #
Kamu bisa membuat sebanyak mungkin objek dari kelas yang sama. Setiap objek punya salinan atributnya sendiri dan tidak saling mempengaruhi — mengubah mobil1 tidak akan mengubah mobil2.
Mobil mobil1 = new Mobil("Toyota", "Merah", 0);
Mobil mobil2 = new Mobil("Honda", "Putih", 0);
mobil1.tambahKecepatan(80); // hanya mempengaruhi mobil1
mobil2.tambahKecepatan(60); // hanya mempengaruhi mobil2
mobil1.infoMobil(); // Kecepatan: 80 km/h
mobil2.infoMobil(); // Kecepatan: 60 km/h
Enkapsulasi #
Enkapsulasi menyembunyikan data internal kelas dari akses luar yang tidak terkontrol. Tanpa enkapsulasi, kode di luar kelas bisa mengubah atribut secara sembarangan — termasuk mengisi nilai yang tidak valid.
Masalah Tanpa Enkapsulasi #
// ANTI-PATTERN: atribut publik, siapa saja bisa ubah langsung
public class AkunBank {
public double saldo; // berbahaya: tidak ada validasi
}
// Di kode lain:
AkunBank akun = new AkunBank();
akun.saldo = -9999999; // tidak ada yang mencegah ini
Solusi dengan Private dan Getter/Setter #
Jadikan atribut private, lalu sediakan metode dengan validasi sebagai satu-satunya jalan untuk membaca atau mengubah nilainya.
// BENAR: atribut private, akses hanya lewat metode yang punya validasi
public class AkunBank {
private String nomorRekening;
private double saldo;
public AkunBank(String nomorRekening, double saldoAwal) {
this.nomorRekening = nomorRekening;
this.saldo = saldoAwal;
}
// Getter: membaca nilai atribut
public double getSaldo() {
return saldo;
}
// Setter dengan validasi
public void setoran(double jumlah) {
if (jumlah > 0) {
saldo += jumlah;
System.out.println("Setoran berhasil. Saldo sekarang: " + saldo);
} else {
System.out.println("Jumlah setoran tidak valid.");
}
}
public void tarik(double jumlah) {
if (jumlah > 0 && jumlah <= saldo) {
saldo -= jumlah;
System.out.println("Penarikan berhasil. Saldo sekarang: " + saldo);
} else {
System.out.println("Penarikan gagal. Saldo tidak mencukupi.");
}
}
}
Dengan private, atribut saldo tidak bisa diakses langsung dari luar. Semua perubahan harus melewati metode setoran() atau tarik(), yang punya logika validasi di dalamnya.
flowchart LR
A[Kode Luar] -->|"getSaldo()"| B[Getter]
A -->|"setoran(jumlah)"| C[Setter dengan Validasi]
B --> D[("private saldo")]
C --> D
A -. diblokir .-> DPewarisan #
Pewarisan memungkinkan satu kelas mewarisi atribut dan metode dari kelas lain. Kelas yang mewarisi disebut subkelas, dan kelas yang diwarisi disebut superkelas. Gunakan kata kunci extends untuk mendefinisikan hubungan ini.
Mendefinisikan Superkelas dan Subkelas #
// Superkelas: definisi umum untuk semua hewan
public class Hewan {
String nama;
int umur;
public Hewan(String nama, int umur) {
this.nama = nama;
this.umur = umur;
}
public void suara() {
System.out.println("Hewan membuat suara.");
}
public void info() {
System.out.println("Nama: " + nama + ", Umur: " + umur + " tahun");
}
}
// Subkelas: mewarisi semua dari Hewan, lalu meng-override metode suara()
public class Anjing extends Hewan {
public Anjing(String nama, int umur) {
super(nama, umur); // memanggil konstruktor superkelas
}
@Override
public void suara() {
System.out.println(nama + " menggonggong: Guk guk!");
}
}
public class Kucing extends Hewan {
public Kucing(String nama, int umur) {
super(nama, umur);
}
@Override
public void suara() {
System.out.println(nama + " mengeong: Meong!");
}
}
Kata Kunci super dan @Override #
super memanggil konstruktor atau metode dari superkelas. @Override memberitahu compiler bahwa kamu memang sengaja meng-override metode — bukan mengetik nama salah. Tanpa @Override, salah ketik nama metode tidak akan menghasilkan error, hanya diam-diam membuat metode baru yang tidak pernah dipanggil.
public class Main {
public static void main(String[] args) {
Anjing anjing = new Anjing("Rex", 4);
anjing.suara(); // Output: Rex menggonggong: Guk guk!
anjing.info(); // Output: Nama: Rex, Umur: 4 tahun ← diwarisi dari Hewan
}
}
flowchart TD
A["Hewan\n─────────\nnama, umur\nsuara(), info()"] --> B["Anjing\n─────────\n@Override suara()"]
A --> C["Kucing\n─────────\n@Override suara()"]
A --> D["Hewan lain..."]Polimorfisme #
Polimorfisme adalah kemampuan satu variabel bertipe superkelas untuk menampung objek dari subkelas mana pun, dan saat metode dipanggil, Java akan menjalankan versi yang sesuai dengan tipe objek aslinya — bukan tipe variabelnya.
Variabel Bertipe Superkelas #
// Kedua variabel bertipe Hewan, tapi menampung objek berbeda
Hewan hewan1 = new Anjing("Buddy", 3);
Hewan hewan2 = new Kucing("Kitty", 2);
// Java menjalankan metode dari tipe objek yang sebenarnya
hewan1.suara(); // Output: Buddy menggonggong: Guk guk!
hewan2.suara(); // Output: Kitty mengeong: Meong!
Memproses Daftar Campuran #
Tanpa polimorfisme, kamu harus menulis if (instanceof Anjing) dan if (instanceof Kucing) secara terpisah — kode yang sulit diperluas. Dengan polimorfisme, menambah tipe hewan baru tidak mengubah loop sama sekali.
Hewan[] semuaHewan = {
new Anjing("Rex", 5),
new Kucing("Luna", 2),
new Anjing("Max", 1)
};
for (Hewan h : semuaHewan) {
h.suara(); // setiap objek merespons dengan caranya sendiri
}
sequenceDiagram
participant Main
participant hewan1 as hewan1 (Anjing)
participant hewan2 as hewan2 (Kucing)
Main->>hewan1: suara()
hewan1-->>Main: "Buddy menggonggong: Guk guk!"
Main->>hewan2: suara()
hewan2-->>Main: "Kitty mengeong: Meong!"Abstraksi #
Abstraksi menyembunyikan detail implementasi dan hanya mengekspos “kontrak” — apa yang bisa dilakukan, bukan bagaimana caranya. Di Java, abstraksi bisa dicapai dengan kelas abstrak atau interface.
Mendefinisikan Kelas Abstrak #
Kelas abstrak cocok ketika kamu ingin mendefinisikan kerangka yang harus diimplementasikan oleh subkelas, tapi beberapa metode sudah punya implementasi default. Gunakan kata kunci abstract di level kelas dan di setiap metode yang belum punya implementasi.
// Kelas abstrak: tidak bisa diinstansiasi langsung
abstract class Kendaraan {
String nama;
public Kendaraan(String nama) {
this.nama = nama;
}
// Metode abstrak: wajib diimplementasikan oleh subkelas
abstract void bergerak();
// Metode konkret: sudah punya implementasi, bisa dipakai langsung
public void info() {
System.out.println("Kendaraan: " + nama);
}
}
class Mobil extends Kendaraan {
public Mobil(String nama) {
super(nama);
}
@Override
public void bergerak() {
System.out.println(nama + " bergerak dengan roda di darat.");
}
}
class Kapal extends Kendaraan {
public Kapal(String nama) {
super(nama);
}
@Override
public void bergerak() {
System.out.println(nama + " bergerak dengan baling-baling di laut.");
}
}
Memakai Kelas Abstrak #
public class Main {
public static void main(String[] args) {
// ANTI-PATTERN: tidak bisa membuat objek dari kelas abstrak
// Kendaraan k = new Kendaraan("Apapun"); // error: Kendaraan is abstract
// BENAR: buat objek dari subkelas konkret
Kendaraan mobil = new Mobil("Avanza");
Kendaraan kapal = new Kapal("KM Nusantara");
mobil.bergerak(); // Output: Avanza bergerak dengan roda di darat.
kapal.bergerak(); // Output: KM Nusantara bergerak dengan baling-baling di laut.
mobil.info(); // metode warisan dari Kendaraan
kapal.info();
}
}
flowchart TD
A["«abstract»\nKendaraan\n─────────\nnama\nbergerak() — abstract\ninfo() — konkret"] --> B["Mobil\n─────────\n@Override bergerak()"]
A --> C["Kapal\n─────────\n@Override bergerak()"]
A --> D["Pesawat\n─────────\n@Override bergerak()"]Perbedaan kelas abstrak dan interface: kelas abstrak bisa punya atribut dan metode konkret, sedangkan interface (sebelum Java 8) hanya mendefinisikan kontrak tanpa implementasi. Gunakan kelas abstrak ketika subkelas punya beberapa perilaku yang sama; gunakan interface ketika kamu hanya perlu mendefinisikan kemampuan yang bisa dimiliki oleh kelas yang tidak berkaitan.
Kapan Menggunakan Tiap Teknik #
Gunakan ENKAPSULASI jika:
✓ Atribut punya batasan nilai yang harus dijaga (saldo tidak boleh negatif)
✓ Kamu ingin mengontrol siapa yang bisa membaca dan mengubah data
✓ Hampir selalu — ini adalah default yang baik untuk semua kelas
Gunakan PEWARISAN jika:
✓ Ada hubungan "adalah" yang jelas (Anjing adalah Hewan)
✓ Subkelas berbagi banyak atribut dan perilaku dengan superkelas
✓ Kamu ingin meng-override beberapa metode saja, bukan semuanya
✗ Hindari jika hubungannya hanya "menggunakan" (pakai komposisi saja)
✗ Hindari inheritance lebih dari 2-3 level — cepat jadi rumit
Gunakan ABSTRAKSI jika:
✓ Kamu mendefinisikan kerangka yang harus diikuti oleh subkelas
✓ Ada beberapa metode yang perlu implementasi berbeda di setiap subkelas
✓ Kamu ingin mencegah instansiasi langsung dari kelas induk
Ringkasan #
- Kelas adalah cetak biru — ia mendefinisikan atribut dan metode, tapi tidak melakukan apa-apa sampai objek dibuat dengan
new.- Konstruktor dijalankan sekali saat objek dibuat — gunakan untuk inisialisasi atribut awal. Kata kunci
thisdipakai untuk membedakan atribut kelas dari parameter.- Enkapsulasi melindungi data — jadikan atribut
privatedan sediakan getter/setter dengan validasi. Ini mencegah nilai tidak valid masuk ke dalam objek.- Pewarisan berbagi kode — subkelas mewarisi atribut dan metode superkelas. Gunakan
extendsuntuk hubungan “adalah” (Anjing adalah Hewan). Gunakan@Overrideuntuk menandai metode yang di-override.- Polimorfisme menyederhanakan kode — variabel bertipe superkelas bisa menampung objek subkelas apa pun, dan Java memanggil metode yang tepat secara otomatis saat runtime.
- Abstraksi mendefinisikan kontrak — kelas abstrak memaksa subkelas mengimplementasikan metode tertentu, sambil tetap menyediakan implementasi default untuk metode yang dibutuhkan bersama.
- Keempatnya bekerja bersama — aplikasi Java yang baik menggabungkan enkapsulasi sebagai default, pewarisan untuk berbagi kode, polimorfisme untuk fleksibilitas, dan abstraksi untuk mendefinisikan kerangka.