nesne ilkleme bloğu etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
nesne ilkleme bloğu etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

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

6 Mayıs 2011 Cuma

Java Platformunun Dikkate Değer Bir Dili: Scala-1

Başlık yazının konusunu ele veriyor: sınıf dosyasına derlenmek suretiyle JSM tarafından çalıştırılabilir programlar üretmekte kullanılabilecek Scala dili. .NET'in CIL komutları içeren DLL formatına da derlenebilen, fonksiyonel programlama kavramlarını destekleyen bu nesne yönelimli dil, Java'ya eklenmesi düşünülen pek çok kavram için bir laboratuar ve esin kaynağı olma özelliği taşıyor. Scala programlama diline dair dizimizin ilk bölümü olan bu yazıda da bu dili Java bilenlere tanıtmaya çalışacağız.


Basit Bir Scala Programı


Java'da olduğu gibi, Scala'da da her şey bir sınıf içine yazılmak zorundadır. Aslına bakılırsa, ilkel türleri de sınıflarla temsil eden Scala bu konuda Java'dan daha katıdır diyebiliriz. Bir diğer farklılık, ';' karakterinin pek çok C-temelli dil tarafından kabul edilen komut sonlandırıcılık görevindeki değişikliktir. Satır sonlarının komut sonlandırma noktası olarak algılanması nedeniyle, birden fazla komutun aynı satıra konulmaları durumunda kullanılması dışında, Scala'da komut sonlarına ';' yerleştirilmesine gerek yoktur.

SelamMillet.scala
object SelamMillet {
  def main(args: Array[String]) =
    println("Selam millet!")
} // SelamMillet sınıfının sonu
Yukarıda verilen kaynak kodda main metodunun dönüş türünün eksik olduğunu düşünüyorsanız, Scala'nın bir diğer özelliğini söyleyerek yanıt verelim: Scala, ML ve Haskell programlama dillerinde olduğu gibi, program metninin sağladığı bilgilerden yararlanarak programlama öğelerinin türlerini çıkarsamaya çalışır. Bundan dolayı, Scala programcılarının derleyicinin çıkarsayamadığı yerler dışında hiçbir yere tür bilgisi koymasına gerek yoktur. Derleyici tarafından kabul görmekle birlikte, main metodunun imzasının aşağıdaki şekilde yazılması gerekmez.
def main(args: Array[String]): Unit = 
Örneğimizde sınıf kavramının izlerini arayanlar, kendilerini ikna etmekte zorlanabilirler. Zira, sınıf sözcüğü (class) yerini nesne sözcüğüne (object) bırakmıştır. Sınıfın bir şablon, nesnenin ise söz konusu sınıfın bir örneği olduğunu bilenleriniz bilgilerinden kuşku duymaya başladıysalar üzülmesinler, her şey eskiden olduğu gibi. Beklenen class sözcüğü yerine object sözcüğünün olması, tanımlanmakta olan sınıfın bir özelliğini yansıtmaktadır: Java terminolojisi ile açıklayacak olursak, SelamMillet sınıfının yegâne metodu olan main static'tir. Bir diğer deyişle, main metodu, SelamMillet sınıfının nesnesi yaratılmadan kullanılabilir. Nesne paradigması ile bağdaşmayan bu durum—problem çözümünde merkezi rol oynayan nesnenin bırakın kullanılmasını, yaratılması bile söz konusu değil—kimi zaman çalıştırılabilir sınıfın tek bir nesnesinin yaratılıp bu nesneye ileti gönderilmesi yoluyla aşılır. İşte, tasarım desenleri dünyasında tek örnek[li sınıf] (İng., singleton [class]) deseni olarak adlandırılan bu kullanım, Scala'da object anahtar sözcüğünün kullanımı ile belirtilir. Bu şekilde tanımlanmış sınıfların, tüm öğelerinin static olduğu varsayılır. Bundan dolayıdır ki, Scala'da static anahtar sözcüğüne yer yoktur, programcı bu tür özellikleri tek örnekli sınıflar içinde toplayarak bir object olarak tanımlamalıdır.

Kaynak kodun yazılması sonrasında yapılacaklar Java'daki ile aynıdır: ilk adımda sınıf dosyasına çevrilerek derlenen kod, JSM tarafından çalıştırılmalıdır.
# Derleme aşaması
$ scalac -encoding utf-8 SelamMillet.scala
$ ls SelamMillet*
SelamMillet.class   SelamMillet$.class   SelamMillet.scala
# Yorumlama aşaması
$ scala SelamMillet
Selam millet!
Yukarıdaki listelemenin iki noktası dikkatinizi çekmiştir. Öncelikle, Scala programlama dilinin derleyicisi olan scalac komutunun ürettiği iki tane sınıf dosyası vardır. Bunlardan, SelamMillet.class uygulama dosyası iken, SelamMillet$.class tek örnekli sınıf deseninin gerçekleştirildiği kodu içerir. İkinci nokta ise, JSM'nin java yerine scala komutuyla çağrılmasıdır. Bu, her programlama dili için JSM'nin ayrı ayrı gerçekleştirilmesi gerektiği yanılgısına neden olmamalıdır. Çünkü, scala komutu java komutunun Scala'ya has sınıf dosyalarını yükleyerek işini gören uyarlaması olarak düşünülebilir. Dolayısıyla, yorumlama aşması aşağıdaki şekilde de yapılabilir.
# Yorumlama aşaması
$ java -cp .:/usr/share/java/scala-library.jar SelamMillet
Selam millet!
Bunun güzel bir sonucu, çöp toplama, güvenlik denetimleri, anında derleme gibi JSM tarafından sağlanan pek çok hizmetin Scala programları için de otomatikman sağlanmasıdır; Scala dilinin (ve diğer JSM üzerinde çalışan dillerin) geliştiricilerinin bu hizmetleri ayrıca gerçekleştirmelerine gerek yoktur.

