Eğitim Portalı/Java/Optional ile Null Güvenliği
Java01-java/13-optional

Optional ile Null Güvenliği

null, programlamanın en sık hata kaynaklarından biridir; öyle ki onu icat eden Tony Hoare buna "milyar dolarlık hata" demiştir. Bir metot null döndürdüğünde, çağıran taraf bunu kontrol etmeyi unutursa kaçınılmaz sonuç…

Optional ile Null Güvenliği

null, programlamanın en sık hata kaynaklarından biridir; öyle ki onu icat eden Tony Hoare buna "milyar dolarlık hata" demiştir. Bir metot null döndürdüğünde, çağıran taraf bunu kontrol etmeyi unutursa kaçınılmaz sonuç NullPointerException'dır. Java 8, bu probleme zarif bir çözüm getirdi: Optional. Optional, "burada bir değer olabilir ya da olmayabilir" durumunu tip düzeyinde, açıkça ifade eder.

Optional oluşturma

Üç temel fabrika metodu vardır:

Optional<String> dolu = Optional.of("merhaba");        // değer kesin var (null verilemez)
Optional<String> bos  = Optional.empty();              // değer yok
Optional<String> belki = Optional.ofNullable(deger);   // null ise empty olur

Bir metodun "değer bulamayabilir" olduğunu belirtmek için null döndürmek yerine Optional döndürmek çok daha açıktır; çağıran taraf değerin yokluğunu görmezden gelemez.

Değere güvenli erişim

Optional'ın amacı isPresent()/get() ikilisini elle yazmak değildir (bu, null kontrolünün süslü hali olurdu). Asıl güç, değerin yokluğunu zarifçe ele alan metotlardadır:

  • ifPresent(consumer) — değer varsa çalıştır
  • orElse(varsayilan) — yoksa hazır bir değer döndür
  • orElseGet(supplier) — yoksa değeri tembel üret (pahalı varsayılanlar için)
  • orElseThrow(...) — yoksa anlamlı bir exception fırlat
  • ifPresentOrElse(varsa, yoksa) — iki ayrı davranış
String ad = bul(id).orElse("bilinmiyor");
Urun u = bul(id).orElseThrow(() -> new NoSuchElementException("yok"));

Örnek 1 (./Ornek1.java) oluşturma ve erişim metotlarını tek tek gösterir.

Dönüşümler: map, filter, flatMap

Optional'ı asıl güçlü kılan, içindeki değeri açmadan dönüştürebilmendir. Değer yoksa işlemler sessizce atlanır; null kontrolü zincirinden kurtulursun:

  • map(fn) — değer varsa dönüştürür, yoksa empty kalır
  • filter(koşul) — koşul sağlanmazsa empty'ye düşürür
  • flatMap(fn) — dönüşümün kendisi Optional döndürüyorsa iç içe Optional'ı düzleştirir
Optional.of("  Ada  ")
        .map(String::trim)
        .filter(s -> !s.isEmpty())
        .map(String::toUpperCase)
        .orElse("(yok)");

Örnek 2 (./Ornek2.java) map/filter/flatMap'i, alanı null olabilen bir kullanıcı üzerinde gösterir.

Pratik: repository deseni

Optional'ın en doğal kullanımı, "bir şey bulmaya çalışan ama bulamayabilen" metotlardır. Spring Data JPA'nın findById metodunun Optional döndürmesi tesadüf değildir. Örnek 3 (./Ornek3.java) bir ürün deposunda id'ye göre arama yapar; bulunca alanına erişir, bulamayınca orElse/orElseThrow ile zarifçe davranır.

Anti-pattern'ler (kaçın!)

  • optional.get()'i kontrolsüz çağırma — değer yoksa NoSuchElementException atar; bu, null kontrolünü atlamaktan farksızdır. orElse... ailesini kullan.
  • Optional'ı alan, metot parametresi veya koleksiyon elemanı yapma. O, esas olarak dönüş tipi için tasarlandı. (Optional<List<T>> yerine boş liste döndür.)
  • if (opt.isPresent()) opt.get() kalıbı yerine map/ifPresent/orElse kullan.

Özet

Optional ile değerin yokluğunu tip düzeyinde açıkça ifade etmeyi, null kontrolü zincirinden map/filter/flatMap ile kurtulmayı ve orElse/orElseThrow ile zarif varsayılan/hata davranışı kurmayı öğrendik. Bu, hem daha güvenli hem de daha okunabilir kod demektir. Buraya kadar dilin çekirdeğini ve modern özelliklerini gördük; sırada dış dünyayla konuşmaya başlıyoruz: dosya işlemleri ve I/O.

Kod Örnekleri(3)

Ornek1

