Takip et

Giriş: Java’da Listeler Neden Önemlidir?

Giriş: Java’da Listeler Neden Önemlidir? Java’da veri yapıları, programlarımızın veriyi etkin bir şekilde depolaması ve işlemesi için tem

Giriş: Java’da Listeler Neden Önemlidir?

Java’da veri yapıları, programlarımızın veriyi etkin bir şekilde depolaması ve işlemesi için temel taşlardır. Bu yapıların en sık kullanılanlarından biri de “Listeler”dir. Java Collections Framework’ün önemli bir parçası olan List arayüzü, elemanları belirli bir sırada tutan ve indeks tabanlı erişime izin veren dinamik boyutlu bir koleksiyon türüdür.

Geleneksel dizilerin (array) aksine, Listeler dinamik olarak boyutlarını değiştirebilirler. Bu, programlama sırasında kaç eleman depolayacağımızı kesin olarak bilmediğimiz durumlarda büyük bir avantaj sağlar. Örneğin, bir kullanıcının sepetindeki ürünleri, bir veritabanından çekilen kayıtları veya bir dosyadaki satırları depolamak için Listeler idealdir. Listeler, elemanların eklenme sırasını korur ve aynı elemandan birden fazla kez bulundurmaya (duplikatlara) izin verir. Bu rehberde, Java’da Listelerin nasıl kullanılacağını, farklı uygulama türlerini, temel ve gelişmiş işlemlerini ve pratik ipuçlarını detaylı bir şekilde inceleyeceğiz.

Java’da List Arayüzü ve Temel Uygulamaları

Java’da List, java.util paketinde tanımlanmış bir arayüzdür. Bu arayüz, sıralı bir koleksiyonu temsil eder ve elemanlara indeksleri aracılığıyla erişim, ekleme, silme gibi standart işlemleri tanımlar. List arayüzünün birçok farklı uygulaması (implementation) bulunur, ancak en yaygın ve bilinenleri ArrayList ve LinkedList‘tir.

ArrayList

ArrayList, List arayüzünün dizi (array) tabanlı bir uygulamasıdır. Dahili olarak dinamik bir dizi kullanır. Bu yapısı nedeniyle:

  • Elemanlara Erişim: Bir elemana indeksi üzerinden erişim (get(index)) çok hızlıdır, çünkü dizilerde bu işlem doğrudan bellek adresine giderek gerçekleştirilir (O(1) zaman karmaşıklığı).
  • Eleman Ekleme/Silme: Listenin sonuna eleman eklemek genellikle hızlıdır (O(1)). Ancak, listenin başına veya ortasına eleman eklemek veya silmek, sonraki tüm elemanların kaydırılmasını gerektirdiği için daha maliyetlidir (O(n) zaman karmaşıklığı).
  • Hafıza Kullanımı: Genellikle LinkedList‘e göre daha az hafıza kullanır, çünkü her eleman için ek bir bağlayıcı (pointer) bilgisi tutulmaz.

Ne Zaman Kullanılır? Eğer elemanlara sıkça indeks üzerinden erişmeniz veya listenin sonuna eleman eklemeniz gerekiyorsa, ArrayList tercih edilmelidir.


import java.util.ArrayList;
import java.util.List;

public class ArrayListOrnegi {
    public static void main(String[] args) {
        // String türünde bir ArrayList oluşturma
        List urunler = new ArrayList<>();
        urunler.add("Laptop");
        urunler.add("Klavye");
        urunler.add("Mouse");

        System.out.println("Ürünler: " + urunler); // Çıktı: [Laptop, Klavye, Mouse]
    }
}

LinkedList

LinkedList, List arayüzünün çift yönlü bağlı liste tabanlı bir uygulamasıdır. Her eleman (node), kendinden önceki ve sonraki elemanın referansını içerir. Bu yapısı nedeniyle:

  • Elemanlara Erişim: Bir elemana indeksi üzerinden erişim (get(index)) yavaştır, çünkü istenen elemana ulaşmak için listenin başından veya sonundan itibaren elemanlar tek tek gezilmelidir (O(n) zaman karmaşıklığı).
  • Eleman Ekleme/Silme: Listenin başına, sonuna veya ortasına eleman eklemek veya silmek çok hızlıdır (O(1) zaman karmaşıklığı), çünkü sadece ilgili elemanların referansları güncellenir.
  • Hafıza Kullanımı: Her eleman için ek olarak önceki ve sonraki elemanın referansını tuttuğu için ArrayList‘e göre daha fazla hafıza kullanır.

