8 Eylül 2011 Perşembe

Kategorize Etmenin Yararları ve Arayüzler

Her ne kadar Bülent Ortaçgil, beni kategorize etme, demişse de, kategorizasyon çevremizi anlamamızı kolaylaştıran ve sıklıkla başvurduğumuz çok önemli bir keşif aracıdır. Birbirlerine benzesin ya da benzemesin, değişik türden varlıkların ortak yönlerini gruplamamızı olanaklı kılarak soyutlama yapmamızı sağlayan bu araç, keşfimizin paylaşılabilir olması durumunda, kendimizi daha kısa yoldan anlatmamızı da olanaklı kılar. Örneğin, üçüncü bir şahsın obez olduğu konusundaki gözlemimizi paylaştığımızda, karşımızdaki kişi, hangi ulustan olursa olsun, konuşmaya konu olan kişinin saçının, gözlerinin ne renk olduğunu bilemese de nasıl vücut hatlarına sahip olduğunu kafasında canlandırılabilir. Çünkü, ortak tanımı üzerinde anlaşılmış—yani, standart bir tanımı bulunan—obez olma özelliği kişilerin ait olduğu grupları aşan bir özelliktir; birbirleriyle hiç alakası bulunmayan grupların üyeleri arasında ortak payda olarak kullanılabilir. Yani, kafamızın içindeki gri materyalin tasnif etme donanımı, biz istesek de istemesek de, diğer kişileri müstahdem, öğretmen, yönetici, vb. şeklinde sınıflandırdığı gibi, obez, zeki, işbilir, vb. şeklinde de kategorize etmektedir. Yeri geldiğinde, sınıflarla kategoriler bitiştirilmekte ve obez öğretmen, işbilir yönetici gibi yeni sınıflamalar da yapılabilmektedir.1

İşte bu yazımızda, Java programlama dilinin nesneler alemini tasnif etmekte sınıf ile birlikte kullanılabilecek ikinci üstkavramı olan arayüz kavramına değineceğiz. Ama ilk önce pedagojik senaryomuz. Yer ülkemizdeki liselerden birinin müdüriyeti ve uyuşturucunun ilk paketinin bedava olduğunu bildiği için bazı şirketlerce öğretim ordusunun üyelerine "bedava" dağıtılan programlama dillerini kullanmayı reddederek Java'yı tercih eden müdürümüz, yönetimindeki öğrenci ve öğretmenlerin performansını özetleyecek bir programı nasıl yazabileceğini düşünmekte. Müdürümüzün amacı, öğrenci ve öğretmenleri kendi aralarında uygun bir ölçüte göre sıralayıp listelemek. Çiziktirmelerle dolu sayfalara baktığımızda, müdürümüzün öğrencileri önce ortalamaları sonra—ortalamaların eşit olması durumunda—numaraları karşılaştırırak sıraya dizmeyi düşünürken, öğretmenleri önce öğrenci anketlerindeki konu yeterliliği notunun ortalamasını sonra ders yükünü karşılaştırırak sıraya dizmek istediğini görüyoruz.

Yordamsal programlama alışkanlıkları olduğu anlaşılan müdürümüz, ilk çözüm girişiminde Performans adlı bir sınıftaki dört metot ile işini halletmeye çalışmış. Buna göre, sıralama metotları kendilerine geçirilen veri kümesinin elemanlarını ilişkin karşılaştırma metodunu çağırarak sıraya dizmekte.

Performans.java
public class Performans {
  ...
  public static int karşılaştır(Öğrenci sol, Öğrenci sağ) {
    ...
  } // int karşılaştır(Öğrenci, Öğrenci)

  public static int karşılaştır(Öğretmen sol, Öğretmen sağ) {
    ...
  } // int karşılaştır(Öğretmen, Öğretmen)

  public static void sırala(Öğrenci[] veri) { ... }

  public static void sırala(Öğretmen[] veri) { ... }
  ...
} // Performans sınıfının sonu

PerformansÖğrenci ve Öğretmen sınıflarına bağımlı kılması—Öğrenci ve Öğretmen sınıflarının değişmesi ilişkin karşılaştırma metotlarının değişmesine neden olabilir—ve sıralama metotlarının neredeyse aynı olmasından—ne de olsa, aynı algoritma kullanılıyor—huylanan müdürümüz, static niteleyicisinin nesne paradigmasının düşmanı olduğunu anımsaması ve, düşülen nota inanırsak, İnternet'teki bir yazıyı🔎 okuması sonrasında, sınıf ve kalıtlama kavramlarının kullanıldığı bir çözümü yeğleyerek bu çözümden vazgeçmiş.

Karşılaştırılabilir.java
public abstract class Karşılaştırılabilir {
  public abstract int karşılaştır(Karşılaştırılabilir sağ);
} // Karşılaştırılabilir sınıfının sonu

Öğrenci.java
public class Öğrenci extends Karşılaştırılabilir {
  ...
  public int karşılaştır(Karşılaştırılabilir sağ) {
    Öğrenci sağTaraf = (Öğrenci) sağ;
    ... // ileti alıcı ile sağTaraf'ı karşılaştır.
  } // int karşılaştır(Karşılaştırılabilir)
  ...
} // Öğrenci sınıfının sonu

Öğretmen.java
public class Öğretmen extends Karşılaştırılabilir {
  ...
  public int karşılaştır(Karşılaştırılabilir sağ) {
    Öğretmen sağTaraf = (Öğretmen) sağ;
    ... // ileti alıcı ile sağTaraf'ı karşılaştır.
  } // int karşılaştır(Karşılaştırılabilir)
  ...
} // Öğretmen sınıfının sonu

Performans.java
public class Performans {
  ...
  public static void sırala(Karşılaştırılabilir[] veri) {
    ...
  } // void sırala(Karşılaştırılabilir[]) sonu
  ...
} // Performans sınıfının sonu

Anlaşılan, yukarıda kaba hatlarıyla verilen çözümün, Java'nın çoklu sınıf kalıtlamayı desteklememesi nedeniyle, Öğrenci ve Öğretmen'in bir diğer sınıftan daha—mesela, iki sınıfın ortak noktalarını içeren Kişi gibi—türetilmesini engellediğini gören ve karşılaştırmanın genel bir işlem olduğunu düşünen müdürümüz, Karşılaştırılabilir'in aslında gereksiz olduğuna karar vermiş ve söz konusu işleme karşılık gelen iletiyi Object sınıfının içinde aramış. Buna gerekçe olarak da, equals (eşitlik denetimi) ve toString (hoş yazım) gibi tüm nesneler için geçerli olan iletilerin tüm sınıfların ortak noktalarını barındıran Object'te yer alması gerektiğini not düşmüş. Görünen o ki, bu varsayımın arama sonucunda yanlışlanması, müdürümüzü Java programlamayı iyi bilen bir matematik hocasının kapısını çalmaya zorlamış. Derdinin çaresinin Object sınıfında değil, arayüz kavramının kullanımında yattığını söyleyen matematik hocası, küme, matris, karmaşık sayı gibi matematiksel nesnelerin aslında karşılaştırılabilir olmadığı konusunda garanti vererek müdürümüzü ikna etmiş. Gelin bundan sonrasını birlikte görelim.2

Birbirleriyle ilintili veya ilintisiz olabilecek kimi kavramların ortak özelliklerini soyutlamaya yarayan kategoriler Java'da arayüz üstkavramı ile karşılık bulurlar; bir arayüzün tanımlanması, Java sınıf kitaplığını o arayüzü gerçekleştiren ve gerçekleştirmeyen sınıflar olmak üzere iki kategoriye ayırır. Bu tanımda altı çizilmesi gereken noktalar, aynı arayüzü gerçekleştiren sınıfların birbirleriyle ilintisiz olma ve kimi sınıfların arayüzü gerçekleştirmeme olasılığıdır. Örneğin, karşılaştırılabilir sınıflar, Öğretmen ve Müstahdem gibi ortak üstsınıfları (Çalışan) yoluyla ilintilendirilebilirken, tamsayılar ve vergi mükellefleri gibi alakasız da olabilirler. Aynı şekilde, tamsayılar karşılaştırılabilirlik özelliği taşırken, disk dosyalarını soyutlayan sınıfların aynı özelliği taşıması beklenmez.3

Karşılaştırılabilir.java
public interface Karşılaştırılabilir {
  public int karşılaştır(Object);
  /* public static final */ String
    BELİRLEYİCİ_KOMİTE = "Ağır Abiler, A.Ş.";
} // Karşılaştırılabilir arayüzünün sonu

Arayüz tanımı, destekleyen sınıfların gerçekleştirmesi zorunlu olan iletilerin imzalarını ve bu sınıfların üyesi olan nesnelerin tümü için değişmeyen özellikleri içerir. Örneğin, yukarıdaki arayüz karşılaştır adlı bir iletiyi gerçekleştiren sınıflara ait nesneleri soyutlarken, bu nesnelerin ele alınması esnasında yararlanılacak bir sabit tanımlamaktadır.

Arayüz tanımının kuru kuruya kullanıcılara sağlanması pek yararlı olmayacaktır; arayüzü belirleyen geliştiricilerin, arayüzün gerçekleştirimleri arasında tutarlılığı sağlamak adına, derleyicinin kullanım doğruluğu denetimleri için gerekli olan yukarıdaki sözdizimsel tanımın yanısıra, arayüzdeki öğelerin anlamlarına dair açıklamaları da sağlaması gerekir. Örneğin, karşılaştır iletisinin imzasının sağlanması yalnız başına bir işe yaramayacaktır; iletinin ne yaptığına, parametrelerin ve döndürülen değerin ne anlama geldiğine aşağıdaki gibi açıklık getirilmelidir. Yani, arayüzün sözdizimsel tanımına ek olarak anlambilimsel açıklamasının da sağlanması gereklidir.
karşılaştır iletisi, hedef nesne ile argümanda geçirilen nesneyi karşılaştırır. Hedef nesnenin büyük olması durumu döndürülen pozitif bir int ile bildirilirken, eşitlik durumu 0 ile, küçük olma durumu ise negatif bir int ile bildirilir. Hedef nesne ile argümandaki nesnenin uyumsuz olması, kullanıcıya ClassCastException ayrıksı durumuyla bildirilmelidir. Benzer şekilde, argümanda geçirilen tutacağın null olmasının, kullanıcıya NullPointerException ayrıksı durumuyla bildirilmesi beklenir.

Arayüzün gerçekleştirimci ile kullanıcıları arasında sözleşme görevi gören bir bağlantı noktası oluşturduğu unutulmamalıdır; hem gerçekleştirimci hem kullanıcı, arayüz tanımından değişik amaçlarla da olsa yararlanacaktır. Bu, arayüz tanımındaki bir değişikliğin, birbirlerinden çok farklı yerlerde bulunan pek çok kişinin kodunu değiştirmesi gibi maliyetli bir sonuca neden olabileceği anlamına gelir. Dolayısıyla, değişme ihtimali yüksek olan altalan tanımı ve metot gövdesi gibi gerçekleştirim ayrıntılarının arayüz tanımında yer almasına izin verilmez. Altalan ve metot gerçekleştirimlerinin olması gerektiği durumlarda, arayüz yerine soyut sınıf kullanımı düşünülmelidir.

Arayüz tanımındaki tüm öğelerin public erişimli ve tüm veri öğelerinin derleme anı sabiti—yani, static final ile nitelenmiş—olduğu varsayılır. Bunun nedeni, arayüzün, önceki paragrafta değindiğimiz gibi, birbirinden bağımsız gerçekleştirimci ve kullanıcılar arasında sözleşme görevini görmesidir; arayüzde değişmesi muhtemel ve kamuya açık olmayan gerçekleştirim ayrıntıları bulunamaz. [Müzik setinizin arayüzünde kullandırılmayan bir düğmenin bulunmasını mantıklı bulur musunuz?] Bu sayede kullanıcı, arayüzü hangi sınıf gerçekleştiriyor olursa olsun, arayüz tutacağını arkasındaki nesnenin türünden bağımsız bir şekilde ele alıp kodun yeniden kullanımını sağlayabilecektir. Bunu, aşağıdaki örnek üzerinden görelim.
public class Performans {
  ...
  public static void sırala(Karşılaştırılabilir[] veri) {
    if (veri.length <= 1) return;

    boolean takasYapıldı = false;
    int geçiş = 1;
    do {
      takasYapıldı = false;
      for (int i = 0; j < veri.length - geçiş; i++)
        if (veri[i].karşılaştır(veri[i + 1]) > 0) {
          Karşılaştırılabilir geçici = veri[i];
          veri[i] = veri[i + 1];
          veri[i + 1] = geçici;
          takasYapıldı = true;
        } // if sonu
      geçiş++;
    } while(geçiş < veri.length && takasYapıldı);
  } // void sırala(Karşılaştırılabilir[]) sonu
  ...
} // Performans sınıfının sonu
Yukarıdaki koda göre, sırala metodu Karşılaştırılabilir arayüzünü gerçekleştiren herhangi bir sınıfın nesnelerini gösteren tutacaklardan oluşan bir dizi alabilir. Metot içinde, geçirilen dizinin elemanlarının Karşılaştırılabilir arayüzünde listelenen özellikleri kullanılabilecektir. sırala metodunu nesnenin diğer özelliklerinden koparan bu kısıtlama, bir nesneye hangi iletilerin gönderilebileceğinin tutacak süzgecinden geçmesi gerektiği kuralı ile de uyumludur: arayüz tutacağı ile gösterilen bir nesneye, tutacağın izin verdiği—yani, ilişkin arayüzde listelenen—iletiler gönderilebilir; iletinin gönderilmesi sonrasında nesnenin ait olduğu sınıftaki aynı imzalı metot çağrılacaktır. Örneğin, programın çalıştırılması sonrasında yukarıda işaretlenmiş olan satırdaki gönderinin sonrasında hangi metodun çağrılacağına geçirilen dizinin eleman türüne göre çalışma anında dinamik iletimle karar verilecektir. Bir diğer deyişle, bir sınıftaki iletilerin söz konusu sınıfın kalıtlama [ilişkisi] ile ilişkilendirildiği altsınıfların nesnelerine gönderilmesi durumunda olduğu gibi, bir arayüzde listelenen iletiler, söz konusu arayüzün gerçekleştirme [ilişkisi] ile ilişkilendirildiği sınıfların nesnelerine gönderilmeleri durumunda çokbiçimli olarak davranır. Dolayısıyla, sırala Karşılaştırılabilir arayüzünü gerçekleştiren tüm sınıflar için işini sınıfın özelliklerine göre yaparak çalışacak ve bu sayede kodun yeniden kullanımını sağlayacaktır.

