Ö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 Kalıtım 1 - Kalıtımı Neden Kullanırız? Kalıtımı Sağlamak İçin Asgari Şartlar Nelerdir?
  2. Java’da Kalıtım 2- Extends
  3. Java’da Kalıtım 3 - Referans ve Nesne Tipleri
  4. Java’da Kalıtım 3.1 - Static ve Dinamik Tür
  5. Java’da Kalıtım 4 - Görünürlük/Erişim Değiştiricileri
  6. Java’da Kalıtım 5 - Java’da Nesne Oluşturma
  7. Java’da Kalıtım 6 - Sınıf İnşası için Derleyici Kuralları
  8. Java’da Kalıtım 7 - Sınıf Hiyerarşisinde Değişken İlklendirme
  9. Java’da Kalıtım 8 - Alıştırmalar
  10. Java’da Kalıtım 9 - Overriding(Ezici) Metotlar
  11. Java’da Kalıtım 10 - Overloading(Aşırı Yükleme) Metotlar

Genel Bakış

Buradaki amacımız, sınıflar arasındaki “is-a” ilişkisinin ne anlama geldiğini anlamak ve bir önceki bölümde yarım bıraktığımız 3. koşulu yerine getirmek olacaktır.

Kodda tutarlılığı sağlamak ve veri yapısını tek bir sınıfta toplamak için hedefler:


  1. Bütün ortak davranışları bir sınıfta tutmak,
  2. Farklı davranışa sahip olanları ise farklı sınıflara ayırmak
  3. Tüm bu nesneleri tek bir veri yapısında tutmak.

Kodda tutarlılığı sağlamak ve veri yapısını tek bir sınıfta toplamak için ilk iki koşulu, bir önceki bölümde java’da extends anahtar kelimesi kullanarak sağlamıştık. Ortak kodları parent sınıfta, farklı kodları ise child sınıflarda tutarak gerekli koşulları yerine getirmiştik. Şimdi ise 3. koşulu anlamaya çalışalım.

Referans ve Nesne Türleri


java reference and object type, java is-a relationship (java referans ve obje tipi, java is-a ilişkisi)

Referans ve obje tipleri konusuna şu bölümde biraz değinmiştik. Devam etmeden önce göz gezdirmenizde yarar var.

NOT : Bu arada referans tipleri, deklare edilen tipler(declared type) olarak da tanımlanır.

Yukarıdaki şekili biraz yorumlamaya çalışalım istiyorum. Önceki bölümde belirttiğimiz gibi referans ve nesne türleri de her zaman aynı olmayabilir. Heap alanında oluşan nesnenin tipi ile, aynı nesneyi stack alanında temsil eden değişkenin, yani referansın tipi farklı olabilir. Yukarıdaki örnekte Person bir parent(ana) sınıftır. Student ve Faculty sınıfları ise birer child(çocuk) sınıftır ve Person sınıfını extends anahtar kelimesi ile miras almıştır.

Bu sebepten ötürü stack alanında bulunan değişkenin/referansın tipini parent sınıf olan Person yapabiliriz. Bir üst sınıf olduğu için stack alanında Student ve Faculty sınıflarını temsil edebilir. Yalnız önceki bölümde verdiğimiz Map/HashMap ilişkisine benzetmeyin. Çünkü Map bir soyut sınıftır. Person sınıfı her ne kadar bir parent sınıf olsa da, somut bir sınıftır. Somut sınıftan kasıt, Person sınıfının istenirse heap alanında somutlaştırabiliyor olmasıdır. Yani new anahtar kelimesi vasıtasıyla bu sınıftan bir obje oluşturabiliriz.

IS-A İlişkisi

Is-a‘nin kelime anlamı doğrudan yoktur ama bildiğiniz gibi is yardımcı bir fiildir. İngilizce okunuşuyla a ise bir anlamındadır.

  • A Person is a Person : Bir kişi bir kişidir.
  • A Student is a Student : Bir öğrenci bir öğrencidir.
  • A Student is a Person : Bir öğrenci bir bireydir.
  • A Person is a Student : Bir birey bir öğrencidir.