çalıştırılabilir
Ornek1.java
1// Ornek1: Optional oluşturma ve değere güvenli erişim.
2// Çalıştırma: java Ornek1.java
3import java.util.Optional;
4
5public class Ornek1 {
6
7    public static void main(String[] args) {
8        // Optional, "değer olabilir ya da olmayabilir" durumunu açıkça temsil eder.
9        Optional<String> dolu = Optional.of("merhaba");        // değer kesin var
10        Optional<String> bos = Optional.empty();               // değer yok
11        Optional<String> belkiNull = Optional.ofNullable(null); // null ise empty olur
12
13        System.out.println("dolu.isPresent()  : " + dolu.isPresent());
14        System.out.println("bos.isPresent()   : " + bos.isPresent());
15        System.out.println("bos.isEmpty()     : " + bos.isEmpty());
16
17        // ifPresent: değer varsa çalışır, yoksa hiçbir şey yapmaz (null kontrolüne gerek yok).
18        dolu.ifPresent(v -> System.out.println("\nDeğer var: " + v));
19        bos.ifPresent(v -> System.out.println("Bu satır çalışmaz"));
20
21        // orElse: değer yoksa varsayılan döndür.
22        System.out.println("\ndolu.orElse(...)      : " + dolu.orElse("varsayılan"));
23        System.out.println("bos.orElse(...)       : " + bos.orElse("varsayılan"));
24        System.out.println("belkiNull.orElse(...) : " + belkiNull.orElse("null geldi"));
25
26        // orElseGet: varsayılanı SADECE gerektiğinde üret (tembel; pahalı hesaplar için).
27        String sonuc = bos.orElseGet(() -> "hesaplanmış varsayılan");
28        System.out.println("bos.orElseGet(...)    : " + sonuc);
29
30        // ifPresentOrElse: var/yok için iki ayrı davranış.
31        bos.ifPresentOrElse(
32                v -> System.out.println("değer: " + v),
33                () -> System.out.println("\nDeğer yok, alternatif iş yapıldı.")
34        );
35    }
36}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.

Ornek2

çalıştırılabilir
Ornek2.java
1// Ornek2: Optional dönüşümleri — map, filter, flatMap.
2// Çalıştırma: java Ornek2.java
3import java.util.Optional;
4
5public class Ornek2 {
6
7    public static void main(String[] args) {
8        // map: değer varsa dönüştürür; yoksa empty kalır (null kontrolü gerekmez).
9        Optional<String> ad = Optional.of("  Ada Lovelace  ");
10        Optional<String> temiz = ad.map(String::trim).map(String::toUpperCase);
11        System.out.println("map zinciri: " + temiz.orElse("(yok)"));
12
13        Optional<String> bos = Optional.empty();
14        System.out.println("boş.map(...): " + bos.map(String::trim).orElse("(yok)"));
15
16        // filter: koşulu sağlamıyorsa empty'ye düşürür.
17        Optional<Integer> yas = Optional.of(20);
18        String durum = yas.filter(y -> y >= 18)
19                .map(y -> "Reşit (" + y + ")")
20                .orElse("Reşit değil veya bilinmiyor");
21        System.out.println("\nfilter sonucu: " + durum);
22
23        // flatMap: dönüşümün kendisi Optional döndürüyorsa iç içe Optional'ı düzleştirir.
24        Kullanici k = new Kullanici("ada@site.com");
25        Kullanici eksik = new Kullanici(null);
26
27        System.out.println("\nflatMap ile e-posta alanı:");
28        System.out.println("  k     -> " + k.eposta().map(e -> e.split("@")[1]).orElse("alan yok"));
29        System.out.println("  eksik -> " + eksik.eposta().map(e -> e.split("@")[1]).orElse("alan yok"));
30    }
31
32    record Kullanici(String adres) {
33        // Alan (adres) null olabilir; dışarıya Optional olarak sunmak daha güvenlidir.
34        Optional<String> eposta() {
35            return Optional.ofNullable(adres);
36        }
37    }
38}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.

Ornek3

çalıştırılabilir
Ornek3.java
1// Ornek3: Gerçekçi senaryo — repository'den arama ve orElseThrow.
2// Çalıştırma: java Ornek3.java
3import java.util.List;
4import java.util.NoSuchElementException;
5import java.util.Optional;
6
7public class Ornek3 {
8
9    record Urun(int id, String ad, double fiyat) {}
10
11    // Basit bir "repository": id'ye göre ürün bulur, bulamazsa Optional.empty döner.
12    // Optional döndürmek, "null dönebilir" demekten çok daha açık ve güvenlidir.
13    static class UrunDeposu {
14        private final List<Urun> urunler = List.of(
15                new Urun(1, "Klavye", 450),
16                new Urun(2, "Mouse", 250),
17                new Urun(3, "Monitör", 3200)
18        );
19
20        Optional<Urun> bul(int id) {
21            return urunler.stream().filter(u -> u.id() == id).findFirst();
22        }
23    }
24
25    public static void main(String[] args) {
26        UrunDeposu depo = new UrunDeposu();
27
28        // Bulunan ürün: map ile alanına eriş.
29        String ad = depo.bul(2).map(Urun::ad).orElse("bilinmiyor");
30        System.out.println("id=2 ürün adı: " + ad);
31
32        // Bulunamayan ürün: orElse ile varsayılan.
33        double fiyat = depo.bul(99).map(Urun::fiyat).orElse(0.0);
34        System.out.println("id=99 fiyat (yoksa 0): " + fiyat);
35
36        // orElseThrow: değer yoksa anlamlı bir hata fırlat.
37        try {
38            Urun u = depo.bul(99)
39                    .orElseThrow(() -> new NoSuchElementException("Ürün bulunamadı: id=99"));
40            System.out.println(u);
41        } catch (NoSuchElementException e) {
42            System.out.println("\nİstisna: " + e.getMessage());
43        }
44
45        // Zincirleme: bul -> indirim uygula -> biçimlendir
46        String mesaj = depo.bul(3)
47                .filter(u -> u.fiyat() > 1000)
48                .map(u -> u.ad() + " indirimli fiyat: " + (u.fiyat() * 0.9))
49                .orElse("İndirime uygun ürün yok");
50        System.out.println("\n" + mesaj);
51    }
52}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.