yeniden kullanım etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
yeniden kullanım etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

10 Kasım 2011 Perşembe

Soysallık

Kalıtlamanın getirilerinden biri, sınıflar arası ortak yönlerin bir üstsınıfta toplanmasına olanak tanıyarak kodun yeniden kullanımını sağlamasıdır.1 Bu sayede, değişikliğin gerektiği durumlarda birçok sınıfta değişiklik yapmaktansa üstsınıftaki kodun değiştirilmesi ile işin daha kısa sürede ve daha düşük hata oranlı bir biçimde yapılması mümkün olacaktır. Ne var ki, aynı yöntemin değişik türden verileri tutmada yararlanılan veri kaplarının gerçekleştiriminde kullanılması derleme zamanında denetlenebilecek kimi hataların çalışma zamanına kaymasına neden olmaktadır ki, bu piyasaya çıkmış bir yazılımın müşterinin "hatalı" kullanımı sonrasında göçebileceği anlamına gelir. Ne kastettiğimizi son giren ilk çıkar mantığıyla çalışan yığıt veri yapısının aşağıdaki gerçekleştirimi üzerinden görelim.

YığıtBoşDurumu.java
package vy.ayrıksıdurumlar;

public class YığıtBoşDurumu extends RuntimeException { ... }
IYığıt.java
package vy.arayüzler;

import vy.ayrıksıdurumlar.YığıtBoşDurumu;

public interface IYığıt {
  Object çıkar() throws YığıtBoşDurumu;
  void ekle(Object yeniElm);
  Object gözAt() throws YığıtBoşDurumu;
  boolean boşMu();
} // IYığıt arayüzünün sonu
Yığıt.java
package vy;

import java.util.Vector;
import vy.ayrıksıdurumlar.YığıtBoşDurumu;
import vy.arayüzler.IYığıt;

public class Yığıt implements IYığıt {
  public Yığıt() { _kap = new Vector(); }
  ...
  public Object çıkar() throws YığıtBoşDurumu {
    if (boşMu()) throw new YığıtBoşDurumu();

    Object üstteki = _kap.get(_kap.size() - 1);
    _kap.remove(_kap.size() - 1);

    return üstteki;
  } // Object çıkar() sonu 
  ...
  private Vector _kap;
} // Yığıt sınıfının sonu
Ekleme ve çıkarmanın elemanları tutan kabın aynı ucuna—mesela, Vector sonu veya LinkedList başı—yapılarak gerçekleştirilebilecek yığıt, söz konusu işlemlerin ilişkin arayüz tanımında da belirtildiği üzre Object tutacakları ile işlerini görmeleri nedeniyle herhangi türden bir nesneyi tutabilecektir. Buna göre, Yığıt sınıfının nesneleri yeri geldiğinde Öğrenci nesneleri tutarken, yeri geldiğinde Öğretmen nesneleri de tutabilecektir. Ancak, dikkat etmediğimiz takdirde aşağıda olduğu gibi bir durumun ortaya çıkması da olanaklıdır.
public static void yığıtıKullan() {
  IYığıt sınıf = new Yığıt();
  sınıf.ekle(new Öğrenci(...));
  Öğrenci ilkÖğrenci = (Öğrenci) sınıf.gözAt();
  if (Math.random() > Math.random())
    sınıf.ekle(new Öğrenci(...));
    else sınıf.ekle(new Öğretmen(...));
  ...
  Öğrenci sonÖğrenci = (Öğrenci) sınıf.çıkar();
  ...
} // void yığıtıKullan() sonu
Yığıt tanımımız Object tutacağı ile gösterilebilen türden—yani, Java'daki bileşke türlerin tümü—nesne tutabileceği için elemanların türdeş olma garantisi derleyici tarafından denetlenemez. Her ikisi de eninde sonunda Object'ten kalıtladığı için, aynı yığıta Öğrenci nesnesi de Öğretmen nesnesi de eklenebilmektedir. Bu ise, yukarıdaki kod parçasının son satırında olduğu gibi türdeşlik garantisinden hareketle yazılan satırların programın çalıştırılması sırasında ClassCastException ayrıksı durumuna neden olması demektir. Yani, yığıtımız ekleme sırasında itiraz etmediği nesnenin geri döndürülmesi sırasında kullanıcının kodunu göçertmektedir. Bunun önüne geçmek ancak öngörülen türlerin tümü için ayrı yığıt gerçekleştirimlerinin sağlanması ile olanaklıdır. Bu ise aşağıdaki gibi gereğinden kalabalık ve bakımı zor bir sınıf sıradüzeni anlamına gelir.

  • Object
    • Kişi
      • Çalışan
        • Öğretmen
        • Müstahdem
      • Öğrenci
    • Yığıt_Object, Yığıt_Kişi, Yığıt_Çalışan, Yığıt_Öğrenci, Yığıt_Öğretmen, Yığıt_Müstahdem, ...

