İçeriğe geç

Java Optional: NullPointerException'a Kesin Çözüm ve Doğru Kullanım Rehberi

T
Tolgahan
· · 18 dk okuma · 208 görüntülenme

Java Optional: NullPointerException'a Kesin Çözüm ve Doğru Kullanım Rehberi

Java geliştiricilerin en sık karşılaştığı hata hangisi diye sorsanız, cevabı hemen herkes bilir: NullPointerException. Tony Hoare — null referansı icat eden adam — bunu "milyar dolarlık hata" olarak adlandırdı. Haklıydı da. Prodüksiyonda gece 3'te sizi uyandıran, debug etmesi sinir bozucu, kaynağını bulmak için log'lara gömülmeniz gereken hataların büyük bir kısmı null kaynaklıdır.

Java 8 ile birlikte gelen Optional sınıfı, bu probleme zarif bir çözüm sunuyor. Ama dikkat edin — Optional'ı doğru kullanmak ile sadece kullanmak arasında dağlar kadar fark var. Yanlış kullanılan bir Optional, null'dan bile tehlikeli olabilir çünkü sahte bir güvenlik hissi yaratır.

Bu yazıda Optional'ı en ince ayrıntısına kadar öğreneceksiniz: ne zaman kullanmalı, ne zaman kullanmamalı, yaygın hatalar neler, profesyoneller nasıl kullanıyor. Yazıyı bitirdiğinizde Optional konusunda hiçbir sorunuz kalmayacak.


Optional Nedir? Neden Var?

Bir analoji ile başlayalım. Bir restoranda sipariş verdiğinizi düşünün. Garson size "tavuk sote var mı?" diye sorduğunuzda iki farklı cevap gelebilir:

  1. "Evet, işte tabağınız" → Değer var, al kullan.

  2. "Hayır, bugün tavuk sote yok" → Değer yok, ama bu bir hata değil — sadece mevcut değil.

İşte Optional tam olarak bu ikinci senaryoyu modelliyor. Bir değerin var olabileceğini veya olmayabileceğini tip düzeyinde ifade eden bir konteyner (sarmalayıcı) sınıfıdır. java.util.Optional<T> generic bir sınıftır ve içinde ya bir T değeri vardır, ya da hiçbir şey yoktur.

Optional'dan önce Java'da "değer olmayabilir" durumunu ifade etmenin tek yolu null döndürmekti. Ama null'un problemi şu: bir metot User döndürüyorsa, bunun null dönüp dönemeyeceğini metot imzasından anlayamazsınız. Javadoc'a bakmak, kaynak koda dalmak ya da deneyerek öğrenmek zorundasınız. Optional ise bunu tip sisteminde görünür kılar.

// null ile — metot imzası "null dönebilir" demiyor
public User findUserById(Long id) {
    // null dönebilir mi? Kim bilir...
    return userRepository.findById(id);
}

// Optional ile — metot imzası her şeyi anlatıyor
public Optional<User> findUserById(Long id) {
    // Dönüş tipi açıkça söylüyor: "User olmayabilir"
    return userRepository.findById(id);
}

İkinci versiyonda metodu çağıran herkes, dönüş değerinin boş olabileceğini derleme zamanında biliyor. Bu büyük bir fark.


Optional Oluşturma: of, ofNullable, empty

Optional oluşturmanın üç yolu var ve her birinin farklı bir kullanım amacı var.

Optional.of(value) — Değer Kesinlikle Var

Elinizde kesinlikle null olmayan bir değer varsa of() kullanırsınız. Eğer null verirseniz anında NullPointerException fırlatır — bu bilinçli bir tasarım kararıdır.

// Değer kesinlikle var — of() kullan
String name = "Tolgahan";
Optional<String> opt = Optional.of(name);
System.out.println(opt.get()); // Çıktı: Tolgahan

// DİKKAT: null verirsen patlak
String nullName = null;
Optional<String> boom = Optional.of(nullName); // NullPointerException!

Optional.ofNullable(value) — Değer Null Olabilir

Değerin null olup olmadığından emin değilseniz ofNullable() kullanırsınız. Null ise boş Optional döner, değilse dolu Optional döner.

// Dış kaynaktan gelen veri — null olabilir
String city = getUserCityFromDatabase(userId); // null dönebilir
Optional<String> optCity = Optional.ofNullable(city);

// null ise boş Optional
System.out.println(optCity.isPresent()); // false (eğer city null ise)

// değer varsa dolu Optional
String validCity = "İstanbul";
Optional<String> optValid = Optional.ofNullable(validCity);
System.out.println(optValid.isPresent()); // true

Optional.empty() — Boş Optional

Açıkça "değer yok" demek istediğinizde empty() kullanırsınız.

public Optional<User> findUserByEmail(String email) {
    if (email == null || email.isBlank()) {
        return Optional.empty(); // Geçersiz input — boş dön
    }
    User user = database.query(email);
    return Optional.ofNullable(user);
}

💡 İpucu: Hangisini kullanacağınızı şöyle hatırlayın: Değerin null olmayacağından eminseniz of(), emin değilseniz ofNullable(), hiç değer yoksa empty().


Değer Okuma: get, orElse, orElseGet, orElseThrow

Optional'dan değeri çıkarmanın birden fazla yolu var. Ama hepsi eşit değil — bazıları tehlikeli, bazıları zarif.

get() — Kullanmayın (Ciddi)

get() metodu Optional'dan değeri direkt çıkarır. Ama Optional boşsa NoSuchElementException fırlatır. Yani null check yapmadan object.method() çağırmaktan bir farkı yok — aynı problemi farklı bir istisnaya (exception) taşımış olursunuz.

Optional<String> empty = Optional.empty();
String value = empty.get(); // NoSuchElementException! 💥

// get() kullanmak Optional'ın tüm amacını yok eder
// Bunu yapmayın. Ciddi söylüyorum.

orElse(defaultValue) — Yoksa Bu Değeri Kullan

Değer yoksa bir varsayılan (default) değer döndürür. Basit, temiz, güvenli.

Optional<String> optName = Optional.ofNullable(getUserName());
String name = optName.orElse("Misafir");
// Kullanıcı adı varsa onu, yoksa "Misafir" döner

System.out.println(name); // "Misafir" (eğer getUserName() null döndüyse)

orElseGet(supplier) — Yoksa Bu Fonksiyonu Çalıştır

orElse() ile orElseGet() arasındaki fark kritiktir. orElse() her durumda argümanını değerlendirir (evaluate eder), orElseGet() ise sadece değer yoksa çalıştırır.

// orElse — her zaman çalışır (değer olsa bile!)
String name1 = optName.orElse(expensiveDatabaseCall());
// expensiveDatabaseCall() her koşulda çağrılır 😱

// orElseGet — sadece değer yoksa çalışır
String name2 = optName.orElseGet(() -> expensiveDatabaseCall());
// Lambda sadece optName boşsa çağrılır ✅

Bu fark özellikle maliyetli işlemlerde çok önemlidir. Veritabanı sorgusu, API çağrısı, dosya okuma gibi işlemlerde mutlaka orElseGet() kullanın.

orElseThrow() — Yoksa Hata Fırlat

Değerin mutlaka olması gerektiği durumlarda kullanılır. Değer yoksa istediğiniz istisnayı fırlatırsınız.

User user = findUserById(42L)
    .orElseThrow(() -> new UserNotFoundException("ID: 42 olan kullanıcı bulunamadı"));

// Java 10+ ile parametresiz versiyonu da var
User user2 = findUserById(42L)
    .orElseThrow(); // NoSuchElementException fırlatır

⚠️ Dikkat: get() yerine her zaman orElseThrow() kullanın. İkisi de hata fırlatır ama orElseThrow() niyetinizi açıkça belli eder: "Bu değer olmalı, yoksa hata at." get() ise "ne olacağını umursamıyorum" der.


Dönüşüm ve Filtreleme: map, flatMap, filter

Optional'ın asıl gücü bu üç metotta. Bunlar sayesinde null check'leri zincirleme (chain) olarak ortadan kaldırabilirsiniz.

map() — Değeri Dönüştür

İçerideki değere bir fonksiyon uygular ve sonucu yeni bir Optional'a sarar.

Optional<String> optName = Optional.of("tolgahan");

// map ile dönüşüm — büyük harfe çevir
Optional<String> upper = optName.map(String::toUpperCase);
System.out.println(upper.orElse("?")); // Çıktı: TOLGAHAN

// Boş Optional'da map hiçbir şey yapmaz
Optional<String> empty = Optional.<String>empty();
Optional<String> result = empty.map(String::toUpperCase);
System.out.println(result.orElse("?")); // Çıktı: ?

Gerçek dünya senaryosunda map çok işe yarar. Örneğin bir kullanıcının şehrini almak istiyorsunuz:

// Null check zinciri — eski yöntem (çirkin)
String city = null;
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        city = address.getCity();
    }
}

// Optional ile — temiz ve okunabilir
String city = Optional.ofNullable(user)
    .map(User::getAddress)
    .map(Address::getCity)
    .orElse("Bilinmiyor");

İkinci versiyon hem daha kısa hem de niyeti çok daha net ifade ediyor.

flatMap() — İç İçe Optional'ları Düzleştir

Eğer map() ile çağırdığınız fonksiyon zaten bir Optional dönüyorsa, Optional<Optional<T>> gibi iç içe bir yapı oluşur. flatMap() bunu düzleştirir.

// getAddress() Optional<Address> dönüyor diyelim
public Optional<Address> getAddress() {
    return Optional.ofNullable(this.address);
}

// map kullanırsak: Optional<Optional<Address>> — istemediğimiz yapı
Optional<Optional<Address>> nested = optUser.map(User::getAddress);

// flatMap kullanırsak: Optional<Address> — düz ve temiz
Optional<Address> flat = optUser.flatMap(User::getAddress);

// Zincirleme kullanım
String city = optUser
    .flatMap(User::getAddress)  // Optional<Address> dönüyor → flatMap
    .map(Address::getCity)       // String dönüyor → map
    .orElse("Bilinmiyor");

💡 İpucu: Kural basit: çağırdığınız metot düz değer dönüyorsa map(), Optional dönüyorsa flatMap() kullanın.

filter() — Koşula Göre Filtrele

Değer varsa ve koşulu sağlıyorsa Optional'ı döndürür, koşulu sağlamıyorsa boş Optional döner.

Optional<Integer> age = Optional.of(25);

// Yaş 18'den büyükse al, değilse boş Optional
Optional<Integer> adult = age.filter(a -> a >= 18);
System.out.println(adult.isPresent()); // Çıktı: true

Optional<Integer> child = age.filter(a -> a < 18);
System.out.println(child.isPresent()); // Çıktı: false

Gerçek bir kullanım senaryosu:

// Kullanıcının premium olup olmadığını kontrol et
String dashboard = findUserById(userId)
    .filter(User::isPremium)
    .map(user -> "premium-dashboard")
    .orElse("free-dashboard");

Bu tek satırda üç şey yaptık: kullanıcıyı bul, premium mi kontrol et, sonuca göre dashboard belirle. Null check, if-else, boolean kontrol — hepsi tek bir zincirde.


ifPresent ve ifPresentOrElse: Yan Etkiler İçin

Bazen değeri çıkarmak değil, değer varsa bir şey yapmak istersiniz.

Optional<User> optUser = findUserById(42L);

// Değer varsa log'la
optUser.ifPresent(user -> 
    logger.info("Kullanıcı bulundu: {}", user.getName())
);

// Java 9+ : Değer varsa bir şey yap, yoksa başka bir şey yap
optUser.ifPresentOrElse(
    user -> logger.info("Kullanıcı bulundu: {}", user.getName()),
    () -> logger.warn("Kullanıcı bulunamadı")
);

Bu metotlar özellikle yan etki (side effect) gerektiren durumlarda kullanışlıdır: loglama, cache güncelleme, metrik gönderme gibi.


Optional ile Stream Entegrasyonu (Java 9+)

Java 9 ile Optional'a stream() metodu eklendi. Bu sayede Optional'ları Stream pipeline'larına doğal şekilde entegre edebilirsiniz.

List<Optional<String>> optionals = List.of(
    Optional.of("Java"),
    Optional.empty(),
    Optional.of("Python"),
    Optional.empty(),
    Optional.of("C++")
);

// Sadece dolu Optional'ları al ve değerlerini topla
List<String> languages = optionals.stream()
    .flatMap(Optional::stream)  // Boşları eler, doluları açar
    .toList();

System.out.println(languages); // Çıktı: [Java, Python, C++]

Bu pattern özellikle bir liste üzerinde findById gibi Optional dönen metotları çağırdığınızda çok kullanışlıdır:

// Kullanıcı ID listesinden mevcut kullanıcıları bul
List<User> activeUsers = userIds.stream()
    .map(id -> userRepository.findById(id)) // Her biri Optional<User>
    .flatMap(Optional::stream)               // Boşları ele, doluları aç
    .filter(User::isActive)
    .toList();

Yaygın Hatalar — Bunları Yapmayın

Optional'ı her gün kullanan geliştiricilerin bile düştüğü tuzaklar var. Her birini tek tek inceleyelim.

Hata 1: Optional'ı null check için kullanmak

// ❌ YANLIŞ — Optional'ı if-else sarmalayıcı olarak kullanmak
Optional<User> optUser = findUserById(id);
if (optUser.isPresent()) {
    User user = optUser.get();
    // user ile bir şeyler yap
} else {
    // yoksa...
}

// ✅ DOĞRU — fonksiyonel API kullan
findUserById(id)
    .map(user -> processUser(user))
    .orElseGet(() -> handleNotFound());

isPresent() + get() kalıbı, if (x != null) ile birebir aynı şey. Optional'ın sağladığı hiçbir avantajı kullanmıyorsunuz. Optional'ın gücü map, flatMap, filter, orElse gibi fonksiyonel metotlarında.

Hata 2: Optional'ı metot parametresi olarak kullanmak

// ❌ YANLIŞ — parametre olarak Optional almak
public void sendEmail(Optional<String> cc) {
    // Çağıran kişi null Optional bile geçebilir!
    // sendEmail(null) → NullPointerException 😱
}

// ✅ DOĞRU — overload kullan
public void sendEmail() {
    sendEmail(null); // internal call
}

public void sendEmail(String cc) {
    // cc null ise cc olmadan gönder
}

Optional metot parametresi olarak kullanılmak üzere tasarlanmamıştır. Joshua Bloch, Brian Goetz ve Java ekibinin tamamı buna karşıdır. Parametre olarak Optional almak gereksiz sarmalama yaratır ve çağıran tarafta Optional.of() yazmak zorunda bırakır.

Hata 3: Optional'ı alan (field) olarak kullanmak

// ❌ YANLIŞ — sınıf alanı olarak Optional
public class User {
    private Optional<String> middleName; // Kötü fikir
}

// ✅ DOĞRU — alan null olsun, getter Optional dönsün
public class User {
    private String middleName; // null olabilir, sorun değil

    public Optional<String> getMiddleName() {
        return Optional.ofNullable(middleName);
    }
}

Optional Serializable değildir — yani Optional alan içeren sınıflar serileştirme (serialization) sırasında sorun çıkarabilir. Ayrıca her alana Optional sarmak gereksiz bellek tüketimi yaratır. Optional, dönüş tipi olarak tasarlanmıştır.

Hata 4: Optional.of()'a null vermek

// ❌ Bu satır NullPointerException fırlatır
String value = null;
Optional<String> opt = Optional.of(value); // BOOM! 💥

// ✅ Null olabilecek değerler için ofNullable kullan
Optional<String> opt = Optional.ofNullable(value); // Güvenli

Bu hata özellikle dış kaynaktan gelen verilerde sık yapılır. Veritabanından, API'den, kullanıcı girdisinden gelen hiçbir şeye "kesinlikle null değildir" diye güvenmeyin.

Hata 5: orElse ile pahalı işlem yapmak

// ❌ YANLIŞ — orElse her zaman argümanı çalıştırır
User user = findUserById(id)
    .orElse(createDefaultUser()); // HER ZAMAN çalışır!

// ✅ DOĞRU — orElseGet sadece gerektiğinde çalıştırır
User user = findUserById(id)
    .orElseGet(() -> createDefaultUser()); // Sadece user yoksa çalışır

Bu fark küçük görünür ama performans açısından büyük etkisi olabilir. orElse() içindeki ifade her koşulda değerlendirilir — veritabanı sorgusu, HTTP çağrısı, dosya okuma gibi işlemlerde bu ciddi bir performans kaybına yol açar.


Best Practices — Profesyonel İpuçları

1. Optional'ı Sadece Dönüş Tipi Olarak Kullanın

Optional'ın birincil kullanım amacı, bir metodun "değer döndürmeyebileceğini" belgelemektir. Bu amaç dışında kullanmayın.

// ✅ Dönüş tipi olarak — harika
public Optional<User> findByEmail(String email) { ... }

// ❌ Parametre olarak — yapma
public void process(Optional<String> input) { ... }

// ❌ Alan olarak — yapma
private Optional<String> nickname;

// ❌ Collection'da — yapma
List<Optional<String>> names; // Anlamsız

2. Zincirleme Kullanın — İç İçe if'lerden Kaçının

// ❌ Eski alışkanlık — iç içe null check
Order order = getOrder(orderId);
if (order != null) {
    Customer customer = order.getCustomer();
    if (customer != null) {
        Address address = customer.getShippingAddress();
        if (address != null) {
            return address.getCity();
        }
    }
}
return "Bilinmiyor";

// ✅ Optional zinciri — düz, temiz, okunabilir
return getOptionalOrder(orderId)
    .map(Order::getCustomer)
    .map(Customer::getShippingAddress)
    .map(Address::getCity)
    .orElse("Bilinmiyor");

Aradaki fark sadece estetik değil — Optional zinciri niyeti çok daha net ifade ediyor: "Siparişi al, müşterisini al, adresini al, şehrini al. Herhangi bir adımda değer yoksa 'Bilinmiyor' de."

3. or() ile Alternatif Optional Zinciri (Java 9+)

// Önce cache'e bak, yoksa veritabanına bak, o da yoksa default oluştur
User user = findInCache(id)
    .or(() -> findInDatabase(id))
    .or(() -> findInLdap(id))
    .orElseGet(() -> createGuestUser());

or() metodu, Optional boşsa alternatif bir Optional döndürür. Birden fazla kaynaktan veri aramak için mükemmel bir pattern'dır.

4. Primitive Optional'ları Kullanın

Wrapper tipler yerine primitive Optional kullanmak boxing/unboxing maliyetini ortadan kaldırır.

// ❌ Boxing maliyeti var
Optional<Integer> optAge = Optional.of(25);

// ✅ Primitive Optional — daha performanslı
OptionalInt optAge = OptionalInt.of(25);
OptionalLong optCount = OptionalLong.of(1_000_000L);
OptionalDouble optScore = OptionalDouble.of(95.5);

// Kullanım
int age = optAge.orElse(0);
long count = optCount.orElseThrow();

5. Optional'ı Equals ve HashCode ile Sorunsuz Kullanın

Optional, içindeki değere göre equals ve hashCode hesaplar. Bu sayede karşılaştırma doğal çalışır:

Optional<String> a = Optional.of("Java");
Optional<String> b = Optional.of("Java");
Optional<String> c = Optional.empty();
Optional<String> d = Optional.empty();

System.out.println(a.equals(b)); // true
System.out.println(c.equals(d)); // true
System.out.println(a.equals(c)); // false

Gerçek Dünya Örneği: Kullanıcı Profil Servisi

Şimdi öğrendiğimiz her şeyi birleştiren gerçek dünyaya yakın bir örnek yazalım. Bir kullanıcı profil servisi düşünün — kullanıcıyı bul, profilini getir, tercihlerine göre yanıt oluştur:

import java.util.Optional;
import java.util.Map;

class User {
    private final String name;
    private final String email;
    private final Profile profile;

    User(String name, String email, Profile profile) {
        this.name = name;
        this.email = email;
        this.profile = profile;
    }

    String getName() { return name; }
    String getEmail() { return email; }
    Optional<Profile> getProfile() { return Optional.ofNullable(profile); }
}

class Profile {
    private final String bio;
    private final String avatarUrl;
    private final String theme;

    Profile(String bio, String avatarUrl, String theme) {
        this.bio = bio;
        this.avatarUrl = avatarUrl;
        this.theme = theme;
    }

    Optional<String> getBio() { return Optional.ofNullable(bio); }
    Optional<String> getAvatarUrl() { return Optional.ofNullable(avatarUrl); }
    String getTheme() { return theme != null ? theme : "light"; }
}

class UserService {
    private final Map<Long, User> users = Map.of(
        1L, new User("Tolgahan", "tolgahan@example.com",
                new Profile("Java geliştirici", "https://example.com/avatar.png", "dark")),
        2L, new User("Ayşe", "ayse@example.com",
                new Profile(null, null, null)),
        3L, new User("Mehmet", "mehmet@example.com", null)
    );

    // Optional dönen metot — kullanıcı olmayabilir
    Optional<User> findById(Long id) {
        return Optional.ofNullable(users.get(id));
    }

    // Kullanıcının bio'sunu getir — tüm zincir Optional
    String getUserBio(Long userId) {
        return findById(userId)
            .flatMap(User::getProfile)    // Optional<Profile>
            .flatMap(Profile::getBio)      // Optional<String>
            .orElse("Henüz bir bio eklenmemiş");
    }

    // Kullanıcının avatar URL'sini getir — varsayılan ile
    String getAvatarUrl(Long userId) {
        return findById(userId)
            .flatMap(User::getProfile)
            .flatMap(Profile::getAvatarUrl)
            .orElse("https://example.com/default-avatar.png");
    }

    // Kullanıcının temasını getir — map kullanımı
    String getUserTheme(Long userId) {
        return findById(userId)
            .flatMap(User::getProfile)
            .map(Profile::getTheme)
            .orElse("light");
    }

    // Profili olan kullanıcıların bilgilerini getir — filter kullanımı
    Optional<String> getVerifiedUserEmail(Long userId) {
        return findById(userId)
            .filter(user -> user.getProfile().isPresent()) // Profili olmalı
            .map(User::getEmail);
    }
}

class Main {
    public static void main(String[] args) {
        UserService service = new UserService();

        // Tolgahan — profili ve bio'su var
        System.out.println(service.getUserBio(1L));
        // Çıktı: Java geliştirici

        System.out.println(service.getAvatarUrl(1L));
        // Çıktı: https://example.com/avatar.png

        System.out.println(service.getUserTheme(1L));
        // Çıktı: dark

        // Ayşe — profili var ama bio ve avatar yok
        System.out.println(service.getUserBio(2L));
        // Çıktı: Henüz bir bio eklenmemiş

        System.out.println(service.getAvatarUrl(2L));
        // Çıktı: https://example.com/default-avatar.png

        // Mehmet — profili yok
        System.out.println(service.getUserBio(3L));
        // Çıktı: Henüz bir bio eklenmemiş

        System.out.println(service.getUserTheme(3L));
        // Çıktı: light

        // Olmayan kullanıcı
        System.out.println(service.getUserBio(99L));
        // Çıktı: Henüz bir bio eklenmemiş

        // Verified email kontrolü
        System.out.println(service.getVerifiedUserEmail(1L));
        // Çıktı: Optional[tolgahan@example.com]

        System.out.println(service.getVerifiedUserEmail(3L));
        // Çıktı: Optional.empty
    }
}

Bu örnekte dikkat edin: hiçbir yerde if (x != null) yazmadık. Her şey Optional zinciriyle çözüldü. Herhangi bir noktada değer yoksa — kullanıcı yok, profil yok, bio yok — zincir güvenle boş Optional'a düşüyor ve varsayılan değer döndürülüyor.


Optional Ne Zaman Kullanmalı, Ne Zaman Kullanmamalı?

Bu sorunun net bir cevabı var:

Kullanın:

  • Metot dönüş tipi olarak, "değer olmayabilir" durumunu ifade etmek için

  • Stream pipeline'larında ara dönüşüm olarak

  • Null olabilecek değerlerden güvenli zincirleme okuma yapmak için

Kullanmayın:

  • Metot parametresi olarak

  • Sınıf alanı (field) olarak

  • Collection elemanı olarak (List<Optional<T>> anlamsız)

  • Performansın kritik olduğu tight loop'larda (Optional nesne oluşturma maliyeti var)

  • Sadece isPresent() + get() yapacaksanız (null check ile aynı şey)

  • İlkel (primitive) tipler için (OptionalInt, OptionalLong, OptionalDouble kullanın)


Spring Boot ile Optional Kullanımı

Spring Data JPA, Optional ile doğal uyumludur. findById metodu zaten Optional döner:

@Service
public class ProductService {

    private final ProductRepository repository;

    public ProductService(ProductRepository repository) {
        this.repository = repository;
    }

    public ProductDTO getProduct(Long id) {
        return repository.findById(id)
            .map(this::toDTO)
            .orElseThrow(() -> new ProductNotFoundException(
                "Ürün bulunamadı: " + id
            ));
    }

    public String getProductCategory(Long id) {
        return repository.findById(id)
            .map(Product::getCategory)
            .map(Category::getName)
            .orElse("Kategorisiz");
    }

    private ProductDTO toDTO(Product product) {
        return new ProductDTO(
            product.getId(),
            product.getName(),
            product.getPrice()
        );
    }
}

Spring'de Optional kullanımı çok doğal ve temiz. Repository zaten Optional dönüyor, service katmanında zincirleme kullanarak temiz ve güvenli bir akış elde ediyorsunuz.


Performans Notu

Optional her kullanıldığında heap'te bir nesne oluşturur. Milyonlarca iterasyonun olduğu tight loop'larda bu maliyet hissedilebilir. Ama %99.9 kullanım senaryosunda bu maliyet tamamen ihmal edilebilir düzeydedir.

Erken optimizasyon tüm kötülüklerin anasıdır. Önce doğru ve okunabilir kod yazın, performans problemi olduğunu ölçtüğünüzde optimize edin. Optional'ın sağladığı güvenlik ve okunabilirlik, nanosaniye düzeyindeki nesne oluşturma maliyetinden çok daha değerlidir.


Özet

  • Optional, bir değerin var olabileceğini veya olmayabileceğini tip düzeyinde ifade eden bir sarmalayıcıdır. NullPointerException'a karşı güçlü bir savunma hattıdır.

  • `of()` kesinlikle null olmayan değerler, `ofNullable()` null olabilecek değerler, `empty()` açıkça boş Optional için kullanılır.

  • `get()` kullanmayın. Bunun yerine orElse(), orElseGet() veya orElseThrow() tercih edin.

  • `map()`, `flatMap()`, `filter()` ile zincirleme kullanım, iç içe null check'lerin yerini alır ve kodu çok daha temiz, okunabilir hale getirir.

  • Optional sadece dönüş tipi olarak kullanılmak üzere tasarlanmıştır. Parametre, alan veya collection elemanı olarak kullanmayın.

  • `orElse()` vs `orElseGet()` farkını bilin: pahalı işlemlerde her zaman orElseGet() kullanın.

  • Spring Data JPA ile Optional doğal uyumludur — findById zaten Optional döner, service katmanında zincirleme kullanarak temiz bir mimari oluşturabilirsiniz.

Optional'ı doğru kullandığınızda kodunuz daha güvenli, daha okunabilir ve niyetinizi çok daha net ifade eden bir hale gelir. Null'un "milyar dolarlık hatası"nı Optional ile kontrol altına alabilirsiniz — yeter ki kurallarına uyun.

Paylaş:
Son güncelleme: Jul 19, 2026

Yorumlar

Giriş yapın ve yorum bırakın.

Henüz yorum yok

Düşüncelerinizi paylaşan ilk siz olun!

Bu yazıyı beğendiniz mi?

Bültene abone olun ve yeni yazılardan ilk siz haberdar olun. Spam yok, söz.

İlgili Yazılar