Java Sınıfların Tekrardan Kullanılması


Belli bir amaç için yazılmış ve doğruluğu kanıtlanmış olan sınıfları , yeni uygulamaların içersinde kullanmak, hem iş süresini kısaltacaktır hemde yeni yazılan uygulamada hata çıkma riskini minimum'a indirecektir. Uygulamalarımızda daha evvelden yazılmış ve doğruluğu kanıtlanmış olan sınıfları tekrardan kullanmanın iki yöntemi vardır.
Birinci yöntem komposizyon'dur.Bu yöntemde daha önceden yazılmış ve doğruluğu kanıtlanmış olan sınıf tipindeki değişkenimizi , yeni yazılan sınıfın içersinde doğrudan kullanabiliriz. Daha önceki bölümlerde komposizyon yöntemini çokca kullandık.
İkinci yöntem ise kalıtımdır(inheritance) , bu yöntemde yeni oluşturacağımız sınıfı , daha evvelden yazılmış ve doğruluğu kanıtlanmış olan sınıfıdan türetiriz, böylece yeni oluşan sınıf , türetildiği sınıfın özelliklerine sahip olur , artı , oluşan bu yeni sınıf kendisine ait yeni özelliklerde ekleyebilir.

Komposizyon

Komposizyon yönetmini daha önceki örneklerimizde kullandık , baştan ne olduğu bilinçli bir şekilde örnek üzerinde açıklarsak ;
gösterim-1
class Meyva {
 
    //...
}
gösterim-2
class Elma {
 
    private Meyva m = new Meyva();
    //...
}

Elma sınıfı , Meyva sınıfını direk olarak kendi içersinde tanımlayarak, Meyva sınıfının içersindeki özellikleri erişebilir .Burada yapılan iş Elma sınıfını Meyva sınıfına bağlamaktır.
Sınıfların arasında ilişkiyi UML diyagramında gösterirsek

Şekil-1

Başka bir örnek verirsek ;
ör-Motor.java
public class Motor {
    private static int  motor_gucu = 3600;
    
    public  void calis() {
        System.out.println("Motor Calisiyor") ;
    }
 
    public  void dur() {
        System.out.println("Motor Durdu") ;
    }   
}
Şimdi bu motor sınıfını , arabamızın içersine yerleştirelim ;
ör-AileArabasi.java
public class AileArabasi {
    public Motor m = new Motor();
 
    public void hareketEt() {
        m.calis();
        System.out.println("Aile Arabasi Calisti");
    }
 
    public void dur() {
        m.dur();
        System.out.println("Aile Arabasi Durdu");
    }
    
    public static void main(String args[]) {
        AileArabasi aa = new AileArabasi() ;
        aa.hareketEt();
        aa.dur();
    }
}
AileArabası sınıfının içersine Motor tipinde global değişken yerleştirerek , iki sınıfı birbirine bağlamış olduk.AileArabası sınıfının hereketEt() ve dur() metodlarında, önce Motor sınıfına ait metodlar direk olarak çağrıldı.Bu ilişkiyi UML diyagramında incelersek .

Şekil-2

Motor sınıfının private erişim belirliyicisine sahip olan motor_gucu değişkenine , AileArabasi sınıfından ulaşamayız,nedenlerini bir önceki bölümde incelemiştik. AileArabasi sınıfı Motor sınıfının sadece iki adet public metoduna erişebilir , calis() ve dur().Olaylara kuş bakışı bakarsak , karşımızdaki manzara aşağıdaki gibidir.

Şekil-3
AileArabasi sınıfını çalıştırırsak , ekrana gelen çıktının aşağıdaki gibi olması gerekir;
Motor Calisiyor
Aile Arabasi Calisti
Motor Durdu
Aile Arabasi Durdu

Komposizyon yöntemine en iyi örnek , bir zamanların ünlü çizgi filmi Voltran'dır. Bu çizgi filmi hatırlayanlar bileceklerdir ki , büyük ve yenilmez olan robot'u (Voltran) oluşturmak için değişik ufak robotlar biraraya gelmekteydi. Kollar , bacaklar ,gövde ve kafa bölümü. Bizde kendi Voltranımızı oluşturalım ;
ör-Voltran.java
class Govde {
        void benzinTankKontrolEt() {}
}
 
class SolBacak {
        
        void maviLazerSilahiAtesle() {}       
}       
 
class SagBacak {
        void kirmiziLazerSilahiAtesle() {}
}
 
class SagKol {
        void hedeHodoKalkaniCalistir() {}
}
 
class SolKol {
        void gucKaynagiKontrolEt() {}
}
 
class Kafa {
        
        void tumBirimlereUyariGonder() {}
        void dusmanTanimlamaSistemiDevreyeSok() {}
}
 
public class Voltran {
         
          Govde    gv    = new Govde();
          SolBacak slb   = new SolBacak();
          SagBacak sgb   = new SagBacak();
          SagKol   sgk   = new SagKol() ;
          SolKol   slk   = new SolKol() ;
          Kafa     kf    = new Kafa() ;
           
          public static void main(String args[]) {
                Voltran vr = new Voltran() ;
                vr.kf.dusmanTanimlamaSistemiDevreyeSok();
                vr.kf.tumBirimlereUyariGonder();
                vr.sgb.kirmiziLazerSilahiAtesle();
          }     
}       
Voltran sınıfı , 6 değişik sınıf tarafından oluşturulmaktadır.6 değişik sınıf'a ait özellikler daha sonradan Voltran sınıfının içersinde ihtiyaçlara göre kullanılıyor.Oluşan olayları UML diyagramında tanımlarsak ;


Şekil-4

Kalıtım

Kalıtım konusu nesneye yönelik programlamanın (object oriented programming ) en önemli kavramlarından bir tanesidir.Kalıtım kavramı , kısaca bir sınıfdan diğer bir sınıfın türemesidir. Yeni türeyen sınıf , türetilen sınıfın global değişkenlerine (statik veya değil ) ve metodlarına (statik veya değil) otomatik olarak sahip olur (private olanlar hariç).
Unutulmaması gereken nokta , yeni türeyen sınıf , türetilen sınıfın private global değişkenlerine (statik veya değil ) ve metodlarına (statik veya değil ) otomatik sahip olamaz ayrıca yeni türeyen sınıf eğer türetilen sınıf ile ayrı paketlerde ise yeni türeyen sınıf , türetilen sınıfın sadece public ve protected erişim belirliyicisine sahip olan global değişkenlerine (statik veya değil ) ve metodlarına (statik veya değil ) otomatik olarak sahip olabilir.
gösterim-3
class Kedi {
   //..
}
 
class Kaplan extends Kedi {
   //..
}
Kedi sınıfından türeyen Kaplan sınıfı . İki sınıf arasındaki ilişkiyi şöyle tarif edebiliriz , her kaplan bir kedi dir.Yani her kaplan kedisel özellikler taşıyacaktır ama bu özelliklerin üzerine kendisine biseyler eklemiştir.
Yazılış ifadesi olarak , türeyen sınıf isminin yanına extends ifadesini koyarak , hemen sonrasında türetilen sınıfın kendisini yerleştiririz.Yukarıdaki örneğimizi UML diyagramında göstermeye çalışırsak ;

Şekil-5
Kedi ve Kaplan sınıflarımızı biraz daha geliştirelim ;
ör-KediKaplan.java
class Kedi {
 
        protected int ayakSayisi = 4 ;
        
        public void yakalaAv() {
               System.out.println("Kedi sinifi Av yakaladi");
        }
        
        public static void main(String args[]) {
               Kedi kd= new Kedi() ;
               kd.yakalaAv() ;
        } 
        
}
 
class Kaplan extends Kedi {
 
        public static void main(String args[] ) {
               Kaplan  kp = new Kaplan();
               kp.yakalaAv();
               System.out.println("Ayak Sayisi = " + kp.ayakSayisi) ;
        }       
}
Kaplan sınıfı Kedi sınıfından türemiştir.Görüldüğü üzere Kaplan sınıfının içersinde ne yakalaAv() metodu ne de ayaksayisi değişkeni tanımlanmıştır. Kaplan sınıfı bu özelliklerini kendisinin ana sınıfı olan Kedi sınıfından almıştır.
Kedi sınıfının içersinde tanımlanmış ayaksayisi değişkeni , protected erişim belirliyicisine sahiptir.Bunun anlamı , bu değişkene aynı paket içersinde olan sınıflar ve ayrı paket içersinde olup bu sınıftan türetilmiş olan sınıfların erişebileceğidir.Böylece Kaplan sınıfı ister Kedi sınıfı ile aynı pakette olsun ister olmasın , Kedi sınıfına ait global int ilkel (primitive) tipinindeki değişkene (ayaksayisi) erişebilir.
Her sınıfın içersine main metodu yazarak onları tek başlarına çalışabilir bir hale sokarız (standalone application) ,bu yöntem sınıfları test etmek açısından iyidir.Örneğin Kedi sınıfını çalıştırmak için java Kedi veya Kaplan sınıfını çalıştırmak için java Kaplan dememiz yeterli olacaktır.

Gizli Kalıtım

Oluşturduğumuz her yeni sınıf otomatik ve gizli olarak Object sınıfından türer.Object sınıfı Javadaki tüm diğer sınıfların ana sınıfıdır.
ör-YeniBirSinif.java
public class YeniBirSinif {
    public static void main(String[] args) {
          YeniBirSinif ybs1 = new YeniBirSinif(); 
            YeniBirSinif ybs2 = new YeniBirSinif(); 
            System.out.println("YeniBirSinif.toString() " + ybs1 ) ;
            System.out.println("YeniBirSinif.toString() " + ybs2 ) ;
            System.out.println("ybs1.equals(ybs2) " + ybs1.equals(ybs2) ) ;
            // ....
    }
}
Uygulamamızın çıktısı aşağıdaki gibi olur ;
YeniBirSinif.toString() YeniBirSinif@82f0db
YeniBirSinif.toString() YeniBirSinif@92d342
ybs1.equals(ybs2) false
YeniBirSinif sınıfımızda , toString() ve equals() metodları tanımlanmamasına rağmen bu metodları kullandık , nasıl ? . Biz yeni bir sınıf tanımladığımızda , Java gizli ve otomatik olarak extends Object , ibaresini yerleştirir.
gösterim-4
public class YeniBirSinif extends Object {
Bu sayede Object sınıfına ait metodları kullanabiliriz. Object sınıfına ait obje metodları aşağıdaki gibidir ;
· clone() : Bu objenin aynısını klonlar ve yeni bir obje geri döndürür.
· equals(Object obj) : obj objesi , bu objeye eşit mi kontrolü yapar.
· finalize() : Bu objeye herhangi bir değişken bağlı bulunmadığında ,çöp toplayıcısı bu objeyi hafızadan silmeden önce çağırdığı metod.
· getClass() : Bu objenin çalışma anında Class bilgilerini geri döner .
· hashCode() : Bu objenin hash kodunu geri döner .
· notify() : Bu objenin monitöründe olan tek thread'i uyandırır. (ilerleyen bölümlerde inceliyeceğiz)
· notifyAll() : Bu objenin monitöründe olan tüm thread leri uyandırır. (ilerleyen bölümlerde inceliyeceğiz)
· toString() : Bu objenin String tipinden ifadesini geri döner .
· wait() : O andaki thread'in beklemesini sağlar ; bu bekleme notify() veya notifyAll() metodları sayesinde sona erer.
· wait (long timeout) : O andaki thread'in belirtilen süre kadar beklemesini sağlar ; bu bekleme notify() veya notifyAll() metodları sayesinde de sona erebilir.
· wait (long timeout , int nanos) : O andaki thread'in belirtilen gerçek süre kadar beklemesini sağlar ; bu bekleme notify() veya notifyAll() metodları sayesinde de sona erebilir.
Kısacası , oluşturululan her yeni sınıf , yukarıdaki , metodlara otomatik olarak sahip olur . Bu metodları yeni oluşan sınıfların içersinde tekrardan istediğimiz gibi yazabiliriz.Örneğin finalize() metodu kendi sınıfımızın içersinde farklı sorumluluklar verebiliriz(çizgi çizen bir objenin , hafızadan silinirken çizdiği çizgileri temizlemesi gibi) . Bu olaya , ana sınıfın metodlarını iptal etme (override) denir. Biraz sonra iptal etme(override) konusunu daha detaylı inceliyeceğiz.
Akıllara şöyle bir soru gelebilir , Kaplan sınıfı hem Kedi sınıfından hemde Object sınıfından mı türemiştir ? cevap hayır.Java programlama dilinde çoklu kalıtım (multiple inheritance) yoktur. Aşağıdan yukarıya doğru gidersek , Kaplan sınıfı Kedi sınıfından türemiştir , Kedi sınıfada Object sıınıfından (gizli ve otomatik olarak). Sonuçta Kaplan sınıfı hem Kedi sınıfının hemde Object sınıfına ait özellikler taşıyacaktır . Aşağıdaki şeklimizde görüldüğü üzere her sınıf sadece tek bir sınıfdan türetilmiştir.Object sınıfı tüm sınıfların türetildiği en tepedeki sınıfıdır.

Şekil-6
Çoklu kalıtım (multiple inheritance) , bazı konularda faydalı olmasının yanında bir çok sorun oluşturmaktadır. Örneğin iki ana sınıf düşünün , bunların aynı isimde değişik işlemler yapan metodları bulunsun.Bu olay türetilen sınıfın içersinde bir çok probleme yol açacaktır. Bu sebeblerden dolayı Java programlama dilinde çoklu kalıtım yoktur.
Java programlama dilinde çoklu kalıtım'ın faydalarından yararlanmak için interface kavramı kullanılır.Interface kavramını ilerleyen bölümlerde inceliyeceğiz.

Kalıtım ve ilk değer alma sırası

Tek bir sınıf içersinde ilk değerlerin nasıl alındığını geçmiş bölümlerde incelimiştik (bkz). İşin içersine birde kalıtım kavramı girince olaylar biraz karışabilir.Kalıtım kavramı bir sınıfdan , başka bir sınıf kopyalamak değildir. Kalıtım kavramı türeyen bir sınıf , türetilen ana sınıfın özelliklerini taşır ama kendisine ait değişik özelliklerde taşıyabilir.
Bir Obje oluşurken , bu objeye ait yapılandırıcının çağrıldığını önceki bölümlerden biliyoruz , peki aynı olaya birde kalıtım kavramı ile birlikte bakalım ;
ör-IlkDegerVermeSirasi.java
class Hayvan {
    public Hayvan() {
        System.out.println("Hayvan Yapilandiricisi");
    }
}
 
class Yarasa extends Hayvan {
    public Yarasa() {
        System.out.println("Yarasa Yapilandiricisi");
    }
}
 
class UcanYarasa extends Yarasa{
    public UcanYarasa() {
        System.out.println("UcanYarasa Yapilandiricisi");
    }
 
    public static void main(String args[]) {
           UcanYarasa uy = new UcanYarasa();
    }
}
UcanYarasa objesi oluşmadan evvel , UcanYarasa sınıfının ana sınıfı olan Yarasa objesi oluşturulmaya çalışılacaktır.Fakat Yarasa sınıfıda Hayvan sınıfından türemiştir , Yarasa sınıfının ana sınıfı Hayvan sınıfıdır . Hayvan sınıfıda Object sınıfından türemiştir .


Şekil-7
Object sınıfını bir kenara koyarsak , ilk olarak Hayvan sınıfının yapılandırıcısı çalışacaktır , daha sonra Yarasa sınıfının yapılandırıcısı çalışacaktır ve en son olarak UcanYarasa sınıfının yapılandırıcısı çalışacaktır.Bu yapılandırıcılar fark edildiği üzere varsayılan yapılandırıcıdır (default constructor).Uygulamanın çıktısı aşağıdaki gibi olacaktır ;
Hayvan Yapilandiricisi
Yarasa Yapilandiricisi
UcanYarasa Yapilandiricisi
Paremetre alan yapılandırıcılar ve kalıtım
Parametre alan yapılandırıcıları , İşin içersine kalıtım girdiğinde direk olarak çağıramayız , bu yapılandırıcıları çağırmak için özel bir ifade kullanmamız gereklidir. Ana sınıfın parametre alan yapılandırıcısı açık olarak super anahtar kelimesi ile çağırmak gereklidir.
ör-IlkDegerVermeSirasiParametreli.java
class Insan {
    public Insan(int par) {
        System.out.println("Insan Yapilandiricisi " + par);
    }
}
 
class ZekiInsan extends Insan {
    public ZekiInsan(int par) {
        super(par+1); //dikkat
        System.out.println("ZekiInsan Yapilandiricisi " + par);
    }
}
 
class Hacker extends ZekiInsan{
    public Hacker(int par) {
        super(par+1); //dikkat
        System.out.println("Hacker Yapilandiricisi " + par);
    }
 
    public static void main(String args[]) {
           Hacker hck = new Hacker(5);
    }
}
Yukarıdaki örneğimizde , her sınıf , yapılandırıcısına gelen değeri bir arttırıp ana sınıfının yapılandırıcısına göndermektedir.Fark edildiği üzere ana sınıfın parametre alan yapılandırıcısını çağırırken super anahtar kelimesini kullandık.Uygulamanın çıktısı aşağıdaki gibidir.
Insan Yapilandiricisi 7
ZekiInsan Yapilandiricisi 6
Hacker Yapilandiricisi 5
Dikkat edilmesi gereken ikinci husus , aynı this anahtar kelimesinin kullanılışı gibi , super anahtar kelimeside içinde bulunduğu yapılandırıcının ilk satırında yer almalıdır.
ör-IlkDegerVermeSirasiParametreli.java-hatalı
class Insan {
    public Insan(int par) {
        System.out.println("Insan Yapilandiricisi " + par);
    }
}
 
class ZekiInsan extends Insan {
    public ZekiInsan(int par) {
        System.out.println("ZekiInsan Yapilandiricisi " + par);
        super(par+1); //  ! hatalı !
    }
}
 
class Hacker extends ZekiInsan{
    public Hacker(int par) {
        System.out.println("Hacker Yapilandiricisi " + par);
        super(par+1); // ! hatalı !
    }
 
    public static void main(String args[]) {
           Hacker hck = new Hacker(5);
    }
}
IlkDegerVermeSirasiParametreli.java örneğini derlerseniz ;
gösterim-5
javac IlkDegerVermeSirasiParametreli.java
Aşağıdaki derleme-anı (compile-time) hatası ile karşılaşırsınız.
IlkDegerVermeSirasiParametreli.java:8: cannot resolve symbol
symbol : constructor Insan ()
location: class Insan
public ZekiInsan(int par) {
^
IlkDegerVermeSirasiParametreli.java:10: call to super must be first statement in constructor
super(par+1);
^
2 errors

Komposizyon mu ? Kalıtım mı ?

Yeni oluşturduğunuz sınıfın içersinde , daha evvelden yazılmış sınıfların özelliklerinden faydalanmak istiyorsanız bunun iki yolu olduğunu belirtmiştik ; Komposizyon ve Kalıtım .Peki hangi yöntemi ne zaman tercih etmeliyiz.
Komposizyon , daha evvelden yazılmış sınıfların özelliklerini kullanmak için temiz bir yöntemdir.Yeni oluşturulan sınıfın içersinde , kullanmak istediğimiz sınıflara ait değişkenler tanımlıyarak istediğimizi elde ederiz.
ör-Araba.java
class ArabaMotoru {
    public void calis() { }
    public void dur() { }
}
 
class Pencere {
    public void asagiyaCek() { }  
    public void yukariyaCek() { }  
}
 
class Kapi {
    Pencere pencere  = new Pencere();
    public void ac() { } 
    public void kapa() { } 
}
 
 
class Tekerlek {
    public void havaPompola(int olcek) { }
    
}
 
public class Araba {
 
    ArabaMotoru arbm = new ArabaMotoru();
    // 2 kapili spor bir araba olsun 
    Kapi  sag_kapi  = new Kapi();
    Kapi  sol_kapi  = new Kapi();
    Tekerlek[]  tekerlekler = new Tekerlek[4] ;
    public Araba() {
        for (int i  = 0 ; i < 4 ; i++ ) {
            tekerlekler[i] = new Tekerlek();
        }
    }
    public static void main ( String args[] ) {
        Araba araba = new Araba();
        araba.sag_kapi.pencere.yukariyaCek();
        araba.tekerlekler[2].havaPompola(70);
    }
}

Peki ya kalıtım kavramını ne zaman kullanmalıyız ? Daha evvelden yazılmış biri sınıfın , belli bir problem için yeni versiyonunu yazma işlemi , kalıtım kavramını yeterince açıklar. Fakat kalıtım konusunda türetilen sınıf ile türeyen sınıf arasında bir ilişki olmalıdır. Bu ilişki "bir" ilişkisidir. Örneğin Elma ve Meyva sınıflarını göz önüne alırsak , şöyle bir söz yanlış olmaz sanırım, Elma bir Meyvadır . Bu iki sınıf arasında "bir" ( is -a ) ilişkisi olduğundan , kalıtım kavramını bu sınıflar üzerinde rahatca kullanabiliriz.
Örnekleri çoğaltmak mümkündür. UçanYarasa , Yarasa ve Hayvan arasındaki ilişkiyi açıklarsak ;
· UçanYarasa bir Yarasadır ;
· Yarasa bir Hayvandır ;
· O zaman UçanYarasa'da bir Hayvandır.
· Hayvan'da bir Objedir. (şekil-7)

İptal etmek (Overriding)

Ana sınıf içersinde tanımlanmış bir metod ,bu ana sınıfdan türeyen bir sınıf içersinde iptal edilelebilir.
ör-KitapEvi.java
class Kitap {
    public int sayfaSayisiOgren() {
        System.out.println("Kitap - sayfaSayisiOgren() ");
        return 440;
    }
 
    public double fiyatOgren() {
        System.out.println("Kitap - fiyatOgren() ");
        return 2500000 ;
    }
 
    public String yazarIsmiOgren() {
        System.out.println("Kitap - yazarIsmiOgren() ");
        return "xy";
    }
}
 
class Roman extends Kitap {
    
    public static void main( String args[] ) {
        Roman r = new Roman();
        int sayfasayisi = r.sayfaSayisiOgren();
      double fiyat  = r.fiyatOgren();
        String yazar = r.yazarIsmiOgren();
    }
}
Uygulamamızı javac KitapEvi.java komutu ile derlenikten sonra , java Roman komutunu çalıştırdığımızda , uygulamanın çıktısı aşağıdaki gibi olur ;
Kitap - sayfaSayisiOgren()
Kitap - fiyatOgren()
Kitap - yazarIsmiOgren()
Roman sınıfının içersinde sayfaSayisiOgren() ,fiyatOgren() , yazarIsmiOgren() metodları olmamasına rağmen çağırabildik.Bunun sebebinin kalıtım olduğu biliyoruz. Türeyen sınıf , türediği sınıfa ait global değişkenleri (statik veya değil) ve metodları (statik veya değil) kendisine alır (private ve final olanları alamaz, ve aynı paket içinde değilse friendly olanlarıda alamaz ,sadece protected olanları alabilir ). KitapEvi.java örneğimizde Roman sınıfının yaptığıda tam olarak budur.Peki şimdi Roman sınıfının içersinde sayfaSayisiOgren() ve fiyatOgren() adında iki metod oluşturabilirmiyim ? Oluşturursam nasıl etkiler meydana gelir ?
Aynı örneğimizin ikinci bir versiyonunu yazalım ;
ör-KitapEvi2.java
class Kitap {
        public int sayfaSayisiOgren() {
               System.out.println("Kitap - sayfaSayisiOgren() ");
               return 440;
        }
 
        public double fiyatOgren() {
               System.out.println("Kitap - fiyatOgren() ");
               return 2500000 ;
        }
 
        public String yazarIsmiOgren() {
               System.out.println("Kitap - yazarIsmiOgren() ");
               return "xy";
        }
}
 
class Roman extends Kitap {
 
        public int sayfaSayisiOgren() {
               System.out.println("Roman - sayfaSayisiOgren() ");
               return 569;
        }
 
        public double fiyatOgren() {
               System.out.println("Roman - fiyatOgren() ");
               return 8500000 ;
        }
 
 
        public static void main( String args[] ) {
               Roman r = new Roman();
               int sayfasayisi = r.sayfaSayisiOgren();
               double fiyat  = r.fiyatOgren();
               String yazar = r.yazarIsmiOgren();
        }
}
sayfaSayisiOgren() ve fiyatOgren() metodlarından hem ana sınıfın içersine (Kitap) hemde ana sınıfdan türeyen yeni sınıfın içerisine (Roman) yazmış olduk . Peki bu durumda uygulamanın ekrana basacağı sonuç nasıl olur ? Uygulamamızı derleyip , çalıştırınca , ekrana basılan sonuç aşağıdaki gibidir ;
Roman - sayfaSayisiOgren()
Roman - fiyatOgren()
Kitap - yazarIsmiOgren()
Roman sınıfının içersinde , ana sınıfa ait metodların aynısını tanımladıktan sonra , Roman sınıfının sayfaSayisiOgren() ve fiyatOgren() metodlarını çağrınca , artık otomatik olarak ana sınıfın metodları devreye girmedi , bunun yerine Roman sınıfının sayfaSayisiOgren() ve fiyatOgren() metodları devreye girdi.Yani Roman sınıfı , türetiği sınıfın (Kitap) sayfaSayisiOgren() ve fiyatOgren() metodlarını iptal etmiş oldu .
Ana sınıfa ait metodları iptal ederken dikkat edilmesi gereken önemli hususlardan biri erişim belirliyicilerini iyi ayarlamaktır.Konuyu hatalı bir örnek üzerinde gösterirsek ;
ör-Telefonlar.java
class Telefon {
    protected void aramaYap() {
        System.out.println("Telefon.aramaYap()");
    }
}
 
class CepTelefonu extends Telefon {
    private void  aramaYap() {  // ! hatali !
        System.out.println("CepTelefon.aramaYap()");  
    }
}
Yukarıdaki örneğimizi derlediğimizde, Java bizi şöyle bir hata mesajı verecektir ;
Telefonlar.java:10: aramaYap() in CepTelefonu cannot override aramaYap() in Tele
fon; attempting to assign weaker access privileges; was protected
private void aramaYap() {
^
1 error
Bu hatanın türkçe açıklaması ; iptal eden metodun -CepTelefonu.aramaYap()- , iptal edilen metodun -Telefon.aramaYap()- erişim belirliyicisinden daha erişilebilir bir erişim belirliyicisene sahip olması gerektiğini belirtir.
En erişilebilir erişim belirliyicisinden , en erişilemez erişim belirliyicisine doğu sıralarsak ;
· public ; her yerden erişilmeyi sağlayan erişim belirliyicisi .
· protected ; aynı paket içersinden ve bu sınıfdan türemiş sınıflar tarafından erişilmeyi sağlayan erişim belirliyicisi .
· friendly ; sadece aynı paket içersinden erişilmeyi sağlayan erişim belirliyicisi .
· private ; sadece kendi sınıfı içersinden erişilmeyi sağlayan , başka heryerden erişimi kesen erişim belirliyicisi.
Daha fazla detay için bkz
Olaylara bu açıdan bakarsak , ana sınıfa ait a() isimli public bir metod var ise , bu sınıftan türeyen bir sınıf ana sınıfa ait a() metodunu iptal etmek için , erişim belirliyicisi kesin kes public olmalıdır. Eğer aynı a() metodu protected olsaydı , o zaman türeyen sınıf bu metodu iptal etmek için erişim belirliyicisini public veya protected yapabilirdi.
ör-Hesap.java
class HesapMakinesi {
     void hesapla(double a , double b) {
        System.out.println("HesapMakinesi.hesapla()");
    }
}
 
class Bilgisayar extends HesapMakinesi {
    protected void hesapla(double a , double b) {
        System.out.println("HesapMakinesi.hesapla()");
    }
}
Yukarıdaki örneğimizde , ana sınıfa ait olan friendly erişim belirliyicisi olan hesapla metodu , türeyen sınıf tarafından iptal edilmiştir.Bu doğrudur , çünkü protected erişim belirliyicisi , friendly erişim belirliyicisine göre daha erişilebilirdir.Fakat bu iki sınıf değişik paketlerde olsalardı -ki şu an varsayılan paketin içersindeler - türeyen sınıf (Bilgisayar) ana sınıfın hesapla metoduna ulaşamıyacağından dolayı , iptal etme(overriding) kavramı daha başmadan sona erecekti .
Anlatılan olayları bir örnek üzerinde göstermek istersek ; Öncelikle HesapMakinesi ve Bilgisayar sınıfları public sınıf yapıp ayrı ayrı dosyalara kayıt ettek gerekli , daha sonra bunları farklı paketlerin altına kopyalamalıyız. Not : iki ayrı sınıfı değişik paketlere kopyaladim ,özellikle Hesapmakinesi sınıfını public yapmalıyız , yoksa değişik paketlerdeki sınıflar tarafından erişilemez , dolayısıyla kendisinden türetilme yapılamaz.
HesapMakinesi sınıfını tr.edu.kou.math , Bilgisayar sınıfını ise tr.edu.kou.util paketinin içersine yerleştirelim ;
ör-HesapMakinesi.java
package tr.edu.kou.math;
 
public class HesapMakinesi {
     void hesapla(double a , double b) {
        System.out.println("HesapMakinesi.hesapla()");
    }
}
ör-Bilgisayar.java
package tr.edu.kou.util;
 
import tr.edu.kou.math.* ;
 
public class Bilgisayar extends HesapMakinesi {
    protected void hesapla(double a , double b) {
        System.out.println("HesapMakinesi.hesapla()");
    }
 
    public static void main(String args[]) {
         Bilgisayar b = new Bilgisayar();
         b.hesapla(3.15, 5.6);
         HesapMakinesi hm = new HesapMakinesi();
         // hm.hesapla(3.15, 5.6); ! Hata ! baska paket icersinden erisilemez
    }
}
Yukarıdaki örneklerimizi derleyip , Bilgisayar sınıfını çalıştırırsanız , sorunsuz çalışacaktır. Yukarıda anlatılanlar yalanmıydı diyenler için hemen açıklamamızı yapalım ; tr.edu.kou.util paketinin içersindeki türeyen Bilgisayar sınıfının protected erişim belirliyicisine sahip olan hesapla() metodu , tr.edu.kou.math paketinin içersindeki türetilen HesapMakinesi sınıfının friendly erişim belirliyicisine sahip olan hesapla() metodunu iptal edemez , edemez çünkü türeyen sınıf (Bilgisayar) bu metodun varlığından bile haberdar değildir. tr.edu.kou.util paketinin içersindeki türeyen Bilgisayar sınıfının içersindeki hesapla() metodu , iptal etme kavramından gayet uzaktır. Ayrıca tr.edu.kou.math paketinin içersindeki türetilen HesapMakinesi sınıfının friendly erişim belirliyicisine sahip olan hesapla() metoduna erişemediğimizi ispatlamak için Bilgisayar.java dosyasındaki yorum kısmını kaldırarak derlemeye çalışırsak, aşağıdaki hata mesajı ile karşılaşırız;
Bilgisayar.java:13: hesapla(double,double) is not public in tr.edu.kou.math.Hesa
pMakinesi; cannot be accessed from outside package hm.hesapla(3.15, 5.6);
^
1 error

İptal etmek(Overriding) ve adaş metodların(Overload) birbirlerini karıştırılması

Ana sınıfa ait bir metodu iptal etmek isterken yanlışlıkla adaş metodlar yazılabilir.
ör-CalisanMudur.java
class Calisan {
    public void isYap(double a) {
         System.out.println("Calisan.isYap()");
    }
}
 
 
class Mudur extends Calisan {
    public void isYap(int a) { // adas metod (overloaded)
         System.out.println("Mudur.isYap()");
    }
 
    public static void main(String args[]) {
        Mudur m = new Mudur();
        m.isYap(3.3);
    }
}
Her Müdür bir Çalışandır ilkesinden yola çıkılarak yazılmış bu örneğimizdeki büyük hata iki kavramın - (iptal etmek ve adaş metodlarının) - birbirlerine karıştırılmasıdır. Böyle bir hata çok kolay bir şekilde yapılabilir ve fark edilmesi bir o kadar güçtür. Buradaki yanlışlık , metodların parametrelerindeki farklılıktan doğmaktatır , kodu yazan kişi , ana sınıfa ait olan isYap() metodu iptal ettiğini kolaylıkla zannedebilir ama aslında farkına bile varmadan adaş metod oluşturmuştur.Uygulamanın çıktısı aşağıdaki gibidir ;
Calisan.isYap()

Yukarı Çevirim (Upcasting)

Kalıtım(inheritance) kavramı sayesinde , türeyen sınıf ile türetilen sınıf arasında bir ilişki kurulmuş olur.Bu ilişkiyi şöyle açıklayabiliriz " türeyen sınıfın tipi, türetilen sınıf tipindedir" . Yukarıdaki örneğimizi tekrarlarsak , her elma bir meyvadır, diyebiliriz.Elma ve Meyva sınıfları arasındaki ilişki kalıtım kavramı sayesinde sağlanmış olur. Her elma bir meyvadır veya her müdür bir çalışandır örneklerimiz sadece sözel örnekler değildir , bu ilişki Java tarafından somut olarak desteklenmektedir.
Başka bir kalıtım örneğini şöyle açıklayabiliriz , her futbolcu bir sporcudur. Bu ifade bize , Sporcu sınıfının içersindeki metodların otomatik olarak Futbolcu sınıfının içersinde olduğunu söyler, yani Sporcu sınıfına gönderilen her mesaj rahatlıkla Futbolcu sınıfınada gönderilebilir çünkü Futbolcu sınıfı Sporcu sınıfından türemiştir.Eğer Sporcu sınıfının içersinde calis() adında bir metod var ise bu metodun aynısından Futbolcu sınıfının içersinde de olacağı kesindir.Javanın bu ilişkiye nasıl somut olarak destek verdiğini aşağıdaki örneğimizde görebiliriz ;
ör-Spor.java
class KontrolMerkezi {
    public static void checkUp(Sporcu s) {
        // ..
        s.calis();
    }
}
 
class Sporcu {
    public void calis() { 
        System.out.println("Sporcu.calis()");
    } 
}
 
class Futbolcu extends Sporcu {
    public void calis() {  // iptal etme (Overriding)
        System.out.println("Futbolcu.calis()");
    }
    public static void main(String args[]) {
        Sporcu s = new Sporcu();
        Futbolcu f = new Futbolcu();
        KontrolMerkezi.checkUp(s);
        KontrolMerkezi.checkUp(f);
    }
}
KontrolMerkezi sınıfının statik bir metodu olan checkUp() , Sporcu tipinde parametre kabul etmektedir.İlginç olan nokta bu metoda Futbolcu tipindeki obje referansını gönderdiğimizde hiç bir hata ile karşılaşmamamızdır.Burada bir hata yoktur çünkü Her Futbolcu bir Sporcudur.Türetilmiş sınıfın (Futbolcu içersinde kendine has bir çok metod olabilir , ama en azından türediği sınıfın (Sporcu) içersindeki metodlara sahip olacaktır .Sporcu tipinde parametre kabul eden her metod'a Futbolcu tipinde parametre gönderebiliriz. Bu ilişkiyi UML diyagramında gösterirsek ;

Şekil-8
Sporcu.java örneğimizde , türeyen sınıf(Futbolcu) türetildiği sınafa (Sporcu) doğru çevrilmektedir , yani yukarı doğru çevrilmektedir (Upcasting). Yukarı doğru çevirim her zaman güvenlidir , sonuçta daha özel bir tipten daha genel bir tipe doğru daralma vardır. Bu sebebten dolayı yukarı doğru çevrimlerde , özel olarak bir ifade veya belirteç kullanmak zorunda değilizdir.

Yukarı çevirim (Upcasting) olayi "Komposiyon mu , Kalıtım mı ? kullanmalıyım" sorusuna da ışık tutmuştur.Sorulması gereken soru "yukarı doğru çevirime ihtiyacım var mi ?" Eger sorunun cevabi "evet" ise , kalitim (inheritance) kullanmamız gerekir.

Yorumlar

Bu blogdaki popüler yayınlar

Soru ve Cevaplarla Kompanzasyon

Aktif Güç Nedir,Reaktif Güç Nedir

Sinusoidal AC dalga şekli, Tepe, ortalama, efektif(rms), değer tanımları...