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.

Hiç yorum yok:

Yorum Gönder

Not: Yalnızca bu blogun üyesi yorum gönderebilir.