~BöLÜM-5~

 

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-Sporcu.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. 

Final Kavramı

Final kelimesinin sözlük anlamı "son"  demektir.Java programlama dilindeki final kavramıda , sözlük anlamıyla paralel şekilde davranır. Java programlama dilinde final anahtar kelimesi değiştirilemezliği simgiler. Değiştirilemezliğin seçilmesi iki sebebten dolayı olabilir , birincisi tasarım ikincisi ise verimlilik ; Global olan değişkenlere (statik ve değil) , metodlara(statik veya değil) veya sınıflara final kavramını uygulayabiliriz. 

Global değişkenler ve Final Kavramı 

Global değişkenler ile final kavramı birleştiği zaman , ortaya diğer programlama dillerindeki sabit değer özelliği ortaya çıkar.Global olan sabit değişkenler ister statik olsun veya olmasın final özelliğine sahip olabilir. Java programlama dilinde final olan global değişkenlerin değeri derleme anında veya çalışma anında belli olabilir ama dikkat edilmesi gereken husus, final global değişkenlere sadece bir kere değer atanabiliyor olmasıdır.Sonuç olarak global olan final değişkenleri ikiye ayırabiliriz ;

  • Derleme anında değerlerini bilebildiğimiz final global değişkenler.

  • Çalışma  anında değerlerini bilebildiğimiz final global değişkenler.

ör-FinalOrnek

class  Kutu {
	int i  = 0 ;
}

public class FinalOrnek {

	final int X_SABIT_DEGER = 34 ;
	final static int Y_SABIT_DEGER = 35 ;

	final int  A_SABIT_DEGER  = (int)(Math.random()*50);

	final Kutu k = new Kutu() ;

	public static void main(String args[]) {
		FinalOrnek fo = new FinalOrnek();
		//fo.X_SABIT_DEGER = 15 ! Hata !
		//fo.Y_SABIT_DEGER = 16 ! Hata !
		//fo.A_SABIT_DEGER = 17 ! Hata !
		fo.k.i = 35 ; // dogru
		// fo.k = new Kutu()  ! hata ! 
		System.out.println("X_SABIT_DEGER = "+fo.X_SABIT_DEGER) ;
		System.out.println("Y_SABIT_DEGER = "+fo.Y_SABIT_DEGER) ;
		System.out.println("A_SABIT_DEGER = "+fo.A_SABIT_DEGER) ;
		System.out.println("Kutu.i = "+fo.k.i) ;

	} 
}

Yukarıdaki örneğimizi incelersek , X_SABIT_DEGER ve Y_SABIT_DEGER değişkenlerinin değerlerini derleme anında bilebiliriz , ama A_SABIT_DEGER değişkenin değerini derleme anında bilmek zordur (Math sınıfına ait statik bir metod olan random() , 1 ile 50 arasında rasgele sayılar üretir), bu değişkenin değeri çalışma anında belli olacaktır fakat tüm final özelliğine sahip olan değişkenlere sadece ve sadece bir kere değer atanmaktadır.

Bir global değişkene , final ve statik özellikler belirtirseniz , global değişkenimiz , bu sınıfa ait olan tüm objeler için tek olur (bkz)  ve değeri sonradan değiştiremez.

Final kavramında , ilkel tipdeki değişkenler ile obje tipindeki değişkenlerin arasında büyük bir fark vardır.Yukarıdaki örneğimizi incelerseniz , X_SABIT_DEGER, Y_SABIT_DEGER , A_SABIT_DEGER değişkenleri  hep ilkel tipdeydi  , yani değerlerini kendi üzerlerinde taşıyorlardı . Kutu tipinde k değişkenimizi final yaptığımızda olaylar biraz değişir,  Kutu tipindeki k değişkenini final yaparak , bu değişkenin başka bir Kutu objesine tekrardan bağlanmasına izin vermeyiz ama Kutu tipindeki k değişkeninin bağlı olduğu objenin içeliği değişebilir.Uygulamamızın çıktısı aşağıdaki gibi olur; 

X_SABIT_DEGER = 34
Y_SABIT_DEGER = 35
A_SABIT_DEGER = 39
Kutu.i = 35

 

Final parametreler

Metodlar'a gönderilen parametre değerlerinin değişmemesini istiyorsak , bu parametreleri final yapabiliriz.

ör-FinalParametre.java

public class FinalParametre {
        
    public static int topla(final int a , final int b) {
	// a = 5   ! Hata ! 
	// b = 9   ! Hata !
	return a+b;
    }

    public static void main(String args[] ) {
	  if ( (args.length != 2 )  ) {
	    System.out.println("Eksik veri Girildi") ;
	    System.exit(-1); // Uygulamayi sonlandir
	  }
	  int a = Integer.parseInt(args[0]);
	  int b = Integer.parseInt(args[1]);
	  int sonuc = FinalParametre.topla(a,b);
	  System.out.println("Sonuc = " + sonuc );
    }
}