Java Programlama Dili Sınıflarının Kullanımı


JSM dili olmaları nedeniyle, gerek Java gerekse Scala, kurulumunuzun sınıf yolu üzerindeki tüm sınıf dosyalarını, kaynak kodları hangi dilde yazılmış olursa olsun, kullanabilir. Scala programcısı açısından bakıldığında bu, Scala kaynak kodu içinden Java platformunun bildik tüm işlevselliğinden sanki Scala'da yazılmış gibi yararlanılabileceği anlamına gelir. Bu, aşağıdaki örnekte verildiği gibi işlevselliği kullanmak şeklinde olabileceği gibi, platformdaki bir sınıftan kalıtlama veya bir arayüzü gerçekleştirme şeklinde de olabilir. Ancak, Java'dan gelip Scala kaynak kodu üretenlerin yararlanılacak işlevi Scala sözdizimi ile kullanmaları gerektiğini akılda tutmaları gerekir. Aşağıdaki örnek üzerinden görelim.1

Açıklamalara Scala'nın Java ile farklılık gösteren bir özelliğine dikkat çekerek başlayalım: dosya içinde dosya ile aynı ada sahip bir sınıfın bulunması gerekmez; Scala'da böyle bir zorunluluk yok. Ancak, daha sonraki derleme/çalıştırma komutlarında örneği verildiği gibi, derleyiciye geçirilenin dosya adı iken yorumlayıcıya geçirilenin sınıf dosyasının adı olduğu unutulmamalıdır.

Örneğimizin Scala'ya yeni gelenler için dikkat çeken bir yönü, import bildiriminin ilk satırdaki seçimli kullanımı.2 Tekli ve jokerli3 kullanımlara sahip bu bildirim, örneğimizde de olduğu gibi, bir veya daha fazla sayıda türün görünür kılınmasında da kullanılabilir. Dikkat çekecek bir diğer anahtar sözcük ise, simgesel sabit tanımlamak için kullanılan val.4

Örnek2.scala
import java.util.{Scanner, Vector}

object ScaladanJava {
  val grdKnl = new Scanner(System.in);
  println("İkinci Scala örneğine hoş geldiniz...")

  def main(args: Array[String]) = {
    val tekSayılar = new Vector[Int]()
    print("Artı bir tamsayı giriniz: ")
    val j = grdKnl.nextInt()
    for(i <- 1 to j by 2) tekSayılar.add(i)
    println("1 ile" + j + " arasındaki tek sayılar: " + tekSayılar)
  }

  println("Bu örnek Scala kodundan Java'da yazılmış işlevselliği kullanmaya dönük")
} // ScaladanJava sınıfının sonu
Metot gövdeleri dışındaki komutların varlığı da sizi telaşa sürüklemesin; bu komutların sınıf içindeki sıraları korunacak şekilde sınıf ve/veya nesne ilkleme bloğu içine konulduğunu düşünmeniz işinizi kolaylaştıracaktır. Tek örnekli bir sınıf olması nedeniyle tüm öğeleri static varsayılan sınıfımızda, metot tanımları dışındaki her şey sınıf ilkleme bloğuna konulup uygulamanın çalıştırılması sonrasında ilk iş olarak işlenirken, new işleci ile nesnesi yaratılabilen çok örnekli sınıflarda bu tür öğeler nesne ilkleme bloğuna konulacak ve sentezlenen bu nesne ilkleme bloğu her nesne yaratılışı noktasında yapıcı çağrısı öncesinde işlenecektir.
$ scalac -encoding utf-8 Örnek2.scala
$ scala ScaladanJava
İkinci Scala örneğine hoşgeldiniz...
Bu örnek Scala kodundan Java'da yazılmış işlevselliği kullanmaya dönük
Artı bir tamsayı giriniz: 16
1 ile 16 arasındaki tek sayılar: [1, 3, 5, 7, 9, 11, 13, 15]

Diziler Soysal Bir Türün Örneğidir


Verdiğimiz örneklerde komut satırından geçirilen argümanları tutmak için kullanılan dizi ve ikinci örneğimizdeki Vector nesnesinden de görülebileceği gibi, Scala'da soysallık tür parametrelerine karşılık gelen tür argümanlarının köşeli ayraç çifti arasında geçirilmesiyle sağlanır. Buna göre Vector[Int], Int türlü elemanlara sahip bir kabın kullanılacağını gösterir. Bu tanım sonrasında, söz konusu kap parametrik türün (java.util.Vector) sağladığı tüm işlevsellikten yararlanarak manipüle edilebilir. Bu, soysal Array türünün örneği olan diziler için de geçerlidir. Tanımlanan bir dizi, Array sınıfınca sunulan tüm işlevsellikten yararlanabilecektir.


  1. Gözünüzden kaçmış olabilir, işaret etmekte yarar var: ilk örneğimizin aksine ikinci örneğimizin metot gövdesi kıvrımlı ayraç çifti arasına yazılmıştır. Bunun sebebi, tek komuttan oluşması durumunda Scala'daki metot gövdelerinin, tıpkı Java'daki if, while yapılarının gövdeleri gibi, kıvrımlı ayraç çifti arasına yazılmalarının zorunlu olmamasıdır.
  2. java.lang paketindeki türler, herhangi bir bildirime gerek olmadan Scala derleyicisi tarafından görünür hale getirilirler.
  3. Söz konusu paketin tümünü görünür kılan jokerli kullanım, Java'da "*" ile belirtilirken Scala'da '_' ile belirtilir.
  4. Tanımlayıcının güncellenebilir bir içerik tutabilmesi için val yerine var ile nitelenmesi gerekir.