Ne Zaman Kullanılır? Eğer listenin başına, ortasına veya sonuna sıkça eleman eklemeniz veya silmeniz gerekiyorsa, LinkedList tercih edilmelidir.


import java.util.LinkedList;
import java.util.List;

public class LinkedListOrnegi {
    public static void main(String[] args) {
        // Integer türünde bir LinkedList oluşturma
        List sayilar = new LinkedList<>();
        sayilar.add(10);
        sayilar.add(20);
        sayilar.add(0, 5); // Başa 5 ekle

        System.out.println("Sayılar: " + sayilar); // Çıktı: [5, 10, 20]
    }
}

Vector (Kısaca)

Vector, ArrayList‘e benzer şekilde dizi tabanlıdır ancak senkronize (thread-safe) bir sınıftır. Bu, çoklu iş parçacıklı (multi-threaded) ortamlarda güvenli olduğu anlamına gelir, ancak tek iş parçacıklı ortamlarda performans maliyeti getirir. Genellikle modern Java uygulamalarında ArrayList veya Collections.synchronizedList() ile senkronize edilmiş bir ArrayList tercih edilir.

Listelerle Temel İşlemler

List arayüzü, elemanları yönetmek için zengin bir metod seti sunar. İşte en sık kullanılan temel işlemler:

Eleman Ekleme

  • add(E e): Belirtilen elemanı listenin sonuna ekler.
  • add(int index, E element): Belirtilen elemanı belirtilen indekse ekler. Mevcut elemanlar sağa kaydırılır.

List meyveler = new ArrayList<>();
meyveler.add("Elma");
meyveler.add("Armut");
meyveler.add(1, "Muz"); // Elma, Muz, Armut
System.out.println(meyveler); // Çıktı: [Elma, Muz, Armut]

Elemana Erişim

  • get(int index): Belirtilen indeksteki elemanı döndürür.

String ilkMeyve = meyveler.get(0); // Elma
System.out.println("İlk meyve: " + ilkMeyve); // Çıktı: İlk meyve: Elma

Eleman Güncelleme

  • set(int index, E element): Belirtilen indeksteki elemanı belirtilen elemanla değiştirir ve eski elemanı döndürür.

meyveler.set(1, "Çilek"); // Elma, Çilek, Armut
System.out.println(meyveler); // Çıktı: [Elma, Çilek, Armut]

Eleman Silme

  • remove(int index): Belirtilen indeksteki elemanı siler ve silinen elemanı döndürür.
  • remove(Object o): Belirtilen elemanın ilk geçtiği yeri siler (varsa) ve silinip silinmediğini belirten bir boolean değer döndürür.

meyveler.remove(0); // Çilek, Armut
System.out.println(meyveler); // Çıktı: [Çilek, Armut]

boolean silindiMi = meyveler.remove("Armut"); // Çilek
System.out.println("Armut silindi mi? " + silindiMi); // Çıktı: Armut silindi mi? true
System.out.println(meyveler); // Çıktı: [Çilek]

Liste Bilgileri

  • size(): Listedeki eleman sayısını döndürür.
  • isEmpty(): Liste boşsa true, aksi takdirde false döndürür.
  • contains(Object o): Belirtilen elemanı içeriyorsa true, aksi takdirde false döndürür.
  • indexOf(Object o): Belirtilen elemanın ilk geçtiği yerin indeksini döndürür. Bulunamazsa -1 döndürür.
  • clear(): Listedeki tüm elemanları siler.

