18 Mayıs 2011 Çarşamba

Nesnelerin İlklenmesi

İki önceki yazımızda🔎 programcının yeni bir sınıf tanımlamak yoluyla Java platformunu nasıl geliştirebileceğine ve sınıfların nesnelerinin nasıl yaratılıp kullanılabileceğine değinmiştik. Bu yazımızda da, nesnelerin yaratılmaları esnasında ne şekilde ilkleneceğine göz atacağız.

Java'da, diğer nesne paradigmasını destekleyen dillerde olduğu gibi, nesnenin yaratılması iki aşamada tamamlanır: i) nesne için yığın bellekten (İng., heap memory) yerin ayrılması, ii) ayrılan belleğin tutarlı değerlerle ilklenmesi ve yığın bellek dışındaki kaynakların elde edilmesi. Nesnenin yaşam döngüsünü başlatan bu iki adımın atomik olarak icra edildiği ve iki adımdan birinin başarısızlığa uğraması durumunda nesne yaratmanın mümkün olmayacağı unutulmamalıdır. Örneğin, sınırlı bir kaynak olan yığın belleğin tükenmesi olasılığına karşılık programcı, OutOfMemoryError hatasının oluşabileceğinin farkında olmalıdır. Benzer şekilde, ikinci aşamada elde edilmek istenen bir dış kaynağın elde edilememesi de ilkleme esnasında durumu özetleyen bir ayrıksı durumun atılmasına neden olabilir.

Yığın belleğin JSM'nin parçası olan bir bileşen tarafından ayrılması nedeniyle, nesne yaratmak isteyen programcının ilk aşamanın yerine getirilmesi için new işlecini kullanmak dışında bir şey yapması gerekmez. Buna karşılık, ilkleme ve dış kaynak elde edilmesinin söz konusu olduğu ikinci aşama sınıf gerçekleştirimcisinin sağladığı yapıcı metotlardan birinin çağrılması ile tamamlanır. İki aşama arasındaki bağlantı ise derleyicinin new işleci ve uygun bir yapıcıyı ilişkilendirilmesi ile sağlanır. Aşağıdaki örnekler üzerinden anlamaya çalışalım.
char[] karakterDizisi = new char[]{'D', 'i', 'z', 'i'};
String katar1 = new String(karakterDizisi);
String katar2 = new String(karakterDizisi, 0, 3);
İlk satırda oluşturulan dört karakterli dizi kullanılarak yaratılan her iki nesne de yerlerinin ayrılmasının ardından new işleci sonrasında sağlanan sınıftaki (String) yapıcılardan biri kullanılarak ilkleniyor. Buna göre, katar1 tutacağı tarafından temsil edilen nesne, char[] bekleyen yapıcıda dizi içindeki tüm elemanlar kullanılarak ilklenirken, katar2'nin gösterdiği nesne, ilk argümanında geçirilen karakter dizisinin ikinci argümanda verilen konumdan başlayarak üçüncü argümanda sağlanan sayıda karakteri kullanılarak bir başka yapıcıda ilkleniyor.

Genel çerçeveyi çizdikten sonra, nesne ilkleme bağlamında bilinmesi gereken diğer noktalara değinelim. Nesnenin kimi altalanlarına ilk değer sağlanmaması durumu ile başlayalım. İlişkin altalana ait belleğin o anki rasgele içeriğini ilk değer olarak kabul eden C++ dilinin aksine, Java'da ilklenmeyen altalanlar aşağıdaki tabloda verilen değerlere sahip olacak şekilde ilklenir.

Altalanların varsayılan ilk değerleri
Türİlk değer
Tamsayı türler0
Kayan noktalı türler0.0
char'\u0000'
booleanfalse
Bileşke türlernull

Buna göre, aşağıdaki sınıfın nesnesinin yaratılması durumunda _yaşadığıÜlke adlı altalan null değerine sahip olacaktır.
public class Vatandaş {
  public Vatandaş(String ad, String anneAdı, String babaAdı) {
    _ad = ad;
    _anneAdı = anneAdı;
    _babaAdı = babaAdı;
  } // yapıcı(String, String, String)
  ...
  private String _ad, _anneAdı, _babaAdı;
  private String _yaşadığıÜlke;
} // Vatandaş sınıfının sonu
Altalanların hiçbirine değer sağlanmaması halinde, gerçekleştirimci tarafından yapıcı metot yazılması pek anlamlı olmaz. Boş bir gövdeye sahip boş parametre listeli bir yapıcı ile kendini gösteren böylesine bir durumda derleyici araya girer ve gerçekleştirimciyi yapıcı metot yazma yükümlülüğünden kurtararak tüm altalanları varsayılan değerler ile ilkleyen bir yapıcı sentezler. Ancak, bu dost eli sadece yapıcı sağlanmadığı takdirde uzanacaktır; gerçekleştirimcinin sınıf tanımında bir veya daha fazla sayıda yapıcıya yer vermesi derleyicinin yapıcı sentezlemesinin önüne geçecektir.