Uygulamamız , dışarıdan iki parametre alarak bunları ilkel olan int tipine çeviriyor.Eğer dışarıdan eksik veya fazla parametre girilmiş ise kullanıcı bu konuda uyarılıyor. Daha sonra elimizdeki değerleri FinalParametre sınıfının statik olan   topla() metodu gönderiyoruz. Bu metoda gönderilen parametrelerin değiştirilmesi , final ifadeden dolayı imkansızdır.

Boş (Blank) Final

Java , final olan obje değişkenlerine ilk değeri verme konusunda acele etmez , fakat final olan obje değişkenleri kullanılmadan önce ilk değerlerinin verilmiş olması şarttır. 

ör-BosFinal.java

class Kalem { }

public class BosFinal {
  final int a = 0; 
  final int b; // Bos final
  final Kalem k; // Blank final obje degiskeni
  // Bos final degiskenler ilk degerlerini 
  // yapilandiricalarda icersinde alirlar
  BosFinal() {
    k = new Kalem();
    b = 1; // bos final degiskenine ilk degeri ver
  }
  BosFinal(int x) {
    b = x; // bos final degiskenine ilk degeri ver
    k = new Kalem();
  }
  public static void main(String[] args) {
    BosFinal bf = new BosFinal();
  }
} 

Boş final değişkenlere değerlerini yapılandırıcıların içersinde vermeliyiz.Bir başka nokta ise statik olan global değişkenler boş final olma özelliğinden faydalanamazlar.

Final Metodlar

Türetilen bir sınıf içersindeki metod tarafından iptal edilme riskini sıfar'a indirmek için , final metodlar yazılabilir. Final metodlar iptal edilemezler.

ör-FinalMetod.java


class A {
    public final void ekranaYaz() {
	System.out.println("A.ekranaYaz()");
    }
}

class B extends A {
    public void ekranaYaz() {
	System.out.println("B.ekranaYaz()");
    }
}

A sınıfına ait ekranaYaz() metodu , A sınıfından türetilmiş B sınıfının ekranaYaz() metodu tarafından iptal edilemez(overriding) . FinalMetod.java örneğini derlemeye çalıştığımızda aşağıdaki hata mesajını alırız ; 

