ÖNEMLİ : Kendim için aldığım notlar. Umarım size de bir faydası olur. Kullanılan her bir makale referans olarak eklenmiştir.

Java’da Kalıtım(inheritance) ve Java’da Polimorfizm Serisi


  1. Java’da Polimorfizm 1 - Amaç?
  2. Java’da Polimorfizm 2 - İzlenecek Kurallar Nelerdir?
  3. Java’da Polimorfizm 3 - Casting
  4. Java’da Polimorfizm 4.1 - Statik ve Dinamik Bağlanma 1
  5. Java’da Polimorfizm 4.2 - Statik ve Dinamik Bağlanma 2
  6. Java’da Polimorfizm 4.3 - Dinamik Bağlanma Örnek
  7. Java’da Polimorfizm 5 - Soyut(Abstract) Sınıflar ve Arayüzler(Interfaces)

Polimorfizm Nedir?

Polimorfizm ismi daha çok biyolojik bir terimdir. Polimorfizm biyolojide, iki veya daha fazla farklı fenotipin aynı tür popülasyonunda bulunması anlamına gelmektedir. Basitçe söylemek gerekirse polimorfizm, bir gen üzerinde iki veya daha fazla özelliğin olması ihtimalidir. Örneğin, bir jaguarın cilt rengi açısından birden fazla olası özelliği vardır; açık biçimde veya koyu biçimde olabilirler. Bu çeşitlilik, gen için birden fazla olası varyasyona sahip olduğundan ‘polimorfizm’ olarak ifade edilir. Türkçe çevirisine çok biçimlilik de denilebilir.

Fakat programlama dillerinde ve tür teorisinde polimorfizm, farklı türdeki varlıklara tek bir arayüzün sağlanması veya birden fazla farklı türü temsil etmek için tek bir sembolün kullanılmasıdır. Bir sınıfın alt sınıfları, kendi benzersiz davranışlarını tanımlayabilir, ve yine de üst sınıfın aynı işlevlerinden bazılarını paylaşabilir.

Bu bölümde polimorfizm konusunu ayrıntılı olarak incelemeye çalışacağız. Java dilinde polimorfizm, kalıtımla birlikte sahip olduğumuz en güçlü özelliklerden biridir. Kalıtım konusunu önceki 10 bölüm boyunca etraflıca ele almıştık. Aslında birçok kez polimorfizm kavramına da değindik. Fakat tanımsal olarak ilk kez burada ele alacağız.

Hatırlarsanız kalıtım konusunun 9.bölümü sonunda, aşağıdaki ifadeyi verip, sebebini polimorfizm konusuna geldiğimizde ele alacağımızı söylemiştim.

1
2
Person s = new Student("Hasan", 1234);
System.out.println(s);

Yukarıdaki örnekten de görüleceği üzere, referans türü Person üst-sınıfı (superclass) olan ve nesne türü Student alt-sınıfı olan bir objemiz bulunmaktadır. Burada yaptığımız aslında bir polimorfizmdir. Aşağıdaki şekili bir çok kez ele almıştık. Person üst sınıfını miras alan Student (öğrenci) ve Faculty (öğretim üyesi) sınıfları, aslında her bir öğrenci ve öğretim üyesinin özünde bir Person (kişi) olduğunu ifade etmektedir. Person sınıfı sayesinde daha kapsayıcı bir üst sınıf elde etmiş oluyoruz. Yalnız yazılım dünyasında bu, polimorfizm tanımı için tek başına yeterli değildir. Yani kapsayıcı üst sınıfı kastediyorum! Polimorfizmi anlamak için, "metotların geçersiz kılınması (overriding)" konusunu da ele almamız, ve hatta beraber değerlendirmemiz gerekmektedir.


Java Method Overriding(java metot geçersiz kılma(ezme))

Kalıtım konusunun 9.bölümünde verdiğim tanımı burada yinelemek istiyorum.

Robert C. Martin'in Polimorfizm Tanımı:


Polimorfizm konusu overriding konusu ile iç içe bir konudur ve beraber değerlendirmek çok önemlidir. Robert C. Martin‘in polimorfizm için yaptığı şu tanımı sizinle paylaşmak istiyorum.

The lowest implementation of the method down the hierarchy is automatically invoked, that’s the definition of the polymorphism in JAVA. (Yöntemin hiyerarşide en aşağıda bulunan uygulaması otomatik olarak çağrılır, bu da JAVA’daki polimorfizmin tanımıdır.)

Burada en aşağıdaki uygulamadan(lowest implementation) kasıt override edilen metottur. Alt sınıfta bulunan bir metot üst sınıfta bulunan benzer bir metodu geçersiz kıldığında haliyle o yöntemin hiyerarşideki en alt uygulaması olur.

Not: Daha da ilerlemeden önce, referans ve nesne türleri konusuna da göz gezdirmemizde yarar var.

Kalıtım konusunun 9. bölümünde toString() metodunun geçersiz kılınmasını, Student sınıfına kadar ilerletmiştik. Buna ek olarak, Faculty sınıfında da bu metodu geçersiz kılacağız. Amacımız, polimorfizm kavramının ne olduğunu, ve neden yazılımda kullanıldığını anlatmak olacak. Bu arada Faculty sınıfına ek olarak, employeeID üye değişkeni, ve bu private üye değişkenini çağıran bir de getter metodunu ekledik.

Kalıtımın temel hedeflerini belirlerken, tüm nesneleri tek bir veri yapısında tutmakla ilgili bir şeyden bahsetmiştik. Dilerseniz ilgili konuya geri dönüp bakabilirsiniz. Bu, farklı türdeki birden çok nesneyi işaret eden tek bir veri yapınız olabilir.

Java’da polimorfizm’e bir örnek

Hem Student, hem Faculty hem de Person objelerini tek bir veri yapısı olan Person veri yapısında saklamış olduk. Çünkü hem Student hem de Faculty özünde bir Person‘dır. Tabii ki is-a ilişkisinden ötürü bunu söylüyorum.

Java'da polimorfizm'e bir örnek:


1
2
3
4
5
6
7
8
9
10
11
12
public class Person {
    private String name;
    public Person( String n ) {
        this.name = n;
    }
    public String getName() {
        return name;
    }
    public String toString(){
        return this.getName();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Student extends Person {
    private int studentID;
    public int getSID() {
        return studentID;
    }
    public Student (String n, int m) {
        super(n);
        this.studentID = m;
    }
    public String toString(){
        return this.getSID()+": "+this.getName();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Faculty extends Person {
    private String employeeID;
    public String getEmployeeID() {
        return employeeID;
    }
    public Faculty(String n, String m){
        super(n);
        this.employeeID = m;
    }
    public String toString(){
        return this.getEmployeeID()+": "+super.toString();
    }
}

p[i] ekrana yazdırılırken, toString() metodunu ekstradan çağırmamız gerekmediğini önceki bölümlerde belirtmiştim. Normalde bunu, p[i].toString() şeklinde de ifade edebilirdik fakat println metodu toString() metodunu hâlihazırda çağırdığı için, sadece p[i] şeklinde çağırmak da aynı sonucu verecektir.

Burada önemli olan soru şu!!! Size göre p[i] ekrana yazdırılırken, Person sınıfının toString() metodu mu?, Student sınıfının toString() metodu mu? yoksa Faculty sınıfının toString() metodu mu çağrılır? Cevabı aslında önceki bölümlerde kısmen vermiştik.

1
2
3
4
5
6
7
8
Person[] p= new Person[3];
p[0] = new Person("Hasan");
p[1] = new Student("Bilal", 1234);
p[2] = new Faculty("Baran", "ABCD");

for (int i = 0; i < p.length; i++) {
    System.out.println(p[i]);
}


Referans ve Nesne Tipleri, ve bu konuyu peşi sıra takip eden bölümlerde, aslında bu sorunun cevabını etraflıca incelemeye çalıştım fakat polimorfizm tanımını ilk olarak burada dile getiriyorum.

Görüleceği üzere referans tipleri tek bir veri yapısını temsilen Person sınıfıdır. Bu sınıf diğer sınıfları kapsayıcı bir üst sınıftır. Student ve Faculty sınıfları bu sınıfı miras aldığı için is-a ilişkisi kapsamında, Student ve Faculty özünde bir Person diyebiliyorduk ama her kişinin (yani Person’ın) bir öğrenci (Student) veya bir öğretim üyesi (Faculty) olduğunu söyleyemiyorduk. Bu yüzden Person sınıfını, Student ve Faculty nesnelerini tek bir veri yapısında tutan ortak bir sınıf olarak belirleyebildik.

Yalnız şöyle bir ayrıntıdan bahsetmiştik. Referans tipleri, derleme zamanı kararları alınırken, obje/nesne tipleri ise, çalışma zamanı kararları alınırken devreye giriyordu. Şayet yukarıdaki kod bloğunu çalıştırmasaydık, ve derleme zamanında hangi toString() metotları devrede olurdu deseydiniz, hepsi için Person sınıfınınki diyebilirdik. Yalnız program çalıştığında referans tipleri, heap alanında temsil ettiği objeler ile eşleşir. Ama bu, toString() metodunun, hemen p[0] için Person‘daki toString()metoduna, p[1] için Student‘daki toString()metoduna, p[2] için ise Faculty‘deki toString()metoduna, gideceği anlamına gelmez.

Metot Geçersiz Kılmanın (Overriding) Polimorfizm İle İlişkisi

Tam da bu noktada metot geçersiz kılma(overriding) işlemi, polimorfizm kavramını yazılım tarafında tamamlıyor. Burada dinamik polimorfizm (dynamic polymorphism) olarak bilinen bir kavram devreye girer. JVM öncelikli olarak, bu metotların alt sınıflarda geçersiz kılınıp kılınmadığına bakar. Şayet ilgili metot geçersiz kılınmışsa doğrudan geçersiz kılındığı sınıftaki metoda gider. Aksi halde şansını referans tipinin sınıfındaki metotta deneyecektir. Şayet referans tipinin, heap tarafında hangi nesne türüne sahip olduğunu biliyorum diyorsanız, “casting” yaparak sorumluluğu derleyiciden alırsınız. “Casting” konusunu ilerleyen bölümlerde ele aldığım için, burada sadece değinmekle yetiniyorum.

Stack alanındaki referanslar, çalışma zamanında heap alanındaki hangi objeye bağlanacağını bilir. Ama her zaman nesne(dinamik) türüne göre ilgili yöntem çağrılmaz. Şayet overriding işlemi yoksa, referans türü neyse o sınıf içindeki metot çağrılır.

Bu örnekteki toString() metodu, alt sınıflarda geçersiz kılındığı için aşağıdaki sonucu alırız;

  • p[0] referansı Person sınıfının toString() metodunu kullanır,
  • p[1] referansı Student sınıfının toString() metodunu kullanır,
  • p[2] referansı Faculty sınıfının toString() metodunu kullanır,
1
2
3
Hasan
1234: Bilal
ABCD: Baran


Java sanal makinesi (JVM)”, her değişkende referans alınan nesne için uygun yöntemi çağırır. Değişkenin türü tarafından tanımlanan yöntemi çağırmaz. Bu davranış, sanal yöntem çağırma(virtual method invocation) olarak adlandırılır, ve Java dilinde polimorfizm özelliklerinin önemli bir yanını gösterir.

Aslında bir sonraki bölümde bunun nasıl gerçekleştiğini, derleyici ve çalışma zamanı kuralları kapsamında göreceğiz. Özetle polimorfizmin bize verdiği şey, tüm nesnelerimizi büyük bir koleksiyonda tutma yeteneğidir.

Hatırlatma: Dinamik olarak yazılan diller çalışma zamanında tür denetimi gerçekleştirirken, statik olarak yazılan diller derleme zamanında tür denetimi gerçekleştirir.

Referanslar