İlgilenenlerin bir arayüzü gerçekleştirmesi, ilişkin sınıf ile arayüz arasındaki gerçekleştirme ilişkisinin sınıf başlığında belirtilmesi ve arayüzde listelenen tüm iletilere karşılık metotların sağlanması ile mümkün olur. Sınıf ve arayüz arasındaki ilişki, sınıf adından sonra implements anahtar sözcüğü yoluyla belirtilir; kalıtlama ilişkisine dair bir bilginin bulunması durumunda, gerçekleştirme ilişkisine dair bilginin bunu takip etmesi gerekir.
public class Öğrenci implements Karşılaştırılabilir {
  ...
  public int karşılaştır(Object sağ) {
    if (this == sağ) return 0;

    Öğrenci sağTaraf = (Öğrenci) sağ;

    if (_ortalama > sağTaraf._ortalama) return 1;
    else if (_ortalama < sağTaraf._ortalama) return -1;
    if (_no > sağTaraf._no) return 1;
    else if (_no < sağTaraf._no) return -1;
      else return 0;
  } // int karşılaştır(Object) sonu
  ...
  private float _ortalama;
  private long _no;
} // Öğrenci sınıfının sonu
public class Öğretmen implements Karşılaştırılabilir {
  ...
  public int karşılaştır(Object sağ) {
    if (this == sağ) return 0;

    Öğretman sağTaraf = (Öğretmen) sağ;

    if (_ankNotu > sağTaraf._ankNotu) return 1;
    else if (_ankNotu < sağTaraf._ankNotu) return -1;
    if (_dersYükü > sağTaraf._dersYükü) return 1;
    else if (_dersYükü < sağTaraf._dersYükü) return -1;
      else return 0;
  } // int karşılaştır(Object) sonu
  ...
  private float _ankNotu;
  private int _dersYükü;
} // Öğretmen sınıfının sonu
Gerektiği durumlarda, bir sınıf birden çok sayıda arayüz gerçekleştirebilir; bu, gerçekleştirilen arayüzlerin birbirlerinden ',' ile ayrıldığı arayüz listesinin implements'ten sonra belirtilmesi ile mümkün olur. Böyle bir sınıfın kullanıcıları, bu sınıfa ait bir nesneyi sınıf ve üstsınıflarının tutacaklarının yanısıra herhangi bir arayüzünün tutacağı yoluyla da kullanabilir.

Kimi zaman, yeni bir arayüz daha önceden tanımlanmış olan bir arayüzün ileti listesine ekler yapılarak tanımlanabilir. Yazılımların sürüm değişikliği ile birlikte arayüzlerin yeni uyarlamalarının tanımlanması buna tipik bir örnektir. Bu gibi bir durumda, yeni arayüz sıfırdan tanımlanabileceği gibi, eski arayüzden kalıtlayacak şekilde tanımlanabilir. Bu yolu seçmenin getirisi, derleyicinin arayüzler arasındaki kalıtlama ilişkisini sınıflar arasındaki kalıtlamaya benzer bir şekilde yorumlayacak olmasıdır: yeni arayüz (altarayüz) eski arayüz (üstarayüz) gibidir; eski arayüzdeki iletilerin tümünü içeren yeni arayüzün tutacağı, eski arayüz türündeki tutacağın kullanıldığı her yerde kullanılabilir. Ne kastettiğimizi aşağıdaki tanımlar üzerinden görelim.
public interface A1 {
  void i1();
  void i2();
} // A1 arayüzünün sonu
public interface A2 extends A1 {
  void i1(int i);
  void i3();
} // A2 arayüzünün sonu
Sağlanan tanımlara göre, A2 arayüzünü destekleyen bir sınıfın A1'dekileri de içeren dört iletinin karşılığındaki metot gerçekleştirimlerini sağlaması gerekecektir: i1(), i2(), i1(int) ve i3().4 Bu noktada, arayüz kalıtlamanın, sınıf kalıtlamanın aksine, çoklu olduğu unutulmamalıdır. Yani, bir arayüz birden fazla üstarayüzden kalıtlayabilir. Çoklu arayüz kalıtlamadan yararlanıldığı durumlarda, üstarayüzlerden gelen öğelerin ad çakışmalarına dikkat edilmelidir. Aynı imzaya sahip olup değişik dönüş türüne sahip iletilerin kalıtlanması derleme hatasına neden olacağı gibi, aynı ada sahip olup değişik arayüzlerde farklı türlere ait tanımlanan sabitler, kullanıldıkları noktada derleme hatasına yol açacaklardır.

Son olarak, arayüzlerin önemli bir görevine değinerek yazımızı bitirelim: yazılım bileşenleri için standart oluşturmayı mümkün kılmaları. Bileşen standartları, bir bileşenin aynı arayüzleri destekleyen bir diğeri yerine tak-çıkar mantığıyla kodun güncelllenmesini gerektirmeden değiştirilmesine olanak tanır. [Yani, eskimesi nedeniyle doğramanızı değiştirmek istediğinizde duvarın yıkılmasına gerek olmayacaktır.] Bunu aşağıda verilen zamana göre sıraya dizilmiş çizelgeyi izleyerek görelim.

  1. Bir grup uzman, ortaya çıkan ihtiyaç üzerine A1 arayüzünü tanımlar.
  2. Ş1 şirketi, A1'i gerçekleştiren Ş1_A1 sınıfını piyasaya sürer.
  3. Ş2 şirketi, Ş1'den Ş1_A1'i satın alır ve kendi programlarında kullanmaya başlar.
  4. Ş3 şirketi, A1'i gerçekleştiren Ş3_A1 sınıfını piyasaya sürer.
  5. Ş1_A1'in performansından muzdarip Ş2 şirketi, Ş3'ten Ş3_A1'i alır ve Ş1_A1'in yerine kullanmaya başlar.

Dikkat edilecek olursa, Ş3 şirketi Ş3_A1 sınıfını Ş2 sınıfının geliştirdiği programlardan sonra geliştirmiştir. Bir diğer deyişle, Ş2 şirketinin geliştirdiği programlar, kendilerinden sonra geliştirilen bileşenleri kullanmaktadır. Zaman içinde geleceğe yapılan bu yolculuk, arayüzlerin değişmez özellikler barındırması ve sözdizimlerinin yanısıra anlamlarının da tanımlanmak suretiyle gerçekleştirimci kullanıcı arasında bir sözleşme oluşturması ile mümkün olmaktadır.


  1. Obezlik örneğini çok da ilerilere götürmeyiniz. Çünkü, obez olan birisinin zayıflayarak zaman içinde bu özelliğini yitirmesi olanak dahilindedir ki, böylesine bir özelliğin, nesne yönelimli modelleme dünyasının arayüze benzer bir üstkavramı olan rol ile karşılanması daha doğru olacaktır. Arayüzler, ilişkin nesnelerin kaybetmeyeceği özelliklerini tanımlayabilir.
  2. Sağladığımız çözümdeki türlerin soysal tanımlanarak daha güvenli bir çözümün mümkün olduğunun bilinmesinde yarar var.
  3. Aslına bakılacak olursa, karşılaştırılabilirlik kategorisi Java platformundaki Comparable arayüzü tarafından hali hazırda tanımlanmıştır. Dolayısıyla, Karşılaştırılabilir arayüzüne gerek yoktur.
  4. Yeri gelmişken tekrar hatırlatalım: dönüş türü Java'da aynı aduzayındeki iletileri (ve metotları) ayrıştırmak için kullanılmaz. Dolayısıyla, A2'ye int dönüş türlü i1() iletisinin eklenmesi, ortaya çıkan muğlaklık nedeniyle, derleme hatasına yol açacaktır.

22 Ağustos 2011 Pazartesi

Türlerin Evrimi ya da Kalıtlama

Bir şirketin rekabet gücü, hangi alanda faaliyet gösteriyor olursa olsun, şirket içinde tanımlanmış süreçlerin verimli yönetilmesiyle artar. Rekabetin üst düzeyde olduğu ve zamanın çok hızlı aktığı bir sektörde yer alan yazılım şirketleri için bu, alandaki bir problemin düşük hata oranlı, ucuz, randımanlı ve nitelikli bir programa dönüştürülmesinin daha hızlı yapılması demektir. Böylesine zorlu bir amaca erişmek ise daha önceden harcanmış çabaları yinelemekten kaçınarak olanaklı hale gelebilir. Yeniden kullanım—yani, daha önceden geliştirilmiş bileşenlerin kullanımı—aşağıdaki yazılım ölçülerini olumlu bir şekilde etkileyecektir.

  • Hata oranı: Önceden geliştirilmiş bileşenler daha yoğun kullanılıp sınanmış olacağından, bu bileşenleri içeren son ürün daha düşük bir hata oranına sahip olacaktır.
  • Maliyet: Önceden geliştirilmiş bileşenlerin yeniden geliştirilmesine kaynak harcanmayacağı, bu bileşenlerin sadece kullanımı söz konusu olacağı için üretim maliyeti düşecektir.
  • İşlevsel nitelik: Önceden geliştirilmiş bileşenleri yeniden geliştirmekte harcanacak zamanın bir bölümü var olan bileşenlere yeni özellikler eklemekte kullanılabileceği için son ürün daha nitelikli olacaktır.
  • Geliştirme zamanı:1 Önceden geliştirilmiş bir bileşeni kullanmak aynı bileşeni ikinci bir defa geliştirmekten daha hızlı yapılacağı için son ürün daha hızlı bir şekilde üretilecektir.
  • Kaynak verimliliği: Önceden geliştirilmiş bileşenler daha yoğun eniyilenmiş olacakları için bu tür bileşenlerden oluşturulan son ürün daha hızlı ve/veya daha az bellek tutacak şekilde geliştirilecektir.

Dolayısıyla, yazılım geliştirme sürecinin tüm aşamalarındaki ürünlerin olabildiğince yeniden kullanılan bileşenlerle geliştirilmeye çalışılması bir gerekliliktir. Bu, yazımızın konusu olan kalıtlamanın nesne yönelimlilik bağlamında neden öne çıkarıldığının başlıca sebebidir: kalıtlama, kodun2 yeniden kullanımını sağlayan araçlardan biridir. Burada, çok önemli bir noktaya vurgu yapmakta yarar var: kalıtlama bir araçtır, amaç değil; amaç kodun yeniden kullanılması yoluyla yazılımın yukarıda anılan ölçüleri olumlu etkileyecek bir biçimde geliştirilmesidir. Aklınızda olsun!

Yazımızın başlığındaki anıştırmayı açarak başlayalım. Ortaçağ'da takılmayanlarınız bilir; birey olarak sahip olduğumuz biyolojik özellikler, türümüzün kollektif DNA'sındaki genlerin milyarlarca yıldır sürdürdükleri bir ölüm kalım savaşının sonrasında ebeveynlerimizin söz konusu genleri bize aktarmasıyla ortaya çıkar. Kimi zaman, bireyin oluşması3 sürecindeki mutasyonlar, onu (ve onun gen haritasından üretilecek bireyleri) türün geri kalanından farklı kılar. Nadiren de olsa, biriken mutasyonlar, çevresel etkenlerce "ödüllendirilerek" yeni bir türün doğmasına neden olabilir. Belki de, başka bir yerde başka bir zamanda, temel türün geri kalan bireylerini etkileyen mutasyonlar da olacaktır. Artık, birbirine benzeyen iki tür vardır ve bu benzerlik türlerin tarihçelerini anlatmakta kulanılma gibi bir soyut varoluşa indirgenen temel türde gizlidir; Tabiat Ana, ekonomik davranmış ve yeni türleri oluştururken temel türü yeniden kullanmıştır.