Not: person’ı bazen kişi bazen de birey çevirebilirim. Lütfen buna takılmayın.

İlk üçü okunduğundan kulağa gayet mantıklı gelmektedir. Fakat sonuncusunda mantıksal bir gariplik vardır. Gerçekten de her bir birey bir öğrenci midir???? Öğretim görevlisi de olabilir. Bu sebepten ötürü sonuncu mantıksal olarak yanlıştır. Yani Student s = new Person(); doğru bir is-a ilişkisine sahip değildir. Bu cümleyi şu şekilde de okuyabilirsiniz. new anahtar kelimesi ile somutlaşan her bir kişi(Person) aslında bir öğrenci(Student) midir? Tabii ki değildir.

Doğru ifade şekli tabii ki Person p = new Student(); şeklinde olur. Veyahut Person p = new Faculty(); şeklinde!!!

O halde somutlaştırdığımız her bir Faculty ve Student sınıfı nesnelerini, stack alanında Person sınıfını kullanarak temsil edebilirim.

1
2
3
4
Person[] p= new Person[3];
p[0] = new Person();
p[1] = new Student();
p[2] = new Faculty();

Yani bir Person dizisi hem Student hem de Faculty objelerini saklayabilir. Yazımın başında bahsettiğim son şartı da bu şekilde sağlamış olduk. Yani tüm bu nesneleri tek bir veri yapısında tutmaktan bahsediyorum. Hem Student, hem Faculty hem de Person objelerini tek bir veri yapısı olan Person veri yapısında(data structure) saklamış olduk.

Java’da IS-A İlişkisine Örnek

Şu ana kadar öğrendiklerimizi uygulayacak olursak, aşağıdaki kod çalıştırıldığında nerelerde hata alırız? Bir editörde çalıştırmadan, yani bakarak çözmeye çalışın istiyorum.

1
2
3
4
public class Person {
    private String name;
    public String getName() {return name;}
}
1
2
3
4
public class Student extends Person {
    private int id;
    public int getID() {return id;}
}
1
2
3
4
public class Faculty extends Person {
    private String id;
    public String getID() {return id;}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestFaculty {
    public static void main(String[] args) {
      Student s = new Student();
      Person p = new Person();
      Person q = new Person();
      Faculty f = new Faculty();
      Object o = new Faculty();

      String n = s.getName();
      p = s;
      int m = p.getID();
      f = q;
      o = s;


    }
}
  • Önce hata almadıklarımızdan başlayalım istiyorum. s değişkeninin(referansının) tipi Student sınıfıdır. Student sınıfı normalde getName() yöntemine sahip değildir fakat Person sınıfını miras aldığı için getName() metoduna ulaşabilir.

  • p = s; atama işlemini ele alacak olursak; p değişkeninin(referansının) tipi Person sınıfıdır. s değişkenini(referansını) zaten az önce ele almıştık. Burada yapılmak istenen şey, p değişkeninin(referansının) heap alanında temsil ettiği objeyi, s değişkeninin(referansının) heap alanında temsil ettiği obje ile değiştirmektir. Yani p referansı artık s referansının işaret ettiği objeyi işaret edecektir. Peki bu mümkün müdür? Hemen bakalım. Yapılmak istenen özünde şöyle bir işlemdir. Person p = new Student(); Yani bir is-a ilişkisi vardır. A Student is a Person…. Bir öğrenci bir kişi midir? Cevap: Evet. Her bir öğrenci aslında bir kişidir. Bu yüzden bu atamada bir sakınca yoktur.(Bu arada okumayı soldan sağa yapmadığımızı farkettiğinizi umuyorum. Cümleye new anahtar kelimesinin olduğu yerden başladım. Yani new Student() bir Person mıdır? Kafanız karıştıysa sorabilirsiniz)