Dilimizde iki ucu gaytalı değnek şeklinde nitelenen bu durum, J2SE 5.0 sürümüne kadar geçerli olmuş ve anılan sürüm ile birlikte Java diline eklenen soysallık yoluyla ortadan kaldırılabilmiştir. Bu özellik sayesinde, olası biçimlendirme hatalarının derleme zamanında önüne geçilerek tür güvenliği sağlanmış ve tek bir tür tanımının kullanılması ile kod bakımı kolaylaştırılmıştir. Soysallıktan yararlanarak oluşturulan sıradüzeni aşağıdaki gibi olacaktir.

  • Object
    • Kişi
      • Çalışan
        • Öğretmen
        • Müstahdem
      • Öğrenci
    • Yığıt<E>

Soysal Türlerin Tanımı ve Kullanımı


Soysal türlerin tanımı ve kullanımı metotları andırır. Nasıl ki, metotların hangi türden değerler geçirilerek işletilebileceğini belirtmek için ayraç çifti arasında belirtilen parametre listesi kullanılır, soysal türlerin hangi türler için parametrize edildiğini belirtmek için üçgen ayraç çifti arasında belirtilen tür parametre listesinden yararlanılır. Buna göre, yukarıdaki arayüz ve sınıfın soysal uyarlaması aşağıdaki gibi olacaktır.

IYığıt.java
package vy.arayüzler;

import vy.ayrıksıdurumlar.YığıtBoşDurumu;

public interface IYığıt<E> {
  E çıkar() throws YığıtBoşDurumu;
  void ekle(E yeniElm);
  E gözAt() throws YığıtBoşDurumu;
  boolean boşMu();
} // IYığıt<E> arayüzünün sonu
Yığıt.java
package vy;

import java.util.Vector;
import vy.ayrıksıdurumlar.YığıtBoşDurumu;
import vy.arayüzler.IYığıt;

public class Yığıt<E> implements IYığıt<E> {
  public Yığıt() { _kap = new Vector<E>(); }
  ...
  public E çıkar() throws YığıtBoşDurumu {
    if (boşMu()) throw new YığıtBoşDurumu();

    E üstteki = _kap.get(_kap.size() - 1);
    _kap.remove(_kap.size() - 1);

    return üstteki;
  } // E çıkar() sonu
  ...
  private Vector<E> _kap;
} // Yığıt<E> sınıfının sonu
Bir soysal türün nesnesinin yaratılması, tür parametrelerine karşılık gelen tür argümanlarının soysal türün adının ardından sağlanmasıyla mümkün olur. Bunun sonucunda soysal türün örneği olarak kullanılacak türe parametreli tür denir. Örneğin, aşağıdaki kod parçasında IYığıt<Öğrenci> parametreli türüne sahip sınıf değişkeni Yığıt<Öğrenci> parametreli sınıfına ait bir kabı göstermektedir ve bu kap Öğrenci tutacakları—dolayısıyla, Öğrenci köklü sıradüzenindaki sınıfların türünden nesneler—içerecektir.2 Ayrıca, dikkat ederseniz, tür parametresi ile belirtilen dönüş türlerine sahip iletiler/metotlar (gözAt, çıkar) biçimlendirme olmaksızın kullanılmaktadır; zira, biçimlendirme derleyici tarafından eklenen kod sayesinde otomatikman yapılmaktadır.
public static void yığıtıKullan() {
  IYığıt<Öğrenci> sınıf = new Yığıt<>();
  sınıf.ekle(new Öğrenci(...));
  // Biçimlendirmeye gerek yok
  Öğrenci ilkÖğrenci = sınıf.gözAt();
  if (Math.random() > Math.random())
    sınıf.ekle(new Öğrenci(...));
    else sınıf.ekle(new Öğretmen(...)); // Derleme hatası!!!
  ...
  Öğrenci öğr = sınıf.çıkar();
  ...
} // void yığıtıKullan() sonu
Bu noktada, tür argümanlarının bileşke türlü olmak zorunluluğu hatırlatılmalıdır. Dolayısıyla, IYığıt<int> tamsayılar = new Yığıt<>(); şeklinde bir tanımın yapılması derleyici tarafından kabul görmeyecektir. Ancak bu, parametreli sınıflara ait kaplara ilkel türlü değerlerin konulamayacağı anlamına gelmez. Yapılması gereken, kabı söz konusu ilkel türün karşılığındaki sarmalayıcı tür ile ilan edip işin gerisini derleyiciye bırakmaktır. Derleyici, eklenmek istenen ilkel türlü değeri usulca sarmalarken (İng., boxing, wrapping) döndürülen tutacağın gösterdiği nesneyi açarak (İng., unboxing) içeriği ilkel değere dönüştürecektir.
IYığıt<Integer> tamsayılar = new Yığıt<>();
tamsayılar.ekle(3); // Aşağıdaki ile aynı
tamsayılar.ekle(new Integer(3));
...
int üstteki = tamsayılar.çıkar();
// ≡ int üstteki = tamsayılar.çıkar.intValue();
Gerektiği takdirde, tür parametresine sınır getirerek kullanım noktasında geçirilmesi beklenen türlerin kısıtlanması sağlanabilir. Örnek olarak, sadece Number köklü sıradüzenindeki sınıfların nesnelerini içerebilecek bir kap düşünün. Bu istem, aşağıdaki sınıf başlığında olduğu gibi, Number sınıfının soysal türün parametresine üst sınır olarak getirilmesiyle ifade edilir. Böylece, SayıKabı soysal sınıfına üye parametreli sınıfların tür argümanı Number ya da Number'dan kalıtlayan sınıflara sınırlı olacaktır; derleyici diğer kullanımları hatalı kabul edecektir. Buna göre, SayıKabı<Byte> ve SayıKabı<Float> kabul görürken, SayıKabı<Object> ve SayıKabı<Character> reddedilecektir.
public class SayıKabı<E extends Number> { ... }
İstendiği takdirde, tür parametreleri gerçekleştirilmesi beklenen bir arayüz ile de sınırlandırılabilir. Hatta, sınıf adı ve birden çok arayüz adı birlikte verilerek de sınır konulabilir. Mesela, aşağıdaki soysal türün kullanımı noktasındaki kabul edilebilir tür argümanlarının Snf'den kalıtlaması ve Aryz1 ile Aryz2 arayüzlerini gerçekleştirmesi zorunludur.
public class SoysalTür<E extends Snf & Aryz1 & Aryz2> { ... }

J2SE 5.0 Öncesi Kod İle Birliktelik: Ham Türler


Java'daki soysal türler, J2SE 5.0 sürümüne dek geçen dokuz yıla yakın sürede üretilen kodun kullanılmasını sağlamak adına ham türleri destekler. Bu desteğin amacı, hali hazırda var olan milyonlarca satırın çöpe gitmesini önlemek ve söz konusu soysallık öncesi kodun zaman içinde dönüştürülmesini sağlamaktır.

Ham türlü tanımlayıcılar, soysal bir türün üçgen ayraç çifti ve tür argümanları olmaksızın, yani J2SE 5.0 öncesindeki gibi, kullanılması ile tanımlanır. Soysal türü tür parametrelerine Object geçiriliyormuş gibi kullanmaya denk olan bu kullanım, her şey eninde sonunda Object tutacağı yoluyla görülebileceği için, derleyicinin kullanım hatalarını denetleme yeteneğini ortadan kaldırır. Dolayısıyla, soysallık sonrası yazılan kod içinde ham türlerin kullanımından kaçınılmalıdır.

Ham türlerin kullanımı iki durumda zorunludur: i) Yeni kod içinden soysallık öncesi kod kullanıldığında, ii) eski kod içinden soysal kod kullanıldığında. Örneğimiz ile devam ederek iki duruma da bir bakalım. Varsayalım ki, soysallık öncesinde IYığıt ve Yığıt türlerini tanımladık ve sınadıktan sonra kullanıma sunduk. Bu türler, soysallığa dair tür parametreleri olmadığı için, tür argümanları geçirilerek kullanılamaz; kullanım, ister soysallık öncesi kod içinden olsun isterse soysallık sonrası kod içinden, aşağıdaki gibi olacaktır.
public static yığıtıKullan() {
  IYığıt sınıf = new Yığıt();
  sınıf.ekle(new Öğrenci(...));
  Öğrenci ilkÖğrenci = (Öğrenci) sınıf.gözAt();
  if (Math.random() > Math.random())
    sınıf.ekle(new Öğrenci(...));
    else sınıf.ekle(new Öğretmen(...)); // Derleme hatası vermez!
  ...
  Öğrenci sonÖğrenci = (Öğrenci) sınıf.çıkar();
  ...
} // void yığıtıKullan() sonu
Soysallık sonrası yazılmış olan kullanıcı kodun derlenmesi hataya sebep olmamakla birlikte, derleyicinin, bu çeşit bir kullanımı potansiyel çalışma zamanı hatalarına davet çıkarmak olarak görmesi nedeniyle, denetlenemeyen olası hata uyarısını (İng., unchecked warning) vermesine yol açacaktır. Yani, soysallık öncesindeki gibi sessiz kalmaktansa, derleyici olası hataya işaret etmekte ve bizden ya kullanıcı kodunu değiştirerek duruma açıklık getirmemizi ya da elimizin altındaysa hem kullanılan kodu hem de kullanıcı kodunu soysallık sonrası standartlarına getirmemizi istemektedir. Kullanılan kodun elimizin altında olmaması halinde, iki şey yapılabilir: i) Kullanılan kod soysal değilse, kodumuz içinde kullanımımızın doğru olduğuna dair derleyiciye garanti veririz, ii) kullanılan kod soysalsa soysal türü kullanma niyetimizi belirten tür argümanlarını kullanırız. İlk şık, aşağıdaki şekilde SuppressWarnings açımlamasıyla yerine getirilebileceği gibi derleyiciye geçirilecek -Xlint:-unchecked opsiyonu ile de yerine getirilebilir. Her iki durumda da yaptığımız, derleyiciye "unchecked" etiketli uyarıları göz ardı etmesini söylemektir. Ancak; ilk yöntemde uyarılar açımlamanın öncesine yerleştirildiği metot boyunca göz ardı edilirken, derleme opsiyonu yeğlendiğinde derleyicinin hoşgörüsü derlenen tüm koda yaygınlaştırılmaktadır.
@SuppressWarnings({"unchecked"})
public static void yığıtıKullan() {
  IYığıt sınıf = new Yığıt();
  sınıf.ekle(new Öğrenci(...));
  Öğrenci ilkÖğrenci = (Öğrenci) sınıf.gözAt();
  if (Math.random() > Math.random())
    sınıf.ekle(new Öğrenci(...));
    else sınıf.ekle(new Öğretmen(...));
  ...
  Öğrenci sonÖğrenci = (Öğrenci) sınıf.çıkar();
  ...
} // void yığıtıKullan() sonu
Kullanılan kodun elimizin altında olması halinde, verilen uyarı dikkate alınmalı ve kaynak kod soysallık sonrası standartlara getirilerek yeniden derlenmelidir. Yeniden derlenen kodun eski kullanıcıları bu değişiklikten etkilenmeyeceklerdir. Çünkü, soysallık öncesinde yazılmış kullanıcılar değiştirilmiş kodun gözünde ham türlerden yararlanan yeni koddan farklı değildir.

