Keşke Java'ya bu kitap ile giriş yapmış olsaydım. Bruce Eckel Java anlatmak yerine önce Object Oriented Programming (OOP) mantığını anlatarak işe başlıyor ve kitap boyunca da bu tavrından vazgeçmeyip her zaman OOP odağında anlatmaya devam ediyor.
Ancak Software Principles çalışırken duyabileceğim "favour composition over inheritance" öğütünü inheritance'dan bahsederken anlatması çok etkileyici.
Okumanızı tavsiye ederim.
Polymorphism bölümünde constructor içerisinde override edilmiş metod çağrımına dair anlattıklarını toparlamaya çalışacağım.
Java'da static ve final (private metodlar da dolaylı olarak final'dır) metod çağrımları hariç diğer tüm çağrımlar dynamic binding (late binding, runtime binding) ile çözümlenirler. Yani compile-time'da compiler inheritance hiyararşisinde hangi alt sınıfın metodunun çağrılacağını bilmez buna runtime'da karar verir.
Peki base class constructor içerisinde polymorphic bir metodu çağırırsak hangi metod çağrılır?
Cevap alt sınıf metodu.
Bir örnek ile görelim. Sonra bu durumun yaratabileceği problemlere bakalım.
class Shape { Shape() { System.out.println("shape before draw method"); draw(); System.out.println("shape after draw method"); } void draw() { System.out.println("shape -> draw"); } } public class Circle extends Shape { private int radius = 0; Circle(int radius) { this.radius = radius; System.out.println("circle before draw method"); draw(); System.out.println("circle after draw method"); } @Override void draw() { System.out.println("circle -> draw. radius: " + radius); } public static void main(String[] args) { Shape shape = new Circle(5); } }
Kod çıktısı aşağıdaki gibi.
shape before draw method circle -> draw. radius: 0 shape after draw method circle before draw method circle -> draw. radius: 5 circle after draw method
Shape sınıfı içerisinde draw metodu çağrımında override edilmiş alt sınıf metodu çağrıldı (2. satır). Ancak henüz Circle objesi stable durumda değil. Yine highlight edilmiş satırda görüleceği üzere Circle sınıfına radius değeri olarak 5 göndermemize rağmen henüz Shape constructor çağrımından dönmediği için Circle constructor tamamlanmadı dolayısı ile sınıfımız şu anda hatalı bir statüde duruyor.
Sonuç olarak kazara yapılacak bir hata sonucu böylesi bir bug'ı bulmak biraz zor olabilir.
Aynı örnekte radius primitive olduğu için default 0 değerini aldı. Ancak Integer tipinde bir nesne olarak tanımlayıp örneğin toString() metodunu çağırsaydık Shape sınıfı'nın constructor içerisinden gelen draw çağrımı sonucu NullPointerException alıyor olacaktık.
Bu durumdan kaçınmak için constructor'larda minimum işlem ile objeyi geçerli bir statüye getirip yapabiliyorsak sınıftaki diğer metod çağrımlarından kaçınmalıyız. Constructor'larda güvenle çağırabileceğimiz metodlar üst sınıfta final olarak tanımlananlardır. Çünkü bunların override edilme ihtimalleri olmadığından burada bahsettiğim sorun ortaya çıkmayacaktır.