System.out.println("Liste boyutu: " + meyveler.size()); // Çıktı: Liste boyutu: 1
System.out.println("Liste boş mu? " + meyveler.isEmpty()); // Çıktı: Liste boş mu? false
System.out.println("Çilek içeriyor mu? " + meyveler.contains("Çilek")); // Çıktı: Çilek içeriyor mu? true
System.out.println("Çilek'in indeksi: " + meyveler.indexOf("Çilek")); // Çıktı: Çilek'in indeksi: 0

meyveler.clear();
System.out.println("Liste boş mu (temizlendikten sonra)? " + meyveler.isEmpty()); // Çıktı: Liste boş mu (temizlendikten sonra)? true

Listede Gezinme (Iterasyon)

Listelerdeki elemanlar üzerinde çeşitli yollarla döngü kurulabilir:


List sehirler = new ArrayList<>();
sehirler.add("Ankara");
sehirler.add("İstanbul");
sehirler.add("İzmir");

// 1. Geleneksel for döngüsü
System.out.println("--- Geleneksel for döngüsü ---");
for (int i = 0; i < sehirler.size(); i++) {
    System.out.println(sehirler.get(i));
}

// 2. Gelişmiş for-each döngüsü (Java 5+)
System.out.println("--- For-each döngüsü ---");
for (String sehir : sehirler) {
    System.out.println(sehir);
}

// 3. Iterator kullanımı
import java.util.Iterator;
System.out.println("--- Iterator kullanımı ---");
Iterator iterator = sehirler.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

Gelişmiş Konular ve Pratik İpuçları

Generics (Jenerikler) ile Tip Güvenliği

Java 5 ile tanıtılan Generics, koleksiyonların hangi türden elemanları tutacağını belirtmemizi sağlar. Bu, derleme zamanında tip hatalarını yakalamamıza yardımcı olur ve ClassCastException gibi çalışma zamanı hatalarını önler. Her zaman Generics kullanın!


// Kötü örnek (Generics yok, tip güvenliği zayıf)
List hamListe = new ArrayList();
hamListe.add("Merhaba");
hamListe.add(123); // Farklı tipte eleman eklenebilir

// İyi örnek (Generics ile tip güvenliği sağlanır)
List guvenliListe = new ArrayList<>();
guvenliListe.add("Dünya");
// guvenliListe.add(456); // Derleme hatası verir: int, String'e dönüştürülemez

ListIterator

Iterator‘a ek olarak, List arayüzü ListIterator‘ı da destekler. ListIterator, listelerde hem ileri hem de geri yönde gezinme, eleman ekleme, eleman silme ve eleman değiştirme gibi daha gelişmiş işlemler yapmanızı sağlar.


import java.util.ListIterator;

List harfler = new ArrayList<>();
harfler.add("A");
harfler.add("B");
harfler.add("C");

ListIterator listIterator = harfler.listIterator();
while (listIterator.hasNext()) {
    String harf = listIterator.next();
    if (harf.equals("B")) {
        listIterator.set("X"); // B'yi X ile değiştir
    }
}
System.out.println(harfler); // Çıktı: [A, X, C]

// Geriye doğru gezinme
while (listIterator.hasPrevious()) {
    System.out.println("Geriye: " + listIterator.previous());
}

Değişmez (Immutable) Listeler

Java 9’dan itibaren, List.of() statik fabrika metodu ile değişmez listeler oluşturabilirsiniz. Bu listeler oluşturulduktan sonra eleman eklenemez, çıkarılamaz veya değiştirilemez. Bu, özellikle thread güvenliği ve veri bütünlüğü için faydalıdır.


import java.util.List;

List sabitRenkler = List.of("Kırmızı", "Yeşil", "Mavi");
System.out.println(sabitRenkler); // Çıktı: [Kırmızı, Yeşil, Mavi]

// sabitRenkler.add("Sarı"); // UnsupportedOperationException hatası verir

Performans İpuçları: ArrayList vs LinkedList

  • ArrayList ne zaman?
    • Sıkça indeks tabanlı erişim (get()) yapılıyorsa.
    • Listeye genellikle sonundan eleman ekleniyorsa (add()).
    • Küçük listeler için başlangıçta performansı genellikle daha iyidir.
  • LinkedList ne zaman?
    • Sıkça listenin başına veya ortasına eleman ekleniyor veya siliniyorsa.
    • ListIterator ile ileri-geri gezinme ve eleman ekleme/silme işlemleri yapılacaksa.