  • int m = p.getID(); hata alırız, peki neden? Bildiğimiz gibi p değişkeninin tipi Person sınıfıdır. Person sınıfının da böyle bir metodu olmadığından hata mesajı alırız. Ama şunu söyleyebilirsiniz. İyi de biz bir önceki satırda p referansını/değişkenini artık heap alanında s referansının işaret ettiği Student objesine atamıştık. Artık sahip olması lazım!!!! diye düşünebilirsiniz. Evet tam olarak bunu yaptık. Fakat derleyici bunu bilmiyor. Derleyici bunu bir Person nesnesi olarak görüyor çünkü başlangıçta bu şekilde ayarlanmış. Derleyici p‘nin bir Student referası olduğunu ancak çalışma zamanında(run-time) görecektir. Çünkü referanslar ile ilgili kararlar derleme zamanında, objeler/nesneler ile ilgili kararlar ise çalışma zamanında alınır. Bu yüzden derleyici çalışma zamanına geçmeden obje tiplerini bilmeyecektir.

    Programı daha çalıştırmadığımız için, derleme sırasında hata almış olacağız. Haliyle java p referasını hâlen bir Person olarak düşünecektir. Alınan hata şu şekildedir. Cannot resolve method ‘getID’ in ‘Person’. Aslında derleme zamanında bu hata cast işlemi yapılarak çözülebilir. int m = ((Student) p).getID(); yaptığımızda derleme hatasını bir bakıma engellemiş oluruz. Ama amacımız burada hatanın ne olduğunu göstermek, çözüm yolu aramak değil. Yani Hemen altta derleme zamanı ve çalışma zamanı kararları başlığı altında bu konuya biraz değineceğiz.

    Not:


    Yalnız şunu da belirtmekte yarar var. Peki p referansı çalışma zamanına(runtime) geçtiğinde bu metodu görebilecek mi? Cevap hayır..

    Çünkü bir obje heap alanında oluşturulduğunda hem nesne tipinin instance değişkenlerini hem de parent sınıflarının instance değişkenlerini saklar.

    Fakat parent sınıfın tipiyle stack’den heap’teki bu objeye erişmek istediğimizde, stack’teki bu referans sadece kendi instance metot ve değişkenlerini ve parent sınıflarının instance metot ve değişkenlerini görür.

    Yani kendi üstündeki parent sınıfların bütün özelliklerini görür ama altındakileri göremez. Her ne kadar çalışma zamanında buradaki bir durum olsa da.. İllaki görmek istiyorsanız cast işlemini uygulamanız gerekmektedir. (Aslında bunu da bir şekil üzerinde anlatmam gerekiyor.) Her neyse bu konuya şimdilik burada bir nokta koymak istiyorum.

  • f = q; Şayet tekraradan is-a ilişkisinden yola çıkacak olursak, f değişkeni/referansı stack alanında Faculty tipinde bulunmaktadır. Heap alanında ise yine Faculty tipinde bir nesneye sahiptir. Ama burada f değişkeni, q değişkeninin referans aldığı nesneye eşitlenmek isteniyor. Bu arada q değişkeninin/referansının tipi Person sınıfıdır. Peki bu eşitleme mümkün mü? Yapılmak istenen özünde tam olarak şudur.. Faculty f = new Person(); is-a ilişkisindeki karşılığı A Person is a Faculty.. Yani her bir kişi bir öğretim üyesi midir? Tabii ki hayır. Mantıksal açıdan bu zaten mümkün değildir. Ama şunu da belirtmek isterim. Burada bir derleme hatası alıyoruz. Alınan hata tam olarak şudur. Error: java: incompatible types: Person cannot be converted to Faculty. Yani java ilk olarak, çalışma zamanında heap alanında olacaklardan çok, derleme zamanında var olanlara bakar. Burada da bir Faculty ile bir Person sınıfının atama işlemi vardır. Tam tersi olsaydı, yani Person f = new Faculty(); her bir öğretim üyesi bir kişidir diyebilirdik ama bu şekilde bir atama burada söz konusu değildir.

