Diğer programlama dillerinde olan çoklu kalıtım (multiple inheritance) özelliği Java programlama dilinde yoktur. Java programlama dilinde çoklu kalıtım desteğinden faydalanmak için arayüz (interface) ve dahili sınıflar (inner classes) kullanılır.
Bu makalemizde ise Bu iki destekten inner classes dahili sınıflar kavramını incelemeye çalışacağız.
Dâhili Sınıflar (Inner Classes)
Dâhili sınıflar JDK 1.1 ile gelen bir özelliktir. Bu özellik sayesinde bir sınıf diğer bir sınıfın içerisinde tanımlanabilir; böylece mantıksal bir bütünü oluşturan bir çok sınıf tek bir çatı alında toplanır. Dahili sınıflar yapısal olarak 3 gruba ayrılabilir.
• Dâhili üye sınıflar
• Yerel sınıflar (Local classes)
• İsimsiz sınıflar (Anonymous classes)
Dâhili Üye Sınıflar
Bir sınıfın içerisinde, başka bir sınıfı tanımlamak mümkündür; Şöyle ki...
class CevreliyiciSinif {
class DahiliSinif {
//....
}
//...
} |
Başka bir sınıfın içerisinde tanımlanan bu sınıfa dâhili üye sınıf denir. Dahili sınıfları, çevreleyici sınıfların içerisinde kullanmak, geçen makalelerde incelediğimiz komposizyondan yönteminden farklıdır.
Dâhili üye sınıflar, tek başlarına bağımsız sınıflar gibi düşünülebilir. Örnek üzerinde incelersek,
Hesaplama.java
public class Hesaplama {
public class Toplama { //Dahili uye sinif
public int toplamaYap(int a, int b) {
return a+b ;
}
} // class Toplama
public static void main(String args[]) {
Hesaplama.Toplama ht = new Hesaplama().new Toplama() ;
int sonuc = ht.toplamaYap(3,5);
System.out.println("Sonuc = " + sonuc );
}
} // class Hesapla |
Hesaplama sınıfının içerisinde tanımlanmış Toplama sınıfı bir dahili üye sınıfıdır. Hesaplama sınıfı ise çevreleyici sınıftır. Toplama sınıfına ait bir nesne oluşturmak için, önce Hesaplama sınıfına ait bir nesne oluşturmamız gerekir.
Hesaplama.Toplama ht = new Hesaplama().new Toplama() ; |
ht referansı Toplama dahili üye sınıfı tipindedir; artık bu referansı kullanarak Toplama nesnesine ait toplamaYap() yordamına ulaşabiliriz. Uygulamanın çıktısı aşağıdaki gibdir;
Sonuc = 8
Dâhili Üye Sınıflar ve Erişim
Dâhili üye sınıflara, public, friendly, protected veya private erişim belirleyicileri atanabilir, böylece dâhili üye sınıflarımıza olan erişimi kısıtlamış/açmış oluruz. Dikkat edilmesi gereken diğer bir husus ise bir dâhili üye sınıf private erişim belirleyicisine sahip olsa dahi, çevreleyici sınıf içerisindeki tüm yordamlar tarafından erişilebilir olmasıdır. Bu kısıt ancak başka sınıflar için geçerlidir.
Hesaplama1.java
public class Hesaplama1 {
public class Toplama { // Dahili uye sinif - public
public int toplamaYap(int a, int b) {
return a + b ;
}
} // class Toplama
protected class Cikartma { // Dahili uye sinif - protected
public int cikartmaYap(int a, int b) {
return a - b ;
}
} // class Cikartma
class Carpma { // Dahili uye sinif - friendly
public int carpmaYap(int a, int b) {
return a * b ;
}
} // class Carpma
private class Bolme { // Dahili uye sinif - private
public int bolmeYap(int a, int b) {
return a / b ;
}
} // class Bolme
public static void main(String args[]) {
Hesaplama1.Toplama ht = new Hesaplama1().new Toplama() ;
Hesaplama1.Cikartma hck = new Hesaplama1().new Cikartma() ;
Hesaplama1.Carpma hcp = new Hesaplama1().new Carpma() ;
Hesaplama1.Bolme hb = new Hesaplama1().new Bolme() ;
int sonuc1 = ht.toplamaYap(10,5);
int sonuc2 = hck.cikartmaYap(10,5);
int sonuc3 = hcp.carpmaYap(10,5);
int sonuc4 = hb.bolmeYap(10,5);
System.out.println("Toplama Sonuc = " + sonuc1 );
System.out.println("Cikartma Sonuc = " + sonuc2 );
System.out.println("Carpma Sonuc = " + sonuc3 );
System.out.println("Bolme Sonuc = " + sonuc4 );
}
} // class Hesaplama |
Hesaplama1 sınıfımızın içerisinde toplam 4 adet dâhili üye sınıf mevcuttur. Public erişim belirleyicisine sahip Toplama dâhili üye sınıfı, protected erişim belirleyicisen sahip Cikartma dâhili üye sınıfı, friendly erişim belirleyicisine sahip Carpma dahili üye sınıfı ve private erişim belirleyicisine sahip Bolme üye dahili sınıfı. Hesaplama1 sınıfı, bu 4 adet dahili üye sınıfın çevreliyici sınıfıdır. Çevreleyici olan Hesaplama1 sınıfının statik olan main() yordamına dikkat edilirse, bu yordamın içerisinde tüm (private dâhil) dâhili üye sınıflara erişilebildiğini görülür. Bunun sebebi, main() yordamı ile tüm dâhili üye sınıfların aynı çevreleyici sınıfın içerisinde olmalarıdır. Uygulamanın çıktısı aşağıdaki gibidir:
Toplama Sonuc = 15
Cikartma Sonuc = 5
Carpma Sonuc = 50
Bolme Sonuc = 2
Yukarıdaki örneğin yeni bir versiyonu yazılıp, dahili üye sınıflar ile bunlara ait erişim belirleyicilerin nasıl işe yaradıklarını incelenirse...
Hesaplama2Kullan.java
class Hesaplama2 {
public class Toplama2 { // Dahili uye sinif - public
public int toplamaYap(int a, int b) {
return a + b ;
}
} // class Toplama2
protected class Cikartma2 { // Dahili uye sinif - protected
public int cikartmaYap(int a, int b) {
return a - b ;
}
} // class Cikartma2
class Carpma2 { // Dahili uye sinif - friendly
public int carpmaYap(int a, int b) {
return a * b ;
}
} // class Carpma2
private class Bolme2 { // Dahili uye sinif - private
public int bolmeYap(int a, int b) {
return a / b ;
}
} // class Bolme2
} // class Hesaplama2
public class Hesaplama2Kullan {
public static void main(String args[]) {
Hesaplama2.Toplama2 ht=new Hesaplama2().new Toplama2() ;
Hesaplama2.Cikartma2 hck=new Hesaplama2().new Cikartma2() ;
Hesaplama2.Carpma2 hcp = new Hesaplama2().new Carpma2() ;
// Hesaplama2.Bolme3 hb = new Hesaplama2().new Bolme2() ;
// ! Hata !
int sonuc1 = ht.toplamaYap(10,5);
int sonuc2 = hck.cikartmaYap(10,5);
int sonuc3 = hcp.carpmaYap(10,5);
// int sonuc4 = hb.bolmeYap(10,5); // ! Hata !
System.out.println("Toplama Sonuc = " + sonuc1 );
System.out.println("Cikartma Sonuc = " + sonuc2 );
System.out.println("Carpma Sonuc = " + sonuc3 );
}
} |
Hesaplama2 sınıfımız, toplam 4 adet olan dâhili üye sınıflarının çevreleyicisidir. Dâhili üye sınıfları ve onlara ait erişim belirleyicileri incelenirse:
• Toplama2 sınıfı, public erişim belirleyicisine sahip olan dâhili üye sınıfıdır.
• Cikartma2 sınıfı, protected erişim belirleyicisine sahip olan dâhili üye sınıfıdır.
• Carpma2 sınıfı, friendly erişim belirleyicisine sahip olan dâhili üye sınıfıdır.
• Bolme2 sınıfı, private erişim belirleyicisine sahip olan dâhili üye sınıfıdır.
Hesaplama2Kullan sınıfının statik olan main() yordamının içerisinden, Hesaplama2 sınıfının içerisindeki dahili üye sınıflara erişilebilir mi? Erişilebilir ise hangi erişim belirleyicilerine sahip olan dahili üye sınıflara erişilebilir?
Normalde bir sınıf private veya protected erişim belirleyicisine sahip olamaz ancak dahili sınıflar private veya protected erişim belirleyicisine sahip olabilir. Hesaplama2Kullan sınıfı, Hesaplama2 sınıfı ile aynı paket içerisinde olduğu için, Hesaplama2Kullan sınıfı, Hesapla2 sınıfının içerisinde tanımlanmış olan public, protected ve friendly erişim belirleyicilerine sahip olan dahili üye sınıflara erişebilir ama private erişim belirleyicisine sahip olan Bolme dahili üye sınıfına erişemez. Uygulamanın çıktısı aşağıdaki gibidir;
Toplama Sonuc = 15
Cikartma Sonuc = 5
Carpma Sonuc = 50
Dâhili Üye Sınıflar ve Bunları Çevreleyen Sınıflar Arasındaki İlişki
Dâhili üye sınıflar, içerisinde bulundukları çevreleyici sınıfların tüm alanlarına (statik veya değil private dâhil) ve yordamlarına (statik veya değil-private dâhil) erişebilirler.
Hesaplama3.java
public class Hesaplama3 {
private int sabit1 = 2 ;
private static int sabit2 = 1 ;
public class Toplama3 { //Uye dahili sinif
public int toplamaYap(int a, int b) {
return (a+b) + sabit1 ; // dikkat
}
} // class Toplama3
public class Cikartma3 { //Uye dahili sinif
public int cikartmaYap(int a, int b) {
dekontBilgileriGoster(); // dikkat
return (a-b) - sabit2 ; // dikkat
}
} // class Cikartma3
private void dekontBilgileriGoster() {
System.out.println("Dekont Bilgileri Gosteriliyor");
}
public void ekranaBas(int a , int b ) {
int sonuc = new Toplama3().toplamaYap(a,b);
System.out.println("Sonuc = " + a + " + " + b + " + sabit1 = "+ sonuc);
}
public static void main(String args[]) {
Hesaplama3 h3 = new Hesaplama3();
h3.ekranaBas(10,5);
// Toplama islemi
Hesaplama3.Toplama3 ht3 = h3.new Toplama3() ;
int sonuc = ht3.toplamaYap(11,6);
System.out.println("Sonuc = 11 + 6 + sabit1 = " + sonuc );
// Cikartma islemi
Hesaplama3.Cikartma3 hc3 = h3.new Cikartma3();
int sonuc1 = hc3.cikartmaYap(10,5);
System.out.println("Sonuc = 10 - 5 - sabit2 = " + sonuc1);
}
} // class Hesaplama3 |
Hesaplama3 sınıfının içerisinde iki adet dâhili üye sınıf bulunmaktadır. Bunlar Toplama3 ve Cikartma3 sınıflarıdır. Toplama3 dahili üye sınıfı, Hesaplama3 sınıfı içerisinde global olarak tanımlanmış ilkel (primitive) int tipindeki ve private erişim belirleyicisine sahip olan sabit1 alanına erişebilmektedir. Toplama3 dahili üye sınıfı, Hesaplama3 sınıfı içerisinde tanımlanmış olan sabit1 alanını kullanırken sanki kendi içerisinde tanımlanmış bir alanmış gibi, hiç bir belirteç kullanmamaktadır.
Aynı şekilde Cikartma3 dâhili üye sınıfı, Hesaplama3 sınıfının içerisinde statik olarak tanımlanmış, private erişim belirleyicisine sahip ilkel int tipindeki sabit2 alanını ve private erişim belirleyicisine sahip dekontBilgileriGoster() yordamına direk olarak erişebilmektedir.
Hesaplama3 sınıfının, nesne yordamı olan (-bu yordamın kullanılabilmesi için Hesaplama3 sınıfına ait bir nesne oluşturmak gerekir) ekranaBas(), iki adet parametre alıp, geriye hiçbirşey döndürmez (void). Bu yordamın içerinde Toplama3 dahili üye sınıfına ait nesne oluşturularak, bu dahili üye sınıfın toplamaYap() yordamı çağrılmaktadır. Toplama3 dâhili üye sınıfının toplamaYap() yordamından dönen cevap, ekranaBas() yordamının içerisinde ekrana bastırılır.
Dikkat edilmeye değer diğer bir husus ise sadece bir adet çevreleyici sınıfa ait nesne oluşturup, Bu nesneye bağlı referansı kullanarak, çevreleyici sınıf içerisindeki diğer dâhili üye sınıflara ait nesnelerin oluşturulmasıdır. Olaylara daha yakından bakılırsa;
Hesaplama3 h3 = new Hesaplama3();
Hesaplama3.Toplama3 ht3 = h3.new Toplama3() ;
Hesaplama3.Cikartma3 hc3 = h3.new Cikartma3(); |
Sadece bir adet Hesaplama3 sınıfına ait nesne oluşturuldu. Bu nesneye bağlı referansı kullanarak (h3), diğer dâhili üye sınıflara ait nesneler oluşturulabilir. Buradaki ana fikir, çevreleyici sınıfların içerisinde bulunan her dâhili üye sınıfa ait bir nesne oluşturmak için, her seferinde yeni bir çevreleyici sınıfa ait nesne oluşturma zorunluluğu olmadığıdır. Yani çevreleyici sınıfa ait bir nesne, yine çevreleyici sınıf tipindeki bir referansa bağlanırsa, işler daha kestirmeden çözülebilir. Uygulamanın çıktısı aşağıdaki gibidir;
Sonuc = 10 + 5 + sabit1 = 17
Sonuc = 11 + 6 + sabit1 = 19
Dekont Bilgileri Gosteriliyor
Sonuc = 10 – 5 - sabit2 = 4
Statik Dâhili Üye Sınıflar
Statik (static) olarak tanımlanmış dâhili üye sınıflar, normal dahili üye sınıflardan farklıdırlar. Bu farklılıklar şöyledir:
• Statik dâhili üye sınıfına ait nesne oluşturmak için, onu çevreleyen sınıfa ait bir nesne oluşmak zorunda değilizdir)
• Statik dâhili üye sınıflar, kendilerini çevreleyen sınıfa ait bağlantıyı (-this-) kaybederler.
Statik dahili üye sınıflar, onları çevreleyen üst sınıfa ait global alanlara (statik veya değil) ve yordamlara (statik veya değil) direk ulaşım şansını kaybeder. Bunun sebebi, kendisini çevreleyen sınıf ile arasındaki bağı kopartmış olmasıdır. Buraya kadar ifade edilenleri örnek üzerinde inceleyelim, ama öncesinde UML diyagramı çizilirse...
Hesaplama4 sınıfının içerisinde, 2 adet dâhili üye sınıf oluşturulacaktır; fakat bu dâhili üye sınıflardan biri statik olarak tanımlanacaktır. Bu örnekte statik tanımlanacak olan dâhili üye sınıf, Toplama4 sınıfıdır. Toplama4 sınıfına ait bir nesne oluşturulmak istenirse, bunun hemen öncesinde Hesaplama4 sınıfına ait bir nesne oluşturulmaz. UML diyagramı Java uygulamasına dönüştürülürse...
Hesaplama4.java
public class Hesaplama4 {
int sabit = 2;
private int ozelsabit = 1 ;
public static class Toplama4 { // Statik uye dahili sinif
static int toplam ; // dogru
int sonuc ; // dogru
public int toplamaYap(int a, int b) {
// return (a+b) + sabit ; ! Hata !
sonuc = toplam = a+b;
return sonuc;
}
public void dekontOlustur() {
/* -sabit- alanina ve
-ekranaBas() yordamına ulasabilmek icin
Hesaplama4 sinifina ait nesne olusturmamiz gerekir.
*/
Hesaplama4 hs4 = new Hesaplama4(); //dikkat
int a = hs4.ozelsabit ; // dogru
hs4.ekranaBas() ; //dogru
System.out.println("Dekont olusturuyor = " +
hs4.sabit + " - " +a );
}
} // class Toplama4
public class Cikartma4 { //Uye dahili sinif
int sonuc ;
// static int sonuc1 ; // ! hata!
public int cikartmaYap(int a, int b) {
ekranaBas(); // dikkat
sonuc = (a-b) - ozelsabit;
return sonuc ; // dikkat
}
} // class Cikartma4
private void ekranaBas() {
System.out.println("Hesaplama4.ekranaBas()");
}
public static void main(String args[]) {
// ! Hata !
// Hesaplama4.Toplama4 ht=new Hesaplama4().new Toplama4();
Toplama4 tp4 = new Toplama4();
tp4.dekontOlustur();
int sonuc = tp4.toplamaYap(10,5);
System.out.println("Sonuc = 10 + 5 = " + sonuc );
}
} // class Hesaplama4
class Hesaplama4Kullan {
public static void main(String args[]) {
// ! Hata!
// Hesaplama4.Toplama4 ht=new Hesaplama4().new Toplama4() ;
Hesaplama4.Toplama4 tp4 = new Hesaplama4.Toplama4();
int sonuc = tp4.toplamaYap(10,5);
System.out.println("Sonuc = 10 + 5 = " + sonuc );
}
} // class Hesaplama4Kullan |
Statik dâhili üye sınıf olan Toplama4 sınıfını yakın takibe alıp, neleri nasıl yaptığını inceleyelim. Toplama4 statik dahili sınıfının içerisinde statik global alan tanımlayabiliriz. Statik olmayan dahili üye sınıfların içerisinde statik global alan tanımlanamaz.
Toplama4 statik dahili üye sınıfının, toplamaYap() yordamının içerisinde, Hesaplama4 sınıfına ait global olarak tanımlamış ilkel (primitive) int tipindeki sabit alanına direk erişilemez. Statik dâhili üye sınıflar ile bunları çevreleyen sınıflar arasında this bağlantısı yoktur. Eğer statik dâhili üye sınıfın içerisinden, onu çevreleyen sınıfa ait bir alan (statik olmayan) veya yordam (statik olmayan) çağrılmak isteniyorsa, bu bizzat ifade edilmelidir. Aynı Toplama4 statik dâhili üye sınıfına ait dekontOlustur() yordamının içerisinde yapıldığı gibidir.
dekontOlustur() yordamının içerisinde, Hesaplama4 sınıfına ait nesne oluşturulmadan, sabit, ozelsabit alanlarına ve ekranaBas() yordamına ulaşamazdık. Buradaki önemli nokta, dâhili üye sınıf statik olsa bile, kendisine çevreleyen sınıfın private erişim belirleyicisi sahip olan alanlarına (statik veya değil) ve yordamlarına (statik veya değil) erişebilmesidir.
Hesaplama4 sınıfının statik olan main() yordamının içerisinde, Toplama4 statik dâhili üye sınıfına ait nesnenin nasıl oluşturulduğuna dikkat edelim. Toplama4 statik dâhili üye sınıfına ait nesne oluştururken, onu çevreleyen sınıfa ait herhangi bir nesne oluşturmak zorunda kalmadık.
Son olarak Hesaplama4Kullan sınıfında statik olarak tanımlanan main() yordamının içerisindeki olayları inceleyelim. Başka bir sınıfın içerisinde statik dâhili üye sınıfı ulaşmak için, sadece tanımlama açısından, dahili üye sınıfı çevreleyen sınıfın ismi kullanılmıştır. Mantıklı olanda budur, statik de olsa sonuçta ulaşılmak istenen dâhili üye bir sınıfıdır.
Elimizde iki adet çalıştırılabilir sınıf mevcutur (-main() yordamı olan). Hesaplama4 sınıfını çalıştırdığımızda (java Hesaplama4), sonuç aşağıdaki gibi olur;
Hesaplama4.ekranaBas()
Dekont olusturuyor = 2 – 1
Sonuc = 10 + 5 = 15
Eğer Hesaplama4Kullan sınıfı çalıştırılırsa (java Hesaplama4Kullan), sonuç aşağıdaki gibi olur;
Sonuc = 10 + 5 = 15
Statik Dâhili Üye Sınıflar ve Statik Yordamlar
Statik dâhili üye sınıfların içerisinde statik alanlar bulunduğu gibi, statik yordamlarda bulunabilir. Eğer statik dâhili üye sınıfı içerisinde, statik bir yordam oluşturulmuş ise, bu yordamı çağırmak için ne statik dâhili üye sınıfına ne de onu çevreleyen sınıfa ait herhangi bir nesne oluşturmak gerekmez.
Hesaplama5.java
public class Hesaplama5 {
private static int x = 3 ;
public static class Toplama5 { // Statik uye dahili sinif
static int toplam ; // dogru
int sonuc ; // dogru
public static int toplamaYap(int a, int b) {
// sonuc = a+b + x ; // ! Hata !
toplam = a + b + x;
return toplam;
}
} // class Toplama5
public static void main(String args[]) {
int sonuc = Hesaplama5.Toplama5.toplamaYap(16,8); // dikkat
System.out.println("Sonuc = 16 + 8 = " + sonuc );
}
} // class Hesaplama5 |
Toplama5 statik dâhili üye sınıfının, statik olan toplamaYap() yordamından, Hesaplama5 çevreleyici sınıfına ait ilkel (primitive) int tipinde tanımlanmış x alanına ulaşılabilir. Bunun sebebi x alanında statik olarak tanımlanmış olmasıdır. main() yordamının içerisinde, toplamaYap() yordamının çağrılışına dikkat edilirse, ne Hesaplama5 sınıfına ait nesne, ne de Toplama5 statik dâhili üye sınıfına ait bir nesnenin oluşturulmadığı görülür. Uygulamanın çıktısı aşağıdaki gibidir;
Sonuc = 16 + 8 = 27
Statik ve Final Alanlar
Statik olmayan dâhili üye sınıfların içerisinde, statik alanlar ve yordamlar tanımlanamaz; ama "statik ve final" alanlar tanımlanabilir. Bir alanın hem statik hem de final olması demek, onun SABİT olması anlamına geldiği için, Statik olmayan dâhili üye sınıfların içerisinde statik ve final alanlar kullanılabilir.
StatikFinal.java
class CevreliyiciSinif1 {
class DahiliSinif1 { // Dahili uye siniflar
// static int x = 10 ; // ! Hata !
}
}
// Dogru
class CevreliyiciSinif2 {
class DahiliSinif2 {
int x; // Dogru
}
}
// Dogru
class CevreliyiciSinif3 {
class DahiliSinif3 {
static final int x = 0; // Dogru
}
} |
Dâhili Üye Sınıflar ve Yapılandırıcılar (Constructors)
Dâhili üye sınıfların yapılandırıcıları olabilir.
BuyukA.java
public class BuyukA {
public class B {
public B() { // yapilandirici
System.out.println("Ben B sinifi ");
}
} // class B
public BuyukA() {
System.out.println("Ben BuyukA sinifi ");
}
public static void main(String args[]) {
BuyukA ba = new BuyukA();
}
} |
Dâhili üye sınıfını çevreleyen sınıfa ait bir nesne oluşturulduğu zaman, dâhili üye sınıfına ait bir nesne otomatik oluşturulmaz. Yukarıdaki örneğimizde sadece BuyukA sınıfına ait bir nesne oluşturulmuştur ve bu yüzden sadece BuyukA sınıfına ait yapılandırıcı çağrılacaktır. Eğer dahili üye sınıf olan B sınıfına ait yapılandırıcının çağrılmasını isteseydik, main() yordamının içerisine : " BuyukA.newB() " dememiz gerekirdi.
İç içe Dâhili Üye Sınıflar
Bir sınıfın içerisinde dâhili üye sınıf tanımlayabilirsiniz. Tanımlanan bu dâhili üye sınıfın içerisinde, yine bir dâhili üye sınıf tanımlayabilirsiniz... Bu böyle sürüp gidebilir...
Abc.java
public class Abc {
public Abc() { // Yapilandirici
System.out.println("Abc nesnesi olusturuluyor");
}
public class Def {
public Def() { // Yapilandirici
System.out.println("Def nesnesi olusturuluyor");
}
public class Ghi {
public Ghi() { // Yapilandirici
System.out.println("Ghi nesnesi olusturuluyor");
}
} // class Ghi
} //class Def
public static void main( String args[] ) {
Abc.Def.Ghi ici_ice = new Abc().new Def().new Ghi();
}
} // class Abc |
Bu örnekte iç içe geçmiş üç adet sınıf vardır. Uygulamanın çıktısı aşağıdaki gibi olur:
Abc nesnesi olusturuluyor
Def nesnesi olusturuluyor
Ghi nesnesi olusturuluyor
Soyut (Abstract) Dâhili Üye Sınıflar
Dâhili üye sınıflar, soyut (abstract) sınıf olarak tanımlanabilir. Bu soyut dâhili üye sınıflardan türeyen sınıflar, soyut dâhili üye sınıfların içerisindeki gövdesiz (soyut) yordamları iptal etmeleri gerekmektedir. Örneğimize geçmeden evvel, UML diyagramını inceleyelim.
Hayvan sınıfının içerisinde soyut (abstract) dâhili üye sınıf olarak tanımlanmış Kus sınıfı iki adet gövdesiz (soyut-abstract) yordamı olsun, uc() ve kon(). Kartal sınıfı, soyut dâhili üye sınıf olan Kus sınıfından türetilebilir.
HayvanKartal.java
class Hayvan {
abstract class Kus {
public abstract void uc ();
public abstract void kon();
}
public void avlan() {
System.out.println("Hayvan avlaniyor...");
}
}
class Kartal extends Hayvan.Kus {
public void uc() {
System.out.println("Kartal Ucuyor...");
}
public void kon() {
System.out.println("Kartal Konuyor...");
}
// public Kartal() { } // ! Hata !
public Kartal(Hayvan hv) {
hv.super(); //Dikkat
}
public static void main(String args[]) {
Hayvan h = new Hayvan(); //Dikkat
Kartal k = new Kartal(h);
k.uc();
k.kon();
}
} |
Kartal sınıfının içerisinde, soyut dahili üye sınıf olan Kus sınıfının, gövdesiz olan iki yordamı iptal edilmiştir. Olayları sırası ile inceleyelim, Kartal sınıfına ait bir nesne oluşturulmak istense bunun öncesinde Kus sınıfına ait bir nesnenin oluşturulması gerekir çünkü Kartal sınıfı Kus sınıfından türetilmiştir. Buraya kadar sorun yok, fakat asıl kritik nokta Kus sınıfının dâhili üye sınıf olmasıdır. Daha açık bir ifade ile, eğer Kus sınıfına ait bir nesne oluşturulacaksa, bunun öncesinde elimizde Kus sınıfının çevreleyici sınıfı olan Hayvan sınıfına ait bir nesne bulunması zorunluluğudur. Kus sınıfı statik dahili üye sınıf olmadığından, Hayvan sınıfına bağımlıdır. Uygulamanın çıktısı aşağıdaki gibidir;
Kartal Ucuyor...
Kartal Konuyor...
Kartal sınıfının statik olarak tanımlanmış main() yordamının içerisine dikkat edersek, önce Hayvan sınıfına ait bir nesne sonrada Kartal sınıfına ait bir nesne oluşturduk. Daha sonra Hayvan sınıfı tipinde parametre kabul eden, Kartal sınıfının yapılandırıcısına, bu referansı pasladık. Kartal sınıfına ait yapılandırıcının içerisinde super() anahtar kelimesi ile Hayvan sınıfının varsayılan yapılandırıcısını çağrılmıştır.
CevreliyiciSinif.super() ; |
Eğer Kus sınıfı, statik dâhili üye sınıfı yapılsaydı, super() anahtar kelimesini kullanılmak zorunda değildi. Bunun sebebi, statik olan dâhili üye sınıfların onları çevreleyen sınıflara bağımlı olmamasıdır. Yukarıdaki örnek bu anlatılanlar ışığında tekrardan yazılırsa.
HayvanKartal1.java
class Hayvan1 {
static abstract class Kus1 {
public abstract void uc ();
public abstract void kon();
}
public void avlan() {
System.out.println("Hayvan avlaniyor...");
}
}
class Kartal1 extends Hayvan1.Kus1 {
public void uc() {
System.out.println("Kartal1 Ucuyor...");
}
public void kon() {
System.out.println("Kartal1 Konuyor...");
}
public Kartal1() { } // dogru
public static void main(String args[]) {
Kartal1 k1 = new Kartal1();
k1.uc();
k1.kon();
}
} |
Yukarıdaki örneğimizden görüldüğü üzere, artık Kus sınıfına ait bir nesne oluşturmak istersek, bunun hemen öncesinde Hayvan sınıfına ait bir nesne oluşturmak zorunda değilizdir. Bunun sebebi, Kus sınıfının statik dâhili üye sınıfı olmasından kaynaklanır. Uygulamanın çıktısı aşağıdaki gibidir;
Kartal1 Ucuyor...
Kartal1 Konuyor...
Türetilebilen Dâhili Üye Sınıflar
Dâhili üye sınıflar, aynı normal sınıflar gibi başka sınıflardan türetilebilirler. Böylece diğer dillerde olan çoklu kalıtım desteğinin bir benzerini Java programlama dilinde de bulabiliriz. Dâhili sınıfların varoluş sebeplerini biraz sonra detaylı bir şekilde inceleyeceğiz. Örneğimize geçmeden evvel, UML diyagramımızı inceleyelim;
Dâhili üye sınıf olan SuperMotor sınıfı, Motor sınıfından türetilmiştir. UML diyagramını Java uygulamasını dönüştürüp, olayları daha somut bir şekilde incelersek.
YarisArabasi.java
class Motor {
public void calis() {
System.out.println("Motor Calisiyor");
}
public void dur() {
System.out.println("Motor Durdu");
}
}
public class YarisArabasi {
public void hizYap() {
System.out.println("YarisArabasi hiz yapiyor");
}
public class SuperMotor extends Motor {
public void calis() { // iptal etti (override)
System.out.println("SuperMotor Calisiyor");
}
public void dur() { // iptal etti (override)
System.out.println("SuperMotor Durdu");
}
}
} |
Dâhili üye sınıflar, başka sınıflardan türetilebildiği gibi arayüzlere erişip, bunların içlerindeki gövdesiz yordamları iptal edebilir, aynı normal sınıflar gibi...
Yerel Sınıflar (Local Classes)
Yerel sınıflar, yapılandırıcıların (constructor), sınıf yordamlarının (statik yordam), nesne yordamların, statik alanlara toplu değer vermek için kullandığımız statik bloğun veya statik olmayan alanlara toplu değer vermek için kullandığımız bloğun içerisinde tanımlanabilir. Yerel sınıfların genel gösterimi aşağıdaki gibidir;
public class Sinif {
public void yordam() {
public class YerelSinif {
//...
}
}
} |
Yerel sınıflar, yalnızca içinde tanımlandıkları, yordamın veya bloğun içerisinde geçerlidir. Nasıl ki dâhili üye sınıfların çevreleyici sınıfları vardı, yerel sınıfların ise çevreleyici yordamları veya blokları vardır. Yerel sınıflar tanımlandıkları bu yordamların veya blokların dışarısından erişilemezler. Yerel sınıflara ait ilk özellikleri verelim;
• Yerel sınıflar tanımlandıkları yordamın veya bloğun dışından erişilemezler.
• Yerel sınıflar başka sınıflardan türetilebilir veya arayüzlere (interface) erişebilir.
• Yerel sınıfların yapılandırıcıları olabilir.
Yukarıdaki özelikleri Java uygulamasında ispatlanırsa;
Hesaplama6.java
interface Toplayici {
public int hesaplamaYap() ;
}
public class Hesaplama6 {
public int topla(int a, int b) {
class Toplama6 implements Toplayici {
private int deger1;
private int deger2;
public Toplama6(int deger1, int deger2) { // yapilandirici
this.deger1 = deger1;
this.deger2 = deger2;
}
public int hesaplamaYap() { // iptal etti (override)
return deger1+deger2;
}
} // class Toplama6
Toplama6 t6 = new Toplama6(a,b);
return t6.hesaplamaYap();
}
public void ekranaBas() {
// Toplama6 t6 = new Toplama6(2,6,); // !Hata!-Kapsama alanının dışı
}
public static void main(String args[]) {
Hesaplama6 h6 = new Hesaplama6();
int sonuc = h6.topla(5,9);
System.out.println("Sonuc = 5 + 9 = " + sonuc );
}
} // class Hesaplama6 |
Bu örneğimizde Toplama6 yerel sınıftır. Yerel bir sınıf, başka bir sınıftan türetilebilir veya bir arayüze erişip, onun gövdesiz yordamlarını iptal edebilir, aynı normal sınıflar gibi. Toplama6 yerel sınıfı, Hesapliyici arayüzüne eriştiğinden, bu arayüzün gövdesiz yordamı olan hesaplamaYap() yordamını iptal etmek zorundadır. Toplama6 yerel sınıfı, Hesaplama6 sınıfının topla() yordamının içerisinde tanımlanmıştır. Bunun anlamı, Toplama6 yerel sınıfına yalnızca topla() yordamının içerisinde erişilebileceğidir. Hesaplama6 sınıfının nesne yordamı olan (bu yordama ulaşmak için Hesaplama6 sınıfına ait nesne oluşturmamız gerektiği anlamında...) ekranaBas() yordamının içerisinden, Toplama6 yerel sınıfına ulaşılamaz çünkü Toplama6 yerel sınıfı, ekranaBas() yordamının kapsama alanının dışında kalmaktadır. Uygulamamızın çıktısı aşağıdaki gibi olur;
Sonuc = 5 + 9 = 14
Yerel sınıflara diğer özellikler aşağıdaki gibidir;
• Yerel sınıflar, içinde bulundukları yordamın sadece final olan değişkenlerine ulaşabilirler.
• Yerel sınıflar, statik veya statik olmayan yordamların içerisinde tanımlanabilirler.
• Yerel sınıflar, private, protected ve public erişim belirleyicisine sahip olamazlar sadece friendly erişim belirleyicisine sahip olabilirler.
• Yerel sınıflar, statik olarak tanımlanamaz.
Yukarıdaki kuralları, bir örnek üzerinde uygularsak...
Hesaplama7.java
public class Hesaplama7 {
public static int topla(int a, final int b) {
int a_yedek = a ;
class Toplama7 {
private int x ; // dogru
public int y ; // dogru
// protected int z = a_yedek ; // ! Hata !
int p ; // dogru
public int degerDondur() {
// int degera = a ; // Hata
int degerb = b ;
return b;
}
} // class Toplama7
Toplama7 t7 = new Toplama7();
return t7.degerDondur();
}
public void ekranaBas() {
/* yerel siniflar sadece friendly erisim
belirleyicisine sahip olabilirler
public class Toplama8 {
public void test() {}
} // class Toplama8
*/
} // ekranaBas
public void hesaplamaYap() {
/* yerel sinif sadece friendly erisim
Belirleyicisine sahip olabilirler
static class Toplama9 {
public void abcd() {
}
} // class Toplama9
*/
} // hesaplamaYap
public static void main(String args[]) {
int sonuc = Hesaplama7.topla(5,9);
System.out.println("Sonuc " + sonuc );
}
} // class Hesaplama7 |
Toplama7 yerel sınıfı, Hesaplama7 sınıfının, statik olan topla() yordamının içerisinde tanımlanmıştır ve sadece topla() yordamının içerisinde geçerlidir. Toplama7 yerel sınıfı, topla() yordamının içerisindeki final özelliğine sahip olan yerel değişkenlere erişip onları kullanabilir. Bu sebepten dolayı, ilkel (primitive) int tipinde tanımlanmış olan a ve a_yedek yerel değişkenlerine Toplama7 yerel sınıfının içerisinden erişilemez, bu bir hatadır.
Hesaplama7 sınıfının, nesne yordamı olan ekranaBas() içerisinde tanımlanmış olan Toplama8 yerel sınıfı hatalıdır. Hatanın sebebi Toplama8 yerel sınıfının public erişim belirleyicisine sahip olmasıdır. Yukarıda belirtildiği üzere, yerel sınıflar ancak friendly erişim belirleyicisine sahip olabilir.
Aynı şekilde Hesaplama7 sınıfının, nesne yordamı olan hesaplamaYap() içerisinde tanımlanmış olan Toplama9 yerel sınıfı hatalıdır. Bu hatanın sebebi, Toplama9 yerel sınıfının statik yapılmaya çalışılmasıdır. Az evvel belirtildiği gibi, yerel yordamlar, statik olarak tanımlanamazlardı. Uygulamanın çıktısı aşağıdaki gibidir;
Sonuc 9
İsimsiz Sınıflar (Anonymous Classes)
İsimsiz sınıflar, isimsiz ifade edilebilen sınıflardır. İsimsiz sınıflar havada oluşturulabildiklerinden dolayı birçok işlem için çok avantajlıdır, özellikle olay dinleyicilerin (event listeners) devreye sokulduğu uygulamalarda sıkça kullanılırlar. İsimsiz sınıfların özellikleri aşağıdaki gibidir;
• Diğer dâhili sınıf çeşitlerinde olduğu gibi, isimsiz sınıflar direk extends ve implements anahtar kelimelerini kullanarak, diğer sınıflardan türetilemez ve arayüzlere erişemez.
• İsimsiz sınıfların herhangi bir ismi olmadığı için, yapılandırıcısında (constructor) olamaz.
Yukarıdaki kuralları, bir örnek üzerinde uygularsak...
HEsaplama8.java
interface Toplayici {
public int hesaplamaYap() ;
}
public class Hesaplama8 {
public Toplayici topla(final int a, final int b) {
return new Toplayici() {
public int hesaplamaYap() {
// final olan yerel degiskenlere ulasabilir.
return a + b ;
}
}; // noktali virgul sart
} // topla, yordam sonu
public static void main(String args[]) {
Hesaplama8 h8 = new Hesaplama8();
Toplayici t = h8.topla(5,9);
int sonuc = t.hesaplamaYap();
System.out.println("Sonuc = 5 + 9 = " + sonuc );
}
} // class Hesaplama8 |
Hesaplama8 sınıfının, topla() yordamı Toplayici arayüzü tipindeki nesneye bağlı bir referans geri döndürmektedir. Toplayici arayüzü tipindeki nesneye bağlı bir referans geri döndürmek demek, Toplayici arayüzüne erişip onun gövdesiz olan yordamlarını iptal eden bir sınıf tipinde nesne oluşturmak demektir. Sonuçta bir arayüze ulaşan sınıf, ulaştığı arayüz tipinde olan bir referansa bağlanabilirdi. " Buraya kadar tamam ama isimsiz sınıfımız nerede... diyebilirsiniz. Olaylara daha yakından bakılırsa;
return new Toplayici() {
public int hesaplamaYap() {
// final olan yerel degiskenlere ulasabilir.
return a + b ;
}
}; // noktali virgul sart |
İşte isimsiz sınıfımız !!. Yukarıdaki ifade yerine, topla() yordamın içerisinde yerel bir sınıf da yazılabilirdi.
public Toplayici topla(final int a, final int b) {
public class BenimToplayicim implements Toplayici {
public int hesaplamaYap() {
// final olan yerel degiskenlere ulasabilir.
return a + b ;
}
} // yordam sonu
return new BenimToplayicim();
} |
İsimsiz sınıfları, yerel sınıfların kısaltılmışı gibi düşünebilirsiniz. Yerel sınıflarda return new BenimToplayicim() yerine, isimsiz sınıflarda hangi sınıf tipinde değer döndürüleceği en başta belirtilir.
return new Toplayici() { ....
...
}; |
İsimsiz sınıflarda, yerel sınıflar gibi içinde bulundukları yordamın sadece final olarak tanımlanmış yerel değişkenlerine erişebilirler.
Yukarıdaki örneğimizde, isimsiz sınıfımız, Toplayici arayüzüne erişip onun gövdesiz sınıflarını iptal etmiştir, buraya kadar her şey normal. Peki eğer isimsiz sınıfımız, yapılandırıcısı parametre olan bir sınıftan türetilseydi nasıl olacaktı? Belirtildiği üzere isimsiz sınıfların yapılandırıcısı olamaz.
Hesaplama9.java
abstract class BuyukToplayici {
private int deger ;
public BuyukToplayici(int x) {
deger = x;
}
public int degerDondur() {
return deger;
}
public abstract int hesaplamaYap() ; // iptal edilmesi gerek
}
public class Hesaplama9 {
public BuyukToplayici degerGoster( int gonderilen ) {
return new BuyukToplayici( gonderilen ) {
public int hesaplamaYap() { //iptal etti (override)
return super.degerDondur() + 5 ;
}
}; // noktali virgul sart
} // degerGoster, yordam sonu
public static void main(String args[]) {
Hesaplama9 h9 = new Hesaplama9();
BuyukToplayici bt = h9.degerGoster(5);
int sonuc = bt.hesaplamaYap();
System.out.println("Sonuc = " + sonuc );
}
} // class Hesaplama9 |
BuyukToplayici sınıfı soyuttur, bunun anlamı bu sınıfın içerisinde en az bir tane gövdesiz yordam olduğudur. BuyukToplayici sınıfının içerisindeki hesaplamaYap() gövdesiz yordamını, BuyukToplayici sınıfından türetilen alt sınıflar tarafından iptal edilmek zorundadır.
Bu örneğimizde ilginç olan iki nokta vardır. Birincisi, isimsiz bir sınıfın, soyut bir yordam dan türetilmesi, ikincisi ise türetilme yapılan BuyukToplayici sınıfına ait yapılandırıcısının parametre almasıdır. İsimsiz sınıfımızın yapılandırıcısı olamayacağından dolayı, BuyukToplayici sınıfına ait parametre alan yapılandırıcıyı burada çağıramayız. Bu işlemi BuyukToplayici sınıfından türetilen isimsiz sınıfımızı oluştururken yapmalıyız.
İsimsiz sınıfların içerisinde, onları çevreleyen yordamların final olmayan yerel değişkenleri kullanılamaz. “Peki, ama bu örnekte kullanılıyor...” diyebilirsiniz.
public BuyukToplayici degerGoster( int gonderilen ) {
return new BuyukToplayici( gonderilen ) {
public int hesaplamaYap() { //iptal etti (override)
return super.degerDondur() + 5 ;
}
}; // noktali virgul sart
} // degerGoster, yordam sonu |
İlkel int tipinde tanımlanmış gonderilen yerel değişkeni, degerGoster() yordamına aittir, isimsiz sınıfımızın içerisinde kullanılmamıştır. return new BuyukToplayici(gonderilen) ifadesi, degerGoster() yordamına dahil olduğundan bir sorun çıkmaz. Eğer uygulamamızı çalıştırırsak, ekran çıktısı aşağıdaki gibi olacaktır.
Sonuc = 10
Fiziksel İfade
İçerisinde Java kodları olan fiziksel bir dosya derlendiği (compile) zaman, bu fiziksel dosya içerisinde tanımlanmış her bir sınıf için, fiziksel bir .class dosyası oluşturulur. Peki olaylar dâhili sınıflar içinde aynı mıdır? Her bir dahili sınıf için, bir fiziksel .class dosyası oluşturulur mu? Eğer oluşturuluyorsa ismi ne olur?
Her bir dahili sınıf için (3 çeşit dahili sınıf içinde geçerli) fiziksel .class dosyası oluşturulur. Bu .class dosyasının ismi ise, çevreleyen sınıfın ismi + $ + dahili sınıfın ismi şeklindedir. Hesaplama1.java örneğimizden bir gösterim yaparsak;
Hesaplama1$1.class
Hesaplama1$Bolme.class
Hesaplama1$Carpma.class
Hesaplama1$Cikartma.class
Hesaplama1$Toplama.class
Hesaplama1.class |
Hesaplama1 sınıfı, Bolme, Carpma, Cikartma ve Toplama sınıflarının çevreliyici sınıfıdır, böyle olunca dahili sınıflarımıza ait .class dosyasının ismi de ÇevreliyiciSınıf$DahiliSınıf biçiminde olduğunu görürüz.
Java, Hesaplama9.java örneğimizdeki isimsiz sınıf için nasıl bir .class dosyası oluşturur? Cevabı hemen aşağıdadır;
Hesaplama9$1.class
Hesaplama9.class |
Java, Hesaplama9.java içerisinde belirtilmiş isimsiz sınıfa ait .class dosyası oluştururken isim olarak 1 (bir) kullanmıştır. Eğer aynı çevreliyici sınıf içerisinde iki adet isimsiz sınıf olsaydı, bu isimsiz sınıfların ismi 1 ve 2 olacaktı.
Hesaplama9$1.class
Hesaplama9$2.class
Hesaplama9.class |
Neden Dâhili sınıflar?
Dâhili üye sınıflar, yerel sınıflar, isimsiz sınıflar hepsi çok güzel ama Java programlama dili neden bunlara ihtiyaç duymuş olabilir? Şimdiye kadar normal sınıflarımızla güzel güzel idare edebiliyorduk diyebilirsiniz. Dâhili sınıfların var olmasındaki neden çoklu kalıtıma (multiple inheritance) tam desteği sağlamaktır.
Arayüzler ile çoklu kalıtım desteğini kısmen bulabiliyorduk ama bu tam değildi. Tam değildi çünkü bir sınıf iki normal sınıftan türetilemiyordu, bunun sakıncalarını tartışmıştık. Fakat bazı zamanlarda, arayüzler dışında, normal sınıflara ihtiyaç duyabiliriz. Normal sınıflar derken, soyut olmayan, problem çözmek için tasarlanmış işleyen sınıflardan bahsediyorum. Bu işleyen sınıfların iki tanesine aynı anda ulaşıp türetme yapılmıyorduk ama bu isteğimize artık dâhili sınıflar ile ulaşabiliriz.
Java, dâhili sınıflar ile çoklu kalıtım olan desteğini güvenli bir şekilde sağlamaktadır. Dâhili sınıflar, kendilerini çevreleyen sınıfların hangi sınıftan türetildiğine bakmaksızın bağımsız şekilde ayrı sınıflardan türetilebilir veya başka arayüzlere erişebilir.
Örnek Java kodumuzu incelemeden evvel, UML diyagrama bir göz atalım.
UML diyagramından olayları kuş bakışı görebiliyoruz. AnaSinif sınıfından türetilmiş TüretilmisSinif sınıfının içerisinde iki adet dâhili üye sınıf bulunmaktadır. Dâhili üye sınıf olan BB ve CC sınıfları da, B ve C sınıflarından türetilmişlerdir. Bu örneğimizdeki ana fikir, bir sınıfın içerisinde dâhili üye sınıflar kullanılarak çoklu kalıtımın güvenli bir şekilde yapılabildiğini göstermektir. UML diyagramını Java uygulamasına dönüştürürsek;
TuretilmisSinif.java
class AnaSinif {
public void ekranaBas(String deger) {
System.out.println( deger );
}
}
class B {
public String degerDondur() {
return "B";
}
}
class C {
public int topla(int a , int b) {
return a+b ;
}
}
public class TuretilmisSinif extends AnaSinif {
public class BB extends B {
public BB() { // yapılandırıcı
ekranaBas( "Sonuc = " + degerDondur() );
}
}
public class CC extends C {
public CC( int a , int b ) { // yapılandırıcı
ekranaBas("Sonuc = " + topla(a,b) );
}
}
public static void main( String args[] ) {
TuretilmisSinif.BB tbb= new TuretilmisSinif().new BB();
TuretilmisSinif.CC tcc= new TuretilmisSinif().new CC(6, 9);
}
} |
TuretilmisSinif sınıfımız, AnaSinif sınıfından türetilmiştir fakat bu dâhili sınıfların başka sınıflardan türetilmelerine engel teşkil etmez. Her bir dâhili sınıfın kendine ait bir durumu olabilir. Dâhili sınıflar kendilerini çevreleyen sınıflardan bağımsızdır. Dâhili sınıflar ile onları çevreleyen sınıflar arasında kalıtımsal bir ilişki olmak zorunda değildir, geçen bölümlerde incelediğimiz "bir" ilişkisi, Kaplan bir Kedidir gibi. Örneğimize geri dönersek, B sınıfından türetilmiş BB sınıfı ve C sınıfından türetilmiş CC sınıfı, Anasinif sınıfına ait ekranaBas() yordamını kullanarak sonuçlarını ekrana yansıtabilmektedirler. Olaylara bu açıdan baklacak olursa, TüretilmisSinif sınıfın sanki üç ayrı işleyen (normal) sınıftan güvenli ve kolay bir şekilde türetilmiş olduğu görülür.
Uygulamamızın çıktısı aşağıdaki gibi olur;
Sonuc = B
Sonuc = 15
Toparlamak gerekirse bu makalemizde Java ise dahili sınıf(inner Class) kavramını ayrıntılı bir biçimde incelemeye çalıştık. Bir sonraki Java makalemizde ise Java ile İstisnalar (Exception) kavramına ayrıntılı bir biçimde değinmeye çalışacağız. Daha sonraki makalemizde ise Eclipse editörünü inceleyeceğiz.
Umarım Yararlı olmuştur.
Altındır: Altuğ Bilgin Altıntaş
Turhal Temizer
http://turhal.blogspot.com