İçeriğe geç

Optional'ı Doğru Kullanmak — Yokluğu Tipe Yazmak

Başlangıç 8 dk Çok sık karşılaşılır

Önce şunu oku: Lambda ve Fonksiyonel Arayüzler — Lambda Neyi Yakalar?

30 saniyede özet

Optional, 'bu metot bazen eli boş döner' demenin kibar yoludur. Zincir kurarsan her adımdaki boşluğu kendisi taşır. Dikkat: orElse'e verdiğin yedek her seferinde hazırlanır, orElseGet'inki yalnızca gerekince.

Uygulama her istekte başka bir servise gidip şehir soruyor. Oysa kullanıcıların neredeyse hepsinin şehri zaten kayıtlı. Suçlu tek kelime: orElse.

Yedek yemeği baştan sipariş etmek ya da ancak gerekirse aramak.
Adım adım oku
  1. orElse ile yedek sipariş en baştan verilir: servis, asıl değer olsa da olmasa da çağrılır.
  2. Asıl değer, yani kayıtlı şehir gelir. Yedek servisin cevabı boşuna hazırlanmıştır ve çöpe gider.
  3. orElseGet ise yedeği bir tarif olarak saklar. Yalnızca asıl değer yoksa çalıştırır.
  4. Asıl değer geldiği için yedek servis bu kez hiç aranmadı.
  1. Bayt: Uygulamam her istekte başka bir servise gidip şehir soruyor.

  2. Sen: Ama kullanıcıların şehri zaten kayıtlı değil mi?

  3. Bayt: Kayıtlı! Yine de her seferinde yedek sipariş veriliyor.

  4. Bayt: Tek kelimeyi değiştir, yedek restoran yalnızca gerçekten gerekince aransın.

Optional ne işe yarar?

findById bazen eli boş döner. Eskiden bu null ile anlatılırdı ve metodun imzasında görünmezdi. OptionalDeğer içeren ya da boş olan bir kutu. Bir metodun bazen sonuç döndürmeyebileceğini dönüş tipine yazar; çağıran boş durumu ele almadan değere ulaşamaz.Sözlükte gör → ise “burada bir şey olmayabilir” bilgisini dönüş tipine yazar; çağıran boş durumu düşünmeden değere ulaşamaz.

KullanımUygun muNeden
Optional<User> findById(id)EvetTasarlandığı yer
Alan: Optional<String> nicknameHayırSerializable değil; alan zaten null olabilir
Parametre: send(Optional<Email>)HayırÇağıranı sarmalamaya zorlar; iki metot yaz
Optional<List<Order>>HayırBoş liste zaten “yok” demek
Kafam karıştı, daha basit anlat

Optional bir kutudur: içinde bir şey olabilir ya da boş olabilir. Metot bu kutuyu verdiğinde, “bakmadan açma” diye etiket de vermiş olur.

Hızlı kontrolBaşlangıç

Optional en çok hangi yerde işe yarar?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Her kullanımı Optional için uygun ya da uygun değil olarak ayır.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Uygun

