Java’da Haritalar (Maps) Nedir ve Neden Kullanılır?
Java programlamada veri yapıları, bilgiyi düzenli bir şekilde depolamak ve yönetmek için temel taşlardır. Listeler (List) ve Kümeler (Set) gibi yaygın veri yapılarının yanı sıra, anahtar-değer çiftleri halinde veri depolamanızı sağlayan özel bir yapı daha vardır: Haritalar (Map).
Bir harita, her biri benzersiz bir anahtarla ilişkilendirilmiş bir değer koleksiyonudur. Tıpkı bir sözlük gibi düşünebilirsiniz; her kelime (anahtar) kendi tanımına (değer) sahiptir. Bu yapı, belirli bir anahtara hızlı bir şekilde erişim sağlamak istediğiniz durumlarda son derece faydalıdır. Örneğin, bir kullanıcının ID’sine göre kullanıcı nesnesine, bir ülkenin koduna göre tam adına veya bir ürünün barkoduna göre fiyatına erişmek için haritalar idealdir.
Haritalar, listelerden farklı olarak öğelere indeks yerine anahtarlar aracılığıyla erişim sağlar. Bu, dinamik ve esnek veri modelleri oluşturmak için güçlü bir araçtır ve birçok gerçek dünya uygulamasının temelini oluşturur.
Java’daki Temel Map Türleri
Java’da Map bir arayüzdür ve bu arayüzü uygulayan çeşitli sınıflar bulunur. Her sınıfın kendine özgü özellikleri, performans karakteristikleri ve kullanım senaryoları vardır. En yaygın kullanılan üç türü inceleyelim:
HashMap
HashMap, Java’daki en sık kullanılan Map uygulamasıdır. Anahtarların ve değerlerin depolanması için karma (hashing) mekanizmasını kullanır. Bu sayede, eleman ekleme, silme ve erişim işlemleri ortalama olarak sabit zamanda (O(1)) gerçekleşir, bu da onu çok hızlı yapar.
- Sıralama Garantisi Yoktur: Elemanların eklenme sırasını veya herhangi bir doğal sıralamayı korumaz. Yineleme sırasında elemanların sırası tahmin edilemez.
nullAnahtar ve Değer: Bir tanenullanahtar ve birden fazlanulldeğer kabul edebilir.- Senkronize Değildir: Çoklu iş parçacıklı (multi-threaded) ortamlarda güvenli değildir. Eğer çoklu iş parçacığı erişimi gerekiyorsa
ConcurrentHashMapkullanılmalıdır.
LinkedHashMap
LinkedHashMap, HashMap‘in tüm özelliklerini taşır ancak ek olarak elemanların eklenme sırasını (insertion order) korur. Yani, harita üzerinde yineleme yaptığınızda elemanlar eklendikleri sıraya göre döner. İsteğe bağlı olarak, elemanların son erişim sırasına göre de sıralanabilir (access order), bu da LFU/LRU önbellekleri gibi senaryolar için kullanışlıdır.
- Sıralama Garantisi Var: Elemanların eklenme sırasını korur.
- Performans:
HashMap‘e göre biraz daha yavaştır, çünkü sıralamayı korumak için çift yönlü bir bağlı liste (doubly linked list) yapısı kullanır.
TreeMap
TreeMap, anahtarları doğal sıralarına göre veya bir Comparator aracılığıyla belirtilen özel bir sıralamaya göre sıralar. Bu, haritadaki elemanlara her zaman sıralı bir şekilde erişmek istediğiniz durumlarda kullanışlıdır.
- Sıralama Garantisi Var: Anahtarları sıralı bir şekilde tutar.
nullAnahtar Kabul Etmez: Anahtarların sıralanabilmesi gerektiği içinnullanahtarlara izin vermez. Ancaknulldeğer kabul edebilir.- Performans: Ekleme, silme ve erişim işlemleri O(log N) zaman karmaşıklığına sahiptir, bu da
HashMap‘ten daha yavaştır ancak sıralı erişim sağlar.
Map Temel İşlemleri ve Kullanımı
Java’da bir harita oluşturmak, eleman eklemek, bunlara erişmek ve üzerinde çeşitli işlemler yapmak oldukça basittir. İşte temel işlemlerin bir özeti:
Map Oluşturma
Harita oluşturmak için genellikle HashMap sınıfını kullanırız. Anahtar ve değer tiplerini belirtmeyi unutmayın.
import java.util.HashMap;
import java.util.Map;
public class MapKullanimi {
public static void main(String[] args) {
// String anahtar ve Integer değer tutan bir HashMap oluşturma
Map ogrenciNotlari = new HashMap<>();
System.out.println("Başlangıçta harita boş mu? " + ogrenciNotlari.isEmpty()); // true
}
}
Eleman Ekleme (put)
Bir anahtar-değer çiftini haritaya eklemek için put() metodunu kullanırız. Eğer anahtar zaten varsa, eski değer yeni değerle değiştirilir ve eski değer döndürülür.
// Eleman ekleme
ogrenciNotlari.put("Ali", 85);
ogrenciNotlari.put("Ayşe", 92);
ogrenciNotlari.put("Mehmet", 78);
ogrenciNotlari.put("Zeynep", 92); // Farklı anahtar, aynı değer olabilir
System.out.println("Öğrenci Notları: " + ogrenciNotlari); // {Ali=85, Zeynep=92, Ayşe=92, Mehmet=78} - Sıra garanti değil
Elemanlara Erişim (get)
Belirli bir anahtarla ilişkili değeri almak için get() metodunu kullanırız. Eğer anahtar bulunamazsa null döner.
// Elemana erişim
int aliNotu = ogrenciNotlari.get("Ali");
System.out.println("Ali'nin Notu: " + aliNotu); // 85
// Olmayan bir anahtarı deneme
Integer canNotu = ogrenciNotlari.get("Can");
System.out.println("Can'ın Notu: " + canNotu); // null
// getOrDefault() ile varsayılan değer kullanma
int fatmaNotu = ogrenciNotlari.getOrDefault("Fatma", 0);
System.out.println("Fatma'nın Notu (varsayılan ile): " + fatmaNotu); // 0
Eleman Kontrolü (containsKey, containsValue)
Bir haritanın belirli bir anahtarı veya değeri içerip içermediğini kontrol edebilirsiniz.
// Anahtar kontrolü
boolean aliVarMi = ogrenciNotlari.containsKey("Ali");
System.out.println("Ali haritada var mı? " + aliVarMi); // true
boolean ahmetVarMi = ogrenciNotlari.containsKey("Ahmet");
System.out.println("Ahmet haritada var mı? " + ahmetVarMi); // false
// Değer kontrolü
boolean doksanIkiVarMi = ogrenciNotlari.containsValue(92);
System.out.println("92 notu haritada var mı? " + doksanIkiVarMi); // true
Eleman Silme (remove)
Belirli bir anahtar-değer çiftini haritadan silmek için remove() metodunu kullanırız. Silinen değer döndürülür. Anahtar bulunamazsa null döner.
// Eleman silme
Integer silinenNot = ogrenciNotlari.remove("Mehmet");
System.out.println("Silinen Mehmet'in Notu: " + silinenNot); // 78
System.out.println("Güncel Öğrenci Notları: " + ogrenciNotlari); // {Ali=85, Zeynep=92, Ayşe=92}
// Olmayan bir anahtarı silmeye çalışmak
Integer olmayanSilinen = ogrenciNotlari.remove("Can");
System.out.println("Olmayan Can'ın Notu: " + olmayanSilinen); // null
Boyut ve Temizleme (size, isEmpty, clear)
Haritanın kaç eleman içerdiğini, boş olup olmadığını ve tüm elemanlarını temizlemeyi sağlayan metodlar.
System.out.println("Haritanın boyutu: " + ogrenciNotlari.size()); // 3
System.out.println("Harita boş mu? " + ogrenciNotlari.isEmpty()); // false
ogrenciNotlari.clear(); // Tüm elemanları siler
System.out.println("Harita temizlendikten sonra: " + ogrenciNotlari); // {}
System.out.println("Harita boş mu? " + ogrenciNotlari.isEmpty()); // true
Map Üzerinde Yineleme (Iterating)
Haritadaki elemanlar üzerinde dolaşmak için birkaç farklı yöntem bulunur. İhtiyacınıza göre en uygun olanı seçebilirsiniz.
Anahtarlar Üzerinden Yineleme (keySet())
Sadece anahtarlar üzerinde yineleme yapmak istiyorsanız keySet() metodunu kullanabilirsiniz. Bu metod, haritadaki tüm anahtarları içeren bir Set döndürür.
Map ulkeBaskentleri = new HashMap<>();
ulkeBaskentleri.put("Türkiye", "Ankara");
ulkeBaskentleri.put("Fransa", "Paris");
ulkeBaskentleri.put("Almanya", "Berlin");
System.out.println("--- Anahtarlar Üzerinden Yineleme ---");
for (String ulke : ulkeBaskentleri.keySet()) {
System.out.println("Ülke: " + ulke);
}
Değerler Üzerinden Yineleme (values())
Sadece değerler üzerinde yineleme yapmak istiyorsanız values() metodunu kullanabilirsiniz. Bu metod, haritadaki tüm değerleri içeren bir Collection döndürür.
System.out.println("--- Değerler Üzerinden Yineleme ---");
for (String baskent : ulkeBaskentleri.values()) {
System.out.println("Başkent: " + baskent);
}
Girişler (Entry) Üzerinden Yineleme (entrySet())
Hem anahtarlara hem de değerlere aynı anda erişmek istiyorsanız en verimli yol entrySet() metodunu kullanmaktır. Bu metod, haritadaki her bir anahtar-değer çiftini temsil eden Map.Entry nesnelerinin bir Set‘ini döndürür.
System.out.println("--- Girişler Üzerinden Yineleme ---");
for (Map.Entry entry : ulkeBaskentleri.entrySet()) {
System.out.println("Ülke: " + entry.getKey() + ", Başkent: " + entry.getValue());
}
Java 8 forEach() Metodu ile Yineleme
Java 8 ile birlikte gelen forEach() metodu, haritalar üzerinde yineleme yapmak için daha modern ve okunabilir bir yol sunar. Lambda ifadeleri ile birlikte kullanılır.
System.out.println("--- Java 8 forEach() ile Yineleme ---");
ulkeBaskentleri.forEach((ulke, baskent) ->
System.out.println("Ülke: " + ulke + ", Başkent: " + baskent)
);
Pratik İpuçları ve Gelişmiş Kullanım
Doğru Map Türünü Seçmek
HashMap: Çoğu genel amaçlı kullanım için varsayılan seçimdir. Yüksek performans gerektiren ve sıralamanın önemli olmadığı durumlarda tercih edilir.LinkedHashMap: Elemanların eklenme sırasının korunması gerektiğinde veya LRU/LFU önbellekleri gibi erişim sırasına göre sıralamanın önemli olduğu durumlarda kullanılır.TreeMap: Anahtarların doğal sıralarına göre veya özel birComparatorile sıralanması gerektiğinde kullanılır. Örneğin, bir anahtar aralığındaki elemanlara erişmek (subMap) istediğinizde.ConcurrentHashMap: Çoklu iş parçacıklı (multi-threaded) ortamlarda güvenli harita erişimi sağlamak için kullanılır. Performans açısındanHashtable‘dan daha iyidir.
Değişmez (Immutable) Map’ler Oluşturma
Java 9 ve sonraki versiyonlarda, küçük, değişmez haritalar oluşturmak için Map.of() ve Map.ofEntries() gibi kolaylaştırıcı metodlar bulunmaktadır. Bu haritalar oluşturulduktan sonra değiştirilemezler (ekleme, silme yapılamaz). Güvenlik ve performans avantajları sunarlar.
// Değişmez Map oluşturma (Java 9+)
Map immutableMap = Map.of("Bir", 1, "İki", 2, "Üç", 3);
// immutableMap.put("Dört", 4); // UnsupportedOperationException hatası verir
System.out.println("Değişmez Map: " + immutableMap);
// Varolan bir Map'ten değişmez kopya oluşturma (Java 10+)
Map mutableMap = new HashMap<>();
mutableMap.put("A", "Apple");
Map immutableCopy = Map.copyOf(mutableMap);
// immutableCopy.put("B", "Banana"); // UnsupportedOperationException
System.out.println("Değişmez Kopya Map: " + immutableCopy);
computeIfAbsent() ve merge() gibi Modern Metodlar
Java 8 ile birlikte Map arayüzüne eklenen yeni metodlar, haritalarla çalışmayı daha kolay ve verimli hale getirir.
computeIfAbsent(K key, Function super K, ? extends V> mappingFunction): Eğer belirtilen anahtar haritada yoksa, fonksiyonu çalıştırarak bir değer hesaplar ve bu değeri anahtarla ilişkilendirir.merge(K key, V value, BiFunction super V, ? super V, ? extends V> remappingFunction): Eğer belirtilen anahtar haritada yoksa, değeri ekler. Eğer anahtar varsa, mevcut değer ile yeni değeri birleştirmek için belirtilen fonksiyonu kullanır.
Map kelimeSayilari = new HashMap<>();
kelimeSayilari.put("elma", 2);
// "armut" yoksa, 1 olarak ekle
kelimeSayilari.computeIfAbsent("armut", k -> 1);
System.out.println("ComputeIfAbsent sonrası: " + kelimeSayilari); // {elma=2, armut=1}
// "elma" zaten var, mevcut değer ile yeni değeri topla
kelimeSayilari.merge("elma", 1, (mevcutDeger, yeniDeger) -> mevcutDeger + yeniDeger);
System.out.println("Merge sonrası (elma): " + kelimeSayilari); // {elma=3, armut=1}
// "kiraz" yok, 1 olarak ekle
kelimeSayilari.merge("kiraz", 1, (mevcutDeger, yeniDeger) -> mevcutDeger + yeniDeger);
System.out.println("Merge sonrası (kiraz): " + kelimeSayilari); // {elma=3, armut=1, kiraz=1}
Sonuç
Java’daki haritalar, anahtar-değer çiftleri halinde veri depolamak için vazgeçilmez bir veri yapısıdır. HashMap, LinkedHashMap ve TreeMap gibi farklı türleri, çeşitli performans ve sıralama gereksinimlerine cevap verir. Temel ekleme, erişim, silme ve yineleme işlemleriyle birlikte, Java 8 ve sonrası gelen modern metodlar, haritalarla çalışmayı daha esnek ve verimli hale getirmiştir. Doğru Map türünü seçmek ve gelişmiş özelliklerini kullanmak, uygulamanızın performansını ve okunabilirliğini önemli ölçüde artıracaktır.
Sıkça Sorulan Sorular (SSS)
S: Bir Map’e null anahtar veya değer ekleyebilir miyim?
C: HashMap ve LinkedHashMap bir tane null anahtar ve birden fazla null değer kabul eder. Ancak TreeMap, anahtarların sıralanması gerektiği için null anahtarlara izin vermez.
S: Java Map’leri thread-safe (iş parçacığı güvenli) midir?
C: Hayır, HashMap, LinkedHashMap ve TreeMap varsayılan olarak thread-safe değildir. Çoklu iş parçacıklı ortamlarda kullanıldığında senkronizasyon sorunları yaşanabilir. Bu tür durumlarda java.util.concurrent paketindeki ConcurrentHashMap sınıfı kullanılmalıdır.
S: Bir Map’i nasıl sıralayabilirim?
C: Eğer anahtarlara göre sıralı erişim istiyorsanız TreeMap kullanabilirsiniz. Eğer HashMap veya LinkedHashMap‘i sıralamak istiyorsanız, entrySet()‘i bir List‘e dönüştürüp Collections.sort() metodu ile veya Java 8 Stream API’yi kullanarak anahtarlara ya da değerlere göre sıralayabilirsiniz.
// HashMap'i değerlere göre sıralama örneği
Map notlar = new HashMap<>();
notlar.put("Ali", 85);
notlar.put("Ayşe", 92);
notlar.put("Mehmet", 78);
notlar.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));
// Çıktı: Mehmet: 78, Ali: 85, Ayşe: 92
S: Map.Entry nedir?
C: Map.Entry, Map arayüzünün içinde tanımlanmış bir iç arayüzdür. Bir haritadaki tek bir anahtar-değer çiftini temsil eder. entrySet() metodu, Map.Entry nesnelerinin bir kümesini döndürür ve bu sayede hem anahtara (getKey()) hem de değere (getValue()) aynı anda erişim sağlanır.