• Forum vBulletin altyapısından Xenforo altyapısına geçirildi, bu sebeple eski şifreleriniz ile foruma giriş yapamayacaksınız, parolamı unuttum adımından mailiniz ile şifre sıfırlayarak giriş yapabilirsiniz.

    Üyeliklerinde geçerli bir mail adresi olmadığı için sıfırlama yapamayacak kullanıcılar forum kullanıcı adlarını ve yeni şifrelerini yazarak info@maxigame.org adresine şifre sıfırlamak istediklerine dair bir mail göndersinler şifrelerini sıfırlayıp mail adreslerini güncelleyeceğiz. Şifreniz sıfırlandıktan sonra foruma giriş yapıp tekrar istediğiniz gibi değiştirebilirsiniz.

Java - Polimorfizim

Aktiflik
K.Tarihi
8 Ağu 2010
Mesajlar
6
Puanı
0
Konum
OrDan ßuRd4nN ^^
Java - Polimorfizim


Polimorfizm, nesneye yönelik programlamanın önemli kavramlarından biridir ve sözlük anlamı olarak "bir çok şekil" anlamına gelmektedir. Polimorfizm ile kalıtım konusu iç içedir. Kalıtım konusu bir önceli yazımızda incelenmişti; kalıtım konusunda iki taraf bulunmaktadır, ana sınıf ve bu sınıftan türeyen alt sınıf/sınıflar.

Alt sınıf, türetildiği ana sınıfa ait tüm özellikleri alır; yani, ana sınıf ne yapıyorsa türetilen alt sınıfta bu işlemlerin aynısını yapabilir ama türetilen alt sınıfların kendilerine ait bir çok yeni özelliği de olabilir. Ayrıca türetilen alt sınıfa ait nesnenin, ana sınıf tipindeki referansa bağlamanın yukarı doğru (upcasting) işlemi olduğu geçen bölüm incelenmişti. Burada anlatılanları bir örnek üzerinde açıklarsak;

Kod:
class Universite {
	    	public void dersCalis() {
	        	System.out.println("Herkes ders calisti");
	        	}
	    	} 
	
	    class Ogrenci extends Universite {
	        	public void dersCalis() {
	            	System.out.println("Ogrenci ders calisti");
	        	}
	    	}
	
	    	class Akademisyen extends Universite{
	        	public void dersCalis() {
	            	System.out.println("Akademisyen ders calisti");
	        	}
	    	}
	
	    	public class [B][I]PolimorfizmOrnekBir[/I][/B] {
	
	        	public static void hazirlanSinavVar(Universite uni) {
	             	uni.hazirlanSinavVar(); [B]// ! Dikkat ![/B]
	        	}
	
	        	public static void main(String args[]) {
	             	Universite uni = new Universite();
	            Ogrenci ogr = new  	Ogrenci();
	            Akademisyen  	ogrGor = new Akademisyen();
	            hazirlanSinavVar(uni);  	[B]// yukarı cevirim ! yok ![/B]
	            hazirlanSinavVar(ogr);  	[B]// yukarı cevirim (upcasting) [/B] 
	            hazirlanSinavVar(ogrGor);  	[B]// yukarı cevirim (upcasting)[/B]
	        	}
	}


Yukarıdaki örnekte üç kavram mevcuttur, bunlardan biri yukarı çevirim (upcasting) diğeri polimorfizm ve son olarak da geç bağlama (late binding). Şimdi yukarı çevirim ve polimorfizm kavramlarını açıklayalım. Bu örneğimizde ana sınıf Universite sınıfıdır; bu sınıfdan türeyen sınıflar ise Ogrenci ve Akademisyen sınıflarıdır. Bu ilişki "bir" ilişkisidir ;

Ogrenci bir Universitelidir, veya
Akademisyende bir Universitelidir, diyebiliriz.

Yani Universite sınıfının yaptığı her işi Ogrenci sınıfı veya Akademisyen sınıfı da yapabilir artı türetilen bu iki sınıf kendisine has özellikler taşıyabilir, Universite sınıfı ile Ogrenci ve Akademisyen sınıflarının arasında kalıtımsal bir ilişki bulunmasından dolayı, Universite tipinde parametre kabul eden hazirlanSinavVar() yordamına Ogrenci ve Akademiyen tipindeki referansları paslayabildik, bu özelliğinde yukarı çevirim (upcasting) olduğunu bir önceki yazımızda incelemiştik.

Polimorfizm ise hazirlanSinavVar() yordamının içerisinde gizlidir. Bu yordamın (method) içerisinde Universite tipinde olan uni referansı kendisine gelen 2 değişik nesneye (Ogrenci ve Akademisyen) bağlanabildi; bunlardan biri Ogrenci diğeri ise Akademisyen’dır. Peki bu yordamın içerisinde neler olmaktadır? Sırası ile açıklarsak; ilk önce Universite nesnesine bağlı Universite tipindeki referansı, hazirlanSinavVar() yordamına parametre olarak gönderiyoruz, burada herhangi bir terslik yoktur çünkü hazirlanSinavVar() yordamı zaten Universite tipinde parametre kabul etmektedir.

Burada dikkat edilmesi gereken husus, hazirlanSinavVar() yordamının içerisinde Universite tipindeki yerel uni değişkenimizin, kendi tipinden başka nesnelere de (Er ve Akademisyen) bağlanabilmesidir; yani, Universite tipindeki yerel uni değişkeni bir çok şekle girmiş bulunmaktadır. Aşağıdaki ifadelerin hepsi doğrudur:

Universite a = new Universite() ;
Universite a = new Er();
Universite a = new Akademisyen();

Yukarıdaki ifadelere, Universite tipindeki uni değişkenin açısından bakarsak, bu değişkenin bir çok nesneye bağlanabildiğini görürüz, bu özellik polimorfizm ’dir ki bu özelliğin temelinde kalıtım (inheritance) yatar. Şimdi sıra geç bağlama (late binding) özelliğinin açıklanmasında....

Geç Bağlama (Late Binding)

Polimorfizm olmadan, geç bağlamadan bahsedilemez bile, polimorfizm ve geç bağlama (late binding) bir elmanın iki yarısı gibidir. Şimdi kaldığımız yerden devam ediyoruz, Ogrenci nesnesine bağlı Ogrenci tipindeki referansımızı (ogr) hazirlanSinavVar() yordamına parametre olarak gönderiyoruz.

hazirlanSinavVar(ogr); // yukari dogru cevirim (upcasting)

