Press "Enter" to skip to content

Effective Java Madde 12: toString() Metodunu Her Zaman Geçersiz Kılın

Last updated on January 3, 2020

Her ne kadar java.lang.Object bir toString() gerçekleştirimi sunsa da, geri döndürdüğü karakter dizisi genellikle sizin sınıfınızı kullanmak isteyen bir yazılımcının görmek istediği şey değildir. Bu değer sınıfın adı, ardından gelen ‘@’ karakteri ve nesnenin hash kodunun onaltılık sistemde (hexadecimal) ifadesini içermektedir, örneğin “PhoneNumber@163b91”. toString sözleşmesi, bu metottan döndürülen değerin “kolay okunup anlaşılabilen, kısa ama bilgi verici” olması gerektiğini söyler. “PhoneNumber@163b91” değerinin kısa ve okuması kolay olduğunu savunabilirsiniz ancak “(707) 867-5309” ile karşılaştırıldığında bilgi verici olmadığı açıktır. toString sözleşmesi bunların haricinde “Bu metodu bütün alt sınıfların geçersiz kılması tavsiye edilir” demektedir. Kesinlikle uyulması gereken bir tavsiye!

Her ne kadar equals (Madde 10) ve hashCode (Madde 11) sözleşmeleri kadar kritik olmasa da, güzel bir toString gerçekleştirimi sağlamak sınıfınızı çok daha kolay kullanılabilir hale getirir. toString metodu, bir nesne printf, println gibi metotlara parametre olarak geçildiğinde, karakter dizilerine eklendiğinde veya assert ile birlikte kullanıldığında otomatik olarak işletilir.

PhoneNumber sınıfı için iyi bir toString metodu tanımladıysanız, yararlı debug mesajları yazdırmak aşağıdaki gibi son derece kolay olacaktır.

System.out.println("Failed to connect: " + phoneNumber);

Sınıfınızı kullanan programcılar bunun gibi debug mesajlarını siz bir toString metodu sağlasanız da sağlamasanız da yazdıracaklardır, ancak sağlamazsanız görecekleri mesaj çok da faydalı olmayacaktır. İyi bir toString metodu tanımlamanın bir başka faydası da sınıfınız bir veri yapısı içerisinde kullanıldığında ortaya çıkar. Bir HashMap yazdırdığınızda “{Jenny=PhoneNumber@163b91}” mi yoksa “{Jenny=(707) 867-5309}” mi görmek isterdiniz?

Elverişli durumlarda toString metodu nesne içerisindeki bütün önemli alanları içeren bir karakter dizisi döndürmelidir. Yukarıdaki PhoneNumber sınıfı buna bir örnek olabilir. Eğer nesne çok fazla alan içeriyorsa veya içerdiği alanlar karakter dizisi olarak ifade edilemiyorsa, o zaman “Manhattan white pages (1487536 listings)” gibi makul bir özet karakter dizisi döndürülebilir.

toString metodunu geçersiz kılarken vermeniz gereken önemli kararlardan birisi de döndürdüğünüz karakter dizisinin formatını belirleyip belirlememektir. PhoneNumber gibi değer sınıfları için bunu yapmanız tavsiye edilir. Format belirlemenin avantajı, nesnenizi standart, net ve okunabilir bir karakter dizisi olarak ifade edebilmektir. Bu ifade şekli girdi/çıktı olarak kullanılabilir veya CSV gibi dosyalar içerisinde saklanabilir. Eğer bir format tanımlarsanız, bunun yanında uyumlu bir statik fabrika metodu veya yapıcı metot tanımlamak da mantıklı olacaktır. Böylece programcılar nesnenin kendisi ve karakter dizisi ifadesi arasında kolayca geçiş yapabilirler. Bu yaklaşım BigInteger, BigDecimal gibi birçok değer sınıfı için Java kütüphanesi içerisinde kullanılmıştır.

