İşte nesne yönelimli programlama dillerinin en
önemli kavramlarından birisi. Kalıtım. Normalde bu kavramı herkes gerçek
hayattan biliyor. En basit anlamda, örneğin ben, annemin gözlerini almışım
dediğimde, tıp uzmanlarının buna getirdikleri yorum " siz annenizden kalıtımsal
olarak şu özelikleri almışsınız" oluyor. Programlama dillerinde de kalıtımın
rolünün aynı olduğunu söyliyebilirim. Zaten nesne yönelimli programlama
dillerini tasarlayan uzmanlar, gerçek hayat problemlerini, bilgisayar ortamına
taşıyabilmek amacıyla en etkili modelleri geliştirmişler. İşte bu model
içerisine kalıtımıda katarak çok önemli bir özelliğin kullanılabilmesini
sağlamışlar. Her şey iyi güzel de bu kalıtım kavramının programlama dilleri
içerisinde bir tanımını yapmak lazım. En genel tanımı ile kalıtım, "bir
sınıftan yeni sınıflar türetmektir" diyebilirim.
Bu genel kavramın arkasında elbette pek çok şey
söylenebilir. Herşeyden önce kalıtım yolu ile bir sınıftan, yeni sınıflar
türetilebilmesinin, türetilen sınıflara etkisi nedir? Bu sorunun cevabı
kalıtımında özünü oluşturmaktadır. Türetilen her bir sınıf, türediği sınıfın
özelliklerinide devralır. Buradan, türetilmiş bir sınıf içerisinden,
türediği sınıfa ait üyelere erişilebileceği sonucunu çıkartabiliriz. Elbette bu
erişiminde bazı kuralları vardır. Örneğin erişim belirleyicilerinin etkisi veya
aynı üyelerin kullanılışı gibi durumlar.
Bu temel bilgiler ışığında öncelikle işe basit bir
kalıtım senaryosu ile başlamam gerektiğini düşünüyorum. Neden bir sınıftan başka
sınıflar türetiriz ki? Bunun cevabı son derece güzel. Tüm sınıflarda ortak olan
özellikleri tek bir sınıf içerisinde toparlamak. Bu modellerimizi geliştirirken,
her sınıf için ortak olan üyelerin tekrar yazılmasını engellemekle kalmıyacak,
sınıflar arasında düzenli bir hiyerarşi yapısının oluşmasınıda sağlayacak. Şimdi
güzel bir örnek lazım bana. Gerçek hayat modelleri bu iş için biçilmiş kaftan.
Örneğin, otomobilleri bir temel sınıf olarak düşünebiliriz. Bu sınıftan otomobillere
ait değişik kategorileri türetebiliriz.

İşte basit bir örnek. Buradaki tüm sınıfların ortak
bir takım özellikleri var. Bir motorlarının olması, tekerleklerinin olması,
viteslerinin olması vb. Ama aynı zamanda her ayrı sınıfın kendine has
özellikleride var. Örneğin ralli araçları için güvenlik bariyerlerinin olması,
pilotlar için kaskların kullanılması gibi. Bu tabloyu inceleyince, her ralli
aracı bir otomobildir diyebiliriz. Bu ralli araçlarının otomobil sınıfından
türediğini gösterir. Diğer yandan her wrc bir ralli aracıdır da diyebiliriz. Bu
ise, wrc araçlarının ralli araçlarının bir takım ortak özelliklerine sahip
olduğunu ayrıca otomobillerinde bir takım ortak özelliklerine sahip olduğunu
gösterir. İlk aşamada, Ralli, Ticari, Özel ve Spor sınıflarının Otomobil
sınıfından türediğini söyleyebiliriz. Bununla birlikte WRC ve GrupN sınıflarıda
Otomobil sınıfından türeyen Ralli sınıfından türemiştir. Yani burada şunu
söyleyebilmek mümkündür. WRC sınıfı hem Ralli sınıfının hemde Otomobil sınıfının
özelliklerine kalıtımsal olarak sahiptir.
Otomobil sınıfı dışında başka örneklerde
verebiliriz. Örneğin, değerli dostum Sefer Algan'ın Her Yönüyle C# isimli
kitabında kalıtım için verdiği Memeli hayvanlar örneği gibi.