Bu size ilk başta hata olarak gelebilir, ama arada kalıtım ilişkisinden dolayı (Ogrenci bir Universitelidir) nesneye yönelik programlama çerçevesinde bu olay doğrudur. En önemli kısım geliyor; şimdi, hangi nesnesi dersCalis() yordamı çağırılacaktır? Universite nesnesinin mi? Yoksa Ogrenci nesnesinin mi ? Cevap: Ogrenci nesnesinin dersCalis() yordamı çağırılacaktır. Çünkü Universite tipindeki yerel değişken (uni) Ogrenci nesnesine bağlanmıştır. Eğer Ogrenci nesnesinin dersCalis() yordamı olmasaydı o zaman Universite nesnesine ait olan dersCalis() yordamı çağrılacaktı fakat Ogrenci sınıfının içerisinde, ana sınıfa ait olan (Universite sınıfı) dersCalis() yordamı iptal edildiğinden (override) dolayı, Java, Ogrenci nesnesinin dersCalis() yordamını çağırılacaktır. Peki hangi nesnesinin dersCalis() yordamının çağrılacağı ne zaman belli olur? Derleme anında mı (compile-time)? Yoksa çalışma anında mı (run-time)? Cevap; çalışma anındadır (run-time). Bunun sebebi, derleme anında hazirlanSinavVar() yordamına hangi tür nesneye ait referansın gönderileceğinin belli olmamasıdır.

Son olarak, Akademisyen nesnesine bağlı Akademisyen tipindeki referansımızı hazirlanSinavVar() yordamına parametre olarak gönderiyoruz. Artık bu bize şaşırtıcı gelmiyor... devam ediyoruz. Peki şimdi hangi nesneye ait dersCalis() yordamı çağrılır. Universite nesnesinin mi? Yoksa Akademisyen nesnesinin mi? Cevap Akademisyen nesnesine ait olan dersCalis() yordamının çağrılacağıdır çünkü Universite tipindeki yerel değişkenimiz heap alanındaki Universite nesnesine bağlıdır ve dersCalis() yordamı Akademisyen sınıfının içerisinde iptal edilmiştir (override). Eğer dersCalis() yordamı Akademisyen sınıfının içerisinde iptal edilmeseydi o zaman Universite sınıfına ait (ana sınıf) dersCalis() yordamı çağrılacaktı. Aynı şekilde Java hangi nesnenin dersCalis() yordamının çağrılacağına çalışma-anında (run-time) da karar verecektir yani geç bağlama özelliği devreye girmiş olacaktır. Eğer bir yordamın hangi nesneye ait olduğu çalışma anında belli oluyorsa bu olaya geç bağlama (late-binding) denir. Bu olayın tam tersi ise erken bağlamadır (early binding); yani, hangi nesnenin hangi yordamının çağrılacağı derleme anında bilinmesi. Bu örneğimiz çok fazla basit olduğu için, "Niye ! derleme anında hangi sınıf tipindeki referansın hazirlanSinavVar() yordamına paslandığını bilemeyelim ki, çok kolay, önce Universite sınıfına ait bir referans sonra Ogrenci sınıfına ait bir referans ve en son olarak da Akademisyen sınıfına ait bir referans bu yordama parametre olarak gönderiliyor işte..." diyebilirsiniz ama aşağıdaki örneğimiz için aynı şeyi söylemeniz bu kadar kolay olmayacaktır


Kod:
class Hayvan {
	    	public void avYakala() {
	    	    	System.out.println("Hayvan avYakala");
	    	    	}
	    	}
	
	    	class Kartal extends Hayvan {
	    	    	public void avYakala() {
	    	    	    	System.out.println("Kartal avYakala");
	    	    	}
	    	}
	
	    	class Timsah extends Hayvan{
	    	    	public void avYakala() {
	    	    	    	System.out.println("Timsah avYakala");
	    	    	}
	    	}
	
	    	public class [B][I]PolimorfizmOrnekIki[/I][/B] {
	
	    	    	public static Hayvan rasgeleSec() {
	    	    	    	int sec = ( (int) (Math.random() *3) ) ;
	    	    	    	Hayvan h = null ;
	    	    	    	if (sec == 0) h = new Hayvan();
	    	    	    	if (sec == 1) h = new Kartal();
	    	    	    	if (sec == 2) h = new Timsah();
	    	    	    	return h;
	    	    	}
	
	    	    	public static void main(String args[]) {
	    	    	    	Hayvan[] h = new Hayvan[3];
	    	    	    	[B]// diziyi doldur[/B]
	    	    	    	for (int i = 0 ; i < 3 ; i++) {
	    	    	    	h[i] = rasgeleSec(); //upcasting
	    	    	}    	    	
	    	    	[B]    // dizi elemanlarini ekrana bas[/B]
	    	    	    	for (int j = 0 ; j < 3 ; j++) {
	    	    	    	    	h[j].avYakala(); // !Dikkat!
	    	    	    	}
	    	    	} 
	}



Yukarıdaki örnekte bulunan kalıtım (inheritance) ilişkisini, UML diyagramında gösterirsek:

mak27_1.jpg


PolimorfizmOrnekIki.java örneğimizde rasgeleSec() yordamı, rasgele Hayvan nesneleri oluşturup geri döndürmektedir. Geri döndürülen bu Hayvannesneleri, Hayvan tipindeki dizi içerisine atılmaktadır. Hayvandizisine atılan Kartal ve Timsah nesnelerine Java’nın kızmamasındaki sebep kalıtımdır. Kartal bir Hayvan’dır diyebiliyoruz aynı şekilde Timsah bir Hayvandır diyebiliyoruz; olaylara bu açıdan bakarsak Hayvan tipindeki dizi içerisine eleman atarken yukarı çevirim (upcasting) olduğunu fark edilir.

Geç bağlama ise, Hayvan dizisinin içerisindeki elemanlara ait avYakala() yordamını çağırırken karşımıza çıkar. Buradaki ilginç nokta hangi nesnenin avYakala() yordamının çağrılacağının derleme anında (compile-time) bilinemiyor olmasıdır. Nasıl yani diyenler için konuyu biraz daha açalım. rasgeleSec() yordamını incelersek, Math.random() yordamının her seferinde 0 ile 2 arasında rasgele sayılar ürettiği görülür. Bu üretilen sayılar doğrultusunda Hayvan nesnesi Kartal nesnesi veya Timsah nesnesi döndürülebilir; bu sebepten dolayı uygulamamızı her çalıştırdığımızda Hayvan tipindeki dizinin içerisine değişik tipteki nesnelerin, değişik sırada olabilecekleri görülür. Örneğin PolimorfizmIki uygulamamızı üç kere üst üste çalıştırıp çıkan sonuçları inceleyelim; Uygulamanın çıktısı aşağıdaki gibidir;

Kartal avYakala
Hayvan avYakala
Kartal avYakala


Aynı uygulamamızı tekrardan çalıştırdığımız zaman yeni çıktı;

Timsah avYakala
Timsah avYakala
Hayvan avYakala

Tekrar çalıştırıyoruz;

Timsah avYakala
Hayvan avYakala
Kartal avYakala

Görüldüğü üzere dizi içerisindeki elemanlar her sefersinde farklı olabilmektedir, dizi içerisindeki elemanlar ancak çalışma anında (runtime) belli oluyorlar. h[j].avYakala() derken, derleme anında (compile-time) hangi nesnenin avYakala() yordamının çağrılacağını Java tarafından bilinemez, bu olay ancak çalışma anında (run-time) bilinebilir. Geç bağlama özelliği bu noktada karşımıza çıkar. Geç bağlamanın (late-binding) diğer isimleri, dinamik bağlama (dynamic-binding) veya çalışma anında bağlamadır (runtime-binding).