toString için bir format belirlemenin dezavantajı ise, eğer sınıfınız yaygın olarak kullanılıyorsa belirlediğiniz formatı gelecekte değiştirme şansınızın kalmamasıdır. Programcılar sizin formatınıza uygun olarak karakter dizisi üzerinde işlem yapan kodlar yazacaklardır ve siz formatı değiştirdiğiniz zaman bu kodlar çalışmayacaktır. Bu da sınıfınızı kullanan programcıları memnun etmeyecektir. Bir format belirlemediğiniz zaman gelecek sürümlerde değişiklik yapma esnekliğini korumuş olursunuz.

Bir format belirleseniz de belirlemeseniz de, niyetinizi açık bir biçimde belirtmelisiniz. Format belirlediyseniz de net bir biçimde anlatmalısınız. Mesela Madde 11’de yazdığımız PhoneNumber sınıfı için aşağıdaki gibi bir toString olabilir:

/** 
* Telefon numarası nesnesinin karakter dizisi olarak ifadesini döndürür. 
* Bu karakter dizisi 14 karakterden oluşur ve formatı şu şekildedir 
* "(XXX) YYY-ZZZZ", burada XXX alan kodu, YYY ön ek ve ZZZZ hat numarasıdır. 
* (Her bir büyük harf bir rakamı temsil etmektedir) 
* 
* Eğer bu telefon numarasının üç kısmından herhangi birisi o alanı 
* dolduracak kadar büyük değilse, o zaman başına yeteri kadar 0 eklenir. 
* Mesela, hat numarası 123 ise, karakter dizisinin son 4 karakteri "0123" olacaktır. 
* 
* Alan kodunun ardından kapatılan parantezden sonra bir boşluk karakteri 
* geldiğini gözden kaçırmayın. 
*/ 
@Override 
public String toString() { 
    return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber); 
}

Eğer bir format belirlemezseniz, aşağıdaki gibi biri dokümantasyon yapabilirsiniz:

/** 
* Bu iksirin kısa bir açıklamasını üretir. Üretilen karakter dizisi ileride değişebilir 
* aşağıda tipik bir örnek verilmektedir: 
* 
* "[İksir No: #9: tür=aşk, koku=neft yağı, görünüm=çini mürekkebi]" 
*/ 
@Override 
public String toString() { ... }

Bu açıklamayı okuduktan sonra bu formatı baz alarak kod yazan programcılar, format değiştiği zaman kendilerinden başka suçlayacak kimseyi bulamayacaklardır.

Format belirleseniz de belirlemeseniz de, toString tarafından döndürülen değerde bulunan bütün alanlara erişim sağlayın. Örneğin, PhoneNumber sınıfı alan kodu, ön ek ve hat numarası alanlarına erişim metotları sağlamalıdır. Eğer bunu yapmazsanız, sınıfınızı kullanan programcıları toString metodunun döndürdüğü karakter dizisi ile çalışmaya zorlamış olursunuz. Bu durum da performans kaybına ve gereksiz kod yazılmasına yol açar, hatalı kod ihtimalini artırır ve format değişikliği olduğunda kolayca bozulan, kırılgan yazılımlara sebebiyet verir. Erişim metotları sağlamayarak, toString metodunu, ileride değişebileceğini belirtseniz bile sınıfınız için bir API haline getirmiş olursunuz.

Madde 4’de anlatılan statik yardımcı sınıflar için bir toString() metodu yazmak mantıksızdır. Enum türleri içinse yazmaya gerek yoktur çünkü Java zaten iyi yazılmış bir toString() sağlamaktadır.

Google’ın sağladığı AutoValue aracı ve kod düzenleyicilerin çoğu otomatik olarak toString() yaratmak için kullanılabilir. Bu sayede üretilen metotlar sınıfın içeriği hakkında detaylı bilgi verebilse de, sınıfın özel anlamlarını aktaramazlar. Bu sebeple, örneğin PhoneNumber sınıfı için otomatik üretilen bir toString() metodu yeterli olmayabilir çünkü telefon numaralarının standart bir yazılış biçimi vardır ve buna uyulması gerekir. Ancak yine de bu Object sınıfından gelen versiyona göre çok daha faydalıdır.

Share

Leave a Reply

%d bloggers like this: