Chapter sebelumnya menyimpulkan bahwa Mobil memiliki mesin — tetapi “memiliki” ternyata punya derajat yang berbeda-beda. Pemain milik tim, tetapi pemain tetap hidup ketika tim bubar; item pesanan milik pesanan, dan item tidak punya arti ketika pesanan dihapus; anggota perpustakaan berelasi dengan buku yang dipinjamnya tanpa memilikinya sama sekali. Tiga derajat kelekatan ini dikenal sebagai association, aggregation, dan composition, dan ketiganya memengaruhi cara kita mendesain constructor, method, serta pemilikan referensi. Pada Chapter ini kita implementasikan ketiganya dalam Java dan mengamati akibatnya terhadap siklus hidup objek.
Tiga Tingkat Hubungan Antara Objek #
Sebelum masuk ke kode, kita petakan dulu ketiganya secara ringkas:
| Aspek | Association | Aggregation | Composition |
|---|---|---|---|
| Makna | “berkenalan dengan” | “memiliki bagian yang lepas” | “memiliki bagian yang melekat” |
| Kepemilikan | Tidak ada | Bagian dibagikan, utuh hanya menampung | Bagian milik penuh satu utuh |
| Siklus hidup bagian | Mandiri sepenuhnya | Mandiri dari utuhnya | Terikat pada utuhnya |
| Contoh | Anggota — buku yang dipinjam | Tim — pemain | Pesanan — item pesanan |
| Bentuk di Java | Field sementara atau parameter method | Field collection yang diisi dari luar | Field collection yang diisi dari dalam |
Di atas kertas ketiganya tampak sama-sama berupa field atau parameter — compiler tidak membedakannya. Perbedaannya ada pada keputusan desain: di mana objek bagian diciptakan, dan siapa yang boleh menyimpan referensinya. Keputusan itulah yang menentukan bagian mana yang tetap hidup saat utuhnya dilepas.
Asosiasi: Hubungan Longgar #
Asosiasi adalah hubungan paling longgar: kedua objek berdiri mandiri, dan relasi hanya terbentuk untuk sementara — biasanya lewat pemanggilan method. Anggota meminjam buku, relasi berlangsung beberapa hari, lalu berakhir tanpa mengubah keberadaan kedua objek:
class Buku {
private final String judul;
public Buku(String judul) {
this.judul = judul;
}
public String judul() {
return judul;
}
}
class Anggota {
private final String nama;
private Buku pinjaman; // asosiasi: relasi sementara, bisa berupa null
public Anggota(String nama) {
this.nama = nama;
}
public void pinjam(Buku buku) {
this.pinjaman = buku; // relasi terbentuk saat runtime
System.out.println(nama + " meminjam \"" + buku.judul() + "\"");
}
public void kembalikan() {
System.out.println(nama + " mengembalikan \"" + pinjaman.judul() + "\"");
pinjaman = null; // relasi berakhir; kedua objek tetap hidup
}
}
public class Asosiasi {
public static void main(String[] args) {
Buku buku = new Buku("Java Dasar");
Anggota budi = new Anggota("Budi");
budi.pinjam(buku);
budi.kembalikan();
System.out.println("Buku dan anggota tetap hidup setelah relasi berakhir");
}
}Output yang Dihasilkan:
Budi meminjam "Java Dasar"
Budi mengembalikan "Java Dasar"
Buku dan anggota tetap hidup setelah relasi berakhirDua hal patut dicatat dari contoh tersebut. Pertama, field pinjaman dideklarasikan tanpa final dan diawali nilai null — relasi asosiasi memang bisa hadir dan pergi, bukan bagian permanen dari objek. Kedua, objek Buku diciptakan di main dan hanya dipinjamkan ke Anggota lewat parameter method pinjam(Buku); class Anggota tidak pernah menciptakan buku, sehingga tidak memiliki kendali atas keberadaannya.
Agregasi: Bagian yang Bisa Berdiri Sendiri #
Agregasi memodelkan relasi bagian-utuh, tetapi bagian tetap bisa hidup tanpa utuhnya. Pemain adalah bagian dari tim, namun pemain lahir sebagai objek mandiri dan bisa pindah ke tim lain. Ciri implementasinya: objek bagian diciptakan di luar class utuh, lalu dimasukkan lewat constructor atau setter:
import java.util.ArrayList;
import java.util.List;
class Pemain {
private final String nama;
public Pemain(String nama) {
this.nama = nama;
}
public String nama() {
return nama;
}
}
class Tim {
private final String nama;
private final List<Pemain> pemain; // agregasi: bagian-utuh yang longgar
public Tim(String nama, List<Pemain> pemain) {
this.nama = nama;
this.pemain = pemain;
}
public void daftarPemain() {
System.out.print("Tim " + nama + ": ");
for (int i = 0; i < pemain.size(); i++) {
System.out.print(pemain.get(i).nama());
if (i < pemain.size() - 1) {
System.out.print(", ");
}
}
System.out.println();
}
}
public class AgregasiTim {
public static void main(String[] args) {
// Pemain dibuat DI LUAR tim: siklus hidup pemain tidak bergantung tim.
List<Pemain> pemain = new ArrayList<>();
pemain.add(new Pemain("Budi"));
pemain.add(new Pemain("Siti"));
Tim garuda = new Tim("Garuda", pemain);
garuda.daftarPemain();
// Tim dibubarkan (referensi tim dihapus), pemain tetap hidup.
garuda = null;
System.out.println("Setelah tim dibubarkan, pemain tetap tersedia:");
for (Pemain p : pemain) {
System.out.println(" " + p.nama() + " siap bergabung ke tim lain");
}
}
}Output yang Dihasilkan:
Tim Garuda: Budi, Siti
Setelah tim dibubarkan, pemain tetap tersedia:
Budi siap bergabung ke tim lain
Siti siap bergabung ke tim lainKuncinya ada pada urutan penciptaan. List pemain diisi di main sebelum Tim dibuat, dan constructor Tim hanya menerima list yang sudah berisi — class Tim tidak pernah memanggil new Pemain(...). Ketika variabel garuda di-set ke null, JVM hanya kehilangan jalan menuju objek tim; objek pemain masih terjangkau lewat list di main, sehingga tetap hidup dan siap dipakai tim baru.
Komposisi: Bagian yang Mengikut Utuhnya #
Komposisi adalah relasi bagian-utuh yang paling kuat: bagian diciptakan di dalam utuh, referensinya tidak dibagikan ke luar, dan keberadaannya berakhir bersama utuhnya. Item pesanan tidak pernah hidup di luar pesanan — memisahkan item dari pesanannya tidak bermakna:
import java.util.ArrayList;
import java.util.List;
class ItemPesanan {
private final String namaBarang;
private final int jumlah;
// Constructor tanpa modifier publik: hanya dibuat dari dalam Pesanan.
ItemPesanan(String namaBarang, int jumlah) {
this.namaBarang = namaBarang;
this.jumlah = jumlah;
}
public String ringkas() {
return jumlah + "x " + namaBarang;
}
}
class Pesanan {
private final String nomor;
private final List<ItemPesanan> item; // komposisi: bagian melekat pada utuh
public Pesanan(String nomor) {
this.nomor = nomor;
this.item = new ArrayList<>();
}
// Bagian diciptakan DI DALAM utuh; referensinya tidak dibagikan ke luar.
public void tambahItem(String namaBarang, int jumlah) {
item.add(new ItemPesanan(namaBarang, jumlah));
}
public void cetak() {
System.out.println("Pesanan " + nomor + ":");
for (ItemPesanan i : item) {
System.out.println(" " + i.ringkas());
}
}
}
public class KomposisiPesanan {
public static void main(String[] args) {
Pesanan pesanan = new Pesanan("PSN-001");
pesanan.tambahItem("Buku Java Dasar", 2);
pesanan.tambahItem("Pensil", 5);
pesanan.cetak();
// Saat referensi pesanan dihapus, item di dalamnya ikut tak terjangkau.
pesanan = null;
System.out.println("Pesanan dihapus; item-nya ikut tidak terjangkau");
}
}Output yang Dihasilkan:
Pesanan PSN-001:
2x Buku Java Dasar
5x Pensil
Pesanan dihapus; item-nya ikut tidak terjangkauTiga keputusan desain menjadikan relasi ini komposisi. Pertama, constructor ItemPesanan sengaja tidak diberi modifier public, sehingga hanya kode di berkas yang sama yang bisa menciptakannya. Kedua, objek bagian hanya lahir lewat tambahItem(...) — kode di luar Pesanan tidak pernah memegang referensi ItemPesanan secara langsung. Ketiga, method cetak() menyalin isi ke keluaran alih-alih mengembalikan list internal, sehingga tidak ada pintu bocor untuk referensi bagian.
Percakapan tentang “menghapus referensi” dan “objek tetap hidup” sesungguhnya menyentuh topik yang lebih fundamental: bagaimana JVM mengelola hidup sebuah objek dari new sampai memungutnya kembali. Chapter Object Lifecycle menelusuri perjalanan tersebut tahap demi tahap.
Referensi #
- Oracle: Learning the Java Language — Object-Oriented Programming Concepts
- Oracle: Java Language Specification — jls-8 Class Members
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
AgregasiTim.java
Unduh berkasimport java.util.ArrayList;
import java.util.List;
class Pemain {
private final String nama;
public Pemain(String nama) {
this.nama = nama;
}
public String nama() {
return nama;
}
}
class Tim {
private final String nama;
private final List<Pemain> pemain; // agregasi: bagian-utuh yang longgar
public Tim(String nama, List<Pemain> pemain) {
this.nama = nama;
this.pemain = pemain;
}
public void daftarPemain() {
System.out.print("Tim " + nama + ": ");
for (int i = 0; i < pemain.size(); i++) {
System.out.print(pemain.get(i).nama());
if (i < pemain.size() - 1) {
System.out.print(", ");
}
}
System.out.println();
}
}
public class AgregasiTim {
public static void main(String[] args) {
// Pemain dibuat DI LUAR tim: siklus hidup pemain tidak bergantung tim.
List<Pemain> pemain = new ArrayList<>();
pemain.add(new Pemain("Budi"));
pemain.add(new Pemain("Siti"));
Tim garuda = new Tim("Garuda", pemain);
garuda.daftarPemain();
// Tim dibubarkan (referensi tim dihapus), pemain tetap hidup.
garuda = null;
System.out.println("Setelah tim dibubarkan, pemain tetap tersedia:");
for (Pemain p : pemain) {
System.out.println(" " + p.nama() + " siap bergabung ke tim lain");
}
}
}
Asosiasi.java
Unduh berkasclass Buku {
private final String judul;
public Buku(String judul) {
this.judul = judul;
}
public String judul() {
return judul;
}
}
class Anggota {
private final String nama;
private Buku pinjaman; // asosiasi: relasi sementara, bisa berupa null
public Anggota(String nama) {
this.nama = nama;
}
public void pinjam(Buku buku) {
this.pinjaman = buku; // relasi terbentuk saat runtime
System.out.println(nama + " meminjam \"" + buku.judul() + "\"");
}
public void kembalikan() {
System.out.println(nama + " mengembalikan \"" + pinjaman.judul() + "\"");
pinjaman = null; // relasi berakhir; kedua objek tetap hidup
}
}
public class Asosiasi {
public static void main(String[] args) {
Buku buku = new Buku("Java Dasar");
Anggota budi = new Anggota("Budi");
budi.pinjam(buku);
budi.kembalikan();
System.out.println("Buku dan anggota tetap hidup setelah relasi berakhir");
}
}
KomposisiPesanan.java
Unduh berkasimport java.util.ArrayList;
import java.util.List;
class ItemPesanan {
private final String namaBarang;
private final int jumlah;
// Constructor tanpa modifier publik: hanya dibuat dari dalam Pesanan.
ItemPesanan(String namaBarang, int jumlah) {
this.namaBarang = namaBarang;
this.jumlah = jumlah;
}
public String ringkas() {
return jumlah + "x " + namaBarang;
}
}
class Pesanan {
private final String nomor;
private final List<ItemPesanan> item; // komposisi: bagian melekat pada utuh
public Pesanan(String nomor) {
this.nomor = nomor;
this.item = new ArrayList<>();
}
// Bagian diciptakan DI DALAM utuh; referensinya tidak dibagikan ke luar.
public void tambahItem(String namaBarang, int jumlah) {
item.add(new ItemPesanan(namaBarang, jumlah));
}
public void cetak() {
System.out.println("Pesanan " + nomor + ":");
for (ItemPesanan i : item) {
System.out.println(" " + i.ringkas());
}
}
}
public class KomposisiPesanan {
public static void main(String[] args) {
Pesanan pesanan = new Pesanan("PSN-001");
pesanan.tambahItem("Buku Java Dasar", 2);
pesanan.tambahItem("Pensil", 5);
pesanan.cetak();
// Saat referensi pesanan dihapus, item di dalamnya ikut tak terjangkau.
pesanan = null;
System.out.println("Pesanan dihapus; item-nya ikut tidak terjangkau");
}
}