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 null
dö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 null
dö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ızorElse(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…