Evrim dersimizi burada keserek, Java programlamada kalıtlama ne şekilde ifade edilebilir, ona bir bakalım. Ancak, önce anlatımımızda yararlanacağımız pedagojik senaryoyu ortaya koyalım. Mekânımız, öğrenci ve çalışanlarla dolu bir ilköğretim okulu koridoru; görevimiz, sürdürmekte olduğumuz bir bilimsel çalışma için koridordan geçmekte olan kişilere soru sorarak veri toplamak; amacımız ise, işimizi en kısa sürede bitirmek. Amacımıza doğru kişilere doğru soruları yöneltirsek daha kolay erişebiliriz. Örneğin, herkese "Adınız ne?" sorusunu yöneltmek mümkünken, görünüşü öğrenci olduğunu haykıran birine "Kaç çocuğunuz var?" sorusunu yöneltmek pek de akıl kârı olmayacaktır. Kimi zaman, farklı gruptan kişilere aynı soruyu yöneltebilmemize karşın, yanıtın elde ediliş biçimi muhatabın ait olduğu gruba göre değişebilir. Örneğin, aylık gelir sorulduğunda, yanıt müstahdem için maaş ve ekstraların toplanmasıyla bulunurken, öğretmen için verilen özel derslerin ücretinin de eklenmesi gerekecektir.

  • Kişi: Ad ...
    • Çalışan: Çocuk sayısı, maaş, ekstra gelir, toplam aylık gelir ...
      • Öğretmen: Verilen dersler, özel ders geliri ...
      • Müstahdem: Kömür yardımı, ...
    • Öğrenci: Sınıf ...

O zaman, gelin yukarıda verilen özeti Java'da nasıl ifade edeceğimize bakalım. Bunu yaparken de, işimize evrim saatini işletmeye başlayan ve koridordan geçen herkesin ortak noktalarını tanımlayan kişi kavramını gerçekleştirmekle girişelim.

Kişi.java
public abstract class Kişi {
  ...
  public final String ad() { return _ad; }
  protected final void adDeğiştir(String yeniAd) {
    _ad = yeniAd;
  } // void adDeğiştir(String) sonu
  ...
  private String _ad;
} // Kişi sınıfının sonu

Kişi muhatap = new Kişi(...); // Derleme hatası!

Sağlanan kod parçasına bakıldığında göze ilk çarpan, Kişi sınıfının, nesnesinin yaratılamayacağını belirtecek şekilde abstract nitelenmek suretiyle, soyut sınıf olarak tanımlanmış olmasıdır. Bu çeşit sınıflar, kendisinden kalıtlamak suretiyle türetilecek sınıfların gerçekleştirilmesi bağlamında çerçeveyi çizmek amacıyla tanımlanırlar. Örneğimizde, koridordan geçmekte olan kişinin hangi grupta olmasına bağlı olmaksızın geçerli olan ad bilgisinin sorgulanması ve güncellenmesi işlemleri gerçekleştirilmiş ve böylece söz konusu işlemin diğer sınıflarda yinelenmesinin önüne geçilmiştir. Sağlanan yeniden kullanım sayesinde, değişiklik gereksiniminin belirmesi durumunda veya ortaya çıkan bir hatanın giderilmesi istendiğinde, bakılması gerekecek yer sayısı bire indirilmiş ve böylece kodun bakımı kolaylaştırılmıştır.

Göze çarpan ikinci nokta, adDeğiştir metodunun tanımında kullanılan niteleyici: protected. Bu erişim niteleyicisi, ilişkin öğeye, ait olduğu sınıfın paketindeki ve aynı sınıfın kökü olduğu kalıtım sıradüzenindeki sınıflar tarafından erişim izni verildiğini söylüyor. Buna göre, adDeğiştir metodu Kişi ve dolaylı veya dolaysız Kişi'den kalıtlayan sınıflara ve Kişi ile aynı paketteki sınıflara public gibi gözükürken, diğer sınıflara private gibi gözükecektir.

Açıklamaya muhtaç bir diğer nokta, metotlarımızı nitelemekte kullandığımız final. İliştirildiği programlama öğesinin tanımında aldığı değerin daha sonra değişmeyeceğini ilan eden bu niteleyicinin kullanımı, söz konusu öğenin veri olması durumunda sabitliğe işaret ederken, örneğimizde olduğu gibi bir davranışsal öğeyi nitelemesi durumunda bu öğeye içinde bulunulan sınıftan türetilecek altsınıflarda ezilmek suretiyle yeni bir gerçekleştirim sağlanamayacağını belirtir.

Peki ama Java'da kalıtlama nasıl ifade edilir? Yani, bir kavramın bir diğeri gibi olduğuna dair gözlemimizi Java kaynak koduna nasıl aktarabiliriz? Yanıt oldukça basit: türetilmekte olan sınıfın tanımında sınıf adını takiben kullanılan extends ile sınıfı üstsınıfa bağlamak işimizi görür.

Öğrenci.java
public class Öğrenci extends Kişi {
  ...
  public String sınıfı() { return _sınıf; }
  ...
  private int _sınıf;
} // Öğrenci sınıfının sonu

Yukarıdaki tanıma göre, Öğrenci sınıfının Kişi'den türetildiğini gören derleyici, Öğrenci nesnelerinin Kişi nesneleri gibi olduğunu ve bunun sonucu olarak Kişi'de tanımlanan iletileri de alabileceğini bilir. Bundan dolayıdır ki, aşağıdaki çıktı komutu, gönderilen ileti Öğrenci sınıfında bulunmamasına rağmen bekleneni yapacak ve öğr ile temsil edilen öğrencinin Kişi'den kalıtladığı ad özelliğini çıktı ortamına basacaktır.
Öğrenci öğr = new Öğrenci(...);
System.out.println(öğr.ad());
Bu noktada, C++ ile nesne yönelimli programlama yapanlara iki hatırlatma yapmakta yarar var. Öncelikle, Java'da sınıf kalıtlaması teklidir; bir sınıf sadece bir sınıftan türetilebilir.4 Ayrıca, sınıf tanımı başlığında belirtilmemiş olsa bile, her sınıf bir diğerinden kalıtlar; üstsınıf bilgisinin eksik olduğu tanımlarda, sınıfın Object sınıfından kalıtladığı varsayılır. Dolayısıyla, şu ana kadar ki kısmi sınıf sıradüzeni şöyle oluşacaktır.

  • Object
    • Kişi
      • Öğrenci

Buna göre, Öğrenci nesneleri, geçişli olan gibi olmak ilişkisi nedeniyle, Öğrenci ve Kişi sınıflarındaki iletilerin yanısıra, Object sınıfındaki equals, toString gibi iletilere de yanıt verecektir.

Gelelim, koridorda rastlayacağımız çalışanların ortak yönlerini temsil etmek için sağlayacağımız sınıfa. Aşağıdaki kod parçasından da görülebileceği gibi, müstahdem ve öğretmenlerin paylaştığı fakat öğrencilerin sahip olmadıkları özellikleri soyutlayan bu sınıf, Kişi sınıfı gibi soyut tanımlanmış. Sınıfın soyut olmasına ek olarak, metotlardan biri de (sızlan) soyut ilan edilmiş. Bu, içinde bulunulan sınıfta söz konusu iletiye dair bir metot gövdesi sağlanmayacağı anlamına gelir.

Çalışan.java
public abstract class Çalışan extends Kişi {
  ...
  public int aylıkGelir() { return _ekstralar + _maaş; }
  public final int çocukSayısı() { return _çckSayısı; }
  public abstract void sızlan();
  ...
  private int _çckSayısı, _ekstralar, _maaş;
} // Çalışan sınıfının sonu

abstract ilan edilen bir metot, içinde bulunduğu sınıfı da otomatikman soyut kılar. Çünkü, metot gövdesinin bulunmaması ilişkin iletinin nesneye gönderilmesi durumunda çağrılacak bir metodun olmaması anlamına gelir ki. bu, arayüzde listelenen bir işlevin yerine getirilmediği anlamını taşır. [Müzik setinizdeki ses düğmesinin (ileti) donanımdaki devrelere (metot) bağlı olmadığı için işlev görmemesinin pek de mantıklı olmadığını takdir edersiniz.] Çözüm, bu tür aykırı durumların önüne geçmek için ortaya çıkmalarını engellemekten geçer. Dolayısıyla, sınıfın da abstract ilan edilmesi gereklidir.

Farkındayım, çok uzun oldu. Ama, işin en heyecanlı kısmına geldik. Onun için biraz daha sabredin de birlikte aşağıda verilen Öğretmen sınıfının gerçekleştirimine göz atalım. Somut—yani, nesnesi yaratılabilir— bir sınıf olarak tanımlanan Öğretmen sınıfında daha önceden görmediğimiz iki şey var: Override açımlaması ve super ayrılmış sözcüğü.

Öğretmen.java
import java.util.Vector;

public class Öğretmen extends Çalışan {
  ...
  @Override
  public int aylıkGelir() {
    return super.aylıkGelir() + _özelDersGeliri;
  } // int aylıkGelir() sonu

  @Override
  public void sızlan() {
    System.out.print("Ne olacak bu memleketin hali!!!");
  } // void sızlan() sonu

  public Vector<String> dersler() {
    return _verdiğiDersler;
  } // Vector<String> dersler() sonu
  ...
  private int _özelDersGeliri;
  private Vector<String> _verdiğiDersler;
} // Öğretmen sınıfının sonu

super, içinde bulunulan sınıfın üstsınıfındaki bir özelliğin kullanılmak istendiğini belirtir. Buna göre, öğretmenlerin gelirleri, Çalışan sınıfında sağlanan toplama özel derslerden alınan ücretin eklenmesi ile hesaplanacaktır. super'in konulmaması, metodun kendisini çağırarak sonsuz döngüye girmesine neden olacaktır.

@Override açımlaması, iliştirildiği metodun kalıtlanılan aynı imzalı bir metodu ezmekte olduğunu ilan eder. Bundan hareketle derleyici, üstsınıflarda—Object, Kişi ve Çalışan— bu imzaya sahip bir metodun varlığını denetler ve sonucun olumsuz olması durumunda hata vererek derlemeyi durdurur. Zorunlu olmayan bu açımlamanın kullanılması, metot imzasının yanlış yazılması durumunda ortaya çıkabilecek sinsi mantık hatalarının önüne geçilmesine yarar. Örnek olarak, yukarıdaki kod parçasında aylıkGelir yerine yanlışlıkla aylıkgelir yazdığınızı düşünün. @Override açımlaması olmadan yazıldığı takdirde, derleyici aylıkgelir ve aylıkGelir iletilerine karşılık, biri üstsınıftan kalıtlanan diğeri sınıf tarafından sağlanan, iki metot bulunduğunu düşünecektir. @Override açımlamasının kullanılması durumunda ise, metot adlarındaki küçük farklılık görülecek ve yazım hatası mantıksal hataya dönüşmeden derleme hatası olarak yakalanacaktır.

Peki ama o zaman, Öğretmen sınıfındaki sızlan metodu @Override ile açımlanırken neden aşağıda verilen Müstahdem sınıfındaki aynı imzalı metot açımlanmamıştır? İşin sırrı, sızlan metodunun üstsınıfta soyut tanımlanmış olmasında yatmaktadır. Bu, somut bir altsınıfın anılan metoda dair bir gerçekleştirim vermeden kullanılamayacağı anlamına gelir; altsınıfta sızlan metodunun sağlanmaması, derleyici tarafından yakalanacak ve gerçekleştirim sağlanmadıkça derleme hatası ortadan kalkmayacaktır. Yani, metodun üstsınıfta soyut ilan edilmesi zaten @Override açımlamasının görevini görmektedir; söz konusu metodun ayrıca açımlanması gereksizdir.

Müstahdem.java
public class Müstahdem extends Çalışan {
  ...
  public boolean kömürYardımı { return _kömürYardımı; }

  public void sızlan() {
    System.out.print("Allah devletimize milletimize zeval vermesin!!!");
  } // void sızlan() sonu
  ...
  private boolean _kömürYardımı;
} // Müstahdem sınıfının sonu

Evet, problemimizde geçen kavramlara karşılık gelen sınıfları gerçekleştirdik. Sıra, bu sınıfları ve bu sınıfların nesnelerini nasıl kullanabileceğimizi anlamaya geldi. Aşağıdaki kod parçasını takip ederek görelim.
Çalışan birisi;
if (Math.random() > 0.5)
  birisi = new Öğretmen(...);
  else birisi = new Müstahdem(...);
System.out.print(birisi.aylıkGelir());
System.out.print(birisi.dersler()); // Derleme hatası!
Kod parçasına göre, Çalışan türünde ilan edilen birisi adlı tutacak, üretilen rastgele sayıya göre kimi zaman bir Öğretmen nesnesini gösterirken kimi zaman bir Müstahdem nesnesini göterecektir. Bu bağlamda, iki nokta sizi kod parçamızda hata olabileceği düşüncesine sevkedebilir.

  1. Nasıl olur da, soyut tanımlanan Çalışan sınıfı birisi adlı değişkenin türünü tanımlamakta kullanılabilir? Yanıt: Bileşke türden değerlerin tutacak ve nesne olmak üzere iki kısımdan oluştuğunu unutmuşa benziyorsunuz; burada yaptığımız nesne yaratmak değil, nesne iliştirilmemiş bir tutacak tanımlamak. [Bileşke türlerin bu özelliği size yabancı geliyorsa, şu yazıyı🔎 okumanızı tavsiye ederim.]
  2. Nasıl olur da, Çalışan türündeki bir tutacak Öğretmen veya Müstahdem türünde bir nesne gösterebilir? Yanıt: her iki sınıfın nesneleri de Çalışan merceğinden görülebilir. Ne de olsa, öğretmen veya müstahdem, her ikisi de bir çalışandır. Ancak, nesne ne türden olursa olsun, birisi tutacağı yoluyla arkadaki nesneye Çalışan sınıfının arayüzünde bulunmayan iletiler gönderilemez.

Örneğimize derleyicinin gözlerinden bakarak ikinci maddeyi açalım. Birincil görevi koddaki tanımlayıcıların ilan edildiklerine uygun bir biçimde kullanıldığını denetlemek olan derleyici, birisi adlı değişkenin doğru kullanıldığını garanti etmek isteyecektir. Bu, birisi vasıtasıyla arkadaki nesnenin doğru kullanılmasının—yani, nesneye anlayabileceği iletilerin gönderilmesinin—her koşulda sağlanması demektir. Örneğimizi, durum durum ele alarak görelim.

  1. birisi'nin arkasındaki nesne Öğretmen türünde: Nesnemiz, Öğretmen sınıfının arayüzündeki iletilere yanıt verebilir. Bu iletiler, Object, Kişi, Çalışan ve Öğretmen sınıflarının arayüzlerindeki iletilerin bileşkesidir.
  2. birisi'nin arkasındaki nesne Müstahdem türünde: Nesnemiz, Müstahdem sınıfının arayüzündeki iletilere yanıt verebilir. Bu iletiler, Object, Kişi, Çalışan ve Müstahdem sınıflarının arayüzlerindeki iletilerin bileşkesidir.

Aynı kodun kullanılıyor olmasına karşın, programın çalışması sırasında rastgele sayı üretecinin döndürdüğü değere göre dinamik olarak belirlenen kullanım senaryosu, doğruluk denetimi esnasında elinde kaynak kod ve kaynak kodun derlenmiş hali olan sınıf dosyalarından başka bir şey bulunmayan derleyicinin nesnenin türünden yararlanarak karar veremeyeceği anlamına gelir. Programı çalıştırarak nesnenin hangi sınıfa ait olacağını görme lüksü olmayan derleyici, görevini koddan çıkarsayabileceği bilgilerle görmek zorundadır. Aradığımız bilgi, nesnelere erişimde aracılık eden tutacaktadır: Derleyici, nesnenin doğru kullanımını denetlemek için statik tür olarak da adlandırılan tutacak türünü kullanır. Yani, bir nesneye hangi iletilerin gönderilebileceğine nesneyi gösteren tutacağın türü kullanılarak karar verilir. Bundan dolayıdır ki, derleyici birisi'nin arkasındaki nesneye dersler iletisinin gönderilmesine izin vermeyecektir. Çünkü, Çalışan türündeki bir tutacak, Çalışan'ın kökü olduğu sınıf sıradüzeni içindeki somut sınıfların türünden olan nesneleri gösterebilir ve bu nesnelerin bazıları Öğretmen'e özel dersler iletisini anlamaz.

Peki, gönderilmesine izin verilen bir ileti, hangi metodun çağrılmasına yol açar? Buna, koridorda rastladığımız çalışanlara aylık gelirlerini sorduğumuz senaryoyu düşünerek yanıt vermeye çalışalım. Çalışanın müstahdem olması durumunda yanıt, maaş ve ek gelirlerin toplanması ile verilirken, muhatabımızın öğretmen olması durumunda maaş ve ek gelirlere özel derslerden alınan ücretlerin eklenmesi gerekecektir. İşin kısası, aynı soru muhatabımızın özelliklerine bağlı olarak farklı yöntemler kullanılarak yanıtlanacaktır. Vardığımız sonucu programlama sözlükçemiz ile ifade edecek olursak, aynı gönderinin (ileti gönderme) farklı metotların çağrılmasına neden olabileceğini söyleyebiliriz. Çokbiçimlilik olarak adlandırılan bu özelliğin örneği, aylıkGelir iletisinin gönderilmesinde görülebilir: birisi'nin süzgecinden geçen aylıkGelir iletisi, rastgele sayı üreticinin döndürdüğü değere bağlı olarak yaratılan nesnenin türüne—dinamik tür olarak da adlandırılır—göre ya Öğretmen sınıfındaki ya da Müstahdem sınıfındaki aynı imzalı metodun çağrılmasına neden olacaktır. Yani, bir nesneye gönderilen ileti sonucu hangi metodun çağrılacağına nesnenin türüne bakılarak karar verilir. Bir iletinin çağrılacak metoda çalışma anında bağlanmasına ise dinamik iletim denir.

Özetleyecek olursak;

  1. Öncelikle, derleme sırasında tutacak türü kullanılarak gönderinin doğruluk denetimi yapılır. Bu denetim sırasında, farklı imzalara sahip iletiler farklı addedilir.
  2. Programın çalışması sırasında, nesnenin türüne bakılarak hangi metodun çağrılacağına karar verilir.

Evet, kalıtlama ile ilgili ilk yazımız için bu kadar yeter sanırım. Aklınızdan çıkmaması gereken şu sloganı yineleyerek kapatalım: kalıtlama bir araçtır, amaç değil; amaç kodun yeniden kullanılması yoluyla yazılımın yukarıda anılan ölçüleri olumlu etkileyecek bir biçimde geliştirilmesidir.


  1. Altını çizmekte yarar var; en son okudukları programlama kitabı 16 ikillik mimarilerin teknoloji harikası olarak tanıtıldığı yıllarda basılmış kişilerin aksine, bir sonraki maddede atıfta bulunulan çalışma hızının artırılmasından bahsetmiyoruz. Bilgisayar donanımının görece değerinin azalıp, en değerli kaynak olarak yerini geliştiriciye bıraktığını vurgulamak için geliştirme zamanı diyoruz. Nedenimiz basit: aklı başında şirket yöneticileri, düşük maliyetli bir kaynaktan (işlemci, bellek, vb.) ziyade yüksek maliyetli kaynağın (insan) verimliliğini artırmaya öncelik verir. Ancak bu, düşük maliyetli kaynağın har vurup harman savrularak kullanılabileceği anlamına gelmemelidir.
  2. Kalıtlama, çözümleme ve tasarım aşamalarında da kullanılan bir araçtır. Ancak, yazımızda kalıtlamanın Java programı yazarken nasıl kullanılabileceğine değinmekle yetineceğiz.
  3. Aslında, yapılan araştırmalar, mutasyonların bireyin yaşamı sırasında maruz kaldığı yüksek radyasyon nedeniyle de olabileceğini gösteriyor.
  4. Eksiklik gibi gözüken bu durum, çoğu zaman sınıfların arayüz(ler) gerçekleştirmesi ile giderilebilir.

17 Ağustos 2011 Çarşamba

Java ve J'li Harf Çorbası

Java programlamaya başlayanların aklını karıştıran konuların başında, ekseriye J ile başlayan teknoloji, standart ve ürün adları gelir. Çoğu zaman arka planda ne olup bittiğinin bilinmemesinden kaynaklanan bu durum, pek çok zamansa Sun şirketinin seçtiği (ve değiştirdiği) adlandırma biçiminden kaynaklanır.

İnternet olgusunun ayırdındaki herkesin bildiği ve yazıya giriş tümcesinden de anlaşılabileceği gibi, Java gözde bir programlama dilinin adıdır. Java kullanılarak yazılan programlar, genelde derlemeli-yorumlama yöntemi ile çalıştırılır. Yani; Java kaynak kodu önce derlenerek [Bytecode adındaki] bir ara dilin komutlarını içeren sınıf dosyalarına çevrilir, sonra Java Sanal Makinesi (JSM; İng., Java Virtual Machine [JVM]) adı verilen bir diğer sistem yazılımı tarafından çalıştırılır. JSM, Bytecode komutlarından oluşan sınıf dosyalarını yorumlamanın yanısıra, sınıf yükleme ve çalıştırma sırasında güvenlik denetimleri de yapar. Örneğin, derleme ile yükleme arasında geçen zaman zarfında sınıf dosyasında oluşabilecek değişiklikler, sınıfın yüklenmesi sırasında yapılan sağlamalar ile saptanabilir. Böylece, kimi kötü niyetle yapılan değişikliklerin kullanıcıya zarar vermesinin  önüne geçilmiş olur. Benzer şekilde, programı çalıştıran kullanıcının yetkisiz olduğu güvenlik zaafı yaratabilecek işlemler programın çalıştırılması sırasında yapılan denetimler ile engellenir. JSM'nin bir diğer önemli görevi, kullanılmaz hale gelmiş yığın bellek bölgelerini toplayan çöp toplayıcı bileşeni yoluyla kaynak yönetimini kolaylaştırmaktır. Bu sayede, Java programcıları kaynak yönetimine dönük daha az kod yazacaklar ve bunun sonucunda üretilen kod daha güvenilir olacaktır.

Rekabete açık bir sektör olan yazılım geliştirme işinde başarıya giden yol, olabildiğince az kaynak (geliştirici zamanı, bilgisayar zamanı ve belleği, vb.) kullanarak piyasanın rağbet edeceği nitelikli ürünleri en kısa sürede geliştirip piyasaya sürmekten geçer. Bu bağlamda uygulanan evrensel kural, aynı ürünün tekrar tekrar geliştirilmesinden kaçınarak daha önceden yapılmış eş işlevli bir ürünün kullanılmasıdır. Yeniden geliştirme için harcanacak zamanın ürünün eniyileştirilmesi ve son ürüne tümleştirilmesine ayrılması, daha güvenilir ve daha yüksek performanslı yazılım üretilmesini olanaklı kılacaktır. İşte bu noktada, Java geliştiricisinin hizmetine Java ortamı (platform) adı verilen standart kitaplıklar sunulur. Söz konusu ortam, hedeflenen kitleye bağlı olarak farklı işlevsellikler sunabilir ve buna bağlı olarak değişik şekillerde adlandırılabilir. Java SE, standart (dizüstülü/masaüstülü) geliştiriciye hitap ederken, Java EE başta İnternet olmak üzere dağıtık ortamlarda çalışacak sunucu tarafı programların geliştirilmesini kolaylaştıran yazılım çerçeveleri içerir; Java ME ise mobil aygıtlar başta olmak üzere gömük sistemler düşünülerek hazırlanmıştır.

Java platformlarının oluşturulması, Java Topluluğu'nun ortaklaşa çabasıyla ortaya çıkarılan çeşitli standartların ürün haline getirilmesiyle mümkün olur. Örneğin, programcıların sıklıkla ihtiyaç duydukları veri yapılarına dair arayüz/sınıf tanımlarının bulunduğu Java Kapları (İng., Java Collections) Java SE'nin bir parçası iken, bileşen temelli dağıtık sunucu programları geliştirmekte kullanılan Java Şirket Taneleri (İng., Enterprise Java Beans [EJB]) Java EE'nin bir parçasıdır.

Java geliştiricileri çalıştıkları platformdaki sınıf dosyası kitaplıklarına ek olarak,  yazılım geliştirmenin değişik aşamalarında kullanılmak üzere, derleyici, JSM, arşivci ve dokümantasyon aracı gibi bir çok sistem programına ihtiyaç duyar. Bu araçlar ve platformun bileşkesi, Yazılım Geliştirme Kutusu (YGK; İng., Software Development Kit [SDK]) olarak adlandırılır. Söz konusu platformun Java SE olması durumunda, adlandırma Java Geliştirme Kutusu (JGK; İng., Java Development Kit [JDK]) şeklinde yapılır.

Java kaynak kodu yazmaktansa Java platformunda hazırlanmış programları kullananların geliştirme araçlarının tümüne ihtiyacı olmaz. Örneğin, bu kişiler programları çalıştırmak için JSM'ne ihtiyaç duyarken, derleyici ve arşivci gibi sistem programlarına ihtiyaç duymayacaklardır. Sadece kullanıcı olmak özelliği taşıyan bu kişiler için Java Çalıştırma Ortamı (JÇO; İng., Java Runtime Environment [JRE]) yeterli olacaktır.

Pek çok zaman, kullanılan platform hangisi olursa olsun, Java geliştiricileri ilişkin kısaltmayı da kısaltarak, Java SE/Java EE/JavaME yerine Java demek yoluna giderler. Ancak bu kullanımın programlama dili adı kullanımından ayırt edilmesi gerekir. Çünkü; en yaygın dilin Java olmasına karşın Java platformu, kaynak kodları sınıf dosyasına derlenebilen tüm dillere açıktır. Ne de olsa, programlar sınıf dosyalarının JSM üzerinde çalıştırılması ile işini görmektedir. Dolayısıyla, Python'un Java platformu uyarlaması olan Jython veya Groovy'nin platform indinde Java programlama dilinden hiçbir farkı yoktur. Ne demek istediğime aşağıdaki örnek üzerinden açıklık getireyim.


Sırasıyla, A ve B şirketleri tarafından yazılan a.groovy ve b.py dosyalarının derlenmesiyle elde edilen a.class ve b.class, iki şirketin ortak müşterilerinden birinin yazdığı Prog.java içinden kullanılmaktadır. Sınıf yolu (İng., classpath) ayarlarına uygun bir şekilde müşteri bilgisayarının erişimindeki disk konumlarına yerleştirilen bu dosyalar, Prog.java dosyasındaki ilişkin atıfların doğruluk denetimlerinin yapılması noktasında derleyici tarafından kullanılır. Derleyicinin kaynak kod yerine sınıf dosyasından yararlanabilmesini olanaklı kılan özellik, sınıf dosyası formatı ve dosyanın içeriğini oluşturan Bytecode komutlarının standardize edilmiş olmasıdır. Bir diğer deyişle, Java kaynak kodlarının taşınabilirliğine ek olarak, sınıf dosyaları da nesne kodu düzeyinde taşınabilirliğe sahiptir. Daha sonra, derleme sonucunun olumlu olması durumunda üretilen Prog.class, JSM'ne geçirilerek çalıştırılacak ve Java, Python ve Groovy dillerinin işlevsellik kattıkları program işini görecektir.

16 Ağustos 2011 Salı

Bazı Önemli Sınıflar-java.util.Arrays

Dizilere uygulanabilecek işlemlerin eleman erişim ve güncelleme ile uzunluk sorgulamaya sınırlı olması, size Java'da bir şeylerin eksik olduğunu düşündürebilir. Java platformu tasarımcıları da bu görüşü paylaşıyor olmalılar ki, java.util paketindeki Arrays sınıfı vasıtasıyla epey geniş bir işlevsellik sunmuşlar. İşte bu yazıda yapacağımız, anılan sınıf tarafından sağlanan ve değişik eleman türlerine göre ezilmiş metotlara bakmak olacak.

Kopyalama işlemi ile başlayalım. Dizileri tanıttığımız yazıdan🔎 da hatırlayacağınız gibi, dizi türlü bir değişkenin bir diğerine atanması, etkisini tutacaklar aracılığıyla göstereceği için kopyalama değil paylaşmaya neden olacaktır; kopyalama döngü içinde her elemanın teker teker kopyalanması ile mümkündür. Ancak, standart olmayan bu çözümün yerine, ilk argümanındaki dizinin başlangıcından itibaren ikinci argümandaki kadar elemanı kopyalayan ve çıkardığı kopyanın tutacağını döndüren Arrays.copyOf metodunun kullanılması yerinde olacaktır.
import java.util.Arrays;
...
Integer[] tekler = new Integer[] {1, 3, 5, 7, 9};
Integer[] tKopya = Arrays.copyOf(tekler, tekler.length);
Benzer bir işleve sahip Arrays.copyOfRange, ikinci ve üçüncü argümanlarının işaret ettiği indisler arasındaki dilimi kopyalayarak işini görür. Dolayısıyla, Arrays.copyOf ile Arrays.copyOfRange arasında şu denklik kurulabilir.

Arrays.copyOf(dz. i) ≡ Arrays.copyOf(dz, 0, i)

Bileşke türlü değerlerin Java'da tutacak ve nesne olmak üzere iki parça ile temsil edildiğini unutanları şaşırtacak bir diğer işlem eşitlik denetimidir. Ancak, == ile işin olmayacağını bilip şansını equals iletisi ile denemek isteyenler için de sonuç hayal kırıklığı olacaktır. Bu durumun nedeni dizi türlerinin Java programlama dili tarafından ele alınışında yatar: eleman sayısını dizinin türünü belirlemekte kullanmayan Java derleyicisi, her farklı eleman türü için, programcı tarafından doğrudan erişilemeyen ve Object sınıfından kalıtlayan özel bir sınıf sentezler. Bu bilgi, sentezlenen sınıflarda Object'teki equals gerçekleştiriminin ezilmediği bilgisiyle birleştirildiğinde, equals kullanımının neden derdimize çare olmadığı görülecektir: dizi nesnelerine equals iletisinin gönderilmesi Object'te sağlanan ve == ile aynı şekilde çalışan metodun işlemesine neden olacaktır. Bir diğer deyişle, dizi nesneleri için equals ile == arasında bir fark yoktur. O zaman, ne yapmamız gerek? Yanıt, Arrays sınıfındaki iki metottan birini kullanmaktan geçer. Bunlardan ilki, tek boyutlu dizilerin eşitlik denetimini yapan Arrays.equals metodudur.
boolean eşitMi = tekler == tKopya; // eşitMi ← false
eşitMi = tekler.equals(tKopya); // eşitMi ← false
eşitMi = Arrays.equals(tekler, tKopya); // eşitMi ← true
Arrays.equals metodunun neden tek boyutlu dizilere sınırlı olduğunu, çok boyutlu dizilerin Java'da nasıl temsil edildiğine bir kez daha değinerek açıklık getirelim: Java'da çok boyutlu diziler, elemanları dizi olan diziler şeklinde tutulur. Dolayısıyla, dizinin elemanlarına equals iletisini göndermek suretiyle işini gören Arrays.equals metodunun çok boyutlu diziler için iş görmesi olanaksızdır. Bunu, aşağıdaki kod parçası üzerinden görelim.
char[][] bulmaca = new char[10][12];
char[][] bulmaca2 = new char[10][12];
// Dizileri eşit içeriğe sahip olacak şekilde doldur
eşitMi = Arrays.equals(bulmaca, bulmaca2); // eşitMi ← false
eşitMi = Arrays.deepEquals(bulmaca, bulmaca2); // eşitMi ← true
Yukarıdaki dizilerin eşitlik denetimi için Arrays.equals metodunun kullanılması, dizilerin eleman türü olan ve derleyici tarafından sentezlenen char[] sınıfındaki equals metodunun kullanılması ile işini görecektir. Bu ise, söz konusu metodun == ile aynı şekilde çalışması nedeniyle, dizilerin karşılıklı elemanlarının eşitlik yerine aynılık denetimi yapılarak kontrol edilmesi anlamına gelir ve beklediğimiz sonucu vermez. Çözüm, Arrays.deepEquals metodunun kullanımından geçer.

Eşitlik denetimi için öne sürülen sebepten ötürü, hoş yazım ve kıyım işlemleri1 de metot çiftleri ile karşılanır: Arrays.toString, Arrays.deepToString ve Arrays.hashCode, Arrays.deepHashCode.

Arrays sınıfında yer alan bir diğer metot, kendisine geçirilen dizinin elemanlarını aynı değer ile doldurmaya yarayan fill metodudur. Bu metot, dizinin tümü ve bir dilimi üzerinde etkisini gösteren iki farklı uyarlamaya sahiptir ve bu uyarlamalar aşağıdaki denklikle ilişkilendirilebilir.

Arrays.fill(dz. i) ≡ Arrays.fill(dz, 0, dz.length, i)

Değineceğimiz bir sonraki metot, belki de gezegenimizdeki kurulu bilgisayar sistemlerinin işlemcilerini en çok meşgul eden arama işleminin gerçekleştirimini sağlar. Algoritma derslerinden tanıdık ikili aramayı gerçekleştiren Arrays.binarySearch metodu, ilk argümanında sağlanan elemanları artan sırada dizilmiş dizinin içinde bir anahtar değerin geçip geçmediğine bakar. Denetim sonucunun olumlu olması durumunda, anahtarın geçtiği dizi elemanının indisi döndürülür;2 aramanın başarısız olması durumunda ise, anahtarın sırayı bozmayacak şekilde diziye eklenmesi halinde eklemenin yapılacağı indisin -1 ile çarpımından 1 çıkarılması sonucu elde edilen değer döndürülür. Daha hızlı sonuç elde edilmesi amacıyla, arama dizinin belirli bir dilimine sınırlandırılabilir. Tüm dizi ve dizi dilimi üzerinde çalışan uyarlamalar arasındaki ilişki aşağıda verilmiştir.

Arrays.binarySearch(dz. anahtar)

Arrays.binarySearch(dz, 0, dz.length, anahtar)


Arrays.binarySearch metodunun bileşke türlü elemanlara sahip bir dizide arama yapması öncesinde, ilişkin türe dair sınıfın gerçekleştiriminde equals metodunun uygun bir biçimde sağlanmış olmasına dikkat edilmelidir. Aksi takdirde, dizide var olan bir anahtarın bulunmadığının ilan edilmesi gibi bir hata ortaya çıkabilir. Böylesine bir durumun önüne geçmek için başvurulacak ikinci bir yol, java.util.Comparator arayüzünü gerçekleştiren karşılaştırıcı bir sınıfta equals iletisinin gerçekleştirilmesi ve bu sınıfın bir nesnesinin diğer argümanların ardından son argüman olarak Arrays.binarySearch'e geçirilmesidir.

İkili aramanın hünerini sıralı bir dizi üzerinde göstermesi, dizinin sıralı bir şekilde oluşturulması veya ikili arama öncesinde sıralanması zorunluluğunu da beraberinde getirir. Programcıya yüklenen bu sorumluluk, Arrays.sort metodu ile hafifletilebilir. İsteğe göre dizinin tümü veya belirli bir dilimini sıralayan bu metot, dizinin eleman türünün karşılaştırılabilir olmasını bekler. Bu ise, bileşke türler için ilişkin sınıfın Comparable arayüzünü gerçekleştirmesi ve/veya karşılaştırma ölçütünün java.util.Comparator arayüzünü gerçekleştiren bir sınıf vasıtasıyla söz konusu işlevselliğin sağlanması gerektiği anlamına gelir.

Arrays.sort(dz) ≡ Arrays.sort(dz, 0, dz.length)

Göz atacağımız son metot olan Arrays.asList, yegâne argümanındaki diziyi Veri Kapları Çerçevesi'nce tanımlanan java.util.List ve java.util.RandomAccess arayüzlerine sahip bir nesneye çevirir ve bu nesneyi gösteren bir java.util.List tutacağı döndürür. Nesneye gönderilen iletilerin diziyi de etkilemesi nedeniyle, bu işlem dizimizin işlevselliğinin java.util.List arayüzündeki iletilerle—java.util.RandomAccess bir gösterge arayüz (İng., marker interface)3 olduğu için bu yönde bir katkı sağlamaz—genişletilmesini sağlar. Ancak, bu arayüzün veri kümesini büyütmeye yönelik iletilerinin, dizinin statik doğası nedeniyle, kullanılamayacağı unutulmamalıdır.
List<Integer> tList = Arrays.asList(tekler);
// tekler[2]'de 121 değerine sahip olacak.
tList.set(2, 121);
tList.add(123); // → UnsupportedOperationException


  1. Kıyım işlemi, doğrudan veya dolaylı bir biçimde dizi gibi rasgele erişimli veri yapılarının kullanıldığı arama algoritmalarında, aranan anahtar değerinin tamsayıya çevrilmesini sağlayarak rasgele erişimli veri yapılarının avantajlarından yararlanmayı olanaklı kılar.
  2. Aranmakta olan anahtar değerin dizi içinde birden çok geçmesi durumunda, hangi indisin döndürüleceği konusunda garantili bir tahmin yapılması olanaklı değildir.
  3. İleti içermeyen arayüzlere gösterge arayüz denir.

4 Ağustos 2011 Perşembe

Arşiv Dosyaları

Java'nın kısa sürede yaygınlaşmasındaki önemli etkenlerden biri—belki de en önemlisi—Bytecode komutları içeren standardize edilmiş sınıf dosyalarının sanal makine üzerinde çalışmasıyla sağlanan makine kodu düzeyindeki taşınabilirlik özelliğidir. Java'nın doğum yeri olan Sun şirketi tarafından "Bir kez yaz, her yerde çalıştır" (İng., write once, run anywhere (WORA), write once, run everywhere (WORE)) sloganıyla tanıtılan bu özellik sayesinde, Java programlarının derlendikten sonra JSM bulunduran herhangi bir platformda çalıştırılması mümkün olmaktadır. Bunun için, gerekli sınıf dosyalarının konulduğu bir siteden indirilmek veya CD içinde verilmek suretiyle kullanıcıya sağlanması yeterli olacaktır.

  1. [Geliştirici] Geliştirilen programı derleyerek sınıf dosyası haline çevir.
  2. Sınıf dosyalarını kullanıcıya sağla.
  3. [Kullanıcı] Programı çalıştır.

Yazılımın büyümesiyle birlikte yukarıdaki 2 nolu adıma konu olan dosyaların sayısı artacak ve bu da dağıtım işini zorlaştıracaktır. Ayrıca, gereksinilen disk alanı ve indirme zamanı da olumsuz etkilenecektir. Örnek olarak, 90'ların ortalarında Microsoft'un Wintel bağımlısı çözümlerine karşı alternatif olarak ortaya çıktığında büyük heyecan yaratan uygulamacıkların (İng., applet) örün tarayıcıları tarafından çalıştırılmasına göz atalım. Kendisine bağlantı veren sayfalara dinamik içerik sağlayan ve etkisini kullanıcı tarafında çalıştırılarak gösteren uygulamacıklar işlerini şöyle görür.

  1. Kullanıcı uygulamacık bağlantısı barındıran bir sayfaya tıklar.
  2. Sayfa ile birlikte uygulamacığı oluşturan sınıf dosyaları ve diğer kaynaklar (görüntü, ses, vb.) indirilir.
  3. Uygulamacık, tarayıcıdaki JSM tarafından çalıştırılır.

Yukarıda kabaca anlatılan sürecin hızı, uygulamacığın gerçekleştiriminde seçilen algoritma ve veri yapıları seçimlerine ek olarak, 2. adımda indirilen dosyaların sayısı ve büyüklüğüne göre değişecektir. Dosyaların artması ve büyümesi performansı ve kullanıcı deneyimini olumsuz yönde etkileyecektir. İşte bundan dolayı—daha genel bir ifadeyle, yazılım dağıtımını daha etkin hale getirmek için—Java platformu tasarımcıları arşiv dosyalarını sağlamışlardır. Buna göre, geliştiricilerden beklenen, birlikte indirilecek/kurulacak dosya ve kaynakları sıkıştırarak bohçalayan arşiv dosyalarının kullanımıdır.

Arşiv Manipülasyonu


Arşiv dosyaları Yazılım Geliştirme Kutusu'nun parçası olan jar adlı arşivci komutu yardımı ile oluşturulabilir. Bu komut, ZIP-temelli formata sahip bir arşiv oluşturmanın yanısıra, verilen bir arşivin güncellenmesi, içeriğinin listelenmesi ve açılması için de kullanılabilir. Hangi işlemin kastedildiği komut adını takiben verilen ve aşağıdaki değerleri alabilen bir opsiyon ile belirtilir. Listelenenlere ek olarak, f opsiyonunun işlemin hedefi olan arşiv dosyasının adı için kullanıldığı bilinmelidir. Ayrıca, işlemin neler yaptığını daha ayrıntılı bir biçimde standart çıktıya basan v opsiyonu da kimi zaman yardımcı olabilir. Son olarak, girdi sağlanmasını gerektiren birden çok opsiyonun kullanılması durumunda, arşivcinin opsiyon sırası ile girdi sırasının aynı olduğunu varsaydığı unutulmamalıdır.1

Arşiv dosyası işlemleri
OpsiyonAnlamı
cOluşturma
uGüncelleme
tİçerik listeleme
xAçma

Buna göre, aşağıdaki komut o anki çalışma dizininin içindeki a.class ve b.class dosyalarının yanısıra images dizininin içindeki dosyalardan oluşan ArşivDosyası.jar adlı bir arşiv yaratacaktır.2 Dizin içindeki dosyaların ele alınması sırasında, altdizinlerin özyinelemeli bir biçimde işlendiği unutulmamalıdır. Yani, komutumuz images içindeki tüm dosyaları, altdizinler içindekiler de dahil olmak üzere, arşive katacaktır. Arşive nelerin girdiğini görmek isterseniz, v opsiyonu işinizi görecektir.
$ # Arşiv oluşturma
$ jar cf ArşivDosyası.jar a.class b.class images
İşlemin daha az zahmetli olmasını sağlamak için, dosya adı sağlama sırasında joker karakterinden yararlanılabilir. Örneğin, çalışma dizini içindeki sınıf dosyalarının a.class ve b.class'a sınırlı olması durumunda aşağıdaki komut yukarıdaki ile aynı işlevi görecektir.
$ jar cf ArşivDosyası.jar *.class images
Arşivin oluşturulması sırasında, girdi dosyalarının dizin yapısı korunur. Örneğin, yukarıdaki komutların icra edilmesi sonrasında a.class ve b.class arşivin kök dizininde yer alırken, görüntü dosyaları images altdizini içine konulmuş olacaktır. Bu davranışın değiştirilerek görüntü dosyalarının da sınıf dosyaları ile aynı dizine konulması istenecek olursa, -C opsiyonunun kullanılması gerekecektir. Bu opsiyon, geçici olarak kendisinden sonra sağlanan altdizine geçer ve dosya adını arşivleme sanki geçici altdizinde yapılıyormuş gibi arşive ekler. Buna göre, aşağıdaki komut o anki çalışma dizininde bulunan sınıf dosyaları ile images altdizinindeki tüm dosyaları arşivin kök dizinine yerleştirir.
$ jar cf ArşivDosyası.jar *.class -C images *
Kimi zaman, sıfırdan oluşturmak yerine ufak bir ekleme veya değişiklik yaparak var olan bir arşivi güncellemek isteyebiliriz. Oluşturmaya göre daha ucuz olan bu işlem u opsiyonu ile gerçekleştirilebilir. Arşiv oluşturma ile aynı argümanlara sahip bu işlem, kendisine geçirilen dosyanın arşivde var olması durumunda yeni dosyayı arşivdekinin üzerine yazar. Buna göre, aşağıdaki örnek kullanım ArşivDosyası.jar dosyasına c.class dosyasını eklerken, arşivde hali hazırda var olan a.class dosyasını yeni haliyle güncelleyecektir.
$ # Arşiv güncelleme
$ jar uf ArşivDosyası.jar a.class c.class
Daha önceden oluşturulmuş bir arşivin içeriği t opsiyonu ile listelenebilir. Standart çıktıya basılan listede arşivdeki dosya ve altdizinlerin kök dizine göre adları yer alır. Altdizinlerin / ile sonlandırıldığı bu listelemenin daha ayrıntılı yapılabilmesi için v opsiyonu kullanılabilir.
$ #Arşiv içeriği listeleme
$ jar tf bsh-2.0b4.jar
META-INF/
META-INF/MANIFEST.MF
bsh/
bsh/BSHAllocationExpressin.class
bsh/BSHAmbiguousName.class
...
Bir arşivin açılması arşivciye x opsiyonunun sağlanmasıyla mümkün olur. Arşiv içindeki dosya sıradüzenini disk üzerinde aynen oluşturan bu işlemin hali hazırda var olan dosyaların üzerine yazacağı unutulmamalıdır.
$ # Arşiv açma
$ jar xf bsh-2.0b4.jar

Arşiv Manifestosu


İçerik listeleme örneği farklı arşivler için denenecek olursa, META-INF dizini içindeki MANIFEST.MF dosyasının her zaman çıktıda yer aldığı görülecektir. Sakın, bunun arşiv yaratıcılarının sizi paranoyaya sürüklemek amacıyla sözleşerek uyguladığı bir komplo olduğunu düşünmeyin; nasıl ki, gemiler taşıdıkları kargonun ayrıntısını içeren bir manifesto bulundururlar, arşiv dosyaları da söz konusu arşivin sahip olduğu özellikleri belirtmek amacıyla META_INF/MANIFEST.mf dosyası içinde bir manifesto bulundurur.

Aksine bir yönlendirmede bulunmadığınız takdirde, arşiv oluşturma sırasında sizin için minimal içerikli bir manifesto oluşturulur.3 Manifest-Version: 1.0 satırından oluşan bu manifestoyu arşiv oluşturma veya güncelleme sırasında sağlanacak m opsiyonu ile isteğinize göre değiştirebilirsiniz. Yapılacak değişikliklerin üzerine yazma yerine kaynaştırarak işini gördüğü akıldan çıkarılmamalıdır. Ne demek istediğimizi bir örnekle görelim. Varsayalım ki, ArşivDosyası.jar işini görebilmek için YardımcıArşiv1.jar ve YardımcıArşiv2.jar arşivlerine ihtiyaç duyuyor. Bu durum, anılan bağımlılık bilgisini içeren bir dışsal manifesto dosyasının MANIFEST.MF'ye yamanması ile karşılanabilir.

EkManifesto.mf
Class-Path: YardımcıArşiv1.jar YardımcıArşiv2.jar
jar umf EkManifesto.mf ArşivDosyası.jar

EkManifesto.mf dosyasının MANIFEST.MF'ye yamandığı yukarıdaki satırın işlenmesi sonrasında MANIFEST.MF dosyası şu içeriğe sahip olacaktır.

MANIFEST.MF
Manifest-Version: 1.0
Class-Path: YardımcıArşiv1.jar YardımcıArşiv2.jar

İndeksleme


Dosya ve altdizin sayısının artmasıyla birlikte arşivdeki bir dosyanın bulunup kullanılması daha uzun bir zaman alacaktır. Böyle bir durumda, i opsiyonunun var olan bir arşiv ile birlikte arşivciye geçirilmesi aramayı hızlandıran bir indeks dosyası yaratarak maliyeti azaltabilir.
$ # Arşiv indeksleme
$ jar i ArşivDosyası.jar
META-INF altdizini içindeki INDEX.LIST dosyasına yazılan indeksleme bilgisinin, arşivin her güncellenmesi sonrasında yeniden yaratılması gerektiği unutulmamalıdır. Ayrıca, indekslemenin diğer işlemlerden ayrı yapılmasının zorunlu olduğu da akıldan çıkarılmamalıdır.

Arşiv Kullanımı


Edindiğiniz arşiv dosyalarından değişik şekillerde yararlanabilirsiniz. Öncelikle, arşiv dosyaları sınıf yolu üzerindeki bir dizine açılmak suretiyle kullanılır hale getirilebilir. Daha zahmetsiz bir diğer yöntem, arşiv dosyasını CLASSPATH ortam değişkenine eklemekten geçer. Örnek olarak, ArşivDosyası.jar'ı kullanan Prog.java adındaki bir dosyanın nasıl derleneceğine bakalım.
javac -cp ".:/home/tevfik/Java Paketleri/ArşivDosyası.jar:$CLASSPATH" Prog.java
Yukarıdaki komuta göre, Prog.java dosyasında ihtiyaç duyulan sınıf dosyaları öncelikle o anki çalışma dizininde, sonra /home/tevfik/Java Paketleri dizininde bulunan ArşivDosyası.jar arşivinde ve nihayet CLASSPATH ortam değişkenince işaret edilen yerlerde aranacaktır. Bu noktada, CLASSPATH ortam değişkeninin içerdiği arşiv dosyası atıflarının mutlak konum verilerek yapılması gerekirken, komut satırında yapılacak uygulamaya özel eklemelerin göreceli olarak da verilebileceği akılda tutulmalıdır.

Arşiv dosyaları, edimli kütüklere benzer şekilde bir komut gibi de kullanılabilir. Bunun için, öncelikle arşiv manifestosuna giriş noktasını içeren çalıştırılabilir sınıfın hangisi olduğunu gösteren Main-Class anahtarı ve ilişkin değerin eklenmesi gerekir. Yukarıda gösterdiğimiz şekilde manifestoya yapılacak bir değişiklikle sağlanabilecek bu bilgi, arşivciye e opsiyonu ile de bildirilebilir.
$ # Arşiv giriş noktası
$ jar cfe ArşivDosyası.jar AnaSınıf.class
Yukarıdaki komutun işlenmesi sonrasında, arşivci tarafından oluşturulacak minimal manifesto Main-Class: AnaSınıf.class satırını da içerecektir.

Giriş noktası bilgisine sahip bir arşivin çalıştırılması oldukça basittir; yapılması gereken, JSM'ye -jar opsiyonunun geçirilmesinden ibarettir. Böylesine bir kullanım, JSM'ye argümanda sağlanan dosyanın bir arşiv olduğu ve çalışmanın çalıştırılabilir sınıf adının manifestodaki Main-Class'a dair değerden öğrenilip bulunması sonrasında başlayacağı anlamına gelir.
$ # Arşiv çalıştırma
$ java -jar ArşivDosyası.jar


  1. Pek fazla kullanılmasa da, arşivciye sağlanan 0 opsiyonu arşivin sıkıştırma yapılmaksızın oluşturulacağı anlamına gelir.
  2. Unix temelli işletim dizgelerinden alışmış olanlara uyarı: opsiyon öncesinde - yok.
  3. İstenecek olursa, M opsiyonu ile minimal manifestonun oluşturulmasının önüne geçilebilir.

14 Temmuz 2011 Perşembe

Paketler ve Sınıf Yolu

Okumakta olduğu üniversitenin kütüphanesinde kitap arayan hevesli bir öğrenciyi düşünün. Bu öğrenci, belli bir düzene göre düzenlenmiş kütüphaneyi bu düzene uygun biçimde dolaşarak arayışını kolaylaştırabilir. Örneğin, kitapların fakülte, dal ve konu sıradüzenine (hiyerarşi) göre gruplanması durumunda, programlama dersine dair bir kitabı arayan öğrenci, arayışını önce mühendislik kitaplarına, sonra mühendislik kitapları içindeki bilgisayar mühendisliği kitaplarına ve nihayet bilgisayar mühendisliği kitapları içindeki programlama kitaplarına kısıtlayacaktır. Son adımda ise, konuya dair kitaplar içinde yapılan aramayı takiben, istenen kitap ya bulunacak ya da bulunamayacaktır. Kütüphanedeki kitap sayısı düşünüldüğünde, bu yöntemin kütüphanedeki tüm kitapları baştan sona sırasal bir biçimde tarayarak aramaktan çok daha verimli olacağı kesindir.

Java platformundaki türler de (sınıf ve arayüzler) kütüphanedeki kitapları andıran bir sıradüzenine konulur. Aynı konudaki kitapların kütüphanede aynı bölgeye yerleştirilmesinde olduğu gibi, ilişkili türler aynı paket içine konulur. Gerektiği takdirde, birbirine yakın kavramların gruplandığı paketler bir üstpaket içine yerleştirilebilir. Paket içine paketlerin ve/veya türlerin konulması ile oluşturulan bu sıradüzeninin amacı, platformdaki türlerin algılanmasını ve ilişkin sınıf dosyalarının ihtiyaç duyan paydaşlarca bulunmasını kolaylaştırmaktır.

Paket oluşturmanın bir diğer sebebi, farklı kişiler tarafından hazırlanmış türlerin ad çakışması olmadan kullanılmasını olanaklı kılmaktır. Ne kastettiğimizi, bir sergideki tabloların envanterini tutmak için yazılmış olan (Prog.java dosyasındaki) Java programının resmedildiği aşağıdaki şekil üzerinden görelim.1


abc şirketi tarafından hazırlanan ve nesne kabı olarak hizmet veren Tablo sınıfı ile, def şirketince gerçekleştirilen ve sanat eseri olan tablo kavramını soyutlayan aynı adlı sınıftan yararlanılan Prog.java içindeki tür tanımlarının başarılı bir biçimde derlenebilmesi için aynı ada sahip her iki sınıfın birbirinden ayırt edilmesini mümkün kılacak bilginin sağlanması veya sınıf adlarından birisinin ya da her ikisinin de değiştirilmesi gerekir. Kaynak kodun elimizde olmayabileceğini düşünürsek, ad değiştirmenin çoğu zaman pek de uygulanabilir bir seçenek olmadığı görülecektir. Yapılması gereken, her iki şirketin de olası kullanım senaryolarını göz önünde bulundurarak tür tanımlarını diğer şirketlerinki ile çakışma ihtimali olmayan bir paketin içine koymasıdır. Bu bağlamda önerilen yöntem, şirketin İnternet üzerindeki varlığını temsil eden ve diğer şirketlerinki ile çakışması olanaksız olan alan adının kullanılmasıdır. Ek olarak, şirket içindeki yazılım envanterini tutmayı kolaylaştırmak adına, bazı ek bilgiler kullanılabilir. Buna göre, abc şirketinin ürettiği Tablo.java dosyasındaki Tablo sınıfının aşağıdaki gibi yazıldığı düşünülebilir: Türkiye'deki bir ticari şirket olan abc'nin yazılım mimarlarınca belirlenen kurallara bağlı olarak, Tablo sınıfı bakımını kolaylaştırmak adına diğer veri yapıları ile birlikte bulunabileceği vy paketine koyulmakta.

Tablo.java
package tr.com.abc.vy;

...

public class Tablo {
  ...
} // Tablo sınıfının sonu

Bu noktada, hemen ilk uyarımızı yapalım: dosya içinde tanımlanan türlerin ait olduğu paketi ilan eden package bildirimi, kaynak kodun yorum satırları dışındaki ilk öğesi olmalıdır. Ayrıca, aynı dosyada sadece bir paket bildirimi bulunabilir.

Yapılan tanımların paketlere yerleştirilmesi, söz konusu türlerin kullanımı aşamasında bazı şeylere dikkat edilmesini gerektirir. Öncelikle, atıfta bulunurken artık türün yerleştirildiği paket de hesaba katılmalıdır; türün adı kaynak dosyada sağlanan ilişkin tanımdaki ile aynı değildir. Örneğin, tr.com.abc.vy paketindeki Tablo sınıfı, tr.com.abc.vy.Tablo adına sahiptir, Tablo değil. Dolayısıyla, bu sınıfın nesnesi aşağıdaki gibi yaratılmalıdır.

...

tr.com.abc.vy.Tablo tablo =
  new tr.com.abc.vy.Tablo(...);

Yukarıdaki gibi uzun adların kullanılmasının doğuracağı olası yazım hataları iki şekilde azaltılabilir. Öncelikle, programlama dilinden bağımsız bir şekilde kullanılan geliştirme ortamlarındaki ad tamamlama özelliğinden yararlanılabilir. Java programcısı olarak bizi ilgilendiren ikinci seçenek ise, package bildiriminin kullanıcı tarafındaki tamamlayıcısı olarak görülebilecek import bildirimidir. Buna göre, Tablo sınıfı aşağıdaki gibi de kullanılabilir.

...
import tr.com.abc.vy.Tablo;
...
Tablo tablo = new Tablo(...);

import bildirim(ler)inin package bildiriminin ardından gelmesi gerektiği unutulmamalıdır. Ayrıca, java.lang paketinin içindeki türlerin özel bir şekilde ele alındığı ve Java derleyicisi tarafından otomatikman program içine getirildiği hatırda tutulmalıdır.

Program içinde yararlanılan tür sayısının artmasıyla birlikte import bildiriminin jokerli uyarlamasının kullanımı düşünülebilir. Bu bildirim, söz konusu paket içindeki tüm türlere kısa adlarıyla atıfta bulunmayı olanaklı kılar.

...
import tr.com.abc.vy.*;
...
Tablo tablo = new Tablo(...);

Bu noktada bir uyarının daha yapılması yerinde olacaktır: bir paketin jokerli import bildirimi ile görünür kılınması, paketin "içindeki" diğer paketlerin görünürlüğünü etkilemez; tüm paketlerin ayrı ayrı görünür kılınması gerekir. Çünkü, paketler sadece türler için aduzayı görevi görürler, diğer paketler için değil. Mesela, java.util paketinin görünür kılınması java.util.regex paketinin içindeki sınıfları görünür hale getirmeyecektir.

Değinilmesi gereken bir diğer bildirim, J2SE 5 ile birlikte Java'ya eklenen static import bildirimidir. Bu bildirim, bir sınıfın—paketin değil!—static bir öğesinin (veya tüm static öğelerinin) sınıf adı ile nitelenmeksizin kullanılabilmesini sağlar. Buna göre, aşağıdaki kodda java.lang.System sınıfının tüm static öğeleri sınıf adıyla nitelenmeksizin kullanılabilirken, java.lang.Math sınıfının yalnızca PI adlı özelliği benzer biçimde kullanılabilecektir.

...
import static java.lang.Math.PI;
import static java.lang.System.*;
...
double alan = PI * Math.pow(yarıçap, 2);
out.println("Alan: " + alan);

Gerçekleştirimci tür tanımını package bildirimi ile bir pakete koyar, kullanıcı söz konusu türü kısa adı ile kullanmak isteyecek olursa import bildiriminden yararlanır demekle işimizin bittiğini zannediyorsanız, üzülerek yanıldığınızı söylemeyelim. Kullanıcı, program içinde yararlanılan türlere ait gerçekleştirimleri içeren sınıf dosyalarının nerede aranması gerektiğine dair bilgiyi derleyiciye sağlamalıdır. Kütüphanede bıraktığımız hevesli öğrenciye dönecek olursak, öğrencimizin bir şekilde kütüphanenin bulunduğu yeri bilmesi gerekir. Aslına bakılırsa, öğrencimizin biraz daha azimli davranıp, aranan kitabın bulunmaması halinde aramaya devam edeceği diğer kütüphanelerin adresini öğrenmesinde yarar olacaktır. Buna göre, kitap arama aşağıdaki adımlar izlenerek yapılır diyebiliriz.

  1. Adres listesinden bir sonraki kütüphanenin adresini bul. Böyle bir adresin olmaması durumunda, boynunu bükerek evine dön.
  2. Adreste belirtilen kütüphanede aramaya başla.
  3. Aramanın başarıyla sona ermesi durumunda kitabı çantaya koy. Aksi takdirde, 1. adıma git.
  4. Kitabın gerekli bölümlerini oku.

Algoritmamızı Java derleyicisinin2 sınıf dosyası arama algoritmasına uyarlayacak olursak...

  1. Sınıf yolu listesinden bir sonraki sınıf yolu dizininin konumunu bul. Böyle bir konumun olmaması durumunda, kullanılmak istenen türe dair tanımın bulunmadığını ilan et ve derlemeyi başarısızlıkla bitir.
  2. Sınıf dosyasını sınıf yolu dizininden başlayarak ara. Bunun için, türün uzun adındaki '.' karakterlerini yol ayırıcı karakteri—Unix temelli sistemlerde '/', MS Windows temelli sistemlerde '\'—ile değiştir. Buna göre, tr.com.abc.vy.Tablo sınıfı sınıf yolu dizininin içindeki tr/com/abc/vy altdizininde aranacaktır.
  3. Aramanın başarıyla sona ermesi durumunda sınıf dosyasını belleğe yükle.. Aksi takdirde—yani, Tablo.class dosyasının bulunmaması durumunda— 1. adıma git.
  4. Türün kullanımı noktasında, kullanımın doğruluğunun denetimi için belleğe yüklenen dosyadaki bilgilere başvur.

Benzetmeyi tamamlamak için yapmamız gereken, sınıf yolu listesini Java derleyicisine aktarmak. Bu, sınıf dosyalarının aranmaya başlanacağı dizinlerin konumlarını tutan CLASSPATH ortam değişkenine PATH ortam değişkenine benzer bir şekilde değer sağlanması yoluyla olur. Bir diğer yöntem, Java derleyicisine -cp opsiyonu ile uygulama bazında bu bilginin verilmesini içerir. Aşağıda her iki yöntemin birlikte kullanımına dair bir örnek verilmektedir.3

javac -cp ".:/Java Paketleri/sergi:$CLASSPATH" Prog.java

Prog.java dosyasındaki Java programlama dili tanım(lar)ını derleyen bu komut, kullanım doğruluğunu denetlemek için ihtiyaç duyulan türlerin karşılığındaki sınıf dosyalarını önce derlenmekte olan kaynak dosya ile aynı dizinde, sonra /Java Paketleri/sergi ve nihayetinde CLASSPATH ortam değişkeni ile belirtilen dizinler içinde arayacaktır. Arama sırasında sınıf dosyasından daha sonra güncellenmiş aynı adlı bir .java dosyasına rastlanması durumunda, arama başarıyla bitmiş kabul edilecek, derleyici kaynak dosyayı derleyerek denetimlerinde bu derlenmiş dosyadan yararlanacaktır.

Son olarak, bu günceyi daha önce ziyaret edip de kod örneklerine göz atmış olanlardan bazılarınızın, package bildirimi olmaksızın yazılan türlere ne olduğunu sorduklarını duyar gibiyim. Bu haklı sorunun yanıtını vererek yazımızı bitirelim: söz konusu türler, varsayılan paket dediğimiz ve kaynak kodun bulunduğu dizinle ilişkilendirilen paketin içinde konuluyordu.



  1. Şekli bir yerden hatırladıklarını düşünenler belleklerinin oynadığı bir oyuna kurban edilmiyorlar; benzer bir şekil, Java adıyla birlikte anılan kavram ve kısaltmalara değindiğimiz yazıda🔎 da vardı. Dolayısıyla, bu yazıda değindiğimiz kavramların diğer JSM dillerinden yararlanılarak üretilmiş sınıf dosyaları için de geçerli olduğu unutulmamalı.
  2. Sınıf dosyası arama Java derleyicisine sınırlı bir işlem değil. Sınıf dosyaları ile işi olan tüm sistem yazılımlarının bu algoritmayı kullanması söz konusu olacaktır ki, buna en güzel örnek sınıf dosyalarını işleten JSM'dir.
  3. MS Windows temelli işletim dizgelerinde çalışanların ':' yerine ';', $CLASSPATH yerine %CLASSPATH% kullanmaları gerekecektir.

3 Haziran 2011 Cuma

Sınama Aracı Olarak BeanShell

Herhangi bir mühendislik ürününün en önemli özelliği, ister oturduğunuz ev olsun isterse kullanmakta olduğunuz yazılım, işlevini spesifikasyonlarına uygun bir şekilde görmesidir. Doğruluk olarak adlandırılan bu özellik, ürünün inşa edilmesi sırasında yapılan tetkikler ile denetlenebileceği gibi kullanım esnasında ortaya çıkan aykırı davranışların yönlendirmesi sonrasında yapılan bakım çalışmaları ile de sağlanabilir.1 İşte bu yazımızda, Java programlama dili kullanılarak yazılan sınıfların nasıl sınanabileceğine dair bir seçenek sunan BeanShell'e göz atacağız. Bir diğer deyişle, Java kaynak kodunun doğruluğunun nasıl temin edilebileceğine değineceğiz.


Temel BeanShell

BeanShell yorumlayıcısı, gerçekleştirimi barındıran arşiv dosyasının http://beanshell.org/ adresindeki Download sekmesinden indirilip sınıf yolu üzerindeki bir yere konulmasından2 sonra ilişkin sınıfın JSM'ye yüklenmesi ile başlatılır. Kimi ortamlarda, komut satırından bsh girilmesi de yeterli olacaktır.3 Yorumlayıcıdan çıkış ise BeanShell komut kabuğunda girilecek exit ile sağlanabilir.

