Optional Kullanımı
Giriş
NullPointerException — Java geliştiricilerinin en sık karşılaştığı hata. Tony Hoare, null referansı icat ettiği için bunu "milyar dolarlık hata" olarak nitelendirdi. Her yerde null kontrolü yapmak kodu çirkinleştirir, bir tane unutmak uygulamayı çökertir.
Java 8 ile gelen Optional sınıfı bu soruna zarif bir çözüm sunuyor. "Bu değer olmayabilir" bilgisini tip sistemine taşıyarak, null kontrollerini daha okunabilir ve güvenli hale getiriyor.
Optional Nedir?
Analoji: Hediye Kutusu
Optional'ı hediye kutusu gibi düşün. Kutunun içinde bir hediye olabilir veya kutu boş olabilir. Kutuyu açmadan önce "içinde bir şey var mı?" diye kontrol edersin. Kutunun kendisi hiçbir zaman null değil — kutu her zaman var, ama içi dolu veya boş olabilir.
İşte Optional tam olarak bu: bir değeri saran (wrap eden) konteyner. İçinde değer var (present) veya yok (empty).
Optional Oluşturma
Üç yol var:
import java.util.Optional;
// 1. Optional.of() — değer kesinlikle null değilse
Optional<String> isim = Optional.of("Ali");
// Optional.of(null) → NullPointerException fırlatır!
// Optional.of(null); // ❌ BOOM!
// 2. Optional.ofNullable() — değer null olabilirse
String deger = kullanicidanAl(); // null dönebilir
Optional<String> belki = Optional.ofNullable(deger);
// 3. Optional.empty() — boş Optional
Optional<String> bos = Optional.empty();Hangisini ne zaman kullanırsın?
of()— Null olmadığından eminsen. Null gelirse hata istersin (fail-fast).ofNullable()— Null gelebilir, sorun değil.empty()— Bilinçli olarak "değer yok" demek istiyorsun.
// Tipik kullanım: bir method null dönebilir
public Optional<Kullanici> kullaniciBul(int id) {
Kullanici k = veritabaninaBak(id);
return Optional.ofNullable(k); // null ise empty, değilse wrapped
}Değere Erişim
isPresent() ve isEmpty()
Optional<String> opt = Optional.of("Ali");
if (opt.isPresent()) {
System.out.println("Değer: " + opt.get());
}
if (opt.isEmpty()) { // Java 11+
System.out.println("Değer yok");
}get() — Tehlikeli
Optional<String> opt = Optional.empty();
String deger = opt.get(); // ❌ NoSuchElementException!⚠️ Dikkat:
get()metodunu direkt kullanma. Boş Optional'daNoSuchElementExceptionfırlatır. Bu, null kontrolü yapmadan erişmekle aynı şey — Optional'ın amacını yok eder. Aşağıdaki alternatifleri kullan.
ifPresent() — Güvenli Erişim
Optional<String> isim = Optional.of("Ali");
// Değer varsa çalıştır, yoksa hiçbir şey yapma
isim.ifPresent(i -> System.out.println("Merhaba " + i));
// Java 9+ — değer yoksa da bir şey yap
isim.ifPresentOrElse(
i -> System.out.println("Merhaba " + i),
() -> System.out.println("İsim bulunamadı")
);Varsayılan Değer Stratejileri
orElse() — Sabit Varsayılan
Optional<String> opt = Optional.empty();
// Değer yoksa varsayılanı kullan
String isim = opt.orElse("Misafir");
System.out.println(isim); // MisafirorElseGet() — Lazy Varsayılan
Optional<String> opt = Optional.empty();
// Değer yoksa Supplier çalıştır
String isim = opt.orElseGet(() -> veritabanindanVarsayilanGetir());orElse vs orElseGet — Önemli Fark:
// orElse: Değer OLSA BİLE sağ taraf her zaman çalışır
String isim1 = Optional.of("Ali").orElse(pahaliIslem()); // pahaliIslem() ÇALIŞIR!
// orElseGet: Değer varsa sağ taraf ÇALIŞMAZ
String isim2 = Optional.of("Ali").orElseGet(() -> pahaliIslem()); // pahaliIslem() çalışmazorElse basit sabit değerler için, orElseGet pahalı işlemler (DB sorgusu, API çağrısı) için kullan.
orElseThrow() — Hata Fırlat
Optional<Kullanici> opt = kullaniciBul(42);
// Değer yoksa özel exception fırlat
Kullanici k = opt.orElseThrow(
() -> new KullaniciBulunamadiException("ID: 42")
);
// Java 10+ — parametresiz versiyon (NoSuchElementException fırlatır)
Kullanici k2 = opt.orElseThrow();// Pratik kullanım: Service katmanında
public Kullanici kullaniciGetir(int id) {
return kullaniciRepository.findById(id)
.orElseThrow(() -> new NotFoundException("Kullanıcı bulunamadı: " + id));
}Dönüşüm: map() ve flatMap()
Optional'ın asıl gücü burada. Zincirleme dönüşümlerle temiz, okunabilir kod yazabilirsin.
map() — Değeri Dönüştür
Optional<String> isim = Optional.of("ali veli");
// İçindeki değeri dönüştür
Optional<String> buyukHarf = isim.map(String::toUpperCase);
System.out.println(buyukHarf.get()); // ALI VELI
// Boş Optional'da map hiçbir şey yapmaz
Optional<String> bos = Optional.<String>empty().map(String::toUpperCase);
System.out.println(bos.isPresent()); // false// Zincirleme dönüşüm
Optional<String> sonuc = Optional.of(" ali veli ")
.map(String::trim)
.map(String::toUpperCase)
.map(s -> s.replace(" ", "_"));
System.out.println(sonuc.get()); // ALI_VELIflatMap() — İç İçe Optional'ı Düzleştir
// Bir method Optional döndürüyorsa ve sen de Optional içindeysen
public Optional<String> emailBul(int kullaniciId) {
return Optional.ofNullable(veritabanindanEmailAl(kullaniciId));
}
// map kullanırsan: Optional<Optional<String>> — iç içe!
Optional<Optional<String>> icice = Optional.of(42).map(this::emailBul);
// flatMap kullanırsan: Optional<String> — düz!
Optional<String> duz = Optional.of(42).flatMap(this::emailBul);// Pratik örnek: Zincirleme navigation
public record Adres(String sehir) { }
public record Kullanici(String isim, Optional<Adres> adres) { }
Optional<Kullanici> kullanici = kullaniciBul(1);
// Kullanıcının şehrini güvenle al
String sehir = kullanici
.flatMap(Kullanici::adres) // Optional<Adres>
.map(Adres::sehir) // Optional<String>
.orElse("Bilinmiyor"); // StringBu kodu Optional olmadan yazsaydın:
// Optional olmadan — null kontrol cehennemi
String sehir = "Bilinmiyor";
if (kullanici != null) {
Adres adres = kullanici.getAdres();
if (adres != null) {
if (adres.getSehir() != null) {
sehir = adres.getSehir();
}
}
}Hangisi daha okunabilir? 🙂
filter() — Koşullu Filtreleme
Optional<Integer> sayi = Optional.of(42);
// Koşulu sağlıyorsa Optional döndür, sağlamıyorsa empty
Optional<Integer> cift = sayi.filter(n -> n % 2 == 0);
System.out.println(cift.isPresent()); // true
Optional<Integer> buyuk = sayi.filter(n -> n > 100);
System.out.println(buyuk.isPresent()); // false — 42 > 100 değil// Pratik: Kullanıcı aktifse getir
Optional<Kullanici> aktifKullanici = kullaniciBul(1)
.filter(Kullanici::isAktif);or() — Alternatif Optional (Java 9)
Optional<String> birincil = Optional.empty();
Optional<String> yedek = Optional.of("Yedek değer");
// Birincil boşsa yedek Optional'ı dene
Optional<String> sonuc = birincil.or(() -> yedek);
System.out.println(sonuc.get()); // Yedek değer// Zincirleme kaynak deneme
Optional<String> ayar = sistemdenOku()
.or(() -> dosyadanOku())
.or(() -> varsayilanDegerOku());Stream ile Entegrasyon
// Optional → Stream (Java 9)
Optional<String> opt = Optional.of("Ali");
Stream<String> stream = opt.stream(); // 0 veya 1 elemanlı stream
// Listeden Optional'ları düzleştir
List<Optional<String>> optListesi = List.of(
Optional.of("Ali"),
Optional.empty(),
Optional.of("Veli"),
Optional.empty()
);
List<String> isimler = optListesi.stream()
.flatMap(Optional::stream)
.toList();
// [Ali, Veli] — boşlar otomatik atılırAnti-Patterns — Yapma!
1. Optional'ı Method Parametresi Olarak Kullanma
// ❌ KÖTÜ — Optional parametre olarak
public void kaydet(Optional<String> isim) {
String deger = isim.orElse("Varsayılan");
// ...
}
kaydet(Optional.of("Ali"));
kaydet(Optional.empty());
// ✅ İYİ — Method overloading veya nullable parametre
public void kaydet(String isim) {
// ...
}
public void kaydet() {
kaydet("Varsayılan");
}Neden? Optional parametre kullanmak API'yi karmaşıklaştırır. Çağıran taraf Optional.of() yazmak zorunda kalır. Optional dönüş tipi için tasarlandı, parametre için değil.
2. Optional'ı Field Olarak Kullanma
// ❌ KÖTÜ
public class Kullanici {
private Optional<String> email; // Serialization sorunları + gereksiz overhead
}
// ✅ İYİ — Field nullable olsun, getter Optional döndürsün
public class Kullanici {
private String email; // null olabilir
public Optional<String> getEmail() {
return Optional.ofNullable(email);
}
}3. isPresent() + get() Kullanmak
// ❌ KÖTÜ — Optional'ın amacını öldürüyor
Optional<String> opt = methodCagir();
if (opt.isPresent()) {
String deger = opt.get();
System.out.println(deger);
}
// ✅ İYİ
methodCagir().ifPresent(System.out::println);
// VEYA
String deger = methodCagir().orElse("varsayılan");4. Optional.of() ile null Sarma
// ❌ KÖTÜ — NPE!
String deger = null;
Optional<String> opt = Optional.of(deger); // NullPointerException!
// ✅ İYİ
Optional<String> opt = Optional.ofNullable(deger);5. Koleksiyonları Optional ile Sarma
// ❌ KÖTÜ — boş liste yeterli
public Optional<List<String>> isimleriGetir() {
List<String> isimler = bul();
return isimler.isEmpty() ? Optional.empty() : Optional.of(isimler);
}
// ✅ İYİ — boş liste döndür
public List<String> isimleriGetir() {
List<String> isimler = bul();
return isimler != null ? isimler : List.of();
}💡 Altın Kural: Optional'ı sadece method dönüş tipi olarak kullan. Parametre, field veya koleksiyon sarmalayıcı olarak kullanma.
Tam Örnek: Service Katmanı
public class KullaniciService {
private final KullaniciRepository repo;
public KullaniciService(KullaniciRepository repo) {
this.repo = repo;
}
// Optional döndür — "bulunamayabilir" mesajını tip sistemiyle ver
public Optional<Kullanici> idIleBul(int id) {
return repo.findById(id);
}
// Kesinlikle bulunmalı — exception fırlat
public Kullanici idIleGetir(int id) {
return repo.findById(id)
.orElseThrow(() -> new NotFoundException("Kullanıcı bulunamadı: " + id));
}
// Zincirleme işlem
public String kullaniciSehri(int id) {
return repo.findById(id)
.flatMap(Kullanici::getAdres)
.map(Adres::getSehir)
.orElse("Belirtilmemiş");
}
// Filtreleme + dönüşüm
public Optional<String> aktifKullaniciEmail(int id) {
return repo.findById(id)
.filter(Kullanici::isAktif)
.flatMap(Kullanici::getEmail);
}
}Pratik Örnek: E-Ticaret Sepet
Gerçek bir senaryoda Optional'ın zincirleme gücünü görelim:
public record Urun(String adi, double fiyat) { }
public record SepetItem(Urun urun, int adet) { }
public class Sepet {
private final Map<String, SepetItem> items = new HashMap<>();
public void ekle(String urunId, Urun urun, int adet) {
items.put(urunId, new SepetItem(urun, adet));
}
public Optional<SepetItem> itemBul(String urunId) {
return Optional.ofNullable(items.get(urunId));
}
// Ürünün toplam fiyatını al
public double urunToplam(String urunId) {
return itemBul(urunId)
.map(item -> item.urun().fiyat() * item.adet())
.orElse(0.0);
}
// Ürün adını al
public String urunAdi(String urunId) {
return itemBul(urunId)
.map(SepetItem::urun)
.map(Urun::adi)
.orElse("Bilinmeyen ürün");
}
// Belirli tutarın üstündeki ürünleri bul
public Optional<SepetItem> pahaliUrunBul(String urunId, double esik) {
return itemBul(urunId)
.filter(item -> item.urun().fiyat() > esik);
}
}Kullanım
Sepet sepet = new Sepet();
sepet.ekle("P001", new Urun("Laptop", 25000), 1);
sepet.ekle("P002", new Urun("Mouse", 350), 2);
System.out.println(sepet.urunToplam("P001")); // 25000.0
System.out.println(sepet.urunToplam("P999")); // 0.0 (ürün yok)
System.out.println(sepet.urunAdi("P002")); // Mouse
System.out.println(sepet.urunAdi("P999")); // Bilinmeyen ürün
sepet.pahaliUrunBul("P001", 10000)
.ifPresent(item -> System.out.println("Pahalı: " + item.urun().adi()));Pratik Örnek: Yapılandırma Okuyucu
Birden fazla kaynaktan yapılandırma okuma — Optional zincirleme:
public class YapilandirmaOkuyucu {
// Sırasıyla: çevre değişkeni → sistem özelliği → varsayılan dosya
public String degerOku(String anahtar) {
return cevredenOku(anahtar)
.or(() -> sistemOzelligindenOku(anahtar))
.or(() -> dosyadanOku(anahtar))
.orElseThrow(() ->
new IllegalStateException("Yapılandırma bulunamadı: " + anahtar));
}
private Optional<String> cevredenOku(String anahtar) {
return Optional.ofNullable(System.getenv(anahtar));
}
private Optional<String> sistemOzelligindenOku(String anahtar) {
return Optional.ofNullable(System.getProperty(anahtar));
}
private Optional<String> dosyadanOku(String anahtar) {
// Dosyadan oku — yoksa empty
return Optional.empty(); // Basitleştirilmiş
}
}Bu pattern çok güçlü — fallback zincirleri oluşturabilirsin.
Optional ve Primitive Tipler
Optional<Integer> kullanmak boxing/unboxing maliyeti getirir. Java bunu çözmek için primitive Optional sınıfları sunar:
OptionalInt sayi = OptionalInt.of(42);
OptionalDouble ondalik = OptionalDouble.of(3.14);
OptionalLong buyuk = OptionalLong.of(1_000_000_000L);
// Kullanım
int deger = sayi.orElse(0);
sayi.ifPresent(s -> System.out.println("Sayı: " + s));
// Stream'den dönen
OptionalInt max = IntStream.of(3, 1, 4, 1, 5, 9)
.max();
System.out.println(max.orElse(-1)); // 9// OptionalInt'in map'i yok! getAsInt() veya orElse() kullanman gerekir
OptionalInt opt = OptionalInt.of(42);
// opt.map(...) // ❌ Yok!
int deger = opt.orElse(0); // ✅⚠️ Not:
OptionalInt,OptionalDouble,OptionalLongsınıflarındamap(),flatMap(),filter()metotları yok. Bu yüzden zincirleme dönüşümler gerekiyorsaOptional<Integer>kullanmak zorunda kalabilirsin. Performans kritik döngülerde primitive versiyonları tercih et.
Optional ve Exceptions
Optional ile exception fırlatma stratejileri:
// Farklı exception tipleri
Kullanici k1 = optKullanici
.orElseThrow(() -> new NotFoundException("Bulunamadı"));
Kullanici k2 = optKullanici
.orElseThrow(() -> new BusinessException("İşlem yapılamaz"));
Kullanici k3 = optKullanici
.orElseThrow(IllegalStateException::new); // Method reference// Optional'dan checked exception fırlatma — biraz uğraştırır
public Kullanici kullaniciGetir(int id) throws KullaniciBulunamadiException {
return repo.findById(id)
.orElseThrow(() -> new KullaniciBulunamadiException(id));
// orElseThrow Supplier alır, checked exception sorunsuz çalışır
}Method Tasarımı: Ne Zaman Optional Döndür?
// ✅ Tek sonuç, olmayabilir
public Optional<Kullanici> emailIleBul(String email) { ... }
// ✅ İlk eşleşme, olmayabilir
public Optional<Urun> enUcuzUrunBul(String kategori) { ... }
// ❌ Liste döndürüyorsan Optional gereksiz — boş liste döndür
public List<Urun> urunleriGetir(String kategori) {
// return Optional.ofNullable(sonuclar); // ❌
return sonuclar != null ? sonuclar : List.of(); // ✅
}
// ❌ Boolean döndürüyorsan Optional gereksiz
public boolean aktifMi(int id) { ... } // ✅ Basit boolean yeter
// ❌ Primitive döndürüyorsan dikkatli ol
// OptionalInt veya varsayılan değer kullan
public OptionalInt enYuksekNotu(int ogrenciId) { ... }Optional Cheat Sheet
// Oluşturma
Optional.of(deger) // null → NPE
Optional.ofNullable(deger) // null → empty
Optional.empty() // Boş
// Değer alma
opt.get() // ❌ Tehlikeli
opt.orElse(varsayilan) // Sabit varsayılan
opt.orElseGet(supplier) // Lazy varsayılan
opt.orElseThrow(supplier) // Exception
// Kontrol
opt.isPresent() // Boolean kontrol
opt.isEmpty() // Java 11+
opt.ifPresent(consumer) // Varsa çalıştır
opt.ifPresentOrElse(c, r) // Java 9+
// Dönüşüm
opt.map(fonksiyon) // T → U
opt.flatMap(fonksiyon) // T → Optional<U>
opt.filter(predicate) // Koşullu filtreleme
opt.or(supplier) // Java 9+ — alternatif Optional
opt.stream() // Java 9+ — 0 veya 1 elemanlı StreamOptional ve Spring / Framework Entegrasyonu
Modern framework'ler Optional'ı doğal olarak destekler:
Spring Data JPA
public interface KullaniciRepository extends JpaRepository<Kullanici, Long> {
// Spring Data otomatik Optional döndürür
Optional<Kullanici> findByEmail(String email);
Optional<Kullanici> findByKullaniciAdi(String kullaniciAdi);
}@Service
public class KullaniciService {
private final KullaniciRepository repo;
public KullaniciDTO profilGetir(String email) {
return repo.findByEmail(email)
.filter(Kullanici::isAktif)
.map(this::toDTO)
.orElseThrow(() -> new NotFoundException("Kullanıcı bulunamadı"));
}
}Spring MVC Controller
@GetMapping("/kullanicilar/{id}")
public ResponseEntity<KullaniciDTO> kullaniciGetir(@PathVariable Long id) {
return repo.findById(id)
.map(this::toDTO)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}Optional ve Diğer Diller
Java'nın Optional'ı diğer dillerdeki benzer yapılardan ilham almış:
Scala →
Option[T](Some / None)Kotlin →
?nullable types (String?)Rust →
Option<T>(Some / None)Swift →
Optional<T>(String?)
Kotlin'in yaklaşımı farklı: dil seviyesinde null safety var, ayrı bir tip gerekmez. Java'da Optional bir sınıf olduğu için overhead var, ama mevcut ekosisteme uyum sağlıyor.
⚠️ Performans Notu: Optional her kullanıldığında Heap'te bir nesne oluşturur. Çok sıcak döngülerde (milyonlarca iterasyon) bu overhead fark edilebilir. Ama normal iş mantığında endişelenme — okunabilirlik performanstan daha önemli.
Refactoring: null → Optional
Mevcut kodu Optional'a nasıl geçirirsin?
Adım 1: Null döndüren method'ları Optional'a çevir
// ÖNCE
public Kullanici kullaniciBul(int id) {
Kullanici k = db.query(id);
return k; // null olabilir
}
// SONRA
public Optional<Kullanici> kullaniciBul(int id) {
Kullanici k = db.query(id);
return Optional.ofNullable(k);
}Adım 2: Çağıran kodu güncelle
// ÖNCE
Kullanici k = service.kullaniciBul(42);
if (k != null) {
System.out.println(k.getIsim());
}
// SONRA
service.kullaniciBul(42)
.ifPresent(k -> System.out.println(k.getIsim()));Adım 3: Null check zincirleri temizle
// ÖNCE — iç içe null kontrol
String sehir = null;
Kullanici k = service.kullaniciBul(42);
if (k != null) {
Adres a = k.getAdres();
if (a != null) {
sehir = a.getSehir();
}
}
if (sehir == null) sehir = "Bilinmiyor";
// SONRA — zincirleme Optional
String sehir = service.kullaniciBul(42)
.map(Kullanici::getAdres)
.map(Adres::getSehir)
.orElse("Bilinmiyor");Özet
Optional "bu değer olmayabilir" bilgisini tip sisteminde ifade eder —
Optional.of(),Optional.ofNullable(),Optional.empty()ile oluşturulurorElse sabit varsayılan, orElseGet lazy varsayılan (pahalı işlemlerde tercih et), orElseThrow hata fırlatma
map() değeri dönüştürür, flatMap() iç içe Optional'ı düzleştirir, filter() koşullu filtreleme yapar
get()yerine her zamanorElse,orElseThrowveyaifPresentkullanOptional'ı sadece method dönüş tipi olarak kullan — parametre, field veya koleksiyon sarmalayıcı olarak kullanma
isPresent() + get()yerine fonksiyonel method'ları tercih et — daha temiz, daha güvenli
AI Asistan
Sorularını yanıtlamaya hazır