FinalMetod.java:9: ekranaYaz() in B cannot override ekranaYaz() in A; overridden
 method is final
    public void ekranaYaz() {
                ^
1 error

private ve final

Final ve private erişim belirliyicisine sahip olan bir metod , başka bir metod tarafından iptal ediliyormuş gibi gözükebilir.

ör-SivilPolis.java

class Polis {
    private final void sucluYakala() { // gorunmez,gizli metod
	System.out.println("Polis.sucluYakala()");
    }
}

public class SivilPolis extends Polis {
    public void sucluYakala() { //iptal etme soz konusu degildir
	System.out.println("SivilPolis.sucluYakala()");
    }
}

Private erişim belirliyicisine sahip olan metod , dışarıdan erişilemiyeceğinden dolayı , türetilen sınıflar içersindeki metodlar tarafından iptal edilmesi söz konusu değildir.Private erişim belirliyicisine sahip olan bir metod , bir sınıfın gizli ve özel tarafıdır , o sınıfın arayüzü değildir. Bir sınıfın arayüzleri ,o sınıfa ait public ,protected veya friendly erişim belirliyicilerine sahip olan metodlarıdır.

Final Sınıflar

Bir sınıfı final yaparak , bu sınıftan kalıtım yapılmasını engellemiş oluruz. Bir sınıfın final yapılmasının iki sebebi olabilir, birincisi tasarım , ikincisi ise verimlilik . Final sınıflar komposizyon yöntemi ile kullanabilirler. 

ör-Tv.java

final class Televizyon {
    public void kanalBul() {
    }
}

/*
class SuperTelevizyon extends Televizyon{
} 
*/

class Ev {
    int oda_sayisi = 5 ;
    Televizyon tv = new Televizyon() ;
    public static void main(String args[]) {
	Ev e =  new Ev(); 
	e.tv.kanalBul();
    }

}

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

Diğer programlama dillerinin coğunda , uygulamanın ihtiyaç duyduğu dosyalar tek seferde toplu olarak, uygulamanın başladığı anda yüklenir ve daha sonra ilk değer alma işlemi başlar . Java ihtiyaç duyduğu dosyaları yükleme işlemine daha değişik bir bakış açısıyla bakar.

Javada tüm mantık objeler üzerine kurulmuştur,ve her sınıf kendi fiziksel dosyasında durur.Java bu sınıf dosyalarını,uygulama ihtiyaç duyduğu anda yükleme işlemi başlar.Böylece performans maksimuma çıkartılmış olur.Bir sınıf dosyası ne zaman sınıf yükleyicisi (Class Loader) tarafından hafızaya yüklenir ? Cevap ; eğer bir sınıfa ait statik global değişken veya statik bir metod çağrıldığında , bu sınıf  , sınıf yükleyicisi (Class Loader) tarafından yükleni veya bir sınıfa ait bir obje oluşturmak istersek , önce yükleme işlemi gerçekleşir.

İşin içersine Kalıtım kavramı girdiğinde ,  örneğin B sınıfı  başka bir sınıfdan türemiş (B extends A ) ise o zaman yükleme sırasında ana sınıf da hafızaya yüklenir - Hem B sınıfı hemde A sınıfı , sınıf yükleyicisi (Class Loader) tarafından yüklenir..

ör-Bocekcik.java

class Bocek {
	int a = 10;
	int b;
	Bocek() {
		ekranaBas("a = " + a + ", b = " + b);
		b = 17;
	}
	static int x1 = ekranaBas("static Bocek.x1 ilk deger verildi");
	static int ekranaBas(String s) {
		System.out.println(s);
		return 18;
	}
}

public class Bocekcik extends Bocek {
	int k = ekranaBas("Bocekcik.k ilk deger verildi");
	Bocekcik() {
		ekranaBas("k = " + k);
		ekranaBas("b = " + b);
	}
	static int x2 = ekranaBas("static Bocekcik.x2 ilk deger verildi");
	public static void main(String[] args) {
		ekranaBas("Bocekcik - basla..");
		Bocekcik b = new Bocekcik();
	}
} 

Uygulamanın çıktısı aşağıdaki gibidir ; 

static Bocek.x1 ilk deger verildi
static Bocekcik.x2 ilk deger verildi
Bocekcik - basla..
a = 10, b = 0
Bocekcik.k ilk deger verildi
k = 18
b = 17

Gelişen olayları adım adım açıklarsak  ;


Öncelikle Bocekcik sınıfına ait statik bir metod olan main() çağrılıyor (java Bocekcik komutuyla ). Sınıf yükleyici (Class Loader) Bocekcik.class fiziksel dosyasını bulmaya çalışır. Eger bulursa bu sınıf yüklenir, Bocekcik sınıfının bulunduğunu farzedelim , Bu yükleme esnasında  Bocekcik sınıfın türetildiği  ortaya çıkar ( Bocekcik extends Bocek) . Kalıtım kavramından dolayı Bocek sınıfıda  , sınıf yükleyicisi tarafıdan yüklenir  (eğer Bocek sınıfıda türetilmiş  olsaydi , türetildiği sınıfda  yüklenecekti , bu böyle sürüp gidebilir…). 

Daha sonra statik olan global değiskenlere ilk değerleri verilmeye başlanır. Değer verme işlemi en yukarıdaki  sınıfdan başlar ve türemiş  sınıflara doğru devam eder (aşağıya doğru) . Burada en yukarıdaki sınıf Bocek sınıfıdır - (Object sınıfını ihmal edersek ) . Bu sonuçlar göz önüne alındığında ekrana çıkan ilk iki satırın aşağıdaki gibi olması gerekir.

static Bocek.x1 ilk deger verildi
static Bocekcik.x2 ilk deger verildi

Sırada  main() metodunun çağrılmasına gelmiştir . Ekrana çıkan üçüncü satır ;

Bocekcik - basla..

Sonra  Bocekcik objesi oluşturulur ( Bocekcik b = new Bocekcik() ). Bu oluşturma sırasında ilk olarak en yukarıdaki sınıfa (Bocek sınıfı) ait statik olmayan(non-static) degişkenlere ilk değerleri verilir ve yapılandırıcısı çağrılır. Ekrana çıkan dördüncü satır

a = 10, b = 0


Son olarak Bocekcik sınıfının içersindeki  statik olmayan( non-static) değişkenlere ilk değerleri verilir ve Bocecik sınıfının yapılandırıcısı çağrılır.

Bocekcik.k ilk deger verildi
k = 18
b = 17



ve mutlu son …

Sorular

  1.   (aklınıza gelen bu konular ile ilgili güzel sorular varsa lütfen bildiriniz)

 

Mail Grubu

Java Kitap Projesinin mail grubuna üye olmak için = java_kitap_projesi-subscribe@yahoogroups.com 

Üyelikten çıkmak için =  java_kitap_projesi-unsubscribe@yahoogroups.com

 

Sorulanız ve Yorumlarınız için : upux@yahoo.com

 

Bu dökümanın her hakkı saklıdır.

1