Final ve Geç Bağlama

Bir önceki yazımızda ( sınıfların tekrar kullanılması ), final özelliğinin kullanılmasının iki sebebi olabileceğini belirtmiştik. Bunlardan bir tanesi tasarım diğeri ise verimliliktir. Verimlilik konusu geç bağlama (late binding) özelliği ile aydınlamış bulunmaktadır, şöyle ki, eğer biz bir sınıfı final yaparsak, bu sınıfa ait tüm yordamları final yapmış oluruz veya eğer istersek tek başına bir yordamı da final yapabiliriz. Bir yordamı final yaparak şunu demiş oluruz, bu yordam, türetilmiş olan alt sınıfların içerisindeki diğer yordamlar tarafından iptal edilemesin (override) Eğer bir yordam iptal edilemezse o zaman geç bağlama (late binding) özelliği de ortadan kalkar.

Uygulama içerisinde herhangi bir nesneye ait normal bir yordam (final olmayan) çağrıldığında, Java, acaba doğru nesnenin uygun yordam mu çağrılıyor diye bir kontrol yapar, daha doğrusu geç bağlamaya (late-binding) ihtiyaç var mı kontrolü yapılır. Örneğin Kedi sınıfını göz önüne alalım. Kedi sınıfı final olmadığından dolayı bu sınıftan türetilme yapabiliriz.


Kod:
class Kedi {
	
    public void yakalaAv() {
        System.out.println("Kedi sinifi Av  	yakaladi");
    }
	
	}
	
	class Kaplan extends Kedi {
	
    public static void goster(Kedi k) {
        k.yakalaAv(); 
    }
	
    public void yakalaAv() {
        System.out.println("Kaplan sinifi Av  	yakaladi");
    }
	
    public static void main(String args[] ) {
        Kedi k = new Kedi() ;
        Kaplan kp = new Kaplan();
        goster(k); 
        goster(kp); [B]// yukari dogru  	cevirim (upcasting) [/B]
    } 
	}



Kaplan sınıfına ait statik bir yordam olan goster() yordamının içerisinde Kedi tipindeki k yerel değişkene bağlı olan nesnenin, yakalaAv() yordamı çağrılmaktadır ama hangi nesnenin yakalaAv() yordamı? Kedi nesnesine ait olan mı? Yoksa Kaplan nesnesine ait olan mı? Java, yakalaAv() yordamını çağırmadan önce geç bağlama (late-binding) özelliğini devreye sokarak, doğru nesneye ait uygun yordamı çağırmaya çalışır tabii bu işlemler sırasından verimlilik düşer. Eğer biz Kedi sınıfını final yaparsak veya sadece yakalaAv() yordamını final yaparsak geç bağlama özelliğini kapatmış oluruz böylece verimlilik artar.



KediKaplan2.java

Kod:
class Kedi2 {
	
    public final void yakalaAv() {
        System.out.println("Kedi sinifi Av  	yakaladi");
    }
	
	}
	
	class Kaplan2 extends Kedi2 {
	
    public static void goster(Kedi2 k) {
    [B]// k.yakalaAv(); // ! dikkat !
	[/B]}
	
	[B]/* iptal edemez 
	[/B]    public void yakalaAv() {
        System.out.println("Kaplan sinifi Av  	yakaladi");
    }
	[B]*/
	[/B]    public static void main(String args[] ) {
        Kedi2 k = new Kedi2() ;
        Kaplan2 kp = new Kaplan2();
        goster(k); 
        goster(kp); 
    } 
	}



KediKaplan2.java örneğimizde, yakalaAv() yordamını final yaparak, bu yordamın Kaplan sınıfının içerisinde iptal edilmesini engelleriz; yani, geç bağlama (late binding) özelliği kapatmış oluruz.

Neden Polimorfizm?

Neden polimofizm sorusuna yanıt aramadan önce, polimorfizm özelliği olmasaydı olayların nasıl gelişeceğini önce bir görelim. Nesneye yönelik olmayan programlama dillerini kullanan kişiler için aşağıdaki örneğimiz gayet normal gelebilir. mesaiBasla() yordamının içerisindeki if-else ifadelerine dikkat lütfen.

IsYeriNon.java

