Set

Menjaga keunikan data dengan HashSet, LinkedHashSet, dan TreeSet beserta perbedaan urutannya.

Daftar isi

Set adalah koleksi yang menolak duplikat, sehingga setiap elemen hanya boleh muncul sekali di dalamnya. Kontrak itu membuat Set menjadi alat alami untuk menyaring nilai unik, misalnya daftar kata kunci artikel atau nomor peserta yang sudah terdaftar. Topik ini mempraktikkan perilaku keunikan pada HashSet, lalu membandingkan urutan isi pada HashSet, LinkedHashSet, dan TreeSet.

Keunikan yang ditegakkan otomatis#

Aturan keunikan ditegakkan pada saat elemen ditambahkan, bukan saat dibaca. Metode add mengembalikan true bila elemennya benar-benar baru dan false bila sudah ada, sehingga keberhasilan penambahan bisa diperiksa tanpa pencarian manual. Pemeriksaan keanggotaan lewat contains juga menjadi operasi sehari-hari Set, dan pada HashSet biayanya mendekati konstan karena bekerja lewat tabel hash.

java DemoHashSet.java
import java.util.HashSet;
import java.util.Set;

public class DemoHashSet {

    public static void main(String[] args) {
        Set<String> buahUnik = new HashSet<>();
        System.out.println("tambah apel   : " + buahUnik.add("apel"));
        System.out.println("tambah jeruk  : " + buahUnik.add("jeruk"));
        System.out.println("tambah apel lagi: " + buahUnik.add("apel"));

        System.out.println("isi set   : " + buahUnik);
        System.out.println("jumlah    : " + buahUnik.size());
        System.out.println("ada jeruk? : " + buahUnik.contains("jeruk"));
        System.out.println("ada mangga? : " + buahUnik.contains("mangga"));

        buahUnik.remove("jeruk");
        System.out.println("sesudah remove: " + buahUnik);
    }
}
text Keluaran
tambah apel   : true
tambah jeruk  : true
tambah apel lagi: false
isi set   : [jeruk, apel]
jumlah    : 2
ada jeruk? : true
ada mangga? : false
sesudah remove: [apel]

Pemanggilan add("apel") yang kedua mengembalikan false dan isi set tetap dua elemen, jadi tidak ada penyaringan manual yang perlu ditulis. Layak dicatat pula mengapa Set lebih cocok untuk pemeriksaan keanggotaan daripada List. Pencarian contains pada List menyusuri elemen satu per satu, sehingga biayanya tumbuh seiring ukuran daftar, sedangkan tabel hash milik HashSet melompat langsung ke letak kandidatnya. Pada daftar dengan ratusan elemen yang sering diperiksa, perbedaan itu terasa nyata. Urutan cetak [jeruk, apel] bukan urutan penyimpanan dan bukan urutan alfabet, melainkan jejak struktur tabel hash internal. Urutan inilah yang menjadi pembahasan bagian berikutnya.

Tiga implementasi, tiga kebijakan urutan#

Ketiganya menegakkan keunikan dengan cara yang sama, tetapi berbeda pada satu hal yang paling terlihat, yaitu urutan saat isinya disusuri. Program berikut memasukkan susunan elemen yang sama ke ketiga implementasi, lalu mencetaknya apa adanya.

java DemoTigaSet.java
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;

public class DemoTigaSet {

    public static void main(String[] args) {
        Set<String> hashSet = new HashSet<>();
        Set<String> linkedSet = new LinkedHashSet<>();
        Set<String> treeSet = new TreeSet<>();

        String[] masukan = {"mangga", "apel", "jeruk", "durian"};

        for (String buah : masukan) {
            hashSet.add(buah);
            linkedSet.add(buah);
            treeSet.add(buah);
        }

        System.out.println("HashSet      : " + hashSet);
        System.out.println("LinkedHashSet: " + linkedSet);
        System.out.println("TreeSet      : " + treeSet);
    }
}
text Keluaran
HashSet      : [jeruk, apel, mangga, durian]
LinkedHashSet: [mangga, apel, jeruk, durian]
TreeSet      : [apel, durian, jeruk, mangga]

Tiga baris keluaran itu merangkum tiga kebijakan. HashSet mencetak sesuai letak internal tabel hash, urutan yang tidak bisa diprediksi dan tidak dijanjikan spesifikasi. LinkedHashSet menambah rantai rujukan antar-elemen di atas tabel hashnya, sehingga cetakannya persis mengikuti urutan penyimpanan, dengan biaya sedikit memori ekstra untuk menjaga rantai itu. TreeSet menaruh elemennya di pohon terurut, sehingga cetakannya selalu memakai urutan alami elemen, dalam hal ini alfabetis.

Sisi performanya mengikuti struktur masing-masing. HashSet dan LinkedHashSet menawarkan add dan contains dengan biaya rata-rata O(1)O(1), sedangkan TreeSet menanggung O(log⁡n)O(\log n) untuk keterurutannya. Untuk data yang sekadar butuh keunikan, selisih itu jarang terasa, tetapi pada jutaan elemen yang terus diperiksa, pilihan yang tepat menghemat waktu nyata.

Menyaring duplikat dari daftar lain#

Pola pemakaian Set yang paling sering muncul adalah menyaring daftar yang berpotensi berisi kembar. Masukkan seluruh elemen daftar ke dalam Set, dan sisanya adalah elemen unik dalam urutan yang dipilih implementasinya.

java Contoh: menyaring elemen unik dari daftar
// tidak-dijalankan
List<String> semuaTag = List.of("java", "collections", "java", "array", "collections");
Set<String> tagUnik = new LinkedHashSet<>(semuaTag);
System.out.println(tagUnik);   // [java, collections, array]

Konstruktor LinkedHashSet<>(semuaTag) menerima koleksi apa pun sebagai isian awal, sehingga penyaringan berlangsung satu baris. Pola serupa berlaku untuk operasi himpunan, yaitu gabungan, irisan, dan selisih, yang dikerjakan lewat addAll, retainAll, dan removeAll. Program berikut memanfaatkannya untuk membandingkan dua kelompok peserta ujian.

java DemoOperasiHimpunan.java
import java.util.HashSet;
import java.util.Set;

public class DemoOperasiHimpunan {

    public static void main(String[] args) {
        Set<String> pesertaMatematika = new HashSet<>();
        pesertaMatematika.add("Ali");
        pesertaMatematika.add("Budi");
        pesertaMatematika.add("Citra");

        Set<String> pesertaFisika = new HashSet<>();
        pesertaFisika.add("Budi");
        pesertaFisika.add("Dewi");

        Set<String> gabungan = new HashSet<>(pesertaMatematika);
        gabungan.addAll(pesertaFisika);
        Set<String> irisannya = new HashSet<>(pesertaMatematika);
        irisannya.retainAll(pesertaFisika);
        Set<String> selisihnya = new HashSet<>(pesertaMatematika);
        selisihnya.removeAll(pesertaFisika);

        System.out.println("gabungan: " + gabungan);
        System.out.println("irisan  : " + irisannya);
        System.out.println("selisih : " + selisihnya);
    }
}
text Keluaran
gabungan: [Dewi, Budi, Citra, Ali]
irisan  : [Budi]
selisih : [Citra, Ali]

Setiap operasi dimulai dari salinan set, karena addAll, retainAll, dan removeAll mengubah set yang memanggilnya, bukan menghasilkan set baru. Tanpa salinan, pesertaMatematika ikut termodifikasi dan tidak bisa dipakai untuk operasi berikutnya. Budi muncul di irisan karena terdaftar di kedua kelompok, sedangkan Dewi hanya di gabungan karena tidak mengikuti ujian matematika.

Kenyataannya, TreeSet menawarkan bonus yang tidak dimiliki dua saudaranya, yaitu metode pemandu urutan seperti first(), last(), dan headSet(). Metode-metode itu berguna ketika isinya memang punya urutan alami, misalnya nilai ujian atau tanggal.

Kesalahan umum#

  • Mengandalkan urutan HashSet. Urutan itu jejak struktur hash, bukan kontrak. Gunakan LinkedHashSet atau TreeSet sejak awal bila urutan punya arti.
  • Mengira Set bisa diakses lewat indeks. Kontrak Set tidak punya get(i) karena tidak ada konsep posisi yang dijamin. Susuri isinya dengan for-each bila perlu dibaca satu per satu.
  • Menyimpan objek kelas sendiri tanpa equals dan hashCode, lalu bingung karena duplikat tetap lolos. Lengkapi kedua metode itu, atau pakai record yang otomatis menyediakannya.
  • Menilai TreeSet lebih lambat lalu menghindarinya sepenuhnya. Biaya penyisipannya O(log⁡n)O(\log n), masih tergolong cepat untuk ukuran data lazim, dan bonus urutan terjaganya sering melebihi biayanya.

Ringkasan#

  • Set menolak duplikat pada saat add, dan add melaporkan keberhasilannya lewat nilai kembalian boolean.
  • HashSet menjanjikan performa terbaik tanpa urutan, LinkedHashSet mempertahankan urutan penyimpanan, dan TreeSet menjaga isi tetap terurut alami.
  • Urutan HashSet bukan kontrak dan boleh berubah, sehingga kode yang bergantung padanya rapuh.
  • Keunikan dievaluasi lewat equals dan hashCode, sehingga kelas buatan sendiri wajib melengkapinya.

Referensi#