Daha Esnek Metot İmzaları İçin Joker Türler


Pek çok ölümlü Java programcısı için soysallık desteğini ustaların erişilmez alemine sınırlı kılan en başlıca etken, joker tür argümanlarının varlığıdır. Değişik biçimlerde kendini gösteren bu korkunç yaratığı, iki kümenin sahip olduğu ortak elemanların sayısını döndüren metodu gerçekleştirerek tanımaya başlayalım. Veri Kapları Çerçevesi'nin sağladığı Set arayüzünde karşılanmayan bu işlem, birinci argümandaki kümenin her bir elemanının diğer kümede var olup olmamasına göre sayaç değişkenini güncelleyen aşağıdaki metotla gerçekleştirilebilir.
import java.util.Set;
...
public static int ortakElmSayısı(Set km1, Set Km2) {
  int elmSayısı = 0;
  for (Object elm : km1)
    if (km2.contains(elm)) elmSayısı++;

  return elmSayısı;
} // int ortakElmSayısı(Set, Set) sonu
Bir önceki altbölümden dersini almış olanlarınız, metot imzasındaki ham türlere bakıp bildiklerinden kuşkuya düşerek, neden Set<Object> kullanılmamış, diye sorabilirler. Öncelikle, bu arkadaşları doğru yolda olduklarını söyleyerek yatıştıralım; gerçekten de, yukarıdaki imzada bulunan ham türler derleyicinin tür denetim desteğini engellemek suretiyle tehlikeye davet çıkarıyor. Ancak; ham tür yerine Set<Object> kullanmak da sorunu halletmiyor. Şöyle ki; T'nin S'den kalıtladığı ve G'nin soysal tür olduğu bir ortamda, G<T> G<S>'den kalıtlamaz. Bir diğer deyişle, G<T> nesneleri G<S> nesneleri olarak ele alınamaz. Somutlaştıracak olursak, Integer'ın Object'ten kalıtlıyor olmasına karşın, Set<Integer> Set<Object>'ten kalıtlamaz. Bu ise, iki türün uyumsuz olduğu ve birbirlerini ilklemekte veya birbirlerine atanmakta kullanılamayacağı anlamına gelir. Gelin, bu sonucu aşağıdaki kod parçasının üzerinden giderek pekiştirelim. Öncelikle, 3. satırda kümeyi yaratırken elemanlarımızın Integer (ve otomatikman sarmalanarak Integer'a dönüştürülen int) ile tür uyumlu olabileceğini ilan ediyoruz. Daha sonraki satırlarda, verdiğimiz bu söze uyarak kümemize eleman ekliyoruz. Ancak; intKüme ile Set<Object> türlü objKüme'yi ilkleyen son satır, derleyicinin sıkı denetimini aşamıyor ve hataya neden oluyor. Bunun sebebi, iki tutacak tarafından paylaşılan ve başta Integer ile tür uyumlu nesneler ile doldurulacağı ilan edilen küme nesnesinin artık objKüme aracılığıyla Object ile tür uyumlu nesneler—yani, Java nesne alemindeki bütün nesneler—ile doldurulması ihtimalidir.
import java.util.*;
...
Set<Integer> intKüme = new TreeSet<>();
intKüme.add(new Integer(0));
...
Set<Object> objKüme = intKüme; // Derleme hatası!!!
Derdimizin çaresi tür argümanı olarak joker kullanımından geçer. ? ile belirtilen joker, adını bilmediğimiz veya umursamadığımız türler için kullanılır. Aşağıdaki metot imzasını buna uygun okuyacak olursak, ortakElmSayısı'nın herhangi bir türden elemanlara sahip iki Set beklediğini söyleyebiliriz.
public static int ortakElmSayısı(Set<?> km1, Set<?> Km2) {
  ...
} // int ortakElmSayısı(Set<?>, Set<?>) sonu
Şu iki noktanın akılda tutulmasında yarar olacaktır: i) farklı parametrelerde kullanılan jokerler birbirinden bağımsızdır ve farklı türlerle eşleştirilebilirler, ii) joker parametreli türün nesnesine eleman olarak sadece null eklenebilir.3

Yığıt arayüzüne iki yeni ileti ekleyerek devam edelim. Bunlardan yükle, argümanındaki kabın elemanlarını teker teker hedef nesneye eklerken, temizle hedef nesneyi boşaltırken silinen elemenları daha sonraki kullanımlar için argümanda geçirilen kaba kaydediyor.
import java.util.Collection;

public class IYığıt<E> {
  ...
  public void temizle(Collection<E> kopya); // Kapsayıcı değil!
  public void yükle(Collection<E> kaynak); // Kapsayıcı değil!
  ...
} // IYığıt<E> arayüzünün sonu
İlk denememiz, soysal türlerin daha önceden bahsettiğimiz kalıtlama ile birlikte değişmeme özelliğinden dolayı tüm kullanımları kapsamıyor ve kimi zaman kullanıcı tarafında derleme hatasına neden olabiliyor. Anılan özelliği yinelemektense, yükle iletisinin bir kullanım örneğine bakarak durumu anlamaya çalışalım.
IYığıt<Kişi> güruh = new Yığıt<>();
// güruh'a bir şeyler koy
Vector<Öğrenci> sınıf = new Vector<>();
// sınıf'a bir şeyler koy
güruh.yükle(sınıf); // Derleme hatası!!!
Belli ki, yukarıdaki kodu yazan arkadaş Öğrenci sınıfının Kişi'den kalıtladığını düşünerek Vector<Öğrenci> adlı parametreli türün de Collection<Kişi>'yi gerçekleştiren Vector<Kişi>'den kalıtladığı sonucuna varmış. Ne var ki, soysallığın kalıtlama ile birlikte değişme özelliği olmaması nedeniyle, öncülü doğru olan bu tümcenin sonuç kısmı hatalı. Yani, Vector<Öğrenci> Vector<Kişi>'den kalıtlamaz. İş böyle olunca, kodun son satırı parametre (Collection<Kişi>) ile argüman (Vector<Öğrenci>) arasındaki tür uyumsuzluğu nedeniyle derleme hatasına yol açıyor.

İçine düştüğümüz sıkıntı, sınırlı joker kullanımıyla çözülebilir. Yukarıdaki örneği sürdürerek ifade edecek olursak; derleyiciye söylememiz gereken, Yığıt<Kişi> türlü bir yığıta Kişi veya Kişi'den kalıtlayan herhangi bir sınıfa ait elemanlar içeren bir kap ile yükleme yapılabileceğidir. Bu ise, yükle'nin parametresinin Collection<? extends E> türüne sahip ilan edilmesi ile olanaklıdır. Yani, hedef nesneye girdi sağlama görevi gören kap, üst sınırlı joker türüyle tanımlanmalıdır.

temizle iletisinin imzasında ortaya çıkan sorun da sınırlı jokerlerin koşut bir kullanım biçimiyle sağlanabilir. Önce derleyicinin kabul etmeyeceği bir kullanıcı kodu görelim.
IYığıt<Öğrenci> sınıf = new Yığıt<>();
// sınıf'a bir şeyler koy
Vector<Kişi> güruh = new Vector<>();
sınıf.temizle(güruh); // Derleme hatası!!!
Bu örnekte de esnek olmayan bir imzanın cezasını çekiyoruz: Öğrenci tür argümanıyla yaratılan sınıf ancak ve ancak Öğrenci tutacakları içeren bir kaba kaydedilebiliyor. Kullanıcının yapmak istediği gibi kap Kişi eleman türüne sahip olduğunda, derleyici karşımıza dikiliveriyor. Halbuki, Öğrenci tutacağı ile görülebilen nesneler Öğrenci'nın atası olan tüm sınıfların (Kişi ve Object) tutacakları ile de görülebilir. Yani, imzanın kullanıcımızın yapmak istediğine izin verecek şekilde gevşetilmesi gerekir. Bir diğer deyişle, yığıt içeriğinin kaydedildiği kabın eleman türünün Öğrenci ve Öğrenci'nin atası olan herhangi bir sınıf olabileceğini derleyiciye bildirmemiz gerekir. Bu ise, alt sınırlı bir joker türünün kullanımı ile olanaklıdır ve örneğimizde imzadaki parametre türünün Collection<? super E> şeklinde değiştirilmesi işimizi görecektir.

Buna göre, arayüze eklenmek istenen iletilerin imzası şu şekilde oluşur. Ortaya çıkan imzalar, parametre listesindeki kaplar için genelde izlenmesinde yarar olacak bir kuralı da ele vermektedir: Hedef nesneye girdi sağlama görevi gören kaplar üst sınırlı joker tür, hedef nesnenin ürettiği çıktının kaydedildiği çıktı amaçlı kaplar ise alt sınırlı joker tür ile tanımlanmalıdır.
import java.util.Collection;

public class Collections {
  ...
  public void temizle(Collection<? super E> kopya);
  public void yükle(Collection<? extends E> kaynak);
  ...
} // IYığıt<E> arayüzünün sonu

Soysal Metotlar


Veri kapları dışında soysallıktan yararlanılan bir diğer programlama öğesi metotlardır. Genelde soysal kaplar üzerinde çalışan bu metotların soysallığı, niteleyicilerinin sonrasında kullanılan tür parametreleri yoluyla ifade edilir. Standart Veri Kapları Çerçevesi'ndeki değişik türden kaplar üzerinde uygulanabilecek metotları içeren java.util.Collections sınıfında bulunan sıralama metotlarına bakarak görelim.
package java.util;

public class Collections {
  ...
  public static <E extends Comparable<? super E>> void
    sort(List<E> liste) { ... }
  public static <E> void
    sort(List<E> liste, Comparator<? super E> karşılaştırıcı) { ... }
  ...
} // Collections sınıfının sonu
Sıralama, çok özel koşullarda kullanılabilecek bazı algoritmalar dışında, elemanların karşılaştırılması yardımıyla icra edilen bir yeniden düzenleme işlemidir. Dolayısıyla, sıralanması istenen kabın elemanlarının karşılaştırılabilir bir türe ait olması gerekir. Bu beklenti Java'da iki şekilde karşılanabilir:
  1. Elemanların ait olduğu sınıf Comparable arayüzünü gerçekleştirir. Bu noktada, gerçekleştirme ilişkisinin doğrudan olması gerekmediği unutulmamalıdır. Genel olarak, bir nesne üyesi bulunduğu sınıfın atası olan herhangi bir sınıftaki compareTo metodu ile karşılaştırılabilir. Bu sebepten ötürü, List<E>'nin sıralanabilmesi için E'nin Comparable arayüzünü gerçekleştirmesi veya gerçekleştiren bir üstsınıfa sahip olması gerekir. Bu ise yukarıdaki imzada olduğu gibi alt sınırlı bir joker tür ile belirtilebilir.
  2. Elemanları karşılaştırmayı bilen bir başka sınıf bu talebi karşılar. Bunun için, söz konusu sınıfın java.util paketindeki Comparator arayüzünü eleman türüne uyumlu bir şekilde gerçekleştirmesi gerekir. Tür uyumluluğu, bir önceki maddede olduğu gibi alt sınırlı joker tür ile belirtilmelidir.

Soysallık ve Diziler


İlk öğrendikleri programlama dilinin komutsal olması nedeniyle Java'da da gerekli gereksiz dizi kullanmaya alışanları soysal türler kötü bir sürprizle karşılar: dizilerin bileşen türü tür parametresi kullanılarak ifade edilemez. Örneğin, Yığıt sınıfının aşağıdaki şekilde dizi kullanacak biçimde gerçekleştirilmesi derleme hatasına neden olacaktır.
...
public class Yığıt<E> implements IYığıt<E> {
  public Yığıt() { 
    _kap = new E[]; // Derleme hatası!!!
    ...
  } // varsayılan yapıcı sonu
  ...
  private E[] _kap;
} // Yığıt<E> sınıfının sonu
Bunun sebebi, dizi ve soysal türler arasındaki temel bir farktan kaynaklanır: dizilerin tür bilgileri derleme sonrasına da taşınırken, soysal türlerin tür bilgileri derleyicinin kullanımı sonrasında silinir. Mesela, aşağıdaki kod parçasında intDz değişkeninin [ve tüm diğer Integer elemanlı dizilerin] üstnesnesi Integer[].class iken dblDz değişkeninin [ve tüm diğer Double elemanlı dizilerin] üstnesnesi Double[].class'dır. İstenecek olursa, ilişkin üstnesne (ve eleman türünün üstnesi [Integer.class ve Double.class]) kullanılarak içgörü (İng., reflection, introspection) yardımıyla Integer veya Double elemanlı yeni diziler yaratılabilir. Buna karşılık, intVec ve dblVec değişkenlerinin her ikisi de aynı üstnesneye sahip olacaktır: Vector.class. Çünkü, derleme sırasında kullanılan tür bilgileri tür silme (İng., type erasure) sonunda yok olmuş ve tüm parametreli türler aynı üstnesneyle temsil edilmek zorunda kalmıştır.4
Integer[] intDz = new Integer[10]();
Double[] dblDz = new Double[5]();
...
Vector<Integer> intVec = new Vector<>();
Vector<Double> dblVec = new Vector<>();
Bu nedenden ötürü, dizi kullandığımız yukarıdaki gibi durumlarda, ya dizi kullanmaktan vazgeçerek soysal türlerden birine yönelmemiz ya da derleme hatası vermemekle birlikte derleyici uyarısına neden olan şu kodu tercih etmemiz gerekir. Tavsiye edilen birinci yolun seçimidir.
...
public class Yığıt<E> implements IYığıt<E> {
  public Yığıt() { 
    _kap = (E[]) new Object[]; // Derleyici uyarısı!
    ...
  } // varsayılan yapıcı sonu
  ...
  private E[] _kap;
} // Yığıt<E> sınıfının sonu


  1. Anlatım, haklı olarak, kalıtlamanın sadece sınıflar arası geçerli bir ilişki olduğu izlenimini uyandırabilir. Fakat bu kesinlikle doğru değil; kalıtlama sınıflar arasında olduğu gibi arayüzler arasında da geçerli olan bir ilişkidir.
  2. Kod parçasında Java SE 7 ile eklenen elmas işlecinin kullanımına dikkat ediniz. Dolayısıyla, çalışma ortamını henüz güncellememiş olanlar bu kodu denediklerinde derleyici hatası ile karşılaşacaktır. Bu hatanın giderilmesi için tanımın şu şekilde tür çıkarsama olmaksızın yapılması gerekir.

    IYığıt<Öğrenci> sınıf = new Yığıt<Öğrenci>();

    Elmas işleci ve diğer Java SE 7 yenilikleri için buraya🔎 bakınız.
  3. Bazılarınızın bunun çalışma zamanı hatasına gebe bir durum olduğunu söylediklerini duyar gibiyim. Doğru ya, ilk argüman olarak Öğrenci nesneleri tutan, ikinci argüman olaraksa Öğrenci ile alakasız Koyun sınıfının nesnelerini tutan bir kullanım düşünebiliriz. Bu durumda, metodumuzdaki contains iletisinin gerçekleştirimindeki equals çağrısı, Öğrenci nesneleri ile Koyun nesneleri karşılaştırılamayacağı için, çuvallayacak ... mıdır acaba? equals iletisinin Object sınıfında tanımlanan genel sözleşmesine baktığınızda, true döndürme koşulunun hedef nesne ile uyumlu bir türe ait argümandaki null olmayan nesnenin eşit addedilmesi olduğu, geri kalan durumlarda ise false döndürülmesi gerektiğini görürsünüz. Dolayısıyla, equals iletisinin Öğrenci sınıfındaki gerçekleştirimi hedef nesne ile uyumsuz olan bir nesne gördüğünde, kodun devamında bir hatanın oluşmasına sebebiyet vermeden false döndürmelidir.
  4. Bunun bir sonucu olarak, üstnesneden yararlanarak işini gören instanceof işleci de soysal türün ham tür halini bekler. Yani, kullanım if (nesne instanceof Yığıt) ... şeklinde olmalıdır.

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.