Takip et

Java Set – Java’da Küme Yapısı

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 elem

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 Set içinde her eleman yalnızca bir kez bulunabilir. Aynı elemanı tekrar ekleme girişimi göz ardı edilir.
  • Sırasızlık (Genellikle): Set arayüzü, elemanların depolanma sırası hakkında herhangi bir garanti vermez. Ancak, LinkedHashSet gibi bazı uygulamalar ekleme sırasını korurken, TreeSet gibi 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 Set uygulaması (HashSet, LinkedHashSet) bir adet null elemanı depolayabilir. TreeSet ise doğal sıralama veya kullanılan Comparator null elemanları desteklemiyorsa NullPointerException fırlatabilir.
  • Thread-Safe Değildir: Varsayılan Set uygulamaları thread-safe değildir. Çoklu iş parçacıklı ortamlarda kullanıldığında dışarıdan senkronizasyon mekanizmaları (örn. Collections.synchronizedSet()) veya CopyOnWriteArraySet gibi 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 varsa false döner, aksi takdirde true döner.
  • boolean remove(Object o): Belirtilen elemanı kümeden kaldırır. Eğer eleman kümede varsa true döner, aksi takdirde false dö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() ve size() 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 nedeniyle HashSet‘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() ve contains() metotları O(log n) performansına sahiptir. Bu, HashSet ve LinkedHashSet‘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. null eleman 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ı Set uygulaması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 Comparator ile) tutulması gerektiğinde.
    • HashSet ve LinkedHashSet‘e göre daha yavaş (O(log n)), ancak sıralı erişim ve aralık sorguları için idealdir.
    • Elemanların Comparable arayüzünü uygulaması veya bir Comparator sağlanması zorunludur.

Performans İpuçları

  • hashCode() ve equals(): Özel nesneleri HashSet veya LinkedHashSet içinde kullanırken, bu nesneler için hashCode() ve equals() metotlarını doğru bir şekilde override etmek hayati önem taşır. Aksi takdirde, Set elemanları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): HashSet ve LinkedHashSet iç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.
  • TreeSet ve Sıralama: TreeSet kullanırken, elemanların doğal sıralamasını (Comparable) veya özel sıralamasını (Comparator) iyi anlamak önemlidir. Büyük nesneler için Comparator kullanmak, 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ı (Comparable arayüzü ile) veya TreeSet oluşturulurken sağlanan bir Comparator ile 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.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Mobil sürümden çık