Sunday, July 23, 2017

Java'da Nesne Yaratmak Üzerine Bir Örnek

Bu yazıda Java'da nesne yaratılırken mutfakta, arka tarafta neler olduğunu inceleyeceğiz. Bu amaçla hazırlanmış örnek Java koduna göz atalım önce:

package com.example;

public class Question {

    public static void main(String[] args) {
        B b = new B();
    }
}

class A {

    private final D d = new D();

    public A() {
        System.out.println("A's constructor");
    }
}

class B extends A {

    private final E e = new E();

    public B() {
        System.out.println("B's constructor");
    }
}

class C {

    public C() {
        System.out.println("C's constructor");
    }
}

class D {

    public D() {
        System.out.println("D's constructor");
    }
}

class E {

    private final C c = new C();

    public E() {
        System.out.println("E's constructor");
    }
}

Yukarıda verilen kod incelendiğinde sınıflar arasında iki tür ilişki olduğu kolaylıkla görülebilir:
  • Kalıtım İlişkisi
B ile A sınıfları arasında kalıtım ilişkisi bulunuyor. B sınıfı kalıtım yolu ile A'nın tüm özelliklerine sahip olur.
  • İçerme İlişkisi
A sınıfı ile D sınıfı arasında bir içerme ilişkisi bulunuyor. Buna göre A sınıfı üye olarak D sınıfından bir üyeyi nesne olarak içerir. A ile D sınıfları arasındaki içerme ilişkisinin bir benzerini, B ile D sınıfları ve E ile C sınıfları arasında bulunduğunu görüyoruz.
Yukarıdaki örnek kodda yer alan sınıflar arasındaki ilişkileri basitçe Unified Modeling Language (UML) sınıf çizgesi (=class diagram) kullanarak özetleyebiliriz:

Örnek koda ait UML sınıf çizgesi
Şimdi B b= new B() satırında Java Sanal Makinası tarafından sırasıyla hangi işlemlerin gerçekleştirildiğini ve bu işlemler sırasında bellekte oluşan yerleşimi saptamaya çalışalım.

İlk olarak yerel referans değişkeni olan b değişkeni için yığında yer ayrılır. Başlangıçta b değişkeni bellekte geçerli bir B sınıfı nesnesinin referansını taşımıyor. new operatörü ile B sınıfı nesnesi bir dizi işlem sonrasında yaratılacak. new operatörü yarattığı nesnenin referansını dönecektir. Atama operatörü ile bu referans b etiketli göze yazılacaktır.

Java'da tüm nesneler Heap'de yaratılır. Bu nedenle bu nesnelere Heap nesnesi de denir. B tipinden nesneyi Heap'de yaratmaya başlayalım. Bir sınıftan nesne yaratılırken sadece öznitelikleri için bellekten yer ayrılır. B sınıfı yalın bir sınıf değil, A sınıfından türetilmiş durumda. Bu durumda aşağıdaki işlemler sırası ile çalıştırılır:
1. A sınıfından gelen veriler için bellekte yer ayrılır.
Önce d referans değişkeni için Heap'de yer ayrılır. 
2. A sınıfının kurucu fonksiyonu çalışır.
A sınıfının kurucu fonksiyonunun çalışmasını sağlayan satır B'nin kurucu fonksiyonunda gözükmeyen super() çağrısıdır! Türetilmiş sınıf kurucu fonksiyon ilk iş olarak temel sınıfın kurucusu fonksiyonunu super anahtar kelimesi ile çağırmak zorundadır. 
3. B sınıfından gelen veriler için bellekte yer ayrılır.
4. B sınıfının kurucu fonksiyonu çalışır.

d referansı D sınıfıdan bir nesneye referans ediyor. d referans değişkeni tanımlanırken hemen D sınıfından nesneye referans edecek şekilde tanımlanmış: private final D d= new D. D sınıfından nesne yaratılırken D sınıfının kurucu fonksiyonu çalışır ve ekrana ilk mesaj çıkar: D's Constructor. Artık d referansı D sınıfından nesneyi gösteriyor. 
A sınıfından gelen öznitelikler için yer ayrıldıktan ve üyeler ilklendirildikten sonra nihayet A sınıfının kurucu fonksiyonu çalışır. Şu ana kadar ekranda iki satırlık bir çıktı oluştu:
D's Constructor
A's Constructor

Şimdi ise B sınıfından gelen öznitelikler için yer ayrılır. B sınıfı içinde E sınıfından bir referans yer alıyor. Önce e referansı için Heap'de yer ayrılır.

e referans değişkeni hemen E sınıfından bir nesneye referans edecek şekilde tanımlanmış: private final E e= new E(). Bu nedenle E sınıfından bir nesne yaratılır. E sınıfı içinde C tipinden bir referans yer alıyor: c. Önce bu referans değişkeni için yer ayrılır. 

c referans değişkeni hemen  C sınıfı nesnesine referans edecek şekilde tanımlanmış: private final C c = new C(). Bu nedenle, C sınıfından bir nesne yaratılır ve yaratılan nesnenin referansı c'ye kopyalanır. Böylelikle c referansı yeni yaratılan nesneye referans edecek şekilde adresine sahip olur. Bu işlemlerden sonra ekranda üç satırlık bir çıktı görüyoruz:
D's Constructor
A's Constructor
C's Constructor



E sınıfı içindeki referanslar yaratıldıktan ve ilklendirildikten sonra E sınıfı kurucu fonksiyonu çalışır:
D's Constructor
A's Constructor
C's Constructor
E's Constructor
e referansı artık E sınıfından yaratılan nesneye referans ediyor.

Artık B sınıfındaki e referansı da yaratıldı ve ilklendirildi. Şimdi son olarak sıra B sınıfı kurucu fonksiyonuna geldi. En son B sınıfının kurucu fonksiyonu sahne alır:
D's Constructor
A's Constructor
C's Constructor
E's Constructor
B's Constructor
Yaratılan sınıf ne kadar karmaşık olursa olsun, diğer sınıflarla kalıtım ya da içerme ilşkisi gibi ne tür ilişkisi olursa olsun, her zaman en son yaratılan sınıfın kurucu fonksiyonu çalışır! Örneğimizde en son B sınıfının kurucu fonksiyonu çalıştı!

Yukarıda yazının başında verdiğimiz kodda derleyici birkaç yerde araya girer ve kodu değiştirir. İşte gerçekte derlenen kod aşağıda verilmiştir:

package com.example;

public class Question {

    public static void main(String[] args) {
        B b = new B();
    }
}

class A extends Object {

    private final D d = new D();

    public A() {
        super();
        System.out.println("A's constructor");
    }
}

class B extends A {

    private final E e = new E();

    public B() {
        super();
        System.out.println("B's constructor");
    }
}

class C extends Object {

    public C() {
        super();
        System.out.println("C's constructor");
    }
}

class D extends Object {

    public D() {
        super();
        System.out.println("D's constructor");
    }
}

class E extends Object {

    private final C c = new C();

    public E() {
        super();
        System.out.println("E's constructor");
    }
}

Buna göre UML sınıf çizgesini güncellememiz gerekir:

Gerçek UML sınıf çizgesi

Son olarak içerme ilişkisinin uç bir örneğini inceleyeceğiz:

package com.example;

public class CircularComposition {

    public static void main(String[] args) {
        F e = new F();
    }
}

class F {

    private final F f = new F();

    public F() {
        System.out.println("F's constructor");
    }
}

Bu örnekte F sınıfı kendi tipinden bir nesne içeriyor. Dil bu tanıma izin veriyor. Ancak F sınıfından bir nesne yaratmaya çalışırsanız, çalışma zamanında StackOverflow hatası ile karşılaşırsınız. F nesnesi yaratılırken F tipinden bir referans değişkeni olan f için bellekte yer ayrılır ve hemen ardından private final F f= new F() tanımı nedeni ile new operatörü yeniden yep yeni bir F nesnesi yaratmak için yola koyulur ve daha sonra tekrar bir F nesnesi ve sonra tekrar bir F nesnesi ve sonra tekrar ...

No comments:

Post a Comment