Java 8 Optional ve null dönebilen metotlar

Java 8 Optional ve null dönebilen metotlar

Merhabalar, bu yazıda sizlere Java 8 ile beraber gelen Optional sınıfından bahsedeceğim. Bu yazı ile birlikte Java programlama dili ve teknolojileri konusunda ufak bilgiler paylaşmaya devam edeceğim. Umarım faydalı olur.

Optional sınıfının kullanımına bir örnek vererek anlatacağım. Doğrudan anlatabilecek kadar basit bir sınıf olmasına rağmen örnek üzerinden gitmenin daha kalıcı olduğunu düşünüyorum. Örneğimizde bir UserRepository sınıfı tasarlayacağız. Interface’imiz şu şekilde:

UserRepository interface’inde getByUsername() metodunun tanımlandığını görüyoruz. Adından da anlaşılacağı üzere kullanıcı adı ile veritabanında kayıtlı bir kullanıcıyı bulmak istiyoruz. Basitçe aşağıdaki implementasyonu yapabiliriz.

Herhangi bir persistent api kullanarak kullanıcı listesini aldık. Eğer liste boş dönerse girdiğimiz kullanıcı adına sahip bir kullanıcı olmadığı anlamına geldiğinden null object döndük. Eğer kullanıcı varsa listenin ilk elemanını dönüyoruz. Kullandığınız api’nin ne olduğunun bir önemi yok. Bundan farklı bir implementasyonda olabilirdi. Burda dikkat etmemiz gereken eğer veritabanında kullanıcı yoksa ne döneceğimiz. Bu tasarımda null dönmüş olduk.

Başlangıçta tasarım kusursuz görünse de null dönmenin dezavantajları var. Bu sınıfı kullanan istemciye bir sorumluluk yükledik. İstemci kullanıcıyı sorguladıktan sonra ayrıca bir de nulldönüp dönmediğini kontrol etmek zorunda. Aksi takdirde NullPointerException’a sebep olur. Ayrıca bu aslında bir implementasyon detayı. İstemci açısından interface’e baktığınızda aslında null bir değer bekleyemeyiz. Basitçe bir metot var ve kullanıcı adı verip kullanıcı nesnesi alıyoruz. Yani interface de null obje gelebileceğine dair herhangi bir tanımlama yok. Javadoc veya yorum satırı mı geldi aklınıza? Burdaki javadoc veya yorum satırı kötü koda sebep olur. Ayrıca kod açısından yine istemciyi bağlayan birşey yok. Bir diğer sorun ise null dönülmesi bir implementasyon detayı. İmplementasyon yüzünden defensif programlama yapmak zorunda kalıyoruz.

Burdaki durumda aslında kullanıcı nesnesinin null gelebileceğini tahmin edebiliriz. Mantıken baktığınızda kullanıcı veritabanında yoksa ne olur diye düşündüğünüzde null dönebileceğini anlayabilirsiniz. Fakat her durumda bu kadar basit bir durumda olmayabilir. Daha karmaşık bir implementasyonda istemci olarak bunu görmek daha zordur. null dönmek yerine exception kullanarak bir tasarım yapabiliriz:

interface’i de şu şekilde değiştiriyoruz:

Bu implementasyon yukarıda bahsettiğim sorunu çözüyor. Burada basit bir şekilde kullanıcı yoksa UserNotFoundException’u fırlattık. Ayrıca checked exception olduğundan istemci bu metodu çağırırken exception’u handle etmek zorunda veya bir üst katmana bırakabilir. Sonuç olarak implementasyona bakmadan interface bize exception fırlatabileceğini tanımlar.

