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:

AspekAssociationAggregationComposition
Makna“berkenalan dengan”“memiliki bagian yang lepas”“memiliki bagian yang melekat”
KepemilikanTidak adaBagian dibagikan, utuh hanya menampungBagian milik penuh satu utuh
Siklus hidup bagianMandiri sepenuhnyaMandiri dari utuhnyaTerikat pada utuhnya
ContohAnggota — buku yang dipinjamTim — pemainPesanan — item pesanan
Bentuk di JavaField sementara atau parameter methodField collection yang diisi dari luarField 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:

java Asosiasi.java
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:

text
Budi meminjam "Java Dasar"
Budi mengembalikan "Java Dasar"
Buku dan anggota tetap hidup setelah relasi berakhir

Dua 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:

java AgregasiTim.java
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:

text
Tim Garuda: Budi, Siti
Setelah tim dibubarkan, pemain tetap tersedia:
  Budi siap bergabung ke tim lain
  Siti siap bergabung ke tim lain

Kuncinya 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:

text
Pesanan PSN-001:
  2x Buku Java Dasar
  5x Pensil
Pesanan dihapus; item-nya ikut tidak terjangkau

Tiga 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 #

Berkas Sumber

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

AgregasiTim.java

Unduh berkas
java
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");
        }
    }
}

Asosiasi.java

Unduh berkas
java
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");
    }
}

KomposisiPesanan.java

Unduh berkas
java
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");
    }
}