Genel bir kural olarak, çoğu senaryoda ArrayList iyi bir başlangıç noktasıdır. Performans darboğazları fark edildiğinde veya özel durumlar gerektiğinde LinkedList gibi diğer uygulamalar düşünülmelidir.

Thread Güvenliği

ArrayList ve LinkedList senkronize değildir (thread-safe değillerdir). Birden fazla iş parçacığı aynı anda bu listelere erişip değiştirmeye çalışırsa beklenmedik sonuçlar veya hatalar oluşabilir. Thread güvenliği sağlamak için:

  • Collections.synchronizedList(List list) metodunu kullanabilirsiniz. Bu, mevcut listeyi senkronize bir sarmalayıcı ile döndürür.
  • java.util.concurrent.CopyOnWriteArrayList sınıfını kullanabilirsiniz. Bu sınıf, her değişiklikte listenin yeni bir kopyasını oluşturarak thread güvenliği sağlar, ancak büyük listeler ve sık yazma işlemleri için performanslı değildir.

Sonuç ve Sıkça Sorulan Sorular (SSS)

Java’daki Listeler, dinamik ve sıralı veri depolama ihtiyacını karşılayan vazgeçilmez veri yapılarıdır. ArrayList ve LinkedList gibi farklı uygulamaları sayesinde, uygulamanızın özel ihtiyaçlarına göre en uygun çözümü seçme esnekliğine sahipsiniz. Generics kullanımıyla tip güvenliğini sağlamak, ListIterator ile gelişmiş gezinme ve Java 9 ile gelen değişmez listeler gibi özellikler, Listelerin gücünü ve esnekliğini daha da artırmaktadır. Doğru liste türünü seçmek ve temel işlemleri etkin bir şekilde kullanmak, daha performanslı ve hatasız Java uygulamaları geliştirmenizin anahtarlarından biridir.

Sıkça Sorulan Sorular (SSS)

1. ArrayList ve LinkedList arasındaki temel fark nedir?

ArrayList dahili olarak dinamik bir dizi kullanır ve indeks tabanlı erişimde hızlıdır (O(1)), ancak ortadan ekleme/silme yavaştır (O(n)). LinkedList ise çift yönlü bağlı liste kullanır ve ortadan ekleme/silme hızlıdır (O(1)), ancak indeks tabanlı erişim yavaştır (O(n)).

2. Bir Listeyi nasıl senkronize ederim (thread-safe yaparım)?

Senkronize olmayan bir Listeyi (örn. ArrayList) senkronize etmek için Collections.synchronizedList(myList) metodunu kullanabilirsiniz. Alternatif olarak, java.util.concurrent.CopyOnWriteArrayList sınıfını da kullanabilirsiniz, ancak bu sınıfın performans karakteristikleri farklıdır.

3. List yerine ne zaman Set veya Map kullanmalıyım?

  • Set: Eğer elemanların sırası önemli değilse ve yalnızca benzersiz elemanlar depolamak istiyorsanız (duplikatlara izin vermiyorsanız) Set kullanmalısınız (örn. HashSet, TreeSet).
  • Map: Eğer veriyi anahtar-değer çiftleri halinde depolamak ve anahtarlar aracılığıyla değerlere hızlıca erişmek istiyorsanız Map kullanmalısınız (örn. HashMap, TreeMap).

4. List.of() ile oluşturulan liste neden değiştirilemez (immutable)?

List.of() ile oluşturulan listeler, Java 9’dan itibaren gelen “factory methods for immutable collections” (değişmez koleksiyonlar için fabrika metotları) özelliğidir. Bu listeler oluşturulduktan sonra eleman eklenemez, çıkarılamaz veya değiştirilemez. Bu, verinin bütünlüğünü garanti altına almak ve özellikle çoklu iş parçacıklı ortamlarda güvenlik sağlamak için tasarlanmıştır.

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