Bu tasarımın da yine dezavantajları var. İstemci taraf ya bir try-catch bloğu yazmak zorunda. Yani exception mekanizmasını bir istisna durumu için değil normal akışın bir parçası olarak kullandık. Burada örneğin veritabanı bağlantısı kesilirse exception atabilirdik veya istemci geçersiz bir kullanıcı adı gönderdiğinde IllegalArgumentException gibi bir exception fırlatabilirdik. Fakat burdaki durum ne istemciye bağlı bir input hatası ne de beklenmeyen bir durum. O yüzden bu tarz yapılarda Exception kullanmamalıyız.

Çözüm olarak yine ilk tasarımımıza dönüp User entity’si dönmek yerine bir Result objesi dönebiliriz.

User entity’sini doğrudan dönmek yerine araya başka bir sınıf yerleştirerek ilk durumdaki sorunu çözmüş oluyoruz. Artık kullanıcının veritabanında olmayacağını daha net bir şekilde ifade ettik. Bu tür Result sınıflarını immutable tanımlarsak daha iyi bir tasarım yapmış oluruz. Constructor’lara isim veremediğimiz için burada static metod kullanmayı tercih ettim. UserResult sınıfı’da aşağıdaki gibi.

İstemci taraf doesUserExist() metodu ile kontrol edip gereken işlemi yapabilir. Bu şekilde tasarımımız daha stabil oldu. Àyrıca bunun büyük bir proje olacağını düşünürsek UserResult sınıfını daha generic olarak tasarlayabiliriz.

UserRepositoryImpl sınıfımızı da düzenleyelim.

EntityResult gibi result objeleri farklı implementasyonlar da içerebilir. Burda nulldönme problemini için kullandık. Özellikle katmanlar arası iletişim bu şekilde yapılmalıdır.

Java7 ile geliştirme yapsaydık eğer muhtemelen bu şekilde geliştirme yapacağız. Fakat Java 8 de bunun için java.util paketinin içerisinde Optional diye bir sınıf eklendi. Kendi EntityResult gibi sınıflar yazmak yerine null dönebilen metodlar için return type’ı Optional olarak tanımlayabiliriz.

Optional yine tasarladığımız EntityResult gibi generic ve immutable bir sınıf. Yine Optional.empty() aynı şekilde boş bir result dönüyor. null dönebilen yerlerde bunu kullanmalıyız. Optional.of() metodunu da kullanırken dikkatli olmak gerekiyor. Eğer parametre olarak verdiğiniz objenin değeri null ise NullPointerException alırsınız. Eğer bir nesne null olabilirse `Optional.ofNullable metodunu kullanın. Diğer metodlarına da kısaca değinirsem:

  • isPresent() : eğer empty ise false diğer durumda true döner. İlk tasarımımızdaki null kontrolü yerine kullanılabilir.
  • get() : Empty olmayan durumlarda Optional içerisinde tanımlanan nesneyi döner. Mutlaka get den önce isPresent() ile kontrol edilmesi gerekiyor. Aksi takdirde değer empty ise NoSuchElementException alırsınız.
  • orElse(T value) : Eğer varsayılan bir değeriniz varsa kullanabilirsiniz. empty ise verdiğiniz değeri, değilse tanımlanan değeri döner. Örneğin null almak istiyorsanız orElse(null) şeklinde nesneyi alabilirsiniz.

Bunun gibi birçok metodu var. Aynı zamanda fonksiyonel özellikler de taşıyor. Java 8 ile beraber gelmesi bir raslantı değil :) Başlangıçta metodları kullanırken mutlaka javadoc ile inceleyin. get() metodunda olduğu gibi exception alabilirsiniz. Ayrıca eğer Java 8 de Stream Api ile tanıştıysanız muhtemelen Optional sınıfını kullanmışsınızdır. Stream Api’de oldukça fazla kullanılır. Primitive tipler için de OptionalInt, OptionalDouble gibi sınıfları da kullanabilirsiniz.

Bu yazımda örnek bir tasarım ile Optional sınıfını anlatmaya çalıştım. Umarım faydalı olmuştur.

Bir sonraki yazıda görüşmek üzere…

java java8 optional