  • o = s; atama işlemini ele alacak olursak; o değişkeninin tipi Object sınıfıdır. s değişkenini zaten az önce ele almıştık. Şu bilgiyi sanırım herkes vakıftır. Java’da her nesne bir objedir. Yani her nesne Object sınıfını miras alır. Yani Object sınıfını dolaylı olarak miras alır(extends eder). is-a ilişkisinden yola çıkacak olursak, Object o = new Student(); yani her bir öğrenci bir objedir diyebiliriz.

Sonuç olarak aşağıdaki iki atama işlemi derleme zamanı hatası alır.

1
2
f = q;
int m = p.getID();

Aşağıda kodu bu linke ulaşarak da görüntüleyebilirsiniz. Derleme hatası aldığımız yerleri yorum satırı içine aldığımı farkedeceksiniz. Yorum satırını kaldırıp siz de deneyebilirsiniz.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class TestFaculty {
    public static void main(String[] args) {
      Student s = new Student();
      Person p = new Person();
      Person q = new Person();
      Faculty f = new Faculty();
      Object o = new Faculty();

      String n = s.getName();
	      p = s;
	      //int m = p.getID();
	      //f = q;
	      o = s;
	    }
	}

	class Person {
	    private String name;
	    public String getName() {return name;}
	}

	class Student extends Person {
	    private int id;
	    public int getID() {return id;}
	}

	class Faculty extends Person {
	    private String id;
	    public String getID() {return id;}
	}

Derleme Zamanı ve Çalışma Zamanı Kararları


java reference and object type, java compile time decisions and runtime decisions (java referans ve obje tipi, java derleme zamanı ve çalışma zamanı kararları)


Stack ve heap alanında görselleştirdiklerimizden anlaşılacağı üzere referanslar ön tarafta gerçekleşen işlemleri, objeler ise arka tarafta gerçekleşen işlemleri temsil etmektedir. Ön taraf için derleme zamanı, arka taraf, yani merkezde olan işlemler için ise çalışma zamanını düşünebilirsiniz.

1
2
3
4
5
6
7
8
9
public class SampleTest {

    public SampleTest(){
    }
    public int calculate(int m, int z){
        return m+z;
    }
    // ....
}
1
2
3
4
5
6
7
8
public class Sample {
    public static void main(String[] args) {
        SampleTest o = new SampleTest();
        o.calculate(1,2);
        Object o = new SampleTest();
        o.calculate(1,2);
    }
}

Yukarıdaki koda baktığımızda görünüşte bir sıkıntının olmadığını söyleyebilirsiniz. 3.satırda SampleTest tipinde bir nesne yaratılıyor ve bu nesnenin calculate() metodu çağırılarak işlem yapılıyor. Buraya kadar her şey normal fakat 5.satırda bir fark var. Burada referans tipi olarak Object sınıfı tercih edilmiş. Aslında gayet normal diyebilirsiniz. Çünkü şunu biliyoruz ki java’da her şey bir nesnedir ve Object sınıfını miras alır. Bu yüzden de bu satırda herhangi bir derleme hatası almayız. Asıl sorun 6.satırda gerçekleşecektir. Çünkü Java derleme zamanında referans tiplerine bakar. Referans tipimiz olan Object sınıfının da calculate() isminde bir metodu olmadığı için derleme hatası(compile time error) alırız. Fakat yukarıda da değindiğimiz gibi çalışma zamanına geçtiğimizde de bu metot(calculate) yine Object o tarafından görünür olmayacaktı. Görünür kılmak için cast işlemi uygulamak şarttır.

İleriki konularda referans türüne göre derleme zamanında hangi kararların alındığı ve nesne türüne göre çalışma zamanında hangi kararların alındığı hakkında konuşacağız. Ama öncesinde bu konuyla çok bağlantılı olan bir konuyu ele alacağız. Statik ve dinamik türler nelerdir? Bundan sonraki bölümde bu konuyu ele almak istiyorum.

Referanslar