İki sınıf arasında kalıtım özelliğinin olduğunu
anlayabilmek için is-a adı verilen bir ilişkinin kullanıldığını
farkettim. Yani, the cat is a mammiferous. Kedi bir memelidir. Bu
ilişkiyi yakaldıysak işte o zaman kalıtımdan söz edebiliyoruz. Yukarıdaki
örnekte olduğu gibi.
Gelelim kalıtımın java sınıflarında nasıl
uygulandığına. Bu amaçla hemen bir sınıf oluşturuyorum. Konu kalıtım olduğu için
aklıma hemen sevgili Temel geliyor. Beni her zaman neşelendiren Karadenizli
Temel. Kalıtımın nasıl uygulandığını görmek için Temel isimli ana sınıf bence
biçilmiş kaftan. Değerli Temel aslında burada bizim için önemli bir tanımın
temelini oluşturuyor aynı zamanda. Kalıtım senaryolarında, türetmenin yapıldığı
en üst sınıflar Temel Sınıf(base class), bu sınıftan türetilen sınıflarda
Tureyen Sınıf( derived class) olarak adlandırılıyor. Bu kısa bilginin ardından
hemen kodlarımı yazmaya başladım.
class Temel
{
public void Kimim()
{
System.out.println("Ben Temel'im");
}
} |
Hemen ardından bu sınıftan başka bir sınıf
türetiyorum.
class Tureyen extends Temel
{
public void Ben()
{
System.out.println("Ben Türeyen'im");
}
} |
Türetme işi java programlama dilinde extends
anahtar sözcüğü ile yapılıyor. Aslında C# dilinde bu tanımlamayı yapmak daha
kolay. İki nokta üst üste işareti ile :) Şimdide bu sınıfları uygulama içinden
bir kullanmak lazım. İlk olarak merak ettiğim konu Tureyen sınıfa ait bir nesne
üzerinden, temel sınıftaki bir üyeye erişip erişemiyeceğim.
public class Program
{
public static void main(String[] args)
{
Tureyen turemis=new Tureyen();
turemis.Kimim();
}
} |
Bu amaçla Tureyen sınıfa ait bir nesne örneği
oluşturdum ve bu nesne örneği üzerinden Temel sınıf içinde yer alan Kimim isimli
metoda erişmeye çalıştım. İşte sonuç.

İşte kalıtımın doğal sonucu olarak, temel bir sınıftan
türetilmiş bir nesne üzerinden, temel sınıftaki ortak metoda erişme imkanına
sahip oldum. Bu noktada aklıma object sınıfı geliverdi. C# programlama dilinden
biliyordumki, Object sınıfı en tepedeki sınıftı ve diğer tüm sınıfların
atasıydı. Acaba java dilindede bu böylemiydi? Bunu görmenin en kolay yolu object
sınıfına ait toString metodunu herhangibir sınıfa uygulamaktı. Bu amaçla
türemiş sınıf içerisinde toString metodunu kullanmayı denedim.
class Temel
{
public void Kimim()
{
System.out.println("Ben Temel'im");
}
}
class Tureyen extends Temel
{
public void Ben()
{
Integer agirlik=new Integer(125);
System.out.println("Ben Türeyen'im");
System.out.println("Agirlik
"+agirlik.toString());
}
}
public class Program
{
public static void main(String[] args)
{
Tureyen turemis=new Tureyen();
turemis.Ben();
}
} |
Uygulamada, yeni bir integer değişken tanımlayıp bu
değişken üzerinden object sınıfının toString metodunu çalıştırdım. toString
metodu, sayısal değeri string türüne dönüştürmekteydi. Uygulamayı
çalıştırdığımıda aşağıdaki sonucu elde ettim.

Programın çalışmasında özel veya değişik bir sonuç
yoktu. Ancak önemli olan, bir sınıf nesnesinden object sınıfının metodlarına
erişebilmiş olmamdı. Kaynaklardan edindiğim bilgiye göre buna gizli türetme ismi
veriliyor. Bu, oluşturulan her sınıfın object sınıfından gizlice türetildiği ve
bu nedenlede object sınıfına ait temel metodlara ulaşılabildiğini ifade etmekte.
Elbette bu Object sınıfındaki üyelerin, bu sınıftan türeyen her sınıf için
kullanılabilirliğini açıklıyordu. Bu konu ile ilgili olarak, güzel bir kaynakta aşağıdaki resmi elde ettim. Buna göre object sınıfı
javadaki tüm sınıfların atasıydı. Saygı duyulması gereken bir sınıf olarak,
hiyerarşinin en tepesinde yer almaktaydı.

Örneğin, ComponentEvent sınıfı, WindowEvent
sınıfından, WindowEvent sınıfı, AWTEvent sınıfından, AWTEvent sınıfı EventObject
sınıfından ve son olarakta EventObject sınıfıda Object sınıfından türetilmişlerdi.
Bu noktada aklıma böyle bir hiyerarşide ComponentEvent sınıfının Object
sınıfından itibaren nasıl türetildiği geldi. Acaba bir sınıf birden fazla
sınıftan türetilebilirmiydi? Nitekim yukarıdaki şekli hiyerarşik yapısı ile göz
önüne almadığım zaman böyle bir sonuç ortaya çıkıyordu. Bunu anlamın en güzel
yolu, bir sınıfı bir kaç sınıftan türetmeye çalışmaktı. Bu amaçla aşağıdaki gibi
bir bildirim denedim.
class Alt extends Temel,Tureyen
{
} |

Aldığım hata mesajı tam olarak açıklayıcı değildi
aslında. Ancak derleyici virgül yerine { küme parantezini bekliyordu. Bu aslında
bir ipucuydu. Çünkü virgül yerine küme parantezinin istenmesi, bu noktadan
itibaren direkt olarak sınıf bloğu istendiğini gösteriyordu. Dolayısıyla virgül
notasyonu bir işe yaramamıştı. Ancak diğer taraftan, dayanamayıp kaynaklara
baktığımda, java dilindede, C# dilinde olduğu gibi sınıflar arası çoklu
kalıtımın desteklenmediğini öğrendim. Tahmin ettiğim gibi, çoklu kalıtımı
uygulayabilmek amacıyla arayüzler(Interfaces) kullanılacaktı.
Kalıtım ile ilgili bir diğer önemli konu ise yapıcı
metodların bu işteki paylarıydı. Bir kalıtım hiyerarşisi içerisinde acaba en
alttaki sınıfa ait bir nesnenin oluşturulmasının, bu sınıfın türediği sınıfların
yapıcıları üzerinde ne gibi bir etkisi olabilirdi? Bunu görmek için, aşağıdaki
gibi bir uygulama geliştirdim. Bu kez alt alta üç sınıf türettim. Her bir
sınıfın varsayılan yapıcılarını düzenledim.
class Grafik
{
public Grafik()
{
System.out.println("Grafik SINIFI
YAPICISI");
}
}
class Daire extends Grafik
{
public Daire()
{
System.out.println("Daire SINIFI
YAPICISI");
}
}
class Elips extends Daire
{
public Elips()
{
System.out.println("Elips SINIFI
YAPICISI");
}
}
public class Program
{
public static void main(String[] args)
{
Elips e=new Elips();
}
} |
Örneğin hiyerarşisini daha iyi kavrayabilmek
amacıyla kağıt kalemi alıp aşağıdaki gibi grafikleştirmeyide ihmal etmedim.

Burada Elips sınıfı hiyerarşinin el altındaki
sınıftır. Grafik sınıfı ise en üstteki sınıftır. Uygulamayı çalıştırdığımda,
yapıcı metodların bu hiyerarşik yapıya uygun bir biçimde çalıştırıldığını
gördüm. Yani Elips sınıfından bir nesne türettiğimde, java derleyicisi, bu
sınıfın yer aldığı hiyerarşideki en üst sınıfa kadar çıktı ve ilk olarak bu en
üstteki sınıfın yani Grafik sınıfının yapıcısını çağırdı. Bu bana, türetilmiş
bir nesne yaratıldığında, türetilmiş sınıflar için ortak olan özelliklerin,
temel sınıf yapıcısı içerisinden başlangıç ayarlarına getirilebileceğini
gösterdi. Böylece her türemiş nesne sayesinde, temel sınıftaki ortak alanların
değerlerinide nesne oluşturulurken ayarlayabilirdik.

Varsayılan yapıcılar için geçerli olan bu durum
acaba, parametre alan yapıcılar için nasıl işleyecekti? Hiyerarşide üst
sınıflara çıkıldıkça, en üst sınıftan aşağıya doğru tüm yapıcıların mutlaka
çalışacağı kesindi. Peki değeleri nasıl aktaracatık. Daha net düşündüğümde, C#
dilinde yapıcılar için kullandığım base anahtar sözcüğünün yerini java dilinde ne alıcaktı?
Kaynak
araştırmalarım, bunun için super bir anahtar sözcüğün olduğunu gösterdi. Super
ismindeki bu anahtar sözcük kullanım şekli itibariyle, base anahtar sözcüğünün ilkel haliymiş diyebilirim.
Bu durumu incelemek amacıyla aşağıdaki gibi örnek oluşturdum. Burada Grafik
sınıfı temel sınıf olarak, Taban ve Yukselik isminde double tipinden
değişkenlere sahip. Yapıcı metodunu ise bu alanların değerlerini belirlemek
üzere ayarladım. Şimdi gelelim Ucgen sınıfına. Bu sınıfta, Grafik sınıfından
türetiliyor. Yapıcı metodu üç parametre almakta. İlk iki parametre, temel sınıf
olan Grafik sınıfındaki yapıcılar vasıtasıyla ayarlanabilir. İşte bu amaçla
super anahtar kelimesini kullanarak bu iki parametreyi bir üstteki sınıfın
yapıcı metoduna gönderiyorum.
Bu benim ne işime yaradı peki? Uygulamada Ucgen
sınıfından bir nesneyi 3 parametre alan yapıcı metodu ile oluşturduğumda, ilk
iki parametre, temel sınıftaki yapıcıya aktarılıyor ve böylece benim Grafik
sınıfından türettiğim nesnelerin ortak özellikleri olan Taban ve Yüksekliği,
türeyen sınıf içinden tekrar bildirmek zorunda kalmıyorum.
class Grafik
{
double Taban;
double Yukseklik;
public Grafik(double a,double b)
{
Taban=a;
Yukseklik=b;
}
}
class Ucgen extends Grafik
{
String UcgenTuru;
public Ucgen(double yc,double yuk,String tip)
{
super(yc,yuk);
UcgenTuru=tip;
}
public double Alan()
{
return (Taban*Yukseklik)/2;
}
}
public class Program
{
public static void main(String[] args)
{
Ucgen u=new Ucgen(10,20,"Eskenar");
System.out.println("Ucgen alani
"+u.Alan());
}
} |
Örneği çalıştırdığımda aşağıdaki ekran görüntüsünü
elde ettim.

Yapıcı metodlar arasındaki parametre aktarımı ile
ilgili kafama takılan nokta, super tekniği ile acaba hiyerarşinin en tepesindeki
yapıcıyamı gidiliyor sorusuydu. C# dilinde base anahtar kelimesi, bir üstteki
sınıfın yapıcılarına gönderme yapıyordu. Bu durum java dilindede böyle
olmalıydı. Hemen bir deneme uygulaması yazarak konuyu açıklığa kavuşturmaya
çalıştım.
class Tepedeki
{
int ADegeri;
int BDegeri;
public Tepedeki(int a,int b)
{
ADegeri=a;
BDegeri=b;
}
}
class OrtaKat extends Tepedeki
{
String Tanim;
public OrtaKat(int a,int b,String t)
{
super(a,b);
Tanim=t;
}
}
class Bodrum extends OrtaKat
{
public Bodrum(int ad,int bd,String ta)
{
super(ad,bd,ta);
}
public void Yaz()
{
System.out.println(ADegeri+"
"+BDegeri+" "+Tanim);
}
}
public class Program
{
public static void main(String[] args)
{
Bodrum b=new Bodrum(10,4,"Apratman");
b.Yaz();
}
} |
Örnekte Bodrum sınıfının yapıcısında kullandığım
super anahtar sözcüğü ile bir üst sınıftaki yani OrtaKat sınıfındaki yapıcıya üç parametreyide
aktarmış oldum. OrtaKat sınfındaki yapıcı ise gelen parametrelerden ilk ikisini
Tepedeki sınfının yapıcısına aktardı. Aslında bu durumu aşağıdaki gibi
şekilledirmek anlamak açısından daha kolay olucak.

En alttaki sınıf yapıcısından, en üstteki sınıf yapıcısına kadar super
tekniğini kullanarak çıkılabilmekteydi. Ancak önemli olan nokta, super tekniğinin,
C# dilindeki base anahtar sözcüğünde olduğu gibi, türetilen sınıfın bir
üstündeki sınıfa göndermeler yaptığıydı.
Java dilide C# dili gibi kesin askeri kurallar
üzerine kurulmuş bir dil. Bu kanıya nerden mi vardım? Yukarıdaki örnekte
aşağıdaki gibi bir değişiklik yaptım.
class Bodrum extends OrtaKat
{
public Bodrum(int ad,int bd,String ta)
{
System.out.println("super'den onceki
satir");
super(ad,bd,ta);
}
public void Yaz()
{
System.out.println(ADegeri+"
"+BDegeri+" "+Tanim);
}
} |
Tek yaptığım super anahtar sözcüğünün
kullanımından önce basit bir kod satırı eklemekti. Ancak sonuçta aşağıdaki hata
mesajını aldım. Super, yapıcı metod içerisinde mutlaka ilk satırda
kullanılmalıydı.

Kalıtım ile ilgili bir diğer önemli konu ise, C#
dilinden isim gizleme (name hidding) olarak bildiğim konunun nasıl ele
alındığıydı. C# dilinde, türeyen sınıf ve temel sınıflarda aynı üyeleri
tanımladığımızda, türeyen sınıftaki üyenin, temel sınıftaki üyeyi gizlediğini
biliyordum. Bu durumun Java dilinde nasıl oldğunu görmek için tek yapmam
gereken, temel ve türeyen sınıflarda aynı üyeleri kullanıp, türeyen sınıf
nesnesi üzerinden bu üyeye erişmeye çalışmaktı. Bu amaçla aşağıdaki önemsiz,
herhangibir işe yaramayan ama bana isim gizlemenin nasıl olduğunu gösterecek
kodları yazdım.
class Temel
{
public void Metod1()
{
System.out.println("Temel sınıftan
Metod1");
}
}
class Tureyen extends Temel
{
public void Metod1()
{
System.out.println("Tureyen sınıftan
Metod1");
}
}
public class Program2
{
public static void main(String[] args)
{
Tureyen t=new Tureyen();
t.Metod1();
}
} |
Bu uygulamayı derleyip çalıştırdığımda aşağıdaki
sonucu elde ettim.

C# dilindeki gibi olmuş ve türeyen sınıftaki metod
temel sınıftakini gizlemişti. Ancak arada belirgin bir fark vardı. C# dilinde,
derleyici kullanıcıyı metod gizlemeye çalıştığı yönünde uyarır ve new
operatörünü kullanmamızı ister. Bu aynı zamanda, türeyen sınıftaki üyenin, temel
sınıfta aynı isimli başka bir üyeyide gizlediğini açıkça belirtir. Elbetteki
bunun bize sağladığı katkı kodun kolay okunabilirliği ve türeyen sınıftaki hangi
üyelerin temel sınıf içerisinde aynen yer aldığının bilinmesidir. Bu bana
kalırsa Java dilindeki bir eksiklik. C# dilinde bu eksiklik giderilmiş ve new
anahtar sözcüğü işin içine katılmış ki buda bir gerçek.
Java dilinde temel sınıf üyelerinin, türeyen
sınıfta yeniden bildirilmesi override olarak adlandırılıyor. Yani temel
sınıftaki metod türeyen sınıfta geçersiz hale geliyor. Ancak kaynaklardan
edindiğim bilgiye göre, bu üyelerin erişim belirleyicilerinin büyük bir önemi
var. Şöyleki; aşağıdaki örneği uygulamaya çalıştığımda,
class Temel
{
protected void Metod1()
{
System.out.println("Temel sınıftan
Metod1");
}
}
class Tureyen extends Temel
{
private void Metod1()
{
System.out.println("Tureyen sınıftan
Metod1");
}
}
public class Program2
{
public static void main(String[] args)
{
Tureyen tureyen=new Tureyen();
tureyen.Metod1();
}
} |
aşağıdaki hata mesajı ile karşılaşıverdim.

Buradan şu sonuç çıkıyordu. Türeyen sınıfta
geçersiz hale getirilen metod, temel sınıftaki metodlar ile ya aynı erişim
belirleyicisine sahip olmalı yada daha erişilebilir bir erişim belirleyicisi
kullanılmalıydı. Biraz düşündüğümde olayın ciddiyetine vardım. Erişim sözünün
bir cümlede bu kadar çok kullanılması biraz sonra kavramsal açıdan kafada
bulanıklık yapıcak şeyler açıklanması anlamına geliyordu. Gerçektende eğer
türeyen sınıftaki metodu protected veya public yaparsak (ki burada public "daha
erişilebilir" manasına geliyormuş) o zaman kodumuz sorunsuz şekilde çalışacaktı.
Erişim belirleyicilerinin temel sınıf ve türeyen sınıf arasında oynadığı rolü
anlatabilmek için yapılabilecek en güzel şey bu işlemi kafada şekillendirmek ve
grafiğe dökmekti.

Şekildeki okun aşağıya doğru inmesinin sebebi,
erişim belirleyicileri arasındaki erişilebilirlik sınırlarının durumudur. Public
en üst mertebeden bir erişim belirleyicisi olarak her kes tarafında
erişilebilirken, en altta yer alan private erişim belirleyicisi en düşük rütbeli
erişim belirleyicisidir.
Bu şekile göre düşünüldüğünde şunu söyleyebilirim
artık. Temel sınıfta friendly erişim belirleyicisine sahip olan bir üye, türeyen
sınıfta ya friendly olmalıdır yada protected veya public olmalıdır. Denemesi
bedava deyip kolları sıvadım. Temel sınıftaki metodu friendly yaptım ve ilk
olarak türeyen sınıfta aynı erişim belirleyicisini kullandım. Tabi hemen klasik
olarak bir dili yeni öğrenmeye başlayan birisi gibi hataya düştüm ve metodların
başına hemen friendly erişim belirleyicisini yazıverdim. Her şeyi açıkça
belirtecem ya...Oysaki yazmamam zaten bu anlama geliyordu ve Java derleyicim
beni uyararak hatamın farkına varmamı sağladı.
class Temel
{
void Metod1()
{
System.out.println("Temel sınıftan
Metod1");
}
}
class Tureyen extends Temel
{
void Metod1()
{
System.out.println("Tureyen sınıftan
Metod1");
}
} |
Kod sorunsuz bir şekilde çalışmıştı. Şimdi ise,
türeyen sınıftaki metodu friendly erişiminden daha üst kademede olan protected
yaptım.
class Temel
{
void Metod1()
{
System.out.println("Temel sınıftan
Metod1");
}
}
class Tureyen extends Temel
{
protected void Metod1()
{
System.out.println("Tureyen sınıftan
Metod1");
}
} |
|
Kod yine sorunsuz bir şekilde çalıştı. Sırada
türeyen sınıftaki metodu en üst seviyede erişilebilirlik sağlıyan public yapmak
kalmıştı.
class Temel
{
void Metod1()
{
System.out.println("Temel sınıftan
Metod1");
}
}
class Tureyen extends Temel
{
public void Metod1()
{
System.out.println("Tureyen sınıftan
Metod1");
}
} |
Çok güzel. Bu seferde kod çalıştı. Şimdide durumu
ispatlamamı tamamlayacak anti tezi uygulamam gerekiyordu. Yani, türeyen
sınıftaki metodu, temel sınıftakinden daha düşük seviyeli bir erişim belirleyici
ile (bu durumda bir tek private kalıyor) kullanmaya çalışmak.
class Temel
{
void Metod1()
{
System.out.println("Temel sınıftan
Metod1");
}
}
class Tureyen extends Temel
{
private void Metod1()
{
System.out.println("Tureyen sınıftan
Metod1");
}
} |
Ta taaaa.... Beklediğim hata gerçekleşmişti.

Kalıtım ile ilgili temeller bitmek bilmiyordu.
Kaynakları karıştırdıkça düşebileceğim başka tuzaklarda karşıma çıkıyordu.
Bunlardan bir tanesi farklı paketler içerisinde yer alan sınıflar arası kalıtım
söz konusu olduğunda, türeyen sınıftaki üyelerin geçersiz kılınması sırasında
olabilecek olaylardı. Asıl tuzak soru şuydu; Temel sınıfta friendly olarak
tanımlanmış bir üye, başka bir pakette bu sınıftan türeyen bir sınıf içinde
geçersiz kılınırmıydı?
Denemesi bedeva deyip denemektense, önce akıl
muhakemesi yaparak konuya yaklaştım ve bir karara vardım. Friendly erişim
belirleyicisi bir üyeye sadece bulunduğu paket içinden erişim hakkı veriyordu.
Dolayısıyla başka bir pakette, türetilen bir sınıf içinde bu üye geçersiz
kılınamazdı. Herşeyden önce, türeyen sınıf, temel sınıfın bulunduğu paketteki
friendly erişim belirleyicilerinden haberdar değildi ve bu aslında durumu
açıklıyordu. Türeyen sınıftaki metod, türediği sınıftaki metodtan haberdar değil
ise onu nasıl geçersiz kılabilirdiki.
Bu varsayım ışığında, bir örnek ile konuyu
derinlemesine incelemeye karar verdim. Yapmam gereklen bir paket içerisinde yer
alan bir sınıftaki friendly metodu, başka bir pakette bu sınıftan türeyen bir
sınıf içinde geçersiz kılmaya çalışmaktı. Önce temel sınıfın bulunduğu paketi
yazdım.
package com.bsenyurt.mat;
public class Temel
{
void Metod1()
{
System.out.println("Temel sınıftan
Metod1");
}
public int Toplama(int ilksayi,int ikincisayi)
{
return ilksayi+ikincisayi;
}
} |
Şimdi farklı bir pakette, Temel sınıfından başka
bir sınıf türetecek ve Metod1'i buradada kullanacaktım.
package com.bsenyurt.yazi;
import com.bsenyurt.mat.*;
public class Tureyen extends Temel
{
public void Metod1()
{
System.out.println("Tureyen sınıftan
Metod1");
}
public static void main(String[] args)
{
Tureyen t=new Tureyen();
t.Metod1();
}
} |
Uygulama sorunsuz şekilde çalıştı. Ancak
kaynaklarda söylendiği gibi, tureyen sınıfın temel sınıftaki Metod1'in
varlığından haberdar olmadığını nasıl görebilirdim. Sorun buradaydı. Eğer bunu
başarabilirsem, zaten türeyen sınıftaki Metod1 in kendi başına bir metod
olduğunu yani aslında temel sınıftaki metodu geçersiz kılmadığını
söyliyebilirdim. Bu işi iki satırlık kod parçası çözdü. Temel sınıftan bir nesne
türetilecek ve bu nesne üzerinden Metod1 çağırılacaktı.
Temel temel=new Temel();
temel.Metod1(); |

Gerçektende kaynaklarda söylendiği gibi olmuştu.
Temel sınıftan bir nesne oluşturabilmiştim fakat Metod1'e friendly olduğu için
erişememiştim. Dahası bu, türeyen sınıfın Temel sınıftaki metotdan haberdar
olmadığı anlamına gelmekteydi.
Şimdi ise kalıtım ile ilgili olarak aklıma takılan
başka bir konu vardı. Bu c# dilinde sıkça yapılan ve sanal metodların
kullanımını doğuran bir testtir. Acaba türeyen sınıf türüden bir nesneyi bir
temel sınıf nesnesine aktarsak ve ardından, temel sınıf nesnesi üzerinden,
türeyen sınıftaki bir üyeye ulaşmaya çalışsak ne olurdu? C# dilinde bu,
derleyicinin hata vermesine neden olmaktaydı. Çünkü temel sınıf türeyen sınıf
üyeleri hakkında herhangibir bilgiye sahip olamazdı. Ancak sanal metod
tanımalamaları ile, temel sınıf nesnesi üzerinden türeyen sınıftaki bir üyeye
erişmek mümkün olabiliyordu. Yani temel sınıftaki sanal metod türeyen sınıfta
geçersiz kılınıyordu (override). Acaba java dilinde durum nasıldı? Bunu
öğrenmenin yolu her zamanki gibi iyi bir testten geçiyordu.
class Temel
{
public void Metod1()
{
System.out.println("Temel sınıftan
Metod1");
}
}
class Tureyen extends Temel
{
public void Metod1()
{
System.out.println("Tureyen sınıftan
Metod1");
}
public void Metod2()
{
System.out.println("Tureyen sınıftan
Metod2");
}
}
public class Program2
{
public static void main(String[] args)
{
Tureyen tureyen=new Tureyen();
Temel temel=new Temel();
temel=tureyen;
temel.Metod2();
}
} |

Beklediğim gibi bir sonuç. Her ne kadar derleyici
hatası bana fazla anlamlı gelmesede, Temel sınıf nesnesi üzerinden türeyen
sınıftaki üyeye erişememiştim. Peki ya acaba Java'da bana bu imkanı verecek C#
taki sanal metodlar gibi unsurlar varmıydı?
Java dilini öğrenmeye başladığımda bana bu konu
ile ilgili pek çok kaynak gerekmişti. Yakın bir arkdaşım bu konuda bana yardımcı
oldu ve KaZaAraaa (bilmeden, istemeden, ansızın karşımıza çıkan) bir kaç e-book
getirdi. Bu kitaplardan virtual anahtar sözcüğünü arattığımda, sadece JVM (Java
Virtual Machine) konularına ulaştım. Amacım C# taki virtual metodları Java
dilinde bulabilmekti. Ancak henüz bu muradıma erişemedim. Belkide ve büyük
olsalılıkla Java dilinde, virtual metodlar yerine başka elemanlar kullanılıyor
olabilirdi. Kim bilir ilerleyen zamanlarda karşıma çıkar umarım. Belki bir
sonraki hafta yoğun şekilde inceleyeceğim çok biçimlilik (polimorphsym)
konusunda.
Burak Selim ŞENYURT
selim@bsenyurt.com