BÖLÜM 6
|
|
Altuğ
B. Altıntaş
|
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 konusunu geçen bölüm
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. (yorum ekle)
6.1. Detaylar
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; (yorum ekle)
Örnek: PolimorfizmOrnekBir.java (yorum ekle)
class Asker { public void selamVer() { System.out.println("Asker Selam verdi"); }} class Er extends Asker { public void selamVer() { System.out.println("Er Selam verdi"); }}class Yuzbasi extends Asker { public void selamVer() { System.out.println("Yuzbasi Selam verdi"); }}public class PolimorfizmOrnekBir { public static void hazirOl(Asker a) {a.selamVer(); // ! Dikkat ! } public static void main(String args[]) { Asker a = new Asker(); Er e = new Er(); Yuzbasi y = new Yuzbasi();hazirOl(a); // yukarı cevirim ! yok ! hazirOl(e); // yukarı cevirim (upcasting) hazirOl(y); // yukarı cevirim (upcasting) }} |
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 Asker sınıfıdır; bu sınıfdan türeyen sınıflar ise Er ve Yuzbasi sınıflarıdır. Bu ilişki "bir" ilişkisidir ; (yorum ekle)
·
Er bir Askerdir,
veya
·
Yüzbası bir Askerdir,
diyebiliriz.
Yani Asker sınıfının yaptığı her işi Er sınıfı veya Yuzbasi sınıfı da yapabilir artı türetilen bu iki sınıf kendisine
has özellikler taşıyabilir, Asker
sınıfı ile Er ve Yuzbasi sınıflarının arasında kalıtımsal bir ilişki bulunmasından
dolayı, Asker tipinde parametre kabul
eden hazirOl() yordamına
Er ve Yuzbasi tipindeki referansları paslayabildik, bu özelliğinde yukarı
çevirim (upcasting) olduğunu geçen bölüm incelenmişti. (yorum ekle)
Polimorfizm ise hazirOl() yordamının
içerisinde gizlidir. Bu yordamın (method) içerisinde Asker tipinde olan a referansı kendisine gelen 2 değişik nesneye (Er ve Yuzbasi) bağlanabildi; bunlardan
biri Er diğeri ise Yuzbasi’dır. Peki bu yordamın içerisinde
neler olmaktadır? Sırası ile açıklarsak; ilk önce Asker nesnesine bağlı Asker
tipindeki referansı, hazirOl() yordamına
parametre olarak gönderiyoruz, burada herhangi bir terslik yoktur çünkü hazirOl() yordamı zaten Asker tipinde parametre kabul
etmektedir. (yorum ekle)
Burada dikkat edilmesi gereken husus, hazirOl() yordamının içerisinde Asker
tipindeki yerel a değişkenimizin, kendi
tipinden başka nesnelere de (Er ve Yuzbasi) bağlanabilmesidir; yani, Asker tipindeki yerel a değişkeni bir çok şekle girmiş bulunmaktadır.
Aşağıdaki ifadelerin hepsi doğrudur: (yorum ekle)
·
Asker
a = new Asker() ;
·
Asker
a = new Er();
·
Asker
a = new Yuzbasi();
Yukarıdaki ifadelere, Asker tipindeki a 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.... (yorum ekle)
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, Er nesnesine
bağlı Er tipindeki
referansımızı (e) hazirOl() yordamına parametre olarak gönderiyoruz. (yorum ekle)
Gösterim-6.1:
hazirOl(e); // yukari dogru cevirim (upcasting) |
Bu size ilk başta hata olarak gelebilir, ama arada kalıtım ilişkisinden
dolayı (Er bir Askerdir) nesneye yönelik programlama
çerçevesinde bu olay doğrudur. En önemli kısım geliyor; şimdi, hangi
nesnesin selamVer() yordamı çağrılacaktır? Asker nesnesinin mi? Yoksa Er nesnesinin mi ? Cevap: Er nesnesinin selamVer() yordamı çağrılacaktır.
Çünkü Asker tipindeki yerel değişken
(a) Er nesnesine bağlanmıştır. Eğer Er nesnesinin selamVer() yordamı olmasaydı o zaman Asker nesnesine ait olan selamVer() yordamı
çağrılacaktı fakat Er sınıfının
içerisinde, ana sınıfa ait olan (Asker
sınıfı) selamVer() yordamı iptal edildiğinden (override) dolayı, Java, Er nesnesinin
selamVer() yordamını çağırılacaktır.
Peki hangi nesnesinin selamVer() 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ında (run-time).
Bunun sebebi,
derleme anında hazirOl() yordamına
hangi tür nesneye ait referansın gönderileceğinin belli olmamasıdır. (yorum ekle)
Son olarak, Yuzbasi
nesnesine bağlı Yuzbasi
tipindeki referansımızı hazirOl()
yordamına parametre
olarak gönderiyoruz. Artık bu bize şaşırtıcı gelmiyor... devam ediyoruz. Peki
şimdi hangi nesneye ait selamVer() yordamı çağrılır? Asker nesnesinin mi? Yoksa Yuzbasi nesnesinin mi? Cevap Yuzbasi nesnesine ait olan selamVer() yordamının çağrılacağıdır
çünkü Asker tipindeki yerel değişkenimiz
heap alanındaki Yuzbasi nesnesine bağlıdır ve selamVer() yordamı Yuzbasi sınıfının içerisinde iptal
edilmiştir (override). Eğer selamVer() yordamı Yuzbasi sınıfının içerisinde iptal
edilmeseydi o zaman Asker sınıfına
ait (ana sınıf) selamVer() yordamı çağrılacaktı. Aynı
şekilde Java hangi nesnenin selamVer() 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 hazirOl() yordamına
paslandığını bilemeyelim ki, çok kolay, önce Asker sınıfına ait bir referans sonra Er sınıfına ait bir referans ve en son olarak da Yuzbasi 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 (yorum ekle)
Örnek: PolimorfizmOrnekIki.java (yorum ekle)
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 PolimorfizmOrnekIki { 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];// diziyi doldur for (int i = 0 ; i < 3 ; i++) { h[i] = rasgeleSec(); //upcasting }// dizi elemanlarini ekrana bas 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:

Şekil-6.1. Kalıtım, Polimorfizm ve Geç Bağlama
PolimorfizmOrnekIki.java örneğimizde rasgeleSec() yordamı, rasgele Hayvan nesneleri oluşturup geri döndürmektedir.
Geri döndürülen bu Hayvan nesneleri,
Hayvan tipindeki dizi içerisine atılmaktadır. Hayvan dizisine 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. (yorum ekle)
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;
Uygulamamızı çalıştırıyorum. (yorum ekle)
Gösterim-6.2:
java PolimorfizmIki |
Uygulamanın çıktısı aşağıdaki gibidir;
Kartal avYakalaHayvan avYakalaKartal avYakala
Aynı uygulamamızı tekrardan çalıştırıyorum;
Timsah avYakalaTimsah avYakalaHayvan avYakala
Tekrar çalıştırıyorum;
Timsah avYakalaHayvan avYakalaKartal 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). (yorum ekle)
5. bölümde, 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. (yorum ekle)
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. (yorum ekle)
Örnek: KediKaplan.java
(yorum ekle)
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); // yukari dogru cevirim (upcasting) } } |
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 evvel
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. (yorum ekle)
Örnek: KediKaplan2.java
(yorum ekle)
class Kedi2 { public final void yakalaAv() { System.out.println("Kedi sinifi Av yakaladi"); } }class Kaplan2 extends Kedi2 { public static void goster(Kedi2 k) {// k.yakalaAv(); // ! dikkat ! }/* iptal edemez public void yakalaAv() { System.out.println("Kaplan sinifi Av yakaladi"); } */ 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. (yorum ekle)
Neden polimofizm sorusuna yanıt aramadan evvel,
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. (yorum ekle)
Örnek: IsYeriNon.java
(yorum ekle)
class Calisan { public String pozisyon = "Calisan"; public void calis() { }}class Mudur { public String pozisyon = "Mudur";public Mudur () { // yapılandırıcı pozisyon = "Mudur" ; }public void calis() { // iptal etme (override) System.out.println("Mudur Calisiyor"); }}class Programci { public String pozisyon = "Programci";public Programci() { // yapılandırıcı pozisyon = "Programci" ; }public void calis() { // iptal etme (override) System.out.println("Programci Calisiyor"); }}class Pazarlamaci { public String pozisyon = "Pazarlamaci"; public Pazarlamaci() { // yapilandirici pozisyon = "Pazarlamaci" ; }public void calis() { // iptal etme (override) 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] ; //aşağıya çevirim c.calis(); } else if ( o[i] instanceof Mudur ) {Mudur m = (Mudur) o[i] ; //aşağıya çevirim m.calis(); } else if ( o[i] instanceof Programci ) {Programci p = (Programci) o[i] ; //aşağıya çevirim p.calis(); } else if ( o[i] instanceof Pazarlamaci ) {Pazarlamaci paz = (Pazarlamaci) o[i]; //aşağıya çevirim paz.calis(); } //... } } public static void main(String args[]) { Object[] o = new Object[4];o[0] = new Calisan(); // yukarı cevirim (upcasting) o[1] = new Programci(); // yukarı cevirim (upcasting) o[2] = new Pazarlamaci(); // yukarı cevirim (upcasting) o[3] = new Mudur(); // yukarı cevirim (upcasting) 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. (yorum ekle)
Programci CalisiyorPazarlamaci CalisiyorMudur Calisiyor
IsYeriNon.java örneğimizi nesneye yönelik
programlama çerçevesinde tekrardan yazılırsa (yorum ekle)
Örnek: IsYeri.java (yorum ekle)
class Calisan { public String pozisyon="Calisan" ; public void calis() {}}class Mudur extends Calisan { public Mudur () { // yapılandırıcı pozisyon = "Mudur" ; }public void calis() { // iptal etme (override) System.out.println("Mudur Calisiyor"); }}class Programci extends Calisan {public Programci() { // yapılandırıcı pozisyon = "Programci" ; }public void calis() { // iptal etme (override) System.out.println("Programci Calisiyor"); }}class Pazarlamaci extends Calisan {public Pazarlamaci() { // yapılandırıcı pozisyon = "Pazarlamaci" ; }public void calis() { // iptal etme (override) 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(); // !Dikkat! } } public static void main(String args[]) { Calisan[] c = new Calisan[4];c[0] = new Calisan(); // yukarı cevirim gerekmiyor c[1] = new Programci(); // yukarı cevirim (upcasting) c[2] = new Pazarlamaci(); // yukarı cevirim (upcasting) c[3] = new Mudur(); // yukarı cevirim (upcasting) 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. (yorum ekle)
Programci CalisiyorPazarlamaci CalisiyorMudur Calisiyor |
Bu uygulamadaki sınıflara ait UML
diyagramı aşağıdaki gibidir.

Şekil-6.2. Kullanılan sınıf
yapısı
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; (yorum