BÖLÜM 5

 

Altuğ B. Altıntaş
© 2004

 

 

 

 

Sınıfların Tekrar

Kullanılması

 

 

Belli bir amaç için yazılmış ve doğruluğu kanıtlanmış olan sınıfları, yeni uygulamaların içerisinde kullanmak hem iş süresini kısaltacaktır hem de yeni yazılan uygulamalarda hata çıkma riskini en aza indirgeyecektir. Uygulamalarımızda daha evvelden yazılmış ve doğruluğu kanıtlanmış olan sınıfları tekrardan kullanmanın iki yöntemi bulunur. (yorum ekle)

Birinci yöntem komposizyon’dur. Bu yöntem sayesinde daha önceden yazılmış ve doğruluğu kanıtlanmış olan sınıf/sınıfları, yeni yazılan sınıfın içerisinde doğrudan kullanabilme şansına sahip oluruz. Daha önceki bölümlerde komposizyon yöntemini çokça kullandık. (yorum ekle)

İ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ıftan türetilir; böylece yeni oluşan sınıf, türetildiği sınıfın özelliklerine sahip olur; Ayrıca oluşan bu yeni sınıfın kendisine ait yeni özellikleri de olabilir. (yorum ekle)

 

5.1.   Komposizyon

Komposizyon yönetimini daha önceki örneklerde kullanıldı. Şimdi bu yöntemin detaylarını hep beraber inceleyelim. (yorum ekle)

Gösterim-5.1:

 
class Meyva {
  //...
}
 

 

Gösterim-5.2:

 
class Elma {
  private Meyva m = new Meyva();
  //...
}
 

Elma sınıfı, Meyva sınıfını doğrudan kendi içerisinde tanımlayarak, Meyva sınıfının içerisindeki erişilebilir olan özellikleri kullanabilir. Buradaki yapılan iş Elma sınıfını Meyva sınıfına bağlamaktır. Sınıfların arasındaki ilişki UML diyagramında gösterilirse; (yorum ekle)

 

 

Şekil-5.1. Komposizyon-I

 

Başka bir örnek verilirse,

 

Örnek-5.1:  Motor.java (yorum ekle)

 
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çerisine yerleştirelim;

Örnek-5.2:  AileArabasi.java (yorum ekle)

 
public class AileArabasi {
  private 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çerisine, Motor tipinde global bir alan yerleştirilerek, bu iki sınıf birbirine bağlanmış oldu. AileArabası sınıfının hereketEt() ve dur() metotlarında, önce Motor sınıfına ait yordamlar (methods) direk olarak çağrıldı. Bu ilişki UML diyagramında incelenirse:  (yorum ekle)

 

Şekil-5.2.   Komposizyon-II

 

Motor sınıfının private erişim belirleyicisine sahip olan motor_gucu alanına, AileArabasi sınıfının içerisinde ulaşamayız. Bunun nedenlerini bir önceki bölümlerde incelemiştik. AileArabasi sınıfı Motor sınıfının sadece iki adet public yordamına (method) erişebilir: calis() ve dur(). Olaylara kuş bakışı bakarsak, karşımızdaki manzara aşağıdaki gibidir. (yorum ekle)

 

Şekil-5.3. Kuş Bakışı Görünüş

AileArabasi sınıfı çalıştırılırsa, ekrana görülen sonucun aşağıdaki gibi olması gerekir: (yorum ekle)

 

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 robotu (Voltran) oluşturmak için değişik ufak robotlar bir araya gelmekteydi. Kollar, bacaklar, gövde ve kafa bölümü... Bizde kendi Voltran robotumuzu oluşturmak istersek, (yorum ekle)

 

Örnek-5.3:  Voltran.java (yorum ekle)

 
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; bu sınıflara ait özellikler daha sonradan Voltran sınıfının içerisinde ihtiyaçlara göre kullanılıyor. Oluşan olaylar UML diyagramında tanımlanırsa: (yorum ekle)

Şekil-5.4.  Komposizyon - III

 

5.2.   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ıftan diğer bir sınıfın türemesidir. Yeni türeyen sınıf, türetilen sınıfın global alanlarına ve yordamlarına (statik veya değil) otomatik olarak sahip olur (private olanlar hariç). (yorum ekle)

Unutulmaması gereken unsur, yeni türeyen sınıf, türetilen sınıfın private global alanlarına ve yordamlarına (statik veya değil) otomatik olarak 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 belirleyicisine sahip olan global alanlarına (statik veya değil)  ve yordamlarına (statik veya değil) otomatik olarak sahip olur. (yorum ekle)

Gösterim-5.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 bir şeyler eklemiştir. (yorum ekle)

Yazılış ifadesi olarak, türeyen sınıf isminin yanına extends ifadesini koyarak, hemen sonrasında kendisinden türetilme yapılan sınıfın kendisini yerleştiririz (bkz: gösterim-5.3). Yukarıdaki örneğimizi UML diyagramında göstermeye çalışırsak; (yorum ekle)

 

Şekil-5.5.   Kalıtım İlişkisi-I

Kedi ve Kaplan sınıflarımızı biraz daha geliştirelim,

