Java Set – Java’da Küme Yapısı
Java Collections Framework’ün önemli bir parçası olan Set arayüzü, programlama dünyasında benzersiz elemanları depolamak için kullanılan temel bir veri yapısıdır. Matematikteki küme (set) kavramından esinlenerek tasarlanmıştır ve eleman tekrarına izin vermemesiyle öne çıkar. Bu rehberde, Java’daki Set arayüzünü, onun çeşitli uygulamalarını (HashSet, LinkedHashSet, TreeSet), temel özelliklerini, kullanım senaryolarını ve performans ipuçlarını detaylı bir şekilde inceleyeceğiz.
Set Arayüzü Nedir?
java.util.Set, Java Collections Framework’ün bir parçası olan ve java.util.Collection arayüzünü genişleten bir arayüzdür. Temel özelliği, yinelenen elemanlara izin vermemesidir. Bir Set‘e aynı elemanı birden fazla eklemeye çalıştığınızda, bu işlem başarısız olur ve Set‘in içeriği değişmez. Bu özellik, özellikle benzersiz değerler listesine ihtiyaç duyulduğunda veya bir veri kümesinden yinelenenleri temizlemek istendiğinde son derece kullanışlıdır.
Temel Özellikler
- Benzersizlik: Bir
Setiçinde her eleman yalnızca bir kez bulunabilir. Aynı elemanı tekrar ekleme girişimi göz ardı edilir. - Sırasızlık (Genellikle):
Setarayüzü, elemanların depolanma sırası hakkında herhangi bir garanti vermez. Ancak,LinkedHashSetgibi bazı uygulamalar ekleme sırasını korurken,TreeSetgibi diğerleri elemanları doğal sıralarına göre veya özel bir karşılaştırıcıya göre sıralar. - Null Eleman: Çoğu
Setuygulaması (HashSet,LinkedHashSet) bir adetnullelemanı depolayabilir.TreeSetise doğal sıralama veya kullanılanComparatornull elemanları desteklemiyorsaNullPointerExceptionfırlatabilir. - Thread-Safe Değildir: Varsayılan
Setuygulamaları thread-safe değildir. Çoklu iş parçacıklı ortamlarda kullanıldığında dışarıdan senkronizasyon mekanizmaları (örn.Collections.synchronizedSet()) veyaCopyOnWriteArraySetgibi thread-safe alternatifler kullanılmalıdır.
Set Arayüzünün Metotları
Set arayüzü, Collection arayüzünden miras aldığı metotların yanı sıra, benzersizlik özelliğini destekleyen bazı ek davranışlar sunar. İşte bazı temel metotlar:
boolean add(E e): Belirtilen elemanı kümeye ekler. Eğer eleman zaten kümede varsafalsedöner, aksi takdirdetruedöner.boolean remove(Object o): Belirtilen elemanı kümeden kaldırır. Eğer eleman kümede varsatruedöner, aksi takdirdefalsedöner.boolean contains(Object o): Kümenin belirtilen elemanı içerip içermediğini kontrol eder.int size(): Kümedeki eleman sayısını döndürür.boolean isEmpty(): Kümenin boş olup olmadığını kontrol eder.void clear(): Kümedeki tüm elemanları kaldırır.boolean addAll(Collection extends E> c): Belirtilen koleksiyondaki tüm elemanları kümeye ekler.boolean removeAll(Collection> c): Kümeden, belirtilen koleksiyonda bulunan tüm elemanları kaldırır (küme farkı).boolean retainAll(Collection> c): Kümede, yalnızca belirtilen koleksiyonda bulunan elemanları tutar (küme kesişimi).
Set Uygulamaları
Java Collections Framework, Set arayüzünün üç ana uygulamasını sunar; her birinin kendine özgü özellikleri ve performans karakteristikleri vardır:
HashSet
HashSet, Set arayüzünün en yaygın ve genel amaçlı uygulamasıdır. Dahili olarak bir hash tablosu (HashMap) kullanır. Elemanların sırası garanti edilmez (rastgele gibi görünebilir) ve thread-safe değildir.
- Performans:
add(),remove(),contains()vesize()metotları ortalama olarak O(1) (sabit zaman) performansına sahiptir. Bu, özellikle büyük veri kümeleriyle çalışırken çok hızlı işlemler sağlar. En kötü durumda (çok fazla hash çarpışması olduğunda) O(n) olabilir, ancak iyi tasarlanmışhashCode()fonksiyonları ile bu durum nadirdir. - Sıra: Elemanların sırası garanti edilmez.
- Kullanım: Sıranın önemli olmadığı ve hızlı erişim/ekleme/silme işlemlerinin gerektiği durumlarda tercih edilir.
import java.util.HashSet;
import java.util.Set;
public class HashSetOrnegi {
public static void main(String[] args) {
Set meyveler = new HashSet<>();
meyveler.add("Elma");
meyveler.add("Muz");
meyveler.add("Çilek");
meyveler.add("Elma"); // Tekrar eden eleman, eklenmeyecek
System.out.println("HashSet içeriği: " + meyveler); // Sıra garanti edilmez
System.out.println("Muz var mı? " + meyveler.contains("Muz"));
meyveler.remove("Çilek");
System.out.println("Çilek çıkarıldıktan sonra: " + meyveler);
}
}
LinkedHashSet
LinkedHashSet, HashSet‘in tüm özelliklerini taşır ancak ek olarak elemanların eklenme sırasını (insertion order) korur. Dahili olarak hem bir hash tablosu hem de bir çift bağlı liste (doubly linked list) kullanır.
- Performans:
HashSet‘e benzer şekilde, temel işlemler ortalama olarak O(1) performansına sahiptir, ancak bağlı listeyi güncelleme maliyeti nedeniyleHashSet‘ten biraz daha yavaş olabilir. - Sıra: Elemanların eklenme sırasını korur.
- Kullanım: Benzersiz elemanlara ihtiyaç duyulduğunda ve bu elemanların eklenme sırasının korunması gerektiğinde tercih edilir.
import java.util.LinkedHashSet;
import java.util.Set;
public class LinkedHashSetOrnegi {
public static void main(String[] args) {
Set renkler = new LinkedHashSet<>();
renkler.add("Kırmızı");
renkler.add("Mavi");
renkler.add("Yeşil");
renkler.add("Kırmızı"); // Tekrar eden eleman, eklenmeyecek
System.out.println("LinkedHashSet içeriği: " + renkler); // Ekleme sırası korunur
}
}
TreeSet
TreeSet, elemanları sıralı bir şekilde depolayan bir Set uygulamasıdır. Dahili olarak bir Kırmızı-Siyah Ağaç (Red-Black Tree) veri yapısını kullanır. Elemanlar doğal sıralarına göre (Comparable arayüzü ile) veya bir Comparator nesnesi aracılığıyla belirtilen özel bir sıraya göre düzenlenir.
- Performans:
add(),remove()vecontains()metotları O(log n) performansına sahiptir. Bu,HashSetveLinkedHashSet‘ten daha yavaştır, ancak sıralı depolama avantajı sunar. - Sıra: Elemanları her zaman sıralı bir şekilde tutar.
- Kullanım: Elemanların benzersiz olmasının yanı sıra, her zaman sıralı bir şekilde tutulması gerektiğinde tercih edilir.
nulleleman eklenirken dikkatli olunmalıdır, çünkü sıralama için karşılaştırma yapılması gerekecektir.
import java.util.TreeSet;
import java.util.Set;
public class TreeSetOrnegi {
public static void main(String[] args) {
Set sayilar = new TreeSet<>();
sayilar.add(50);
sayilar.add(20);
sayilar.add(80);
sayilar.add(20); // Tekrar eden eleman, eklenmeyecek
sayilar.add(10);
System.out.println("TreeSet içeriği: " + sayilar); // Sıralı bir şekilde
}
}
Hangi Set Uygulamasını Ne Zaman Kullanmalı?
Doğru Set uygulamasını seçmek, uygulamanızın performansını ve davranışını doğrudan etkiler.
HashSet:- En hızlı performans gerektiğinde (O(1) ortalama).
- Elemanların sırasının önemli olmadığı durumlarda.
- En sık kullanılan ve genel amaçlı
Setuygulamasıdır.
LinkedHashSet:- Elemanların benzersiz olmasının yanı sıra, eklenme sırasının korunması gerektiğinde.
HashSet‘e göre biraz daha yavaş olsa da, hala çok iyi performans sunar.
TreeSet:- Elemanların her zaman sıralı bir şekilde (doğal sıralama veya özel
Comparatorile) tutulması gerektiğinde. HashSetveLinkedHashSet‘e göre daha yavaş (O(log n)), ancak sıralı erişim ve aralık sorguları için idealdir.- Elemanların
Comparablearayüzünü uygulaması veya birComparatorsağlanması zorunludur.
- Elemanların her zaman sıralı bir şekilde (doğal sıralama veya özel
Performans İpuçları
hashCode()veequals(): Özel nesneleriHashSetveyaLinkedHashSetiçinde kullanırken, bu nesneler içinhashCode()veequals()metotlarını doğru bir şekilde override etmek hayati önem taşır. Aksi takdirde,Setelemanların benzersizliğini doğru bir şekilde belirleyemez ve yinelenen elemanlar içerebilir veya var olan elemanları bulamayabilir.- Başlangıç Kapasitesi ve Yük Faktörü (Load Factor):
HashSetveLinkedHashSetiçin başlangıç kapasitesini (initial capacity) ve yük faktörünü (load factor) ayarlamak, hash çarpışmalarını azaltarak performansı optimize edebilir. Çok fazla eleman ekleyeceğinizi biliyorsanız, başlangıç kapasitesini yeterince büyük ayarlamak, yeniden boyutlandırma işlemlerini azaltır. TreeSetve Sıralama:TreeSetkullanırken, elemanların doğal sıralamasını (Comparable) veya özel sıralamasını (Comparator) iyi anlamak önemlidir. Büyük nesneler içinComparatorkullanmak, nesnelerin karşılaştırma mantığını daha esnek hale getirir.
Pratik Kullanım Senaryoları
Yinelenen Elemanları Kaldırma
Set‘in en yaygın kullanım alanlarından biri, bir koleksiyondaki yinelenen elemanları hızlıca kaldırmaktır.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class YinelenenleriKaldirma {
public static void main(String[] args) {
List isimler = new ArrayList<>(Arrays.asList("Ali", "Ayşe", "Mehmet", "Ali", "Fatma", "Ayşe"));
System.out.println("Orijinal liste: " + isimler);
// Listeyi Set'e dönüştürerek yinelenenleri kaldırma
Set benzersizIsimler = new HashSet<>(isimler);
System.out.println("Benzersiz isimler: " + benzersizIsimler);
// İsterseniz tekrar List'e dönüştürebilirsiniz
List benzersizIsimlerList = new ArrayList<>(benzersizIsimler);
System.out.println("Benzersiz isimler (liste olarak): " + benzersizIsimlerList);
}
}
İki Kümenin Kesişimi, Birleşimi ve Farkı
Set arayüzünün addAll(), retainAll() ve removeAll() metotları, matematiksel küme işlemlerini kolayca yapmanızı sağlar.
import java.util.HashSet;
import java.util.Set;
public class KumeIslemleri {
public static void main(String[] args) {
Set kumeA = new HashSet<>();
kumeA.add(1); kumeA.add(2); kumeA.add(3); kumeA.add(4);
Set kumeB = new HashSet<>();
kumeB.add(3); kumeB.add(4); kumeB.add(5); kumeB.add(6);
System.out.println("Küme A: " + kumeA);
System.out.println("Küme B: " + kumeB);
// Birleşim (Union)
Set birlesim = new HashSet<>(kumeA);
birlesim.addAll(kumeB);
System.out.println("Birleşim (A U B): " + birlesim); // [1, 2, 3, 4, 5, 6]
// Kesişim (Intersection)
Set kesisim = new HashSet<>(kumeA);
kesisim.retainAll(kumeB);
System.out.println("Kesişim (A ∩ B): " + kesisim); // [3, 4]
// Fark (Difference)
Set fark = new HashSet<>(kumeA);
fark.removeAll(kumeB);
System.out.println("Fark (A - B): " + fark); // [1, 2]
}
}
Özel Nesnelerle Kullanım (hashCode() ve equals() Önemi)
Kendi tanımladığınız sınıfların nesnelerini HashSet veya LinkedHashSet içinde kullanırken, bu nesnelerin benzersizliğini doğru bir şekilde sağlamak için Object sınıfından miras alınan hashCode() ve equals() metotlarını geçersiz kılmanız (override etmeniz) zorunludur. Aksi takdirde, Set aynı içeriğe sahip iki farklı nesneyi iki ayrı eleman olarak kabul edebilir.
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
class Ogrenci {
private String ad;
private int ogrenciNo;
public Ogrenci(String ad, int ogrenciNo) {
this.ad = ad;
this.ogrenciNo = ogrenciNo;
}
public String getAd() { return ad; }
public int getOgrenciNo() { return ogrenciNo; }
@Override
public String toString() {
return "Ogrenci{" + "ad='" + ad + '\'' + ", ogrenciNo=" + ogrenciNo + '}';
}
// hashCode() ve equals() metotlarını override etmek kritik öneme sahiptir.
// IDE'ler (IntelliJ, Eclipse) bu metotları otomatik olarak oluşturabilir.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Ogrenci ogrenci = (Ogrenci) o;
return ogrenciNo == ogrenci.ogrenciNo &&
Objects.equals(ad, ogrenci.ad);
}
@Override
public int hashCode() {
return Objects.hash(ad, ogrenciNo);
}
}
public class OgrenciSetOrnegi {
public static void main(String[] args) {
Set ogrenciler = new HashSet<>();
ogrenciler.add(new Ogrenci("Ahmet", 101));
ogrenciler.add(new Ogrenci("Zeynep", 102));
ogrenciler.add(new Ogrenci("Ahmet", 101)); // Aynı içerikli öğrenci
System.out.println("Öğrenci sayısı: " + ogrenciler.size());
System.out.println("Öğrenciler: " + ogrenciler);
// Eğer hashCode() ve equals() override edilmeseydi, çıktı 3 olurdu!
// Doğru override edildiği için çıktı 2 olacaktır.
}
}
Sonuç ve Sıkça Sorulan Sorular
Java’daki Set arayüzü, benzersiz eleman koleksiyonlarına ihtiyaç duyulan birçok senaryoda vazgeçilmez bir araçtır. HashSet, LinkedHashSet ve TreeSet gibi farklı uygulamaları sayesinde, performans, sıralama ve ekleme sırası gibi gereksinimlere göre en uygun çözümü seçme esnekliği sunar. Koleksiyonunuzdaki veri bütünlüğünü korumak ve yinelenen elemanlardan kaçınmak için Set‘i etkin bir şekilde kullanmak, daha sağlam ve verimli Java uygulamaları geliştirmenize yardımcı olacaktır.
Sıkça Sorulan Sorular (SSS)
1. Java Set null eleman kabul eder mi?
Evet, HashSet ve LinkedHashSet bir adet null eleman kabul edebilir. TreeSet ise doğal sıralama veya kullanılan Comparator‘ın null‘ları nasıl ele aldığına bağlı olarak NullPointerException fırlatabilir. Genellikle, TreeSet‘e null eklemek önerilmez.
2. Java Set thread-safe midir?
Hayır, Set arayüzünün standart uygulamaları (HashSet, LinkedHashSet, TreeSet) thread-safe değildir. Çoklu iş parçacıklı ortamlarda güvenli kullanım için Collections.synchronizedSet(new HashSet()) gibi senkronize edilmiş bir sarmalayıcı kullanabilir veya java.util.concurrent paketindeki CopyOnWriteArraySet gibi thread-safe alternatifleri tercih edebilirsiniz.
3. Set elemanlarını nasıl sıralar?
HashSet: Elemanların sırası garanti edilmez.LinkedHashSet: Elemanların eklenme sırasını (insertion order) korur.TreeSet: Elemanları her zaman sıralı bir şekilde tutar. Bu sıralama, elemanların doğal sıralaması (Comparablearayüzü ile) veyaTreeSetoluşturulurken sağlanan birComparatorile belirlenir.
4. List ve Set arasındaki temel fark nedir?
Temel fark, Set‘in yinelenen elemanlara izin vermemesidir; her eleman benzersizdir. List ise yinelenen elemanları içerebilir ve elemanların eklenme sırasını korur, bu sayede indeks tabanlı erişim mümkündür. Set‘te indeks tabanlı erişim yoktur.
5. Özel nesneleri Set içinde kullanırken hashCode() ve equals() metotları neden önemlidir?
HashSet ve LinkedHashSet, elemanların benzersizliğini ve konumunu belirlemek için bu metotları kullanır. hashCode() bir nesnenin hash kodunu üretirken, equals() iki nesnenin eşit olup olmadığını kontrol eder. Eğer bu metotlar doğru bir şekilde override edilmezse, aynı içeriğe sahip iki farklı nesne Set tarafından farklı olarak algılanabilir ve Set‘in benzersizlik özelliği bozulur.