İlkleme bağlamında bilinmesi yararlı olacak bir diğer konu, ilkleme bloklarının kullanımıdır. Yukarıdaki örneği ele alarak anlamaya çalışalım. Vatandaş sınıfının Türkiye'de kullanılacak olduğunu düşündüğümüzde, yaratılacak nesnelerdeki _yaşadığıÜlke adlı altalanın çoğunlukla "Türkiye" olacağı öngörülebilir. Dolayısıyla, derleyicinin bileşke türler için kullandığı null yerine "Türkiye" değerli bir karakter katarının varsayılan ilk değer olması daha yerinde olacaktır. İşte bu gözlemimizi, derleyicinin kullanacağı varsayılan ilk değerlerin değiştirilmesi için her nesne yaratılması noktasında, nesnenin ilklenmesi öncesinde işlenen bir nesne ilkleme bloğu içinde belirtmemiz gerekir. Aşağıdaki değiştirilmiş sınıf tanımı bunun nasıl yapılabileceğini gösteriyor.
public class Vatandaş {
  // Nesne ilkleme bloğu.
  { 
    System.out.println("Nesne yaratılıyor...");
    _yaşadığıÜlke = "Türkiye";
  }
  public Vatandaş(String ad, String anneAdı) {
    _ad = ad;
    _anneAdı = anneAdı;
  } // yapıcı(String, String)

  public Vatandaş(String ad, String anneAdı, String babaAdı) {
    _ad = ad;
    _anneAdı = anneAdı;
    _babaAdı = babaAdı;
  } // yapıcı(String, String, String)

  public Vatandaş(String ad, String anneAdı, String babaAdı, String ülke) {
    _ad = ad;
    _anneAdı = anneAdı;
    _babaAdı = babaAdı;
    _yaşadığıÜlke = ülke;
  } // yapıcı(String, String, String, String)
  ...
  // Nesne ilkleme bloğu.
  { _babaAdı = "Mehmet"; }
  private String _ad, _anneAdı, _babaAdı;
  private String _yaşadığıÜlke;
} // Vatandaş sınıfının sonu
Dikkat ederseniz, metot gövdeleri dışında bulunan kıvrımlı ayraç çifti arasındaki komutlardan oluşan nesne ilkleme blokları, sınıfın herhangi bir yerine konulabileceği gibi tanım içinde birden çok kez de geçebiliyor. Ortaya çıkacak toplam etki, sınıfın başından sonuna doğru tüm blokların geçiş sırasına göre işlenmesi ile elde edilir. Dolayısıyla, yukarıdaki sınıfta geçen nesne ilkleme bloklarının etkisi aşağıdaki blokla da elde edilebilir.
{
  System.out.println("Nesne yaratılıyor...");
  _yaşadığıÜlke = "Türkiye";
  _babaAdı = "Mehmet";
}
İlkleme amacıyla kullanılabilecek bir diğer programlama aracı, söz konusu sınıfın ilk kullanılışı öncesinde bir kereliğine işlenen sınıf ilkleme bloğudur. Örneğin, aşağıdaki tanıma göre, Vatandaş sınıfının ilk nesnesinin yaratıldığı veya nesne yaratılmadan kullanılabilecek static öğelerinden birinin kullanıldığı nokta öncesinde standart çıktıya "Vatandaş'ın ilk kullanımı..." ve "Lütfen Vatandaş'ı özenle kullanınız..." mesajları basılacak, [daha sonra yaratılacak] nesnelerin paylaşacağı ortak bir özellik olan _vatandaşSayısı 1 ile ilklenecektir. Aynı sınıfın nesnesinin yaratıldığı her noktada işlenen nesne ilkleme bloğu sayesinde ise, çağrılan yapıcı metodun başlaması öncesinde, ilklenmekte olan nesnenin sabit değerli _vatNo altalanı _vatandaşSayısı'nın o anki değeri ile ilklenecektir.
public class Vatandaş {
  // Sınıf ilkleme bloğu.
  static {
    System.out.println("Vatandaş'ın ilk kullanımı...");
    System.out.println("Lütfen Vatandaş'ı özenle kullanınız...");
  }
  // Nesne ilkleme bloğu.
  { _vatNo = _vatandaşSayısı++; }
  ...
  public static long vatandaşSayısı() {
    return _vatandaşSayısı - 1;
  } // long vatandaşSayısı() sonu
  ...
  static { _vatandaşSayısı = 1; }
  private String _ad, _anneAdı, _babaAdı;
  private String _yaşadığıÜlke;
  private final long _vatNo;
  private static long _vatandaşSayısı;
} // Vatandaş sınıfının sonu