Kod:
class Calisan {
    public String pozisyon = "Calisan";
    public void calis() {
    }
	}
	
	class Mudur {
	
    public String pozisyon = "Mudur";
	
    public Mudur () { [B]// yapılandırıcı[/B]
         pozisyon = "Mudur" ;
    }
    public void calis() { [B]// iptal etme (override)[/B]
         System.out.println("Mudur Calisiyor");
    }
	}
	
	class Programci {
	
    public String pozisyon = "Programci";
	
    public Programci() { [B]// yapılandırıcı[/B]
         pozisyon = "Programci" ;
    }
    public void calis() { [B]// iptal etme (override)[/B]
         System.out.println("Programci Calisiyor");
    }
	}
	
	class Pazarlamaci {
	
    public String pozisyon = "Pazarlamaci";
	
    public Pazarlamaci() { [B]// yapilandirici[/B]
         pozisyon = "Pazarlamaci" ;
    }
	
    public void calis() { [B]// iptal etme (override)[/B]
         System.out.println("Pazarlamaci Calisiyor");
    }
	}
	
	
    public class IsYeriNon {
	
         public static void mesaiBasla(Object[] o ) {
	
              for ( int i = 0 ; i < o.length ; i++ ) {
	
                   if ( o[i] instanceof Calisan ) {
                        Calisan c = (Calisan) o[i] ; [B]//aşağıya çevirim[/B]
                             c.calis();
                   } else if ( o[i] instanceof Mudur ) {
                        Mudur m = (Mudur) o[i] ; [B]//aşağıya çevirim[/B]
                             m.calis();
                   } else if ( o[i] instanceof Programci ) {
                        Programci p = (Programci) o[i] ; [B]//aşağıya çevirim[/B]
                             p.calis();
                   } else if ( o[i] instanceof Pazarlamaci ) {
                        Pazarlamaci paz = (Pazarlamaci) o[i]; [B]//aşağıya  	çevirim[/B]
                             paz.calis();
              } 
	
	[B]//...[/B]
         }
    }
	
    public static void main(String args[]) {
         Object[] o = new Object[4];
         o[0] = new Calisan(); [B]// yukarı cevirim (upcasting)[/B]
         o[1] = new Programci(); [B]// yukarı cevirim (upcasting)[/B]
         o[2] = new Pazarlamaci(); [B]// yukarı cevirim  	(upcasting)[/B]
         o[3] = new Mudur(); [B]// yukarı cevirim (upcasting)[/B]
         mesaiBasla(o);
    }



Yukarıdaki örneğimizde nesneye yönelik programlamaya yakışmayan davranıştır (OOP), mesaiBasla() yordamının içerisinde yapılmaktadır. Bu yordamımızda, dizi içerisindeki elemanların teker teker hangi tipte oldukları kontrol edilip, tiplerine göre calis() yordamları çağrılmaktadır. Calisan sınıfından türeteceğim her yeni sınıf için mesaiBasla() yordamının içerisinde ayrı bir if-else koşul ifade yazmak gerçekten çok acı verici bir olay olurdu. Polimorfizm bu durumda devreye girerek, bizi bu zahmet veren işlemden kurtarır. Öncelikle yukarıdaki uygulamamızın çıktısı inceleyelim.

Programci Calisiyor
Pazarlamaci Calisiyor
Mudur Calisiyor

IsYeriNon.java örneğimizi nesneye yönelik programlamaya tekrardan yazılırsa;


IsYeri.Java


Kod:
class Calisan {
    public String pozisyon="Calisan" ;
    public void calis() {}
	}
	
	class Mudur extends Calisan {
	
    public Mudur () { [B]// yapılandırıcı[/B]
        pozisyon = "Mudur" ;
    }
    public void calis() { [B]// iptal etme (override)[/B]
        System.out.println("Mudur Calisiyor");
    }
	}
	
	class Programci extends Calisan {
    public Programci() { [B]// yapılandırıcı[/B]
        pozisyon = "Programci" ;
    }
    public void calis() { [B]// iptal etme (override)[/B]
        System.out.println("Programci  	Calisiyor");
    }
	}
	
	class Pazarlamaci extends Calisan {
    public Pazarlamaci() { [B]// yapılandırıcı[/B]
        pozisyon = "Pazarlamaci" ;
    }
    public void calis() {[B] // iptal etme (override)[/B]
        System.out.println("Pazarlamaci  	Calisiyor");
    }
	}
	
	public class IsYeri {
    public static void mesaiBasla(Calisan[] c ) {
        for (int i = 0 ; i < c.length ; i++)  	{
            c[i].calis();[B]  	// !Dikkat![/B]
        }
    }
    public static void main(String args[]) {
        Calisan[] c = new Calisan[4];
        c[0] = new Calisan(); [B]//  	yukarı cevirim gerekmiyor[/B]
        c[1] = new Programci(); [B]//  	yukarı cevirim (upcasting)[/B]
        c[2] = new Pazarlamaci(); [B]//  	yukarı cevirim (upcasting)[/B]
        c[3] = new Mudur(); [B]// yukarı  	cevirim (upcasting)[/B]
        mesaiBasla(c);
    }



Görüldüğü üzere mesaiBasla() yordamı artık tek satır, bunu polimorfizm ve tabii ki geç bağlamaya borçluyuz. Bu sayede artık Calisan sınıfından istediğim kadar yeni sınıf türetebilirim, yani genişletme olayını rahatlıkla yapabilirim hem de mevcut yapıyı bozmadan. Uygulamanın çıktısında aşağıdaki gibidir.

Programci Calisiyor
Pazarlamaci Calisiyor
Mudur Calisiyor

Bu uygulamadaki sınıflara ait UML diyagramı aşağıdaki gibidir.

mak27_2.jpg


Kullanılan sınıf yapısı

Genişletilebilirlik (Extensibility)

Polimorfizm sayesinde genişletebilirlik olayı çok basite indirgenmiş bulunmaktadır. Genişletebilirlik, mevcut hiyerarşiyi kalıtım yolu ile genişletmedir. Şimdi IsYeri.java örneğimizi biraz daha genişletelim; Yeni uygulamamızın adını BuyukIsYeri.java yapalım, bu uygulamamız için, sınıflara ait UML diyagramı aşağıdaki gibidir;

mak27_3.jpg


Yukarıdaki UML diyagramında görüldüğü üzere mevcut hiyerarşiyi genişlettik ve toplam 4 adet yeni sınıfı sistemimize eklendik (GenelMudur, AnalizProgramci, SistemProgramci, Sekreter). Yukarıdaki UML şeması Java uygulamasına çevrilirse:


BuyukIsyeri.java

Kod:
class Calisan {
    public String pozisyon ;
    public void calis() {}
	}
	
	class Mudur extends Calisan {
	
    public Mudur () { [B]// yapılandırıcı[/B]
        pozisyon = "Mudur" ;
    	}
    public void calis() { [B]// iptal etme (override)[/B]
        System.out.println("Mudur Calisiyor");
    }
	}
	
	class GenelMudur extends Mudur {
	
    public GenelMudur () { [B]// yapılandırıcı[/B]
        pozisyon = "GenelMudur" ;
    }
    public void calis() { [B]// iptal etme (override)[/B]
        System.out.println("GenelMudur Calisiyor");
      }
    public void toplantiYonet() { 
          System.out.println("GenelMudur toplanti yonetiyor");
    } 
	}
	
	class Programci extends Calisan {
	
    public Programci() {[B] // yapılandırıcı[/B]
    pozisyon = "Programci" ;
     }
      public void calis() { [B]// iptal etme (override)[/B]
         System.out.println("Programci Calisiyor");
     }
	}
	
	class AnalizProgramci extends Programci {
	
     public AnalizProgramci() { [B]// yapılandırıcı[/B]
         pozisyon = "AnalizProgramci" ;
     }
    public void analizYap() {
           System.out.println("Analiz Yapiliyor");
    } 
	}
	
	class SistemProgramci extends Programci {
	
    public SistemProgramci() { [B]// yapılandırıcı[/B]
         pozisyon = "SistemProgramci" ;
    }
    public void sistemIncele() {
        System.out.println("Sistem Inceleniyor");
    } 
	}
	
	class Pazarlamaci extends Calisan {
	
     public Pazarlamaci() { [B]// yapılandırıcı[/B]
         pozisyon = "Pazarlamaci" ;
    }
       public void calis() { // iptal etme (override)
         System.out.println("Pazarlamaci Calisiyor");
    }
	}
	
	class Sekreter extends Calisan {
	
    public Sekreter() { [B]// yapılandırıcı[/B]
        pozisyon = "Sekreter" ;
    }
     public void calis() { [B]// iptal etme (override)[/B]
         System.out.println("Sekreter Calisiyor");
     }
	}
	
	public class BuyukIsYeri {
	
     public static void mesaiBasla(Calisan[] c ) {
        for (int i = 0 ; i < c.length ; i++) {
                 c[i].calis(); [B]// ! Dikkat ![/B]
          }
     }
	
    public static void main(String args[]) {
           Calisan[] c = new Calisan[7];
         c[0]=new Calisan(); [B]//yukarı cevirim gerekmiyor[/B]
          c[1]=new Programci(); [B]//yukarı cevirim (upcasting)[/B]
           c[2]=new Pazarlamaci(); [B]// yukarı cevirim (upcasting)[/B]
         c[3]=new Mudur(); [B]// yukarı cevirim (upcasting)[/B]
           c[4]=new GenelMudur(); [B]// yukarı cevirim (upcasting)[/B]
        c[5]=new AnalizProgramci(); [B]// yukarı cevirim (upcasting)[/B]
        c[6]=new SistemProgramci(); [B]// yukarı cevirim (upcasting)[/B]
        mesaiBasla(c);
    }
	}


Yukarıdaki örnekte dikkat edilirse mesaiBasla() yordamı hala tek satır. Uygulamanın çıktısı aşağıdaki gibidir;

Programci Calisiyor
Pazarlamaci Calisiyor
Mudur Calisiyor
GenelMudur Calisiyor
Programci Calisiyor
Programci Calisiyor

Burada yapılan iş, Calisan sınıfından yeni sınıflar türetmektir, bu yeni türetilmiş sınıfların (GenelMudur, AnalizProgramci, SistemProgramci, Sekreter) calis() yordamlarını çağırmak için ekstra bir yük üstlenilmedi (mesaiBaslat() yordamının içerisine dikkat edersek ). Polimorfizm ve tabii ki geç bağlama sayesinde bu işler otomatik olarak gerçekleşmektedir.

Soyut Sınıflar ve Yordamlar (Abstract Classes and Methods)

Soyut kavramını anlatmadan önce, IsYeri.java ve BuyukIsyeri.java örneklerini inceleyelim. Bu uygulamaların içerisinde hiç bir iş yapmayan ve sanki boşuna oraya yerleştirilmiş hissi veren bir sınıf göze çarpar; evet bu Calisan sınıfıdır. Calisan sınıfını daha yakından bakılırsa;

class Calisan {
public String pozisyon = "Calisan";
public void calis() {}
}

Yukarıda görüldüğü üzere Calisan sınıfı hiç bir iş yapmamaktadır. Akıllara şöyle bir soru daha gelebilir "Madem ki Calisan sınıfı hiç bir iş yapmıyor, ne diye onu oraya yazdık"; cevap: birleştirici bir rol oynadığı için, Calisan sınıfını oraya yazdık diyebilirim. Olayları biraz daha detaylandıralım.

Soyut sınıflar, şu ana kadar bildiğimiz sınıflardan farklıdırlar. Soyut (abstract) sınıflarımızı direk new() ile oluşturamayız. Soyut sınıfların var olmasındaki en büyük sebeplerden biri birleştirici bir rol oynamalarıdır. Soyut bir sınıftan türetilmiş alt sınıflara ait nesneler, çok rahat bir şekilde yine bu soyut sınıf tipindeki referanslara bağlanabilirler (yukarı çevirim). Böylece polimorfizm ve geç bağlamanın kullanılması mümkün olur.

Bir sınıfın soyut olması için, bu sınıfın içerisinde en az bir adet soyut yordamının bulunması gerekir. Soyut yordamların gövdesi bulunmaz; yani, içi boş hiçbir iş yapmayan yordam görünümündedirler. Soyut bir sınıfdan türetilmiş alt sınıflar, bu soyut sınıfın içerisindeki soyut yordamları kesin olarak iptal etmeleri (override) gerekmektedir. Eğer türetilmiş sınıflar, soyut olan ana sınıflarına ait bu soyut yordamları iptal etmezlerse, derleme anında (compile-time) hata ile karşılaşılır.

abstract void calis() ; // gövdesi olmayan soyut yordam

Soyut sınıfların içerisinde soyut yordamlar olacağı gibi, gövdeleri olan, yani iş yapan yordamlarda bulunabilir. Buraya kadar anlattıklarımızı bir uygulama üzerinde pekiştirelim;


e-time) hata ile karşılaşılır.

abstract void calis() ; // gövdesi olmayan soyut yordam

Soyut sınıfların içerisinde soyut yordamlar olacağı gibi, gövdeleri olan, yani iş yapan yordamlarda bulunabilir. Buraya kadar anlattıklarımızı bir uygulama üzerinde pekiştirelim;

AbIsyeri.java

Kod:
abstract class Calisan {
	    	public String pozisyon="Calisan" ;
	    	public abstract void calis() ;/[B]/ soyut yordam[/B] 
	    	public void zamIste() { [B]// soyut olmayan yordam[/B]
	        	System.out.println("Calisan zamIste");
	    	}
	}
	
	class Mudur extends Calisan {
	
	    	public Mudur () { [B]// yapılandırıcı[/B]
	        	pozisyon = "Mudur" ;
	    	}
	    	public void calis() { [B]// iptal etme (override)[/B]
	        	System.out.println("Mudur Calisiyor");
	    	}
	}
	
	class Programci extends Calisan {
	
	    	public Programci() { [B]// yapılandırıcı[/B]
	        	pozisyon = "Programci" ;
	    	}
	    	public void calis() { [B]// iptal etme (override)[/B]
	        	System.out.println("Programci Calisiyor");
	    	}
	
	    	public void zamIste() { [B]// iptal etme (override)[/B]
	        	System.out.println("Programci Zam Istiyor");
	    	}
	}
	
	class Pazarlamaci extends Calisan {
	
	    	public Pazarlamaci() { [B]// yapılandırıcı[/B]
	        	pozisyon = "Pazarlamaci" ;
	    	}
	    	public void calis() { [B]// iptal etme (override)[/B]
	        	System.out.println("Pazarlamaci Calisiyor");
	    	}
	}
	
	public class AbIsYeri {
	
	    	public static void mesaiBasla(Calisan[] c ) {
	        	for (int i = 0 ; i < c.length ; i++) {
	            	c[i].calis(); [B]// !Dikkat![/B]
	        	}
	    	}
	
	    	public static void main(String args[]) {
	        	Calisan[] c = new Calisan[3];
	      [B]  	// c[0] = new Calisan(); // soyut sınıflar new ile direk oluşturulamazlar[/B]
	        	c[0] = new Programci(); [B]// yukarı cevirim (upcasting)[/B]
	        	c[1] = new Pazarlamaci();[B]// yukarı cevirim (upcasting)[/B]
	        	c[2] = new Mudur(); [B]// yukari cevirim (upcasting)[/B]
	        	mesaiBasla(c);
	    	}
	}



Yukarıdaki örneğimizi incelersek, soyut (abstract) olan Calisan sınıfının 2 adet yordamı olduğu görürüz. Bu iki yordamdan bir tanesi olan soyut (abstract) calis() yordamıdır. Calisan sınıfından türeyen sınıflar tarafından bu yordam kesin kes iptal edilmek (override) zorundadır. Baştan açıklarsak, eğer bir sınıfın içerisinde soyut (abstract) bir yordam varsa o zaman bu sınıf da soyut (abstract) olmak zorundadır. Fakat soyut olan sınıfların içerisinde normal yordamlarda bulunabilir aynı zamIste() yordamının Calisan sınıfının içerisinde bulunduğu gibi.

Calisan sınıfından türeyen diğer sınıfları incelenirse, bu sınıfların hepsinin, calis() yordamını iptal ettikleri (override) görülür ama aynı zamanda zamIste() yordamı sadece Programci sınıfının içerisinde iptal edilmiştir (override). Eğer ana sınıfın içerisindeki bir yordamın türemiş sınıflar içerisinde iptal edilmeleri (override) şansa bırakılmak istenmiyorsa; o zaman bu yordamın soyut olarak tanımlanması gerekir.

Dikkat edilmesi gereken bir başka nokta, soyut sınıfların direk olarak new() ile oluşturulamıyor olmasıdır. Soyut sınıf demek birleştirici rol oynayan sınıf demektir. AbIsYeri.java uygulamasındaki sınıflara ait UML diyagramı aşağıdaki gibidir;

mak27_4.jpg

AbIsYeri.java uygulamasında kullanılan sınıflar

UML diyagramından daha net bir biçimde görmekteyiz ki türemiş sınıflar, ana sınıfa ait calis() yordamını iptal etmek (override) zorunda bırakılmışlardır. Fakat zamIste() yordamı ise sadece Calisan sınıfından türemiş olan Programci sınıfı içerisinde iptal edilmiştir (override).

Niye Soyut Sınıf ve Yordamlara İhtiyaç Duyarız?

Örneğin hem cep telefonunun ekranına hem de monitörün ekranına çizgi çizdirmek istiyoruz fakat cep telefonu ekranının özellikleri ile monitör ekranının özelliklerinin birbirinden tamamen farklı olması, karşımızda büyük bir problemdir. Bu iki ekrana çizgi çizdirmek için değişik sınıflara ihtiyaç duyulacağı kesindir. Peki nasıl bir yazılım tasarımı yapılmalıdır.

CizimProgrami.java abstract class Cizim {
// soyut yordam
public abstract void noktaCiz(int x , int y) ;

// soyut olmayan yordam
public void cizgiCiz(int x1 , int y1 , int x2 , int y2) {
// noktaCiz(x,y); // yordamını kullanarak ekrana cizgi ciz
}
}

class CepTelefonuCizim extends Cizim {
// iptal ediyor (override)
public void noktaCiz(int x, int y) {
// cep telefonu ekrani icin nokta ciz.....
}
}

class MonitorCizim extends Cizim {
// iptal ediyor (override)
public void noktaCiz(int x, int y) {
// Monitor ekrani icin nokta ciz.....
}
}

public class CizimProgrami {
public void baslat(int x1 , int y1 , int x2 , int y2) {
// cep telefonunun ekranina cizgi cizmek icin
Cizim c1 = new CepTelefonuCizim();
c1.cizgiCiz(x1 , y1 , x2 , y2);
// Monitor ekranina cizgi cizmek icin
Cizim c2 = new MonitorCizim();
c2.cizgiCiz(x1 , y1 , x2 , y2 );
}
}

Cizim sınıfımızın içerisinde bulunan cizgiCiz() yordamı soyut (abstract) değildir fakat noktaCiz() yordamı soyuttur, neden? Sebebi, cizgiCiz() yordamının ekranlara çizgi çizmek için noktaCiz() yordamına ihtiyaç duymasından kaynaklanır. cizgiCiz() yordamının ihtiyaç duyduğu tek şey, ekran üzerinde tek bir noktanın nasıl çizileceğini bilmektir, bu bilgiler cizgiCiz() yordamına verildiği sürece sorun yaşanmayacaktır. Ekrana tek bir noktanın nasıl çizileceğini, Cizim sınıfından türemiş alt sınıflar tarafından verilmektedir.

Cizim sınıfından türemiş sınıflara dikkat edilirse (CepTelefonuCizim ve MonitorCizim), bu sınıfların içerisinde, ana sınıfa ait olan noktaCiz() yordamının iptal edilmiş (override) olduğunu görürüz. Bunun sebebi her bir ekrana (Monitörün ve Cep telefonu ekranı) ait nokta çiziminin farklı olmasından kaynaklanır. Yukarıdaki uygulamamıza ait sınıflar için UML diyagramı aşağıdaki gibidir.

mak27_5.jpg

CizimProgrami.java uygulamasında kullanılan sınıflar

Yukarıdaki örneğimizden çıkartılacak olan ana fikir şöyledir; eğer bir işlem değişik verilere ihtiyaç duyup aynı işi yapıyorsa, bu işlem soyut (abstract) sınıfın içerisinde tanımlanmalıdır.

Yapılandırıcılar İçerisindeki İlginç Durumlar

Yapılandırıcılar içerisinde ne gibi ilginç durumlar olabilir ki diyebilirsiniz? Biraz sonra göstereceğimiz örnek içerisinde polimorfizm ve geç bağlamanın devreye girmesiyle olaylar biraz karışacaktır. Öncelikle yapılandırıcıların ne işe yaradıklarını bir tekrar edelim.

Bir nesne kullanıma geçmeden önce bazı işlemler yapması gerekebilir, örneğin global olan alanlarına ilk değerlerinin verilmesi gerekebilir veya JDBC (Daha sonraki yazılarımızda değinmeye çalışacağız) bağlantısı ile veritabanı bağlanıp bazı işlemleri yerine getirmesi gerekebilir, örnekleri çoğaltmak mümkündür... Tüm bu işlemlerin yapılması için gereken yer yapılandırıcılardır. Buraya kadar sorun yoksa örneğimizi incelemeye başlayabiliriz.

Spor.java abstract class Sporcu {
public abstract void calis();
public Sporcu() { // yapılandırıcı yordam
System.out.println("calis() cagrilmadan önce");
calis(); // ! Dikkat !
System.out.println("calis() cagrildiktan sonra");
}
}

class Futbolcu extends Sporcu {
int antraman_sayisi = 4 ;
public void calis() {
System.out.println("Futbolcu calis() " + antreman_sayisi );
}
public Futbolcu() { // yapılandırıcı yordam
System.out.println("Futbolcu yapilandirici" );
calis();
}
}

public class Spor {
public static void main( String args[] ) {
Futbolcu f = new Futbolcu();
// Sporcu s = new Sporcu(); // ! Hata soyut sınıf !
}}

Soyut sınıflara ait yapılandırıcılar olabilir. Bir önceki yazımızda da incelediği üzere, sınıflara ait nesneleri oluştururken işin içerisinde bir de kalıtım (inheritance) özelliği girdiğinde olayların nasıl değiştiği incelemiştik. Bir sınıfa ait nesne oluşturulacaksa, önce bu sınıfın ana sınıfı var mı diye kontrol edilir; yani, bu sınıf türetilmiş bir sınıf mı kontrolü yapılır. Eğer bu sınıfın türetildiği ana bir sınıf var ise önce bu ana sınıfa ait nesne oluşturulur daha sonra sıra türeyen sınıfımıza ait nesnenin oluşturulmasına gelir. Yukarıdaki örneğimizde kalıtım kavramı kullanılmıştır. Ana sınıf Sporcu sınıfıdır, bu sınıftan türetilmiş olan ise Futbolcu sınıfıdır. Futbolcu bir Sporcudur. Biz Futbolcu sınıfına ait bir nesne oluşturmak istersek, bu olayın daha öncesinde Sporcu sınıfına ait bir nesnesinin oluşacağını açıktır.

Bu örneğimizdeki akıl karıştırıcı nokta, soyut bir sınıfa ait yapılandırıcı içerisinden soyut bir yordamın çağrılıyor olmasıdır. Sporcu sınıfının yapılandırıcısına dikkat ederseniz, bu yapılandırıcı içerisinden soyut bir yordam olan calis() yordamı çağrılmıştır. calis() yordamı hangi amaçla soyut yapılmış olabilir? Bu yordamın soyut yapılmasındaki tek amaç, alt sınıfların bu yordamı iptal etmelerini kesinleştirmek olabilir. Bu örneğimizdeki yanlış, soyut bir sınıfa ait yapılandırıcının içerisinden soyut bir yordamın çağrılmasıdır. Peki böyle bir yanlış yapıldığında nasıl sonuçlar oluşur? Uygulamamızın çıktısı aşağıdaki gibidir.

calis() cagrilmadan önce
Futbolcu calis() 0 --> dikkat
calis() cagrildiktan sonra
Futbolcu yapilandirici
Futbolcu calis() 4

Uygulama çıktısındaki 2. satır çok ilginçtir. Oluşan olayları adım adım açıklarsak;

Futbolcu sınıfına ait bir nesne oluşturulmak istendi ama Futbolcu sınıfı Sporcu sınıfından türetildiği için, ilk önce Sporcu sınıfına ait yapılandırıcı cağrılacaktır.

Sporcu sınıfına ait yapılandırıcının içerisinde soyut olan calis() yordamı çağrıldı. Soyut olan calis() yordamı Futbolcu sınıfının içerisinde iptal edildiği (override) için, Futbolcu sınıfına ait olan calis() yordamı çağrılacaktır fakat temel (primitive) int tipinde olan antreman_sayisi alanına henüz ilk değeri atanmadığından, Java tarafından varsayılan değer (default value) olan 0 sayısı verilmiştir. Buradaki ilginç olan nokta Futbolcu sınıfının içerisindeki calis() yordamı, Futbolcu sınıfına ait yapılandırıcıdan bile önce çağrılmış olmasıdır (-ki bu istenmeyen bir durumdur).

Ana sınıf olan Sporcu sınıfına ait yapılandırıcının çalışması sona erdikten sonra türemiş sınıf olan Futbolcu nesnesine ait global alan olan antreman_sayisi alanının ilk değeri verilmiştir.

En son olarak Futbolcu sınıfına ait yapılandırıcı içerisindeki kodlar çalıştırılarak işlem sona erer.

Kalıtım ve Yukarı Çevirim (Upcasting)

Yukarı çevirim (upcasting) her zaman güvenlidir, sonuçta daha özellikli bir tipten daha genel bir tipe doğru çevirim gerçekleşmiştir. Örneğin elimizde iki tip televizyon bulunsun, biri Xmodel televizyon diğeri Xmodel’den türetilmiş ve daha yeni özelliklere sahip olan Ymodel televizyon. Bu ilişkiyi UML diyagramında gösterirsek.

mak27_6.jpg

Televizyon.java Kalıtım ve Yukarı Çevirim

UML diyagramı Java uygulamasına dönüştürürse;

class Xmodel {
public void sesAc() {
System.out.println("X model televizyon sesAc()");
}
public void sesKapa() {
System.out.println("X model televizyon sesKapa()");
}
public void kanalDegistir() {
System.out.println("X model televizyon kanalDegistir()");
}
}

class Ymodel extends Xmodel {
public void sesAc() { // iptal etme (override)
System.out.println("Y model televizyon sesAc()");
}
public void sesKapa() { // iptal etme (override)
System.out.println("Y model televizyon sesKapa()");
}
public void kanalDegistir() { // iptal etme (override)
System.out.println("Y model televizyon kanalDegistir() ");
}

public void teleText() {
System.out.println("Y model televizyon teleText()");
}
}

public class Televizyon {
public static void main(String args[]) {

// yukarı cevirim ( upcasting )
Xmodel x_model_kumanda = new Ymodel();
x_model_kumanda.sesAc();
x_model_kumanda.sesKapa();
x_model_kumanda.kanalDegistir();

//!! hata !!, bu kumandanın böyle bir düğmesi yok :)
// x_model_kumanda.teleText() ;
}
}

Yukarı çevirim (upcasting) olayında iki taraf vardır, bir tanesi heap alanında nesnenin kendisi diğer tarafta yığın (stack) alanında bulunan referans. Olaylara televizyon ve kumanda boyutunda bakarsak işin sırrı çözülmeye başlar. Elimizde Xmodel televizyon kumandası olduğunu düşünün ama kumanda Ymodel bir televizyonu gösterirsin (gösterebilir çünkü arada kalıtım ilişkisi vardır (Ymodel televizyon bir Xmodel televizyondur), o zaman karşımızda duran Ymodel televizyonun teleText() özelliği olmasına rağmen bunu kullanamayız çünkü Xmodel bir televizyon kumandası, Xmodel televizyon için tasarlandığından, bu kumandanın üzerinde teleText() düğmesi olmayacaktır. Anlattıklar şekil üzerinde gösterilirse:

mak27_7.jpg

Televizyon.java Kalıtım ve Yukarı Çevirim

Aşağıya Çevirim (Downcasting)

Aşağıya çevirim (downcasting), yukarı çevirim (upcasting) işleminin tam tersidir. Aşağıya çevirim (downcasting), daha genel bir tipten, daha özellikli bir tipe doğru geçiş demektir ve tehlikelidir. Tehlikelidir çünkü çevrilmeye çalışılan daha özellikli tipe doğru çevirim esnasında sorun çıkma riski yüksektir. Java programlama dilinde aşağıya çevirim (downcasting) yaparken, hangi tipe doğru çevirim yapılacağı açık olarak belirtmelidir. Fakat yukarı çevirim (upcasting) işleminde böyle bir belirteç koyma zorunluluğu yoktur çünkü oradaki olay daha özellikli bir tipten daha genel bir tipe doğru çevirimdir; yani, güvenlidir. Anlattıkları örnek üzerinde gösterirsek:

Televizyon2.java class Xmodel {
public void sesAc() {
System.out.println("X model televizyon sesAc()");
}
public void sesKapa() {
System.out.println("X model televizyon sesKapa()");
}
public void kanalDegistir() {
System.out.println("X model televizyon kanalDegistir()");
}
}

class Ymodel extends Xmodel {
public void sesAc() { // iptal etme (override)
System.out.println("Y model televizyon sesAc()");
}
public void sesKapa() { // iptal etme (override)
System.out.println("Y model televizyon sesKapa()");
}
public void kanalDegistir() { // iptal etme (override)
System.out.println("Y model televizyon kanalDegistir() ");
}
public void teleText() {
System.out.println("Y model televizyon teleText()");
}
}

public class Televizyon2 {
public static void main(String args[]) {

Object[] ob = new Object[2] ;
ob[0] = new Xmodel() ; // yukari cevirim (upcasting)
ob[1] = new Ymodel() ; // yukari cevirim (upcasting)
for (int i = 0 ; i < ob.length ; i++) {
// asagiya cevirim (Downcasting)
Xmodel x_model_kumanda = (Xmodel) ob ;
x_model_kumanda.sesAc();
x_model_kumanda.sesKapa();
x_model_kumanda.kanalDegistir();
// x_model_kumanda.teleText(); // bu kumanda da böyle bir düğme yok
System.out.println("----------------------------------");
}
}
}

Yukarıdaki uygulamanın çıktısı aşağıdaki gibidir;

X model televizyon sesAc()
X model televizyon sesKapa()
X model televizyon kanalDegistir()
-----------------------------------------------
Y model televizyon sesAc()
Y model televizyon sesKapa()
Y model televizyon kanalDegistir()
-----------------------------------------------

Bu örneğimizde, Xmodel ve Ymodel nesnelerimiz, Object sınıfı tipindeki dizinin içerisine atılmaktadır. Her sınıf, Object sınıfından türetildiği göre, Object sınıfı tipindeki bir dizisinin içerisine Xmodel ve Ymodel nesnelerini rahatlıkla atabiliriz buradaki olay yukarı doğru çevirimdir (upcasting). Artık elimizde Object sınıfı tipinde bir dizisi var. Bunun içerisindeki elemanları tekrardan eski hallerine sokmak için aşağıya doğru çevirim (downcasting) özelliğini kullanmak gereklidir. Daha önceden de ifade edildiği gibi aşağıya doğru çevirim (downcasting) yapılacaksa, bunun hangi tipe olacağı açık olarak belirtilmelidir.

for ifadesinin içerisine dikkat edilirse, Object tipindeki dizisinin içerisindeki elemanları Xmodel nesnesine dönüştürmek için aşağıdaki ifade kullanılmıştır.

// asagiya dogru cevirim (Downcasting) Xmodel x_model_kumanda = (Xmodel) ob ;

Yukarıdaki ifade sayesinde Object sınıfı tipinde olan dizimizin içerisindeki elemanları, Xmodel nesnesine çevirmiş bulunmaktayız. Aslında bu örneğimizde zarardayız, niye derseniz hemen açıklayalım. Sonuçta bizim iki adet hem Xmodel hem de Ymodel nesnelerimiz vardı. Biz bunları Object sınıfı tipine çevirerek yani yukarı çevirim yaparak Object sınıfı tipindeki dizisinin içerisine yerleştirdik, buraya kadar sorun yok. Fakat Object dizisinin içerisinden elemanlarımızı çekerken, bu elemanlarımızın hepsini Xmodel nesnesine çevirdik yani aşağıya çevirim yapmış olduk. Biz böyle yapınca Ymodel nesnemiz yok olmuş gibi oldu.

Yukarı doğru çevirim (upcasting) yaparken, asıl nesnelerimiz değerlerinden birşey kaybetmezler. Örneğin bu uygulamamızda bir Object sınıfı tipindeki dizimizin içerisine Xmodel ve Ymodel nesnelerini atabildik (bunun sebebinin kalıtımdır). Xmodel ve Ymodel nesnelerini, Object nesnelerine çevirerek, bu nesnelerimizin asıl tiplerini değiştirmeyiz, sadece Object dizisinin içerisine atma izni elde ederiz; yani, Object sınıfı tipindeki dizimizin içerisindeki nesnelerimizin orijinal halleri hala Xmodel ve Ymodel tipindedir.

Java çalışma anında (run-time) nesnelerin tiplerini kontrol eder.Eğer bu işlemlerde bir uyumsuzluk varsa bunu hemen kullanıcıya ClassCastException istisnası fırlatarak bildirir. Nesne tiplerinin çalışma anından (run-time) tanımlanması (RTTI : Run Time Type Identification), kodu yazan kişi açısından büyük faydalar içerir. Bu açıklamalardan yola çıkılarak, yukarıdaki uygulama değiştirilirse.

Televizyon3.java class Xmodel {
public void sesAc() {
System.out.println("X model televizyon sesAc()");
}

public void sesKapa() {
System.out.println("X model televizyon sesKapa()");
}

public void kanalDegistir() {
System.out.println("X model televizyon kanalDegistir()");
}
}

class Ymodel extends Xmodel {
public void sesAc() { // iptal ediyor (override)
System.out.println("Y model televizyon sesAc()");
}

public void sesKapa() { // iptal ediyor (override)
System.out.println("Y model televizyon sesKapa()");
}

public void kanalDegistir() { // iptal ediyor (override)
System.out.println("Y model televizyon kanalDegistir() ");
}

public void teleText() {
System.out.println("Y model televizyon teleText()");
}
}

public class Televizyon3 {
public static void main(String args[]) {

Object[] ob = new Object[2] ;
ob[0] = new Xmodel() ;
ob[1] = new Ymodel() ;

for (int i = 0 ; i < ob.length ; i++) {

Object o = ob ;
if (o instanceof Ymodel) { // RTTI
Ymodel y_model_kumanda = (Ymodel) o ; //artık güvende
y_model_kumanda.sesAc();
y_model_kumanda.sesKapa();
y_model_kumanda.kanalDegistir();
y_model_kumanda.teleText() ;
} else if (o instanceof Xmodel) { // RTTI
Xmodel x_model_kumanda = (Xmodel) o; // artik guvenli
x_model_kumanda.sesAc();
x_model_kumanda.sesKapa();
x_model_kumanda.kanalDegistir();

}
}
}
}

Object sınıfı tipindeki dizi içerisinde bulunan elemanların hepsinin Object sınıfı tipinde olma zorunluluğu olduğunu biliyoruz. Xmodel ve Ymodel televizyonları yukarı doğru çevirim özelliği sayesinde Object sınıfı tipine çevirerek, Object sınıfı tipindeki dizi içerisine atabildik. Peki Xmodel ve Ymodel nesnelerimizin özelliklerini sonsuza kadar geri alamayacak mıyız? Geri almanın yolu aşağıya çevirimdir (downcasting).

Aşağıya çevirimin (downcasting) tehlikeli olduğunu biliyoruz. Eğer yanlış bir tipe doğru çevirim yaparsak, çalışma anında Java tarafından ClassCastException istisnası ile durduruluruz. Çalışma anında (run-time) yanlış bir tipe çevirimden korkuyorsak instanceof anahtar kelimesini kulanmamız gerekir. Yukarıdaki örneğimizde (Televizyon3.java) instanceof anahtar kelimesi sayesinde çalışma anında, Object sınıfı tipindeki dizi içerisindeki elemanların asıl tiplerini kontrol ederek aşağıya doğru çevirim yapma imkanına sahip oluruz. Böylece hata oluşma riskini minimuma indirgeriz. Uygulamamızın çıktısı aşağıdaki gibidir;

X model televizyon sesAc()
X model televizyon sesKapa()
X model televizyon kanalDegistir()
Y model televizyon sesAc()
Y model televizyon sesKapa()
Y model televizyon kanalDegistir()
Y model televizyon teleText()

Burada bir yazımızın daha sonuna gelmiş bulunuyoruz. Bu yazımızda OOP bünyesinde çok önemli bir yeri olan Polimorfizm kavramını ayrıntılı bir biçimde incelemeye çalıştık.

Umarım Polimorfizm ile ilgili ayrıntıları yeterince anlatabilmişizdir.

Bir sonraki yazımızda görüşünceye dek
İyi ve Mutlu günler...



 
Geri
Üst