Aşağıdaki kod parçasından da görülebileceği gibi, dinamik türlemeli olan BeanShell'de bir tanımlayıcının ait olduğu türün belirtilmesi gerekmez; aynı tanımlayıcının farklı zamanlarda farklı türlerden değerlere sahip olması da mümkündür. Ancak bu, söz konusu tanımlayıcının kısıtsız bir biçimde kullanılabileceği anlamına gelmez; tanımlayıcıya atfın yapıldığı andaki türle uyumsuz bir kullanım hataya neden olacaktır.
$ # Bazı ortamlarda bsh komutu da aynı görevi görür.
$ java bsh.Interpreter
BeanShell 2.0b4 - by Pat Niemeyer (pat@pat.net)
bsh % // Tanımlayıcı türlerinin ilan edilmesi gerekmez.
ad = 2;
bsh % ad = ad + ".Mehmet";
bsh % print("Ad: " + ad);
Ad: 2.Mehmet
bsh % exit();
$
Standart çıktıya değer göndermek için print'e ek olarak show komutu da kullanılabilir. Her kullanımında bir bayrak değişkeninin o anki durumunu değilleyerek aç-kapa düğmesi gibi işini gören bu komut, işlem gören deyimlerin sonucunun standart çıktıya basılıp basılmayacağını belirler.
bsh % ad = "Ahmet Adnan";
bsh % soyad = "Saygun";
bsh % ad + " " + soyad;
bsh % /* Sonuçlar basılacak */ show();
bsh % ad + " " + soyad.toUpperCase();
<Ahmet Adnan SAYGUN>
bsh % show();
bsh % ad;
bsh % 
Bilinmesinde yarar bulunan diğer BeanShell komutlarından getClassPath, kullanıldığı andaki sınıf yolu ortam değişkeninin (CLASSPATH) hangi dizin ve arşiv dosyalarını içerdiğini döndürürken, addClassPath sınıf yoluna yeni bir dizin veya arşiv dosyası ekler; setClassPath ise, CLASSPATH değişkenini argümanındaki java.net.URL dizisinin elemanlarına sahip olacak şekilde günceller.
bsh % print(getClassPath());
java.net.URL []: {
file:/home/tevfik/,
...
file:/usr/lib/jvm/java-6-openjdk/jre/lib/rt.jar,
}
bsh % pwd();
/home/tevfik
bsh % addClassPath("a/b/c");
bsh % print(getClassPath());
java.net.URL []: {
file:/home/tevfik/,
...
file:/usr/lib/jvm/java-6-openjdk/jre/lib/rt.jar,
file:/home/tevfik/a/b/c,
}
Dikkat edilirse, sınıf yoluna eklenen dizin o anki çalışma dizinine eklenerek oluşturulmuş bir mutlak yol ile belirleniyor; pwd ile sorgulanabilen bu yol cd komutu ile değiştirilebilir. Dosya/dizin işlemeye dönük diğer komutlar aşağıda verilmiştir:
dir
- Argümansız kullanıldığında o anki çalışma dizininin içeriğini listeleyen bu komut, karakter katarı argümanla çağrıldığında listelemeyi söz konusu konumun göreceli olduğunu kabul ederek yapar.
cat
- Argümanındaki metin dosyasının içeriğini basar.
cp
- İlk argümanındaki dosya veya dizini ikinci argümanındaki konuma kopyalar.
mv
- İlk argümanındaki dosya veya dizini ikinci argümanındaki konuma taşır.
rm
- Argümanındaki dosya veya dizini siler.
Yukarıda verilen Unix temelli işletim dizgelerindeki adaşları ile aynı işlevli komutlara ek olarak, exec komutunu kullanarak herhangi bir komutu BeanShell komut satırından çalıştırabiliriz. Örneğin, aşağıdaki komut çalışma dizininin ayrıntılı bir listesini basacaktır.
bsh % exec("ls -la");
Dışsal bir uygulamanın çalıştırılması için kullanılan exec'e benzer bir komut, Lisp temelli dillerden birini bilenlere tanıdık gelecek olan eval'dir. Bu komut, argümanında geçirilen karakter katarını BeanShell komutları olarak algılar ve sanki komut kabuğundan girilmişçesine yorumlar. Dolayısıyla, yorumlama esnasında yaratılan olası yan etkiler komut sonrasında da geçerli olacaktır.
bsh % a = 5;
bsh % b = 10;
bsh % eval("c = a * b;");
bsh % print(c);
50

BeanShell Kaynak Dosyaları

Diğer komut kabuğu dillerinde olduğu gibi, BeanShell de komutların toptan okunup işlenmesi için araçlar sunar. Bunun için kullanılabilecek komutlardan source, Bash'in . komutuna benzer bir şekilde, argümanında verilen ada sahip dosyayı işleyerek yorumlayıcının aduzayını günceller. Örneğin, aşağıda verilen içeriğe sahip Kapasite.bsh dosyasının içselleştirilmesi sonrasında yorumlayıcının aduzayı söz konusu dosyada tanımlanmış olan kapasiteTest metodu ile zenginleştirilmiş olacaktır.

Kapasite.bsh
kapasiteTest(ilkKap, sonKap) {
  vec = new Vector(ilkKap);
  kap = vec.capacity();
  print("İlk kapasite: " + kap);
  for (i = 1; i < sonKap; i++) {
    vec.add(i);
    if (vec.capacity() != kap) {
      kap = vec.capacity();
      print("Yeni kapasite: " + kap);
    }
  }
} // kapasiteTest(ilkKap, sonKap) sonu
bsh % source("Kapasite.bsh");
bsh % kapasiteTest(0, 10);
İlk kapasite: 0
Yeni kapasite: 1
Yeni kapasite: 2
Yeni kapasite: 4
Yeni kapasite: 8
Yeni kapasite: 16
bsh %
Benzer bir etkiye sahip olan run, içselleştirdiği dosyanın içindeki tanımları komutla birlikte yaratılan yeni bir yorumlayıcı aduzayı içine yerleştirir. Yani, run komutunun işlemesi öncesi ve sonrasındaki aduzayları arasında fark olmayacaktır.

Kod Örtüsü ve Nesneler

Fonksiyonel programlama dillerine aşina olanlar bilir, nesne kod örtüsünden (İng., closure) evrilmiş bir kavramdır. Özetleyecek olursak, bir ya da birden çok fonksiyonda serbest tanımlayıcı olarak var olan ve bu fonksiyonlar tarafından "yakalanmak" suretiyle örtülen verilerin bileşkesine nesne denir. Söz konusu nesne, fonksiyonların çağrılması sırasında varsayılan argüman olarak manipüle edilir.

BeanShell, önceki paragrafta ne kastettiğimizi açmak için güzel bir araç sunar: içiçe metotlar. Gelin bunu aşağıdaki sayaç nesnesi gerçekleştiriminden anlamaya çalışalım.

Sayaç.bsh
yeniSayaç(ilkDeğer) {
  print("Hoşgeldiniz...");
  s = ilkDeğer;
  sayaç() { return s; }
  artır() { s = s + 1; }
  print("Gene bekleriz.");
  
  return this;
} // yeniSayaç(ilkDeğer) sonu
Sayaç nesneleri için yapıcı metot görevini gören yeniSayaç, standart çıktıya bilgilendirme mesajları basmaya ek olarak, gövdesinde tanımlanan her iki metotta da serbest değişken olarak geçen s'yi yapıcının çağrıldığı noktada geçirilen argümanın değerine sahip yerel değişken ile bağlamaktadır. Nesne paradigmasından alıştığımız biçimde ifade edecek olursak, yaratılacak sayaç nesnelerinin ilk değeri yapıcıya geçirilen değer olan s adlı bir alt alanı olacak ve bu nesneler artır ve sayaç adlı metotlar ile işlenecektir. Ancak, altalanın dışarıdan müdaheleye karşı korunmadığı dikkatli olanlarınızın gözünden kaçmamıştır.4
bsh % m = yeniSayaç(0);
Hoşgeldiniz...
Gene bekleriz.
bsh % m.artır();
bsh % m.artır();
bsh % n = yeniSayaç(10);
Hoşgeldiniz...
Gene bekleriz.
bsh % print(n.sayaç());
2
bsh % print(m.s);
10 

BeanShell İle Birim Sınama

Bir betik dili olan BeanShell'in yorumlayıcısı, BeanShell'in yanısıra Java programlama dili sözdiziminin büyük kısmını desteklemesi nedeniyle, Java'nın komut kabuğu olarak da görülebilir. Yani, Java komutlarını BeanShell yorumlayıcısının komut satırından etkileşimli bir biçimde girebiliriz. Girilen komutun sonucuna göre bir sonraki komutu seçebileceğimiz ve ortaya çıkacak olası hataları anında görebileceğimiz anlamına gelen bu özellik, BeanShell yorumlayıcısının aynı zamanda Java için verimli bir sınama ortamı oluşturması demektir.

BeanShell'den Java kodu sınamak amacıyla yararlanmak için yorumlayıcı ortamını Java'nın kurallarına daha sıkı uyulan sıkı denetleme kipine geçirmemiz gerekir. Bu, setStrictJava komutunun true argümanı geçirilerek kullanılmasıyla sağlanır.
bsh % ad = "Ahmet Adnan";
bsh % setStrictJava(true);
bsh % soyad = "Saygun";
// Error: EvalError: (Strict Java mode) Assignment to undeclared variable: soyad 
: at line: 3 : in file: <unknown file> : soyad = "Saygun"
bsh % String soyad = "Saygun";
Sıkı denetim kipine geçildikten sonra yapılması gereken şey, birim sınamasını yapmak istediğimiz sınıfı import bildirimi ile görünür hale getirmek ve gerek source (ve run) ile okuyarak gerekse komut satırından girerek sağlanan Java kodu ile sınıfımızın işlevselliğini sınamaktır. Bu noktada, BeanShell yorumlayıcısının JSM'ye yüklenmesi sırasında java.lang paketinin yanısıra java.awt, java.awt.net, java.io, java.net, java.util, javax.swing, javax.swing.event paketlerinin de otomatikman görünür hale getirildiği bilinmelidir. Dolayısıyla, listelenen paketlerin import ile ithal edilmesine gerek yoktur.
bsh % import a.b.c.XYZ;
bsh % XYZ x = new XYZ(), y = new XYZ(10);
bsh % // XYZ nesnelerinin davranışını sına.
bsh % ...
bsh % reloadClasses();
bsh % // Sınamaya yeniden başla.
bsh % ...
Sınanmakta olan sınıfta hata saptanması durumunda ilişkin kod parçası gözden geçirilmeli ve değişiklik yapıldıktan sonra sınıfımızın yeni halinin BeanShell yorumlayıcısına görünür kılınması gereklidir ki, bu reloadClasses komutu ile sağlanır. Argümansız kullanıldığında sınıf yolu üzerindeki sınıflar arasından değişmiş olanların tümünü yeniden JSM'ya yükleyen bu komut, istenecek olursa bir paketteki tüm sınıflara veya tek bir sınıfa yönelik olarak da icra edilebilir. Ancak, arşiv dosyaları içindeki sınıfların yeniden yüklenemeyeceği bilinmelidir.

BeanShell yorumlayıcısına sağlanan Java kodunda kimi kısıtlamaların olduğu unutulmamalıdır. Bunlardan en önemlisi, soysal türlerin tür argüman(lar)ı olmaksızın kullanılması zorunluluğudur. Başta büyük bir gaf gibi gözükebilecek bu eksiklik Java kodunun BeanShell tarafında nasıl ele alındığı öğrenildiğinde mazur görülecektir: yorumlayıcıya görünür kılınan Java sınıfları, kaynak kod düzeyinde ele alınmaz, derleme sonucunda oluşturulan sınıf dosyalarının yüklenmesi yoluyla kullanılır. Bu da, derleme sırasında yer alan tür silme işlemi nedeniyle soysallık bilgilerinin sınıf dosyasına yansıtılmayacağı ve dolayısıyla BeanShell yorumlayıcısının dikkatine sunulan dosyalarda soysallık kavramının aslında hiç olmayacağı anlamına gelir.

Birim sınama bağlamında değineceğimiz son nokta, muhtemelen, tümleşik geliştirme ortamlarına alışık arkadaşların haklı bir beklentisini karşılayacaktır: BeanShell'in tipik kullanımı, yazımızda önerildiği gibi komut satırından çalıştırılan yorumlayıcıya başka bir ortamda geliştirilerek elde edilen sınıf dosyalarının sağlanması ve bu sınıfların sınanması değildir. Normalde, Eclipse, Emacs veya jEdit gibi geliştirme ortamlarında BeanShell yorumlayıcısı, Java düzenleyici penceresine özel bir konsol penceresi olarak eklemlenir ve programcı eşgüdümlü bir biçimde yönetilen iki pencere arasında gidip gelerek sınama işini hızlı bir şekilde yapar.
 

  1. İnşa edilen ürünün yazılım olması durumunda, yapılan tetkike sınama bakım çalışmalarına ise hatadan arındırma denilir.
  2. Bu işlem, indirilen arşiv dosyasının diskte uygun bir konuma yerleştirilmesi sonrasında mutlak konum adı kullanılarak CLASSPATH ortam değişkenine eklenmesi kadar basittir.
  3. Aslında seçeneklerimiz bunlarla sınırlı değil. Görsel arayüz sevdalıları, java bsh.Console ve xbsh komutlarından biri ile emellerine ulaşabilirler.
  4. Verdiğimiz örnek, tüm altalanların tüm metotlarca kullanılacağını düşündürmesin; kimi metotlar kimi altalanları kullanmadan da işini görebilir.