 Örnek-5.4:  KediKaplan.java (yorum ekle)

 
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çerisinde ne yakalaAv() yordamı ne de ayaksayisi alanı tanımlanmıştır. Kaplan sınıfı bu özelliklerini kendisinin ana sınıfı olan Kedi sınıfından miras almıştır. (yorum ekle)

Kedi sınıfının içerisinde tanımlanmış ayaksayisi alanı, protected erişim belirleyicisine sahiptir. Bunun anlamı, bu alana aynı paket içerisinde olan sınıflar ve ayrı paket içerisinde 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 veya olmasın, Kedi sınıfına ait global int ilkel (primitive) tipindeki alanına (ayaksayisi) erişebilir. (yorum ekle)

Her sınıfın içerisine main yordamı yazarak onları tek başlarına çalışabilir bir hale sokabiliriz (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 komut satırından java Kedi veya Kaplan sınıfını çalıştırmak için java Kaplan yazılması yeterli olacaktır. (yorum ekle)

5.2.1. 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ı Java programlama dili içerisinde kullanılan tüm sınıfların tepesinde bulunur. (yorum ekle)

 

Örnek-5.5:  YeniBirSinif.java (yorum ekle)

 
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() yordamları tanımlanmamasına rağmen bu yordamları kullandık, ama nasıl ? Biz yeni bir sınıf tanımladığımızda, Java gizli ve otomatik olarak extends Object, ibaresini yerleştirir. (yorum ekle)

 

Gösterim-5.4:

public class YeniBirSinif extends Object {

 

Bu sayede Object nesnesine ait erişebilir yordamları kullanabiliriz. Object nesnesine ait yordamlar aşağıdaki gibidir: (yorum ekle)

 

·       clone(): Bu nesnenin (this) aynısını klonlar ve yeni nesneyi döndürür. (yorum ekle)

·       equals(Object obj):  obj referansına bağlı olan nesnenin, kendisine (this) eşit olup olmadığı kontrolü yapan yordam. (yorum ekle)

·       finalize(): Çöp toplayıcısı tarafından silinmeden önce çalıştırılan yordam. (yorum ekle)

·       getClass(): Bu nesnenin (this) çalışma anındaki sınıf bilgilerini Class nesnesi şeklinde geri döner. (yorum ekle)

·       hashCode(): Bu nesnenin (this) hash kodunu geri döner.

·       notify(): Bu nesnenin (this), monitöründe olan tek bir iş parçacığını (thread) uyandırır. (ilerleyen bölümlerde inceleyeceğiz) (yorum ekle)

·       notifyAll(): Bu nesnenin (this), monitöründe olan tüm iş parçacıklarını (thread) uyandırır. (ilerleyen bölümlerde incelenecektir) (yorum ekle)

·       toString(): Bu nesnenin (this), String tipindeki ifadesini geri döner. (yorum ekle)

·       wait(): O andaki iş parçacığının (thread) beklemesini sağlar; Bu bekleme notify() veya notifyAll() yordamları sayesinde sona erer. (yorum ekle)

·       wait (long zamanAsimi): O andaki iş parçacığının (thread), belirtilen süre kadar beklemesini sağlar (zamanAsimi); bu bekleme notify() veya notifyAll() yordamları sayesinde de sona erdirilebilir. (yorum ekle)

·       wait (long zamanAsimi, int nanos): O andaki iş parçacığının (thread), belirtilen gerçek süre kadar (zamanAsimi+ nanos) beklemesini sağlar; bu bekleme notify() veya notifyAll() yordamları sayesinde de sona erdirilebilir. nanos parametresi 0-999999 arasında olmalıdır. (yorum ekle)

 

Kısacası, oluşturulan her yeni sınıf, yukarıdaki, yordamlara otomatik olarak sahip olur. Bu yordamları yeni oluşan sınıfların içerisinde tekrardan istediğimiz gibi yazabiliriz (uygun olan yordamları iptal edebiliriz-override). Örneğin finalize() yordamı kendi sınıfımızın içerisinde farklı sorumluluklar verebiliriz (çizgi çizen bir nesnenin, bellekten silinirken çizdiği çizgileri temizlemesi gibi). Bu olaya, ana sınıfın yordamlarını iptal etmek (override) denir. Biraz sonra iptal etmek (override) konusunu daha detaylı bir şekilde incelenecektir. (yorum ekle)

Akıllara şöyle bir soru gelebilir, Kaplan sınıfı hem Kedi sınıfından hem de 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ıfa da Object sınıfından (gizli ve otomatik olarak) türemiştir. Sonuçta Kaplan sınıfı hem Kedi sınıfının hem de 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ıftan türetilmiştir. Object sınıfı, Java programlama dilinde, sınıf hiyerarşinin en tepesinde bulunur. (yorum ekle)

 

Şekil-5.6.  Gizli Kalıtım

 

Ç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 yordamları bulunsun. Bu olay türetilen sınıfın içerisinde birçok probleme yol açacaktır. Bu ve bunun gibi sebeplerden dolayı Java programlama dilinde çoklu kalıtım yoktur. Bu sebeplerin detaylarını ilerleyen bölümlerde inceleyeceğiz. (yorum ekle)

Java programlama dilinde  çoklu kalıtımın faydalarından yararlanmak için Arayüzler (Interface) ve dahili sınıflar (inner class) kullanılır. Bu konular yine ilerleyen bölümlerde inceleyeceğiz. (yorum ekle)

 

5.2.2. Kalıtım ve İlk Değer Alma Sırası

Tek bir sınıf içerisinde ilk değerlerin nasıl alındığı 3. bölümde incelenmişti İşin içerisine birde kalıtım kavramı girince olaylar biraz karışabilir. Kalıtım (inheritance) kavramı bir sınıftan, başka bir sınıf kopyalamak değildir. Kalıtım kavramı, türeyen bir sınıfın, türetildiği sınıfa ait erişilebilir olan özellikleri alması ve ayrıca kendisine ait özellikleri tanımlayabilmesi anlamına gelir. Bir sınıfa ait nesne oluşurken, ilk önce bu sınıfa ait yapılandırıcının (constructor) çağrıldığını önceki bölümlerimizden biliyoruz. (yorum ekle)

Verilen örnekte, UcanYarasa nesnesi oluşmadan evvel, UcanYarasa sınıfının ana sınıfı olan Yarasa nesnesi oluşturulmaya çalışılacaktır. Fakat Yarasa sınıfıda Hayvan sınıfından türetildiği için daha öncesinde Hayvan sınıfına ait olan yapılandırıcı çalıştırılacaktır. Bu zincirleme giden olayın en başında ise Object sınıfı vardır. (yorum ekle)

Örnek-5.6:  IlkDegerVermeSirasi.java (yorum ekle)

 
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();
  }
}

 

Şekil-5.7.   Kalıtım ve ilk değer alma sırası

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ın hepsi, fark edildiği üzere varsayılan yapılandırıcıdır (default constructor). Uygulamanın çıktısı aşağıdaki gibi olacaktır;  (yorum ekle)

 

Hayvan Yapilandiricisi
Yarasa Yapilandiricisi
UcanYarasa Yapilandiricisi

 

5.2.3. Parametre Alan Yapılandırıcılar ve Kalıtım 

Ana sınıfa ait yapılandırıcı çağırma işlemi, varsayılan yapılandırıcılar için otomatik olarak yürürken, parametre alan yapılandırıcılar için olaylar biraz daha değişiktir. Kısacası, ana sınıfın parametre alan yapılandırıcısını açık olarak super anahtar kelimesi ile çağırmak gereklidir. Şöyle ki; (yorum ekle)

 

Örnek-5.7:  IlkDegerVermeSirasiParametreli.java (yorum ekle)

 
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. (yorum ekle)

 

Insan Yapilandiricisi 7
ZekiInsan Yapilandiricisi 6
Hacker Yapilandiricisi 5

Dikkat edilmesi gereken bir başka husus, aynı this anahtar kelimesinin kullanılışı gibi super anahtar kelimesi de içinde bulunduğu yapılandırıcının ilk satırında yer almalıdır. (yorum ekle)

 

Örnek-5.8:  IlkDegerVermeSirasiParametreliAmaHatali.java (yorum ekle)