Sonuç bulunamayabilir ve bu dönüş tipinde görünmeli.

    Uygun değil

    Daha basit ya da daha açık bir yol var.

      get yerine zincir kur

      user.isPresent() ? user.get().address().city() : 'Ankara' — kullanıcı var ama adresi null. Ne olur? Cevabı göster

      NullPointerException. isPresent() yalnızca dıştaki User’ı kontrol etti. İçerideki null adres, düz bir null kontrolündeki gibi gözden kaçtı.

      isPresent() ile get()’i birlikte kullanmak, eski null kontrolünü uzun yoldan yeniden yazmaktır. map ise her adımda karşılaştığı null’ı boş bir Optional’a çevirir ve zinciri kazasız sonuna kadar taşır.

      String city = user.map(User::address)
      .map(Address::city)
      .orElse("Ankara");

      Zincirdeki adım zaten bir Optional döndürüyorsa map yerine flatMap kullan; yoksa kutunun içinde kutu (Optional<Optional<User>>) elde edersin. Değerin mutlaka orada olmasını bekliyorsan get() yerine orElseThrow() yaz: aynı işi yapar, ama niyetin okunur.

      Kafam karıştı, daha basit anlat

      map zincirinde her adım kutuyu açar ve içine bakar. Bir adımda boşluk çıkarsa zincir sessizce durur, sonunda yedek değer gelir.

      Hızlı kontrolOrta

      `Optional<User> findManager(User u)` metodu var. `user.map(this::findManager)` ifadesinin tipi nedir ve ne yazılmalıydı?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Java 10 ile gelen argümansız `orElseThrow()`, `get()` ile nasıl karşılaştırılır?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Satır satır: orElse ne zaman hesaplanır

      orElse sıradan bir metottur ve Java bir metoda vereceği şeyi, metodu çağırmadan önce hazırlar. Yani yedek değer hiç gerekmese bile hesaplanır; buna eager evaluationBir ifadeyi ihtiyaç olup olmadığına bakmadan hemen hesaplamak. Java metot argümanlarını metot çağrılmadan önce hesaplar; orElse(x()) bu yüzden x()'i her zaman çalıştırır.Sözlükte gör → denir.

      Değer varken orElse

      CityLookup.java
      şu an çalışan satırOptional<String> city = Optional.of("İzmir");
      2String a = city.orElse(defaultCity());
      3String b = city.orElseGet(() -> defaultCity());

      Debug

      Adım 1/4

      main Optional dolu.

      city
      = Optional[İzmir]
      Java 21UTF-8LF1:1

      Sol/sağ ok tuşlarıyla da gezebilirsin.

      Yedek sabit bir değerse orElse gayet iyi. Yedeği hazırlamak için bir metot, veritabanı ya da ağ çağrısı gerekiyorsa orElseGet kullan: yedek yalnızca gerektiğinde hazırlanır. Buna lazy evaluationİşi gerçekten ihtiyaç duyulana kadar ertelemek. Stream'de ara işlemler terminal işlem gelene kadar hiç çalışmaz.Sözlükte gör → denir.

      Hızlı kontrolOrta

      Program ne yazdırır?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
      Fallback.java
      1static String fallback() {
      2 System.out.print("F ");
      3 return "yedek";
      4}
      5
      6String a = Optional.of("asıl").orElse(fallback());
      7String b = Optional.of("asıl").orElseGet(() -> fallback());
      8System.out.print(a + " " + b);
      Java 21UTF-8LF

      Kendin gör

      Optional — değer yoksa ne olur, varsa ne boşa gider?

      Tohum 59141
      CityLookup.java
      1Optional<User> user = users.findById(id);
      2String city = user
      3 .map(User::address) // null dönerse boş Optional
      4 .map(Address::city)
      5 .orElse(defaultCity());
      Java 21UTF-8LF

      Oynat ya da adımla.

      Hız
      Adım 0

      Şu an ne oldu?

      Kullanıcının şehrini oku; yoksa varsayılanı kullan

      defaultCity() bir config servisine gidiyor. Hangi durumda çağrılıyor, hangi durumda boşuna?

      Görevler0/3

      • Doğru sonuç al ama varsayılanı boşuna hesaplataçık

        İpucu

        Kullanıcının şehri var; varsayılanın argümanına bak.

      • Optional kullanırken NullPointerException alaçık

        İpucu

        İçerideki bir alan null olsun.

      • Adresi null olan kullanıcı için exception almadan varsayılan şehri döndüraçık

        İpucu

        Her adımda null'ı boşluğa çeviren erişim.

      Olay günlüğü (0)

      Henüz olay yok. Oynat veya adımla.

      1. Varsayılanla oynat. Sonuç doğru, ama defaultCity() boşuna çağrıldı.
      2. Varsayılanı orElseGet yap. Çağrı kayboldu.
      3. Kullanıcıyı “adresi null”, erişimi isPresent() + get() yap. NullPointerException.
      4. Erişimi map zincirine döndür. Aynı kullanıcı için varsayılan şehir geldi.
      5. orElseThrow() seç. Hata “değer yok” diyor; hangi adımın boş olduğunu söylemiyor.
      Kafam karıştı, daha basit anlat

      orElse yedeği her zaman hazırlar, gerekmese bile. orElseGet ise yedeği yalnızca kutu boşsa hazırlar. Yedek pahalıysa ikincisini seç.

      Hızlı kontrolBaşlangıç

      Simülatörde kullanıcı var ama adresi null. `isPresent() + get()` NullPointerException verdi, map zinciri vermedi. Fark nereden geliyor?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Tuzaklar

      Optional döndüren metotta return null. Çağıranın tek güvencesini bozar; Optional.empty() dön.

      Optional.of(x), x null ise hemen patlar. Null olabilecek değer için ofNullable.

      Yokluk bir hataysa, ne olduğunu söyle. orElseThrow(() -> new UserNotFound(id)), “No value present” mesajından çok daha fazlasını anlatır.

      Her null için Optional. Metot içindeki yerel bir değişken için basit bir if daha okunurdur.

      Hızlı kontrolBaşlangıç

      Bu metodu çağıran kod `findCoupon(code).isPresent()` satırında NullPointerException alıyor. Hatalı satır hangisi?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Hatalı satıra dokun, sonra kontrol et.

      CouponService.java
      Java 21UTF-8LF

      Aşağıdaki örnek bir bankanın ‘arkadaşını getir’ kampanyasından ve Optional’ı yalnızca olması gereken yerde kullanıyor: “olmayabilir” diyen dönüş tipi olarak. Alanlarda ve parametrelerde yok; zincir map ve flatMap ile kuruluyor, sınırda anlamlı bir hataya dönüşüyor.

      Derinleş · Bankanın 'arkadaşını getir' kampanyası: Optional'ın doğru yeri 5 dosya · ~84 satır · ilk okumada atlayabilirsin
      Proje dosyaları

      src/main/java/bank/customer/ Customer.java Entity'de alan nullable; getter Optional döndürür. Alan Optional değildir.

      src/main/java/bank/customer/Customer.java
      public class Customer {
      private final long id;
      private final String name;
      private final String referralCode; // code used at onboarding; may be null: plain field, NOT Optional<String>
      public Customer(long id, String name, String referralCode) {
      this.id = id;
      this.name = name;
      this.referralCode = referralCode;
      }
      public long id() { return id; }
      public String name() { return name; }
      // The absence is part of the getter's contract, so it shows up in the return type.
      public Optional<String> referralCode() {
      return Optional.ofNullable(referralCode);
      }
      }

      src/main/java/bank/customer/ CustomerRepository.java Repository: 'bulunamayabilir' dönüş tipinde söylenir.

      src/main/java/bank/customer/CustomerRepository.java
      public interface CustomerRepository {
      Optional<Customer> findById(long id); // not found is a normal outcome
      Optional<Customer> findByReferralCode(String code);
      List<Customer> findByBranch(String branchCode); // empty list, never Optional<List<…>>
      List<Customer> findAll();
      }

      src/main/java/bank/customer/ ReferralService.java Zincir: map, flatMap, filter ve orElseThrow. get() ve isPresent/get ikilisi yok.

      src/main/java/bank/customer/ReferralService.java
      public class ReferralService {
      private final CustomerRepository customers;
      public ReferralService(CustomerRepository customers) {
      this.customers = customers;
      }
      // Who referred this customer, so the campaign bonus goes to the right account?
      // Each step may be missing; no null checks, no get().
      public Optional<String> referrerName(long customerId) {
      return customers.findById(customerId) // Optional<Customer>
      .flatMap(Customer::referralCode) // Optional<String> (not Optional<Optional<…>>)
      .filter(code -> !code.isBlank())
      .flatMap(customers::findByReferralCode) // Optional<Customer>
      .map(Customer::name); // Optional<String>
      }
      public Customer require(long customerId) {
      return customers.findById(customerId)
      .orElseThrow(() -> new CustomerNotFoundException(customerId));
      }
      // orElse evaluates its argument ALWAYS; orElseGet only when empty.
      public String displayName(long customerId) {
      return customers.findById(customerId)
      .map(Customer::name)
      .orElseGet(() -> Placeholders.anonymousName()); // not orElse(Placeholders.anonymousName())
      }
      }

      src/main/java/bank/customer/ Search.java Parametre Optional değil; iki ayrı metot ya da nullable parametre.

      src/main/java/bank/customer/Search.java
      public class Search {
      private final CustomerRepository customers;
      public Search(CustomerRepository customers) {
      this.customers = customers;
      }
      // Not search(Optional<String> branchCode): callers would pass Optional.empty(), or null.
      // Two methods say it more clearly than an Optional parameter.
      public List<Customer> all() {
      return customers.findAll();
      }
      public List<Customer> atBranch(String branchCode) {
      return customers.findByBranch(Objects.requireNonNull(branchCode));
      }
      }

      src/main/java/bank/customer/ CustomerNotFoundException.java Sınır: bulunamadı anlamlı bir hataya dönüşür.

      src/main/java/bank/customer/CustomerNotFoundException.java
      public class CustomerNotFoundException extends RuntimeException {
      public CustomerNotFoundException(long id) {
      super("customer " + id + " not found");
      }
      }

      Kendini sına

      Şimşek turu1/5

      orElse'e verilen ifade, Optional dolu olsa bile hesaplanır.

      Soru 1/2Başlangıç

      `Optional.of(repository.byCode(code))` satırı, kupon bulunamadığında ne yapar?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Aklında kalacak üç şey

      1. 1 Optional bir dönüş tipidir: 'bulunamayabilir' sonucunu metodun imzasına yazar. Alan, parametre ve koleksiyon için kullanılmaz; boş liste zaten 'yok' demektir.
      2. 2 map ve flatMap zinciri her adımdaki yokluğu kendisi taşır. isPresent() ve get() ile elle kontrol etmek aynı null kontrolünü uzun yoldan yazar.
      3. 3 orElse'e verilen yedek, değer varken bile hesaplanır. Yedek pahalıysa ya da yan etkisi varsa orElseGet kullanılır.
      Sonraki kapı Bir Stream'e milyonlarca eleman verip yalnızca ilk üçüne dokunmak nasıl mümkün? Stream API ve Tembellik · 10 dk

      5 kart sonraki derste seni bekliyor

      0/5 kart bu dersten toplandı