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

Salı, Temmuz 26, 2016

Android Programlama - Başlamadan Önce - 0

Bu yazımızda kısaca Android işletim sistemine ve Android Programlamaya başlamak önce temel bilgilere göz atacağız.

Android, tablet, telefon, televizyonlar için hazırlanmış Linux bazlı ve açık kaynak kodlu bir işletim sistemidir. İşletim sistemi Google liderliğinde bir çok şirket tarafından geliştirilmiştir.

Android işletim sisteminde çalışacak olan uygulamalar (application) hazırlamak için Android Uygulama Geliştirme platformlarının kullanılması gerekmektedir.

Android Uygulama Geliştirmesi yapabilmek için temel seviye Java programlama diline hakim olunması gerekmektedir. Uygulamalar Android Studio ile yazılmaktadır. (Eclipse ya da Visual Studio 2015 ile de yazılabilir.)

Android Studio
indirmek için alt kısımdaki adresten yararlanabilirsiniz. https://developer.android.com/studio/index.html . Aklımızda olması açısında IDE java ile hazırlandığı için kurulum esnasında JAVA SDK de kurmak isteyecektir.

İlk yazımızda temel olarak Android işletim sisteminde ve program yazmak için nelere ihtiyacımız olduğuna değindik.

Bir sonraki yazımız olan Android Programlama - Nedir bu Android - 1 içerisinde Android ‘in temel özellikleri ve bugüne kadar çıkmış sürümlerine göz atacağız.

Görüşmek üzere.
Turhal Temizer

Salı, Mart 25, 2008

Java - Exceptions

Bu yazımızda istisnalar üzerinde durulacaktır. İstisna deyince aklınıza ne geliyor? Yanlış yazılmış uygulama mı? Beklenmeyen durum mu? Yoksa her ikisi de mi? İstisna demek işlerin sizin kontrolünüzden çıkması anlamına gelir. Yani karışıklık ortamı, önceden kestirilemeyen... Bir şeylerin ters gitmesi sonucu uygulamanın normal akışına devam edememesi demektir. Bu ters giden bir şeyler ne olabilir? Örneğin kullanıcının uygulamanıza istemeyen veri girmesi olabilir veya açmak istediğiniz dosyanın yerinde olmaması olabilir, örnekleri çoğaltmak mümkündür.

İstisnalara Giriş

Gerçekten tam bir uygulama yazmak ne demektir? Uygulamadan beklenen görevleri yerine getirmesi onu tam bir uygulama yapar mı? Tabii ki yapmaz. Uygulama zaten kendisinden beklenen işi yapmalı, aksi takdirde zaten uygulama olmaz. Bir uygulamanın tam olmasının iki şartı vardır; Birincisi uygulamanın kendisinden beklenen görevleri doğru bir şekilde yerine getirmesidir yani doğruluk, ikincisi ise hatalı davranışlara karşı dayanıklı olmasıdır, sağlamlık. Örneğin bizden iki sayıyı bölmek için bir uygulama istense ne yapılmalıdır, A/ B - A bölüm B çok basit değil mi?. İlk etapta karşı tarafın bizden istediği şey, girilen iki sayının doğru şekilde bölünmesidir - doğruluk, bu öncelikli şarttır, bunda herkes hemfikir. Peki ikinci şart nedir? İkinci şart ise sağlamlıktır, ikinci şart olan sağlamlık genellikle önemsenmez. Bu örneğimizde karşı tarafın bizden istediği olay, iki sayının bölünmesidir ama dikkat edin sayı dedim, kullanıcı int, double veya short ilkel tiplerinde sayı girilebilir. Peki ya kullanıcı String bir ifadeyi uygulamanıza yollarsa ne olur? veya A=5, B=0 girince uygulamanız buna nasıl bir tepki verir? (Not :5/0=sonsuz) Uygulamanız direk olarak kapanır mı? Veya uygulamanız bu anlamsız ifadeleri bölmeye mi çalışır? Eğer siz uygulamayı tasarlayan kişi olarak, bu hataları önceden tahmin etmiş ve önlemleri almışsanız sorun ortaya çıksa bile, uygulama için sorun olmaz ama gerçek dünyada her şeyi öngörebilmek imkansızdır.

Java programlama dili, oluşabilecek hatalara karşı sert bir yaptırım uygular. Dikkat edin, oluşabilecek diyorum. Java programlama dili, ortada hata oluşmasına sebebiyet verebilecek bir durum var ise yazılan Java dosyasını derlemeyerek kodu yazan kişiye gerekli sert tavrı gösterir. Java programlama dilinin bu tavrı doğru mudur? Kimileriniz diyebilir ki, "Java sadece üstüne düşen görevi yapsın, oluşabilecek hataları bana söyleyerek canımı sıkmasın". Bu yaklaşım yanlıştır, Java programlama dilinin amacı kodu yazan kişiye maksimum şekilde yardımcı olmaktır, daha doğrusu insana dayalı oluşabilecek hataları kendi üstüne alıp, hatalı uygulama üretimini minimuma indirgemeyi amaçlayarak tasarlanmıştır. Bunun ilk örneğini çöp toplama (garbage collector) mekanizmasında görmüştük. Diğer dillerde oluşturulan nesnelerin, daha sonradan işleri bitince bellekten silinmemelerinden dolayı bellek yetmezlikleri oluşmaktadır. " Kodu yazan insan, oluşturduğu nesneyi bellekten temizlemez mi? Ben bunu şahsen hiç yapmam. O zaman dalgın insanlar kod yazmasın aaa! " diye bir söz sakın demeyin, çünkü insanoğlu yeri geldiğinde çok dalgın olabilir ve bu dalgınlık uygulamayı bir bellek canavarına dönüştürebilir ayrıca bu tür hataları, uygulamanın içerisinden ayıklamak cidden çok zor bir iştir. Bu yüzden Java programlama dilinde, bir nesnenin bellekten silinmesi kodu yazan kişiye göre değil, çöp toplama algoritmalarına göre yapılır. Java’nın oluşabilecek olan hatalara karşı bu sert tutumu da gayet mantıklıdır. Bu sert tutum sayesinde ileride oluşabilecek ve bulunması çok güç olan hataların erkenden engellenmesini sağlar.

İstisna Nasıl Oluşabilir?

İstisna oluşumuna en basit örnek olarak, yanlış kullanılmış dizi uygulamasını verebiliriz. Java programlama dilinde dizilere erişim her zaman kontrollüdür. Bunun anlamı, Java programlama dilinde dizilerin içerisine bir eleman atmak istiyorsak veya var olan bir elemana ulaşmak istiyorsak, bu işlemlerin hepsi Java tarafından önce bir kontrolden geçirilir. Bunun bir avantajı, bir de dezavantajı vardır. Avantaj olarak güvenli bir dizi erişim mekanizmasına sahip oluruz, dezavantaj olarak ufakta olsa hız kaybı meydana gelir. Fakat böyle bir durumda hız mı daha önemlidir yoksa güvenlik mi? Bu sorunun cevabı Java programlama dili için güvenliktir. Aşağıdaki örneğe dikkat edelim;

package makaleKod;

public class DiziErisim {public DiziErisim()
{
}
public static void main(String[] args) {
{
int sayilar[]={1,2,3,4};
System.out.println("Basla");
for (int i = 0; i < 5; i++) {
System.out.println("--> " + sayilar[i]);
}
System.out.println("Bitti");

}
}
}

ayilar[],ilkel (primitive) int tipinde dizi değişkenidir ve bağlı bulunduğu dizi nesnesinin içerisinde 4 adet int tipinde eleman vardır. for döngüsü sayesinde dizi içerisindeki elemanları ekrana bastırmaktayız. Bu örneğimizdeki hata, for döngüsünün fazla dönmesiyle dizinin olmayan elemanına ulaşmak istememizden kaynaklanmaktadır. Böyle bir hareket, çalışma-anında (run-time) hata oluşmasına sebebiyet verip uygulamamızın aniden sonlanmasına sebebiyet verecektir. Uygulamayı çalıştırıp, sonuçları hep beraber görelim.



Bu örneğimizdeki istisna, ArrayIndexOutOfBoundsException istisnasıdır. Bu istisnanın sebebi, bir dizinin olmayan elemanına erişmeye çalıştığımızı ifade eder. Fark edildiği üzere Java programlama dilinde, oluşan istisnaları anlamak ve yerlerini belirlemek çok zor değildir. Örneğin bu uygulamada istisnanın 10. satırda ortaya çıktığı anlaşılabilmektedir.

Başka İstisnalar Neler Olabilir?

Bir uygulama içerisinde, başka ne tür istisnalar oluşabilir ? Bir kaç örnek verirsek;

Açmak istediğiniz fiziksel dosya yerinde olmayabilir.
Uygulamanıza kullanıcılar tarafında, beklenmedik bir girdi kümesi gelebilir.
Ağ bağlantısı kopmuş olabilir.
Yazmak istediğiniz dosya, başkası tarafından açılmış olduğundan yazma hakkınız olmayabilir.

Olabilir, olmayabilir, belki... Yukarıdaki istisnaların, bir uygulamanın başına gelmeyeceğini kim garanti edebilir? Kimse, peki Java program içerisinde tam bir uygulama nasıl yazılır. Başlayalım...

İstisna Yakalama Mekanizması

Bir istisna oluştuğu zaman uygulamamız aniden kapanmak zorunda mı? Oluşan bu istisnayı daha şık bir şekilde yakalayıp uygulamanın devam etmesini sağlamak mümkün mü? Cevap olarak evet;

try{// Istisnaya sebebiyet verebilecek olan kod
} catch(Exception1 e1) {
//Eger Exception1 tipinde istisna firlatilirsa buraya
} catch(Exception2 e2) {
//Eger Exception2 tipinde istisna firlatilirsa buraya
}

İstisnaya sebebiyet verebilecek olan kod, try bloğunun içerisinde tutularak güvenlik altına alınmış olur. Eğer istisna oluşursa, istisna yakalama mekanizması devreye girer ve oluşan bu istisnanın tipine göre, uygulamanın akışı catch bloklarından birinin içerisine yönlenerek devam eder.

İstisnalar nesnedir. Bir istisna oluştuğu zaman bir çok olay gerçekleşir. İlk önce yeni bir istisna nesnesi belleğin heap alında new() anahtar kelimesi ile oluşturulur. Oluşan bu istisna nesnesinin içerisine hatanın oluştuğu satır yerleştirilir. Uygulamanın normal seyri durur ve oluşan bu istisnanın yakalanması için catch bloğunun olup olmadığına bakılır. Eğer catch bloğu varsa uygulamanın akışı uygun catch bloğunun içerisinden devam eder. Eğer catch bloğu tanımlanmamış ise hatanın oluştuğu yordamı (method) çağıran yordama istisna nesnesi paslanır, eğer bu yordam içerisinde de istisnayı yakalamak için catch bloğu tanımlanmamış ise istina nesnesi bir üst yordama paslanır, bu olay böyle devam eder ve en sonunda main() yordamına ulaşan istisna nesnesi için bir catch bloğu aranır eğer bu yordamın içerisinde de catch bloğu tanımlanmamış ise, uygulananın akışı sonlanır. Bu olayları detaylı incelemeden önce temel bir giriş yapalım;

package makaleKod;

public class DiziErisim2 {

private void calis() {
int sayilar[]={1,2,3,4};
for (int i = 0; i < 5; i++) {
try {
System.out.println("-->"+sayilar[i]);
} catch (ArrayIndexOutOfBoundsException ex) {
System.out.println("Hata olustu" +ex);
}
}
}
public static void main(String args[]) {
System.out.println("Basla");
DiziErisim2 de2=new DiziErisim2();
de2.calis();
System.out.println("Bitti");
}
}

Yukarıdaki uygulamamızda, dizi elemanlarına erişen kodu try bloğu içerisine alarak, oluşabilecek olan istinaları yakalama şansına sahip olduk. Sahip olduk da ne oldu diyenler için gereken açıklamayı hemen yapalım. try-catch istisna yakalama mekanizması sayesinde istisna oluşsa bile uygulamanın akışı aniden sonlanmayacaktır. DiziErisim.java ile DiziErisim2.java uygulamalarının çıktısına bakılırsa aradaki kontrolü hemen fark edilecektir. DiziErisim2.java uygulama örneğimizin çıktısı aşağıdaki gibidir.



Kontrol nerede? Yukarıdaki DiziErisim2.java uygulamasının çıktısının son satırına dikkat ederseniz, "Bitti" yazısının ekrana yazıldığını görürsünüz oysaki bu ifade DiziErisim.java uygulamasının çıktısında görememiştik. İşte kontrol buradadır. Birinci kuralı daha net bir şekilde ifade edersek; try-catch istisna yakalama mekanizması sayesinde, istisna oluşsa bile uygulamanın akışı aniden sonlanmaz.

Yukarıdaki örneğimizde, try-catch mekanizmasını for döngüsünün içerisine koyulabileceği gibi, for döngüsünü kapsayacak şekilde de tasarlanıp yerleştirilebilir.

package makaleKod;

public class DiziErisim3 {

private void calis() {
try {
int sayilar[]={1,2,3,4};
for (int i = 0; i < 5; i++) {
System.out.println("--> " + sayilar[i]);
}
} catch (ArrayIndexOutOfBoundsException ex) {
System.out.println("Hata Yakalandi");
}
}

public static void main(String[] args) {
System.out.println("Basla");
DiziErisim3 de3=new DiziErisim3();
de3.calis();
System.out.println("Bitti");
}
}

Bu uygulama örneği ile DiziErisim2.java örneğimiz arasında sonuç bakımından bir fark yoktur. Değişen sadece tasarımdır, try-catch bloğunun daha fazla kodu kapsamasıdır.

İstisna İfadeleri

Bir yordam hangi tür istisna fırlatabileceğini önceden belirtebilir veya belirtmek zorunda kalabilir. Bu yordamı (method) çağıran diğer yordamlar da, fırlatılabilecek olan bu istisnayı, ya yakalarlar ya da bir üst bölüme iletirler. Bir üst bölümden kasıt edilen, bir yordamı çağıran diğer bir yordamdır. Şimdi bir yordamın önceden hangi tür istisna fırlatacağını nasıl belirtmek zorunda kaldığını inceleyelim.

package makaleKod;

import java.io.*;

public class IstisnaOrnek1 {

public void cokCalis() {
File f = new File("ornek.txt");
BufferedReader bf = new BufferedReader( new FileReader( f ) );
System.out.println(bf.readLine());
}

public void calis() {
cokCalis();
}

public static void main(String args[]) {
IstisnaOrnek1 io1 = new IstisnaOrnek1();
io1.calis();
}
}

java.io paketinin içerisindeki sınıfları henüz incelemedik ama bu örneğimizde kullanılan sınıfların ne iş yaptıklarını anlamak çok zor değil. Burada yapılan iş, aynı dizinde bulunduğu farz edilen ornek.txt dosyasının ilk satırını okumaya çalışmaktır. Yukarıdaki uygulamamızı derlemeye çalışırsak, derleyicinin bize vereceği mesaj aşağıdaki gibi olur.



Biz diskimizde bulunduğu varsayılan bir dosyaya erişip onun ilk satırını okumaya çalışmaktayız. Çok masum gibi gözüken ama tehlikeli istekler. Peki daha detaylı düşünelim ve oluşabilecek olan istisnaları tahmin etmeye çalışalım.

İlk oluşabilecek olan istisna, o dosyanın yerinde olmayabileceğidir. Bu beklenmeyen bir durum oluşturabilir, başka neler olabilir? Bundan ayrı olarak biz sanki o dosyanın orada olduğundan eminmişiz gibi birde onun ilk satırını okumaya çalışıyoruz, bu isteğimizde istisnaya sebebiyet verebilir çünkü dosya yerinde olsa bile dosyanın ilk satırı olmayabilir. Dikkat ederseniz hep olasılıklar üzerinde durmaktayım ama güçlü olasılıklar. Peki bu uygulamayı derlemenin bir yolu yok mu?

Az önce bahsedildiği gibi bir yordam içerisinde oluşmuş olan istisnayı bir üst bölüme yani o yordamı çağıran yordama fırlatabilir. Eğer bir istisna oluşursa bu anlattıklarımıza göre bir yordamın iki şansı vardır diyebiliriz. Birincisi oluşan bu istisnayı ya yakalayıp gereken işlemleri kendi içerisinde sessizce gerçekleştirebilir veya bu istisna ile ben ne yapacağımı bilmiyorum beni çağıran yordam düşünsün diyip, istisna nesnesini bir üst bölüme fırlatabilir.

Aşağıdaki örnekte, oluşan istisnayı aynı yordamın içerisinde yakalanmaktadır; bu yüzden yordamın hangi istisnayı fırlatabileceğini açıklamasına gerek yoktur. Bir yordamın hangi tür istisnayı nasıl fırlatabileceğini açıklama olayını az sonra göreceğiz ama önce aşağıdaki örneğimizi inceleyelim.

package makaleKod;

import java.io.*;

public class IstisnaOrnek2 {

public void cokCalis() {
try {
File f = new File("ornek.txt");
BufferedReader bf=new BufferedReader(new FileReader(f) );
System.out.println(bf.readLine());
} catch (IOException ex) {
System.out.println("Hata Yakalandi =" + ex);
}
}

public void calis() {
cokCalis();
System.out.println("calis() yordamı");
}

public static void main(String args[]) {
IstisnaOrnek2 io2 = new IstisnaOrnek2();
io2.calis();
System.out.println("main() yordamı");
}
}

Verilen örnekte, dosyaya erişirken veya ondan birşeyler okumak isterken oluşabilecek olan istisnalar; java.io.IOException istisna tipini kullanarak yakalanabilir. Zaten IstisnaOrnek1.java uygulamasının derlemeye çalışırken alınan hatadan hangi tür istisna tipinin kullanılması gerektiğini de çıkartabiliriz. java.io.FileNotFound Exception istina tipini, java.io.IOException tipi kullanılarak yakalanabilir bunun nasıl olduğunu biraz sonra göreceğiz.

Yukarıdaki uygulama güzel bir şekilde derlenir çünkü oluşabilecek olan tüm istisnalar için tedbir alınmıştır. Olayların akışını inceliyelim, bu uygulamayı çalıştırdığımız zaman (java IstisnaOrnek2) ilk olarak main() yordamından akışa başlanır. Daha sonra calis() yordamının ve cokCalis() yordamının çağrılması şeklinde akış devam ederken olanlar olur ve cokCalis() yordamının içerisinde istisna oluşur. Çünkü ornek.txt diye bir dosya ortalarda yoktur (yok olduğunu varsayın) ama olsun içimiz rahat çünkü try-catch hata yakalama mekanizmamız mevcuttur.

Olayın akışını açıklamaya başlayalım;

Öncelikle akış, main() yordamının içerisinden başlar. Bu uygulamamızda main() yordamının içerisinden calis() yordamı çağrılmıştır.
calis() yordamının içerisinden cokCalis() yordamı çağrılmıştır.
cokCalis() yordamının içerisinde istisna oluşmuştur çünkü uygulamamızın yer aldığı dizinin içerisinde ornek.txt dosyası aranmış ve bulunamamıştır. Şimdi kritik an geldi, cokCalis() yordamının içerisinde try-catch mekanizması var mı?
Evet, cokCalis() yordamının içerisinde try-catch mekanizması olduğu için, catch bloğuna yazılmış olan kodlar çalışır. Bu uygulamamızda ekrana " Hata Yakalandi =java.io.FileNotFoundException: ornek.txt (The system cannot find the file specified) " basılır, yani dosyanın olmayışından dolayı bir istisna olduğu belirtilir. Not: java.io.IOException istisna tipi, java.io.FileNotFound Exception istisna tipini kapsadığından bir sorun yaşanmaz bunun nasıl olduğunu biraz sonra inceleyeceğiz.
Bitti mi? Tabii ki hayır, uygulamamız kaldığı yerden devam edecektir. Şimdi sıra calis() yordamının içerisindeki henüz çalıştırılmamış olan kodların çalıştırılmasına. Burada da ekrana "calis() yordamı" basılır.
Son olarak akış main() yordamına geri döner ve main() yordamının içerisinde çalıştırılmamış olan kodlar çalıştırılır ve ekrana "main() yordamı" basılır.
Ve uygulamamız normal bir şekilde sona erer.

Uygulamamızın toplu olarak ekran çıktısı aşağıdaki gibidir.



Akıllara şöyle bir soru gelebilir, "Eğer ornek.txt dosyası gerçekten olsaydı yine de try-catch mekanizmasını yerleştirmek zorundamıydık". Cevap evet, az önce bahseldiği gibi ortada istisna oluşma tehlikesi varsa bile bu tehlikenin önlemi Java programla dilinde önceden kesin olarak alınmalıdır.

İstisnaOrnek2.java uygulamamızda, oluşan istisna aynı yordamın içerisinde yakalanmıştır ve böylece uygulamanın akışı normal bir şekilde devam etmiştir. Peki oluşan bu istisnayı aynı yordamın içerisinde yakalamamak gibi bir lüksümüz olabilir mi? Yani oluşan istisna nesnesini -ki bu örneğimizde oluşan istisnamız java.io.FileNot FoundException tipindeydi, bir üst kısma fırlatılabilir mi? Bir üst kısma fırlatmaktan kasıt edilen, istisnanın meydana geldiği yordamı çağıran yordama bu istisna nesnesini fırlatmaktır. "Peki ama niye böyle bişeye ihtiyaç duyalım ki?" diyebilirsiniz. Bunun başlıca sebebi, istisnanın oluştuğu yordam içerisinde, o istisna nesnesi ile ne yapılabileceğinin bilenememesi olabilir. Bir üst kısımda elimizde daha fazla bilgi olabilir, ve bu bilgi çerçevesinde, elimizdeki istisna nesnesini daha güzel bir şekilde değerlendirip, uygulamanın akışını ona göre yönlendirebiliriz.

package makaleKod;

import java.io.*;

public class IstisnaOrnek3 {

public void cokCalis() throws IOException{

File f = new File("ornek.txt");
BufferedReader bf= new BufferedReader( new FileReader(f) );
System.out.println(bf.readLine());

}

public void calis() {
try {
cokCalis();
System.out.println("calis() yordamı");
} catch(IOException ex) {
System.out.println("Hata Yakalandi-calis() =" + ex);
}

}

public static void main(String args[]) {
IstisnaOrnek3 io3 = new IstisnaOrnek3();
io3.calis();
System.out.println("main() yordamı");
}
}

IstisnaOrnek3.java örneğimizde oluşan istisna oluştuğu yordam içerisinde yakalanmamıştır. Peki nasıl olurda derleyici buna kızmaz, cevabı hemen aşağıdadır.

public void cokCalis() throws IOException {
//..
}

Eğer bir istisna oluşursa, istisnanın oluştuğu yordamın yapacağı iki şey vardır demiştik. Birincisi oluşan istisnayı kendi içerisinde try-catch mekanizmasıyla yakalayabilir. İkincisi ise oluşacak olan istisnayı bir üst bölüme (kendisini çağıran yordama) fırlatabilir. Örneğin cokCalis() yordamı "throws IOException" diyerek, kendisini çağıran yordamlara şöyle bir mesaj gönderir, "Bakın benim içimde istisnaya yol açabilecek kod var ve eğer istisna oluşursa ben bunu fırlatırım, bu yüzden başınız çaresine bakın". Olayın akış şemasını anlatmaya çalışalım;

Öncelikle akış, main() yordamının içerisinden başlar. Bu uygulamamızda main() yordamının içerisinden calis() yordamı çağrılmıştır.
calis() yordamının içerisinden cokCalis() yordamı çağrılmıştır.
cokCalis() yordamının içerisinde istisna oluşmuştur çünkü uygulamamızın yer aldığı dizinin içerisinde ornek.txt dosyası aranmış ve bulunamamıştır. Şimdi kritik an geldi, cokCalis() yordamının içerisinde try-catch mekanizması var mı?
Hayır, cokCalis() yordamının içerisinde oluşan istisnayı yakalama mekanizması yoktur(try-catch) ama java.io.IOException tipinde bir hata nesnesi fırlatacağını "throws IOException" diyerek belirtmiştir. İstisna oluşmuş ve istisna nesnesi (java.io.IOException) bir üst bölüme yani calis() yordamına fırlatılmıştır.
Artık istisna nesnemiz calis() yordamının içerisindedir, şimdi sorulması gereken soru " calis() yordamının içerisinde hata yakalama mekanizması var mıdır? "
calis() yordamının içerisinde hata yakalama mekanizması vardır (try-catch) bu yüzden catch bloğunun içerisindeki kod çalıştırılır ve ekrana " Hata Yakalandi-calis() =java.io.FileNotFoundException: ornek.txt (The system can not find the file specified) " basılır, yani dosyanın olmayışından dolayı bir istisna olduğu belirtilir. Dikkat edilirse ekrana " calis() yordamı " basılmadı bunun sebebi istisnanın oluşmasından dolayı akışın catch bloğuna dallanmasıdır. Not: java.io.IOException istisna tipi, java.io.FileNotFoundException istisna tipini kapsadığından bir sorun yaşanmaz bunun nasıl olduğunu biraz sonra inceleyeceğiz.
Son olarak akış main() yordamına geri döner ve main() yordamının içerisinde çalıştırılmamış olan kodlar çalıştırılır ve ekrana "main() yordamı" basılır.
Ve uygulamamız normal bir şekilde sona erer.

Bu örneğimizdeki ana fikir, bir istisna kesin olarak oluştuğu yordamın içerisinde yakalanmayabileceğidir. Fırlatma özelliği sayesinde istisna nesnesi (eğer istisna oluşmuş ise) bir üst bölüme yani istisna oluşan yordamı çağıran yordama fırlatılabilir.

Peki bu istisna nesnesi (java.io.IOException) calis() yordamın yakalanmasaydı ne olurdu? Cevap: O zaman main() yordamın yakalanırdı. Nasıl? Hemen gösterelim.

package makaleKod;

import java.io.*;
public class IstisnaOrnek4 {
public void cokCalis() throws IOException {
File f = new File("ornek.txt");
BufferedReader bf = new BufferedReader( new FileReader( f ) );
System.out.println(bf.readLine());
}
public void calis() throws IOException {
cokCalis();
System.out.println("calis() yordamı");
}
public static void main(String args[]) {
try {
IstisnaOrnek4 io4 = new IstisnaOrnek4();
io4.calis();
System.out.println("main() yordamı");
} catch(IOException ex) {
System.out.println("Hata Yakalandi-main() =" + ex);
}
}
}

Bu sefer biraz daha abartıp, oluşan istisna nesnesini son anda main() yordamında yakalıyoruz. Bu örneğimizde hem istisnanın meydana geldiği cokCalis() yordamı hem de calis() yordamı oluşan istisnayı fırlatmışlardır. Buraya kadar anlattıklarımızı adım adım açıklamaya çalışalım;

Öncelikle akış, main() yordamının içerisinden başlar. Bu uygulamamızda main() yordamının içerisinden calis() yordamı çağrılmıştır.
calis() yordamının içerisinden cokCalis() yordamı çağrılmıştır.
cokCalis() yordamının içerisinde istisna oluşmuştur çünkü uygulamamızın yer aldığı dizinin içerisinde ornek.txt dosyası aranmış ve bulunamamıştır. Şimdi kritik an geldi, cokCalis() yordamının içerisinde try-catch mekanizması var mı?
cokCalis() yordamının içerisinde oluşan istisnayı yakalama mekanizması yoktur (try-catch) ama java.io.IOException tipinde bir hata nesnesi fırlatacağını "throws IOException" diyerek belirtmiştir. İstisna oluşmuş ve istisna nesnesi (java.io.IOException) bir üst bölüme yani calis() yordamına fırlatılmıştır.
Artık istisna nesnemiz calis() yordamının içerisindedir, şimdi sorulması gereken soru " calis() yordamının içerisinde hata yakalama mekanizması var mıdır? "
Cevap hayırdır. calis() yordamı da oluşan istisna nesnesini bir üst bölüme yani kendisini çağıran main() yordamına fırlatmıştır.
İstina nesnemiz main() yordamının içerisine geldi. Sorulması gereken soru " main() yordamının içerisinde hata yakalama mekanizması var mıdır? "
Cevap evettir. Böylece akış main() yordamının içerisindeki catch bloğuna dallanır ve catch bloğunun içerisindeki kod çalıştırılır.
Ve uygulamamız normal bir şekilde sona erer.

Uygulamanın toplu olarak çıktısı aşağıdaki gibidir.



Oluşan bir istisna nesnesini catch bloğunda yakalamanın ne gibi avantajları olabilir? Bu sorunun cevabına değinmeden önce olaylara eğer istisna nesnesi main() yordamında yakalanmasaydı neler olacağını inceleyerek başlayalım.

package makaleKod;

import java.io.*;
public class IstisnaOrnek5 {
public void cokCalis() throws IOException {
File f = new File("ornek.txt");
BufferedReader bf = new BufferedReader( new FileReader(f));
System.out.println(bf.readLine());
}
public void calis() throws IOException {
cokCalis();
System.out.println("calis() yordamı");
}
public static void main(String args[]) throws IOException {
IstisnaOrnek5 io5 = new IstisnaOrnek5();
io5.calis();
System.out.println("main() yordamı");
}
}

Görüldüğü üzere cokCalis() yordamının içerisinde oluşan istisna hiçbir yordam içerisinde hata yakalama mekanizması kullanılarak yakalanmamıştır (try-catch). Bunun yerine tüm yordamlar bu istisna nesnesini fırlatmayı seçmiştir, buna main() yordamıda dahildir. Böyle bir durumda akışın nasıl gerçekleştiğini, adım adım anlatmaya çalışalım;

Öncelikle akış, main() yordamının içerisinden başlar. Bu uygulamamızda main() yordamının içerisinden calis() yordamı çağrılmıştır.
calis() yordamının içerisinden cokCalis() yordamı çağrılmıştır.
cokCalis() yordamının içerisinde istisna oluşmuştur çünkü uygulamamızın yer aldığı dizinin içerisinde ornek.txt dosyası aranmış ve bulunamamıştır. Şimdi kritik an geldi, cokCalis() yordamının içerisinde try-catch mekanizması var mı?
cokCalis() yordamının içerisinde oluşan istisnayı yakalama mekanizması yoktur (try-catch) ama java.io.IOException tipinde bir hata nesnesi fırlatacağını "throws IOException" diyerek belirtmiştir. İstisna oluşmuş ve istisna nesnesi (java.io.IOException) bir üst bölüme yani calis() yordamına fırlatılmıştır.
Artık istisna nesnemiz calis() yordamının içerisindedir, şimdi sorulması gereken soru " calis() yordamının içerisinde hata yakalama mekanizması var mıdır? "
Cevap hayırdır. calis() yordamı da oluşan istisna nesnesini bir üst bölüme yani kendisini çağıran main() yordamına fırlatmıştır.
İstina nesnemiz main() yordamının içerisine geldi. Sorulması gereken soru " main yordamının içerisinde hata yakalama mekanizması var mıdır? "
Cevap hayırdır. Peki ne olacak? Çok basit, uygulama doğal olarak sonla-nacaktır.

Uygulamanın toplu olarak çıktısı aşağıdaki gibidir.



"Hata yakalama mekanizması koyduğumuzda da uygulama sonlanıyordu, şimdide sonlandı bunda ne var ki" diyebilirsiniz. Haklı olabilirsiniz ama önce oluşan bir istisna nesnesi catch bloğunda yakalamanın ne gibi avantajları olabilir?

Oluşan bir istisna nesnesini catch bloğundan yakalamak, daha doğrusu hata yakalama mekanizması kullanmak uygulamayı yazan kişilere büyük kolaylıklar sağlar. En büyük avantaj oluşan hatayı catch bloğunun içerisinde kaydedilirsiniz (logging) (dosyaya ama veri tabanına... gibi gibi...) . Örneğin iyi işleyen bir uygulama yazdınız ve bu uygulama yaptığınız tüm -daha doğrusu aklınıza gelen- testlerden geçmiş herşey harika, kendinize güveniniz gelmiş, dünya gözünüze artık bambaşka bir yer gibi geliyor ama bir gün bir bakıyorsunuz ki uygulamanız çalışması durmuş!! ilk yapacağınız şey "bu uygulamayı kim kapattı!" diye etrafa sormak oysaki kimsenin günahı yok, kimse elini uygulamanıza sürmemiştir zaten böyle bir riski kim alabilir ki? Asıl gerçek, uygulamada ters giden bir şey olmuş ve uygulama kapanmıştır. İşte tam o anda tutunacağınız tek dal dosyaya veya veri tabanına kayıt ettiğiniz hata mesajlarıdır. Bu bakımdan catch bloğunun içerisine oluşan hata ile alakalı ne kadar detaylı bilgi gömerseniz, bu bilgi sizi ileride -eğer hata oluşursa- o kadar yardımcı olacaktır.

IstisnaOrnek5.java kötü bir uygulama örneğidir. Oluşabilecek olan bir istisna, hata yakalama mekanizması (try-catch) ile sizin öngördüğünüz bir yerde yakalanmalıdır. Bir istisna meydana geldiği zaman uygulama mutlaka sonlanmak zorunda değildir. Eğer bir telafisi var ise bu catch bloğunun içerisinde yapılmalı ve uygulama tekrardan ayağa kaldırılmalıdır ama çok ölümcül bir hata ise o zaman hata mesajını kaydetmekten (dosyaya veya veri tabanına.. gibi gibi...) başka yapılacak pek fazla bir şey yoktur.

İstisna Tip Hiyerarşisi

Nasıl olurda java.io.IOException istisna tipi, java.io.FileNotFoundException istisna tipini kapsayabilir? Kapsamak ne demektir? Kapsamak demek, eğer uygulamanızda java.io.FileNotFoundException tipinde bir istisna nesnesi oluşmuşsa (bir istisna oluşmuşsa) bu istisna tipini java.io.IOException tipini kullanarak da catch bloğunda yakalayabileceğiniz anlamına gelir.



Yukarıdaki şemamızdan görüleceği üzere, FileNotFoundException istisna tipi, IOException istisnasının alt kümesi olduğu için, FileNotFoundException tipinde bir istisna nesnesini catch bloğunun içerisinde IOException istisna tipiyle yakalayabiliriz.

Throwable istisna nesnesi, tüm istisna nesnelerinin atasıdır. Yukarıdaki şemamızıa bakarak istisnaları 3 gruba ayırabiliriz.

Error istisna tipi ölümcül bir hatayı işarettir ve telafisi çok zordur, neredeyse imkansızdır. Örneğin OutOfMemoryError (yetersiz bellek) istisnası oluşmuş ise uygulamanın buna müdahele edip düzeltmesi imkansızdır.
RuntimeException istisna tipleri, eğer uygulama normal seyrinde giderse ortaya çıkmaması gereken istisna tipleridir. Örneğin ArrayIndexOutOfBoundsException istisna tipi, bir dizinin olmayan elemanına eriştiğimiz zaman ortaya çıkan bir istisnadır. RuntimeException istisna tipleri, kontrolsüz kodlamadan dolayı meydana gelen istisna tipleri diyebiliriz. Biraz sonra bu istisna tipini detaylı biçimde inceleyeceğiz.
Ve diğer Exception tipleri. Bu istisna tipleri çevresel koşullardan dolayı meydana gelebilir. Örneğin erişmeye çalışan dosyanın yerinde olmaması (FileNotFoundException) veya network bağlantısının kopması sonucu ortaya çıkabilecek olan istisnalardır ve bu istisnalar için önceden bir tedbir alınması şarttır.

Tüm Diğer Exception İstisna Tiplerini Yakalamak

Bir uygulama içerisinde oluşabilecek olan tüm istisna tiplerini yakalamak için aşağıdaki ifadeyi kullanabilirsiniz.

catch (Exception ex) {
//......
}

Tüm istisnaları yakalamak (Error, RuntimeException ve diğer Exception türleri) için Throwable istisna tipini kullanmak iyi fikir değildir. Bunun yerine bu üç gruba ait daha özellikli istisna tiplerinin kullanılmasını önerilir.

RuntimeException İstisna Tipleri

DiziErisim.java uygulama örneğimiz içerisinde istisna oluşma riski olmasına rağmen nasıl oldu da Java buna kızmayarak derledi? Peki ama IstisnaOrnek1.java uygulamasını niye derlemedi? Bu soruların cevapları istisna tiplerinin iyi bilenmesi ile ortaya çıkar.

DiziErisim.java uygulama örneğinde istisna oluşma riski vardır. Eğer uygulamayı yazan kişi dizinin olmayan bir elemanına erişmeye kalkarsa ArrayIndexOutOfBounds Exception hatası alacaktır, yani RuntimeException (çalışma-anı hatası). Peki bunun sebebi nedir? Bunun sebebi kodu yazan arkadaşın dikkatsizce davranmasıdır. Bu tür hatalar derleme anında (compile-time) fark edilemez. Java bu tür hatalar için önceden bir tedbir alınmasını şart koşmaz ama yine de tedbir almakta özgürsünüzdür. Bir dosyaya erişirken oluşacak olan istisnaya karşı bir tedbir alınmasını, Java şart koşar çünkü bu tür hatalar diğer Exception istisna tipine girer. Genel olarak karşılaşılan RuntimeException istisna türlerine bir bakalım;

AritmeticException: Bir sayının sıfıra bölünmesiyle ortaya çıkabilecek olan bir istisna tipidir.
• NullPointerException: Bir sınıf tipindeki referansı, o sınıfa ait bir nesneye bağlamadan kullanmaya kalkınca alınabilecek bir istisna tipi.
String ad == null;
// NullPointerException ! hata !
System.out.println("Ad = " + ad.trim() );

Bu hatayı almamak için,

String ad = " Java Makale Projesi "; // baglama islemi
System.out.println("Ad = " + ad.trim() ); //dogru
// NullPointerException ! hata !

NegativeArraySizeException: Bir diziyi negatif bir sayı vererek oluşturmaya çalışırsak, bu istisna tipi ile karşılaşırız

// NegativeArraySizeException ! hata !
int dizi[] = new dizi[ -100 ];

• ArrayIndexOutOfBoundsException: Bir dizinin olmayan elemanına ulaşmak istendiği zaman karşılaşılan istisna tipidir

• SecurityException: Genellikle tarayıcı (browser) tarafından fırlatılan bir istisna tipidir. Bu istisnaya neden olabilecek olan sebepler aşağıdaki gibidir;
Applet içerisinden, yerel (local) bir dosyaya erişilmek istendiği zaman.
Appletin indirildiği sunucuya (server) değilde değişik bir sunucuya bağlantı kurulmaya çalışıldığı zaman.
Applet içerisinde başka bir uygulama başlatmaya çalışıldığı zaman.

SecurityException istisna tipi fırlatılır.
Önemli noktayı bir kez daha vurgulayalım, RuntimeException ve bu istisna tipine ait alt tipleri yakalamak için, Java derleme anında (compile-time) bizlere bir bir zorlama yapmaz.

Bu makalemizde genel olarak javada istisnalar kavramına giriş yapmaya çalıştık. Bir sonraki java makalemizde istisnalar konusunu incelemeye devam edeceğiz.

Umarım yararlı olmuştur.

Uygulamada bahsettiğimiz sınıfların kaynak kodlarına linkten erişebilirsiniz.

Pazartesi, Mart 17, 2008

Java - Dahili Sınıflar (Inner Classes)

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.

Kaynaklar
KTÜ İsbb ders notları

Java - Arayüzler (Interface)

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 interface arayüz kavramını incelemeye çalışacağız.

Arayüz (Interface)

Arayüzler, soyut (abstract) sınıfların bir üst modeli gibi düşünülebilir, soyut sınıfların içerisinde hem iş yapan hem de hiçbir iş yapmayan sadece birleştirici rol üstlenen gövdesiz yordamlar (soyut yordamlar-abstract methods) vardı. Bu birleştirici rol oynayan yordamlar, soyut sınıftan (abstract class) türetilmiş alt sınıfların içerisinde iptal edilmeleri (override) gerektiğini polimorfizim makalemizde incelenmişti. Arayüzlerin içerisinde ise iş yapan herhangi bir yordam (method) bulunamaz; arayüzün içerisinde tamamen gövdesiz yordamlar (soyut yordamlar) bulunur. Bu açıdan bakılacak olursak, arayüzler, birleştirici bir rol oynamaları için tasarlanmıştır. Önemli bir noktayı hemen belirtelim; ara yüzlere ait gövdesiz (soyut) yordamlar otomatik olarak public erişim belirleyicisine sahip olurlar ve sizin bunu değiştirme imkânınız yoktur. Aynı şekilde arayüzlere ait global alanlarda otomatik public erişim belirleyicisine sahip olurlar ek olarak, bu alanlar yine otomatik olarak final ve statik özelliği içerirler ve sizin bunlara yine müdahale etme imkanınız yoktur.

Birleştiricilik

Bir önceki Java makalemiz olan Polimorfizm yazımızda verilen BüyükIsYeri örneğini, arayüzleri kullanarak baştan yazmadan önce, yeni UML diyagramını inceleyelim;


UML diyagramında görüldüğü üzere, Calisan arayüzü (interface), birleştirici bir rol oynamaktadır. Calisan arayüzünde tanımlanan ve soyut (gövdesiz) calis() yordamı (method), bu arayüze erişen tüm sınıfların içerisinde iptal edilmek zorundadır (override). UML diyagramımızı Java uygulamasına dönüştürülürse;

BuyukIsYeri.java
interface Calisan { // arayuz
public void calis() ;
}

class mudur implements Calisan {
public void calis() { // iptal etti (override)
System.out.println("Mudur Calisiyor");
}
}

class genelMudur extends Mudur {
public void calis() { // iptal etti (override)
System.out.println("GenelMudur Calisiyor");
}
public void toplantiYonet() {
System.out.println("GenelMudur toplanti yonetiyor");
}
}

class programci implements Calisan {
public void calis() { // iptal etti (override)
System.out.println("Programci Calisiyor");
}
}

class AnalizProgramci extends Programci {
public void analizYap() {
System.out.println("Analiz Yapiliyor");
}
}

class SistemProgramci extends Programci {
public void sistemIncele() {
System.out.println("Sistem Inceleniyor");
}
}

class Pazarlamaci implements Calisan {
public void calis() { // iptal etti (override)
System.out.println("Pazarlamaci Calisiyor");
}
}

class Sekreter implements Calisan {
public void calis() { // iptal etti (override)
System.out.println("Sekreter Calisiyor");
}
}

public class BuyukIsYeri {
public static void mesaiBasla(Calisan[] c ) {
for (int i = 0 ; i < c.length ; i++) {
c[i].calis(); // ! Dikkat !
}
}

public static void main(String args[]) {
Calisan[] c = new Calisan[6];
// c[0]=new Calisan(); ! Hata ! arayüz olusturulamaz
c[0]=new Programci(); // yukari cevirim (upcasting)
c[1]=new Pazarlamaci(); //yukari cevirim (upcasting)
c[2]=new Mudur(); //yukari cevirim (upcasting)
c[3]=new GenelMudur(); //yukari cevirim (upcasting)
c[4]=new AnalizProgramci(); //yukari cevirim (upcasting)
c[5]=new SistemProgramci(); //yukari cevirim (upcasting)
mesaiBasla(c);
}
}

Yukarıdaki örneğimiz ilk etapta çekici gelmeyebilir, “Bunun aynısı soyut sınıflarla (abstract class) zaten yapılabiliyordu. Arayüzleri neden kullanayım ki... “ diyebilirsiniz. Yukarıdaki örnekte arayüzlerin nasıl kullanıldığı incelenmiştir; arayüzlerin sağladığı tüm faydalar birazdan daha detaylı bir şekilde incelenecektir.

Arayüzlerin devreye sokulmasını biraz daha yakından bakılırsa.

class Mudur implements Calisan {
public void calis() { // iptal etti (override)
System.out.println("Mudur Calisiyor");
}
}

Olaylara Mudur sınıfının bakış açısından bakılsın. Bu sınıf Calisan arayüzünün gövdesiz yordamlarını iptal etmek (override) istiyorsa, Calisan arayüzüne ulaşması gerekir. Bu ulaşım implements anahtar kelimesi ile gerçekleşir. Mudur sınıfı bir kere Calisan arayüzüne ulaştımı, buradaki gövdesiz yordamları (soyut yordamları) kesin olarak iptal etmek (override) zorundadır. Uygulamanın çıktısı aşağıdaki gibidir;

Programci Calisiyor
Pazarlamaci Calisiyor
Mudur Calisiyor
GenelMudur Calisiyor
Programci Calisiyor
Programci Calisiyor

Arayüz (Interface) ve Soyut Sınıflar (Abstract Classes)

Eğer bir sınıf (soyut sınıflar dâhil) bir arayüze (interface) ulaşmak istiyorsa, bunu implements anahtar kelimesi ile gerçekleştirebileceğini belirtmiştik. Ayrıca eğer bir sınıf bir kere arayüze ulaşımı artık onun tüm gövdesiz yordamlarını (soyut yordamlar) kesin olarak iptal etmesi (override) gerektiğini de belirttik. Peki, eğer soyut bir sınıf (abstract class) bir arayüze ulaşırsa, arayüze ait gövdesiz yordamları kesin olarak, kendi içerisinde iptal etmeli mi? Bir örnek üzerinde incelersek;

hayvanat.java
interface Hayvan {
public void avlan() ;
}

abstract class Kedi implements Hayvan {
}

Yukarıdaki örneğimizi derleyebilir (compile) miyiz? Derlense bile çalışma anında (run-time) hata oluşturur mu? Aslında olaylara kavramsal olarak bakıldığında çözüm yakalanmış olur. Soyut sınıfların amaçlarından biri aynı arayüz özelliğinde olduğu gibi birleştirici bir rol oynamaktır. Daha açık bir ifade kullanırsak, hem arayüzler olsun hem de soyut sınıflar olsun, bunların amaçları kendilerine ulaşan normal sınıflara, kendilerine ait olan gövdesiz yordamları iptal ettirmektir (override). O zaman yukarıdaki örnekte soyut olan Kedi sınıfı, Hayvan arayüzüne (interface) ait gövdesiz (soyut) avlan() yordamını iptal etmek zorunda değildir. Daha iyi anlaşılması açısından yukarıdaki örneği biraz daha geliştirelim ama öncesinde UML diyagramını çıkartalım;


UML diyagramından görüleceği üzere, Kaplan sınıfı, avlan() ve takipEt() yordamlarını (gövdesiz-soyut yordamlarını) iptal etmek zorundadır. UML diyagramını Java uygulamasına dönüştürülürse;

hayvanat2.java
interface Hayvan {
public void avlan() ;
}

abstract class Kedi implements Hayvan {
public abstract void takipEt() ;
}

class Kaplan extends Kedi {
public void avlan() { // iptal etti (override)
System.out.println("Kaplan avlaniyor...");
}

public void takipEt() { // iptal etti (override)
System.out.println("Kaplan takip ediyor...");
}
}

Soyut (abstract) olan Kedi sınıfının içerisinde, herhangi bir gövdesiz yordam (soyut yordam) iptal edilmemiştir (override). İptal edilme işlemlerinin gerçekleştiği tek yer Kaplan sınıfının içerisidir. Soru: Kaplan sınıfı Hayvan arayüzünde (interface) tanımlanmış soyut olan (gövdesiz) avlan() yordamını iptal etmek (override) zorunda mı? Cevap: Evet, çünkü Kaplan sınıfı Kedi sınıfından türetilmiştir. Kedi sınıfı ise Hayvan arayüzüne ulaşmaktadır. Bu sebepten dolayı Kaplan sınıfının içerisinde avlan() yordamı iptal edilmelidir.

En baştaki sorumuzun cevabı olarak, hayvanat.java örneğimiz rahatlıkla derlenebilir (compile) ve çalışma anında (run-time) herhangi bir çalışma-anı istisnasına (runtime-exception) sebebiyet vermez. (Not: İstisnaları (Exception) bir sonraki makalemizde detaylı bir biçimde incelemeye çalışacağız.)

Arayüz (Interface) İle Çoklu Kalıtım (Multiple Inheritance)

İlk önce çoklu kalıtımın (multiple inheritance) niye tehlikeli ve Java programlama dili tarafından kabul görmediğini inceleyelim. Örneğin Programci soyut sınıfından türetilmiş iki adet sınıfımız bulunsun, Yazilimci ve Analist sınıfları. Bu iki sınıftan türetilen yeni bir sınıf olamaz mı? Örneğin CoderTurhal sınıfı; yani, CoderTurhal sınıfı aynı anda hem Yazilimci, hem de Analist sınıfından türetilebilir mi? Java programlama dilinde türetilemez. Bunun sebeplerini incelemeden evvel, hatalı yaklaşımı UML diyagramında ifade edilirse;


Java programlama dili niye çoklu kalıtımı bu şekilde desteklemez? UML diyagramını, hatalı bir Java uygulamasına dönüştürülürse;

Program.java
abstract class Programci {
public abstract void calis();
}

class Yazilimci extends Programci {
public void calis() {
System.out.println("Yazilimci calisiyor....") ;
}
}

class Analist extends Programci {
public void calis() {
System.out.println("Analist calisiyor....") ;
}
}

/*
Bu ornegimiz derleme aninda hata alicaktir.
Java, coklu kalitimi desteklemez
*/
class CoderTurhal extends Yazilimci, Analist {
}

Program.java derleme anında hata alacaktır. Bu ufak ayrıntıyı belirttikten sonra, kaldığımız yerden devam edelim. Java’nın niye çoklu kalıtımı (multiple inheritance) desteklemediğini anlamak için aşağıdaki gösterim incelenmelidir.

Programci s = new CoderTurhal(); // yukari dogru cevirim
s.calis(); // ??

Herhangi bir yerden, yukarıdaki gösterimde belirtildiği gibi bir ifade yazılmış olsa, sonuç nasıl olurdu? Programci tipinde olan referans, CoderTurhal nesnesine bağlanmıştır (bağlanabilir çünkü arada kalıtım ilişkisi vardır). Fakat burada s.calis() ifadesi yazılırsa, hangi nesnenin calis() yordamı çağrılacaktır? Yazilimci nesnesinin mi? Yoksa Analist nesnesinin mi? Sonuçta, calıs() yordamı, Yazilimci ve Analist sınıflarının içerisinde iptal edilmiştir. Bu sorunun cevabı yoktur. Fakat çoklu kalıtımın bu zararlarından arıtılmış versiyonunu yani arayüzleri (interface) ve dâhili sınıflar (inner classes) kullanarak, diğer dillerinde bulunan çoklu kalıtım desteğini, Java programlama dilinde de bulmak mümkündür. ‘Peki ama nasıl?’ diyenler için hemen örneğimizi verelim. Yukarıdaki örneğimizi Java programlama diline uygun bir şekilde baştan yazalım ama öncesinde her zaman ki gibi işe UML diyagramını çizmekle başlayalım;


CoderTurhal, belki aynı anda hem Yazilimci hemde Analist olamaz ama onlara ait özelliklere sahip olabilir. Örneğin Yazilimci gibi buz üzerinde kayabilir ve Analist gibi şut atabilir. Yukarıdaki UML diyagramı Java uygulamasına dönüştürülürse.

Program2.java
interface ProgramYazabilme {
public void programYaz();
}

interface AnalizYapabilme {
public void analizYap();
}

class CoderTurhal implements ProgramYazabilme, AnalizYapabilme {
public void programYaz() {
System.out.println("CoderTurhal program yaziyor");
}
public void analizYap() {
System.out.println("CoderTurhal analiz yapıyor");
}
}

Bu örneğimizde CoderTurhal, ProgramYazabilme ve AnalizYapabilme özelliklerine sahip olmuştur. Arayüzler içerisindeki (ProgramYazabilme,AnalizYapabilme) gövdesiz (soyut) yordamları (programYaz(), analizYap()), bu arayüzlere erişen sınıf tarafından kesinlikle iptal edilmelidir (overrride). Eğer iptal edilmez ise, derleme anında (compile-time) Java tarafından gerekli hata mesajı verilir.

Örneğimizden anlaşılabileceği üzere arayüz (interface) ile soyut (abstract) sınıf arasında büyük fark vardır. En başta kavramsal olarak bir fark vardır. Bu kavramsal fark nedir derseniz hemen açıklayalım; Soyut bir sınıftan türetilme yapıldığı zaman, türetilen sınıf ile soyut sınıf arasında mantıksal bir ilişki olması gerekirdi, örnek vermek gerekirse "Yarasa bir Hayvandır" gibi veya "Müdür bir Çalışandır" gibi… Geçen bölümlerde incelediğimiz bir ilişkisi. Fakat arayüzler ile bunlara erişen sınıflar arasında kalıtımsal bir ilişki bulunmayabilir.

Arayüzlerin Kalıtım (İnheritance) Yoluyla Genişletilmesi

Bir arayüz başka bir arayüzden türetilerek yeni özelliklere sahip olabilir; böylece arayüzler kalıtım yoluyla genişletilmiş olur. Olayları daha iyi anlayabilmek için önce UML diyagramını çizip sonrada Java uygulamasını yazalım.


Avlanabilme arayüzü, DahaHizliKosabilme arayüzünden türemiştir. DahaHizliKosabilme arayüzüde Kosabilme arayüzünde türemiştir. Yukarıdaki UML diyagramımızı Java uygulamasına dönüştürelim;

Jaguar.java
interface Kosabilme {
public void kos();
}

interface DahaHizliKosabilme extends Kosabilme {
public void dahaHizliKos();
}

interface Avlanabilme extends DahaHizliKosabilme {
public void avlan();
}

class RoadRunner implements DahaHizliKosabilme {
public void kos() {
System.out.println("RoadRunner kosuyor, bip ");
}

public void dahaHizliKos() {
System.out.println("RoadRunner kosuyor, bip bippp");
}
}

public class Jaguar implements Avlanabilme {
public void avlan() {
System.out.println("Juguar avlaniyor");
}

public void dahaHizliKos() {
System.out.println("Juguar daha hizli kos");
}

public void kos() {
System.out.println("Juguar Kosuyor");
}
}

Jaguar sınıfı Avlanabilme arayüzüne ulaşarak, avlan(), dahaHizliKos(), kos() yordamlarını iptal etmek (override) zorunda bırakılmıştır. Bunun sebebi Avlanabilme arayüzünün DahaHizliKosabilme arayüzünden, DahaHizliKosabilme arayüzününde Kosabilme arayüzünden türemiş olmasıdır.

RoadRunner sınıfı ise sadece DahaHizliKosabilme arayüzüne erişerek, kos() ve dahaHizliKos() gövdesiz (soyut) yordamlarını iptal etmek (override) zorunda bırakılmıştır. Yine bunun sebebi DahaHizliKosabilme arayüzünün Kosabilme arayüzünden türemiş olmasıdır.

Arayüzlere Özel

Şimdi birazdan inceleyeceğiz olay sadece arayüzler söz konusu olduğunda yapılabilir. İlk olayımız açıklayalım; Bir arayüz (interface) birden çok arayüzden türetilebilir.

interface C {
//..
}

interface B {
//..
}

interface A extends B, C {
//..
}

Yukarıdaki gösterimde, A arayüzü, birbirlerinden bağımsız iki arayüzden türetilmiş oldu. İkinci olay ise daha evvelde incelenmişti (bkz:Program2.java) ama bir kere daha üzerine basa basa durmakta fayda var; bir sınıf birden fazla arayüze rahatlıkla erişebilir.

Çakışmalar

Arayüzlerin içerisinde dönüş tipleri haricinde her şeyleri aynı olan gövdesiz (soyut) yordamlar varsa, bu durum beklenmedik sorunlara yol açabilir. Yazılanları Java uygulaması üzerinde gösterilirse;

Cakisma.java
interface A1 {
public void hesapla();
}

interface A2 {
public void hesapla(int d);
}

interface A3 {
public int hesapla();
}

class S1 implements A1,A2 { // sorunsuz
public void hesapla() { //adas yordamlar(overloaded)
System.out.println("S1.hesapla");
}
public void hesapla(int d) { //adas yordamlar(overloaded)
System.out.println("S1.hesapla " + d );
}
}

/*
! Hatali !, adas yordamlar (overloaded)
donus tiplerine gore ayirt edilemez
*/

class S2 implements A1,A3 {
public void hesapla() {
System.out.println("S2.hesapla");
}

/* ! Hata !
public int hesapla() {
System.out.println("S2.hesapla");
return 123;
}
*/
}

Cakisma.java örneğini derlenirse (compile), aşağıdaki hata mesajı ile karşılaşılır:

Cakisma.java:27: hesapla() in S2 cannot implement hesapla() in A3; attempting to
use incompatible return type
found : void
required: int
class S2 implements A1,A3 {
^
1 error

Bu hatanın oluşma sebebi, A1 ve A3 arayüzlerinin içerisindeki gövdesiz (soyut) yordamlarından kaynaklanır. Bu yordamların isimleri ve parametreleri aynıdır ama dönüş tipleri farklıdır.

public void hesapla(); // A1 arayüzüne ait
public int hesapla(); // A3 arayüzüne ait

İki yordamın adaş yordam (overloaded method) olabilmesi için bu yordamların parametrelerinde kesin bir farklılık olması gerekirdi. İki yordamın dönüş tipleri dışında her şeyleri aynıysa bunlar adaş yordam olamazlar. Olamamalarının sebebi, Java’nın bu yordamları dönüş tiplerine göre ayırt edememesinden kaynaklanır.

Arayüzün (Interface) İçerisinde Alan Tanımlama

Arayüzlerin içerisinde gövdesiz (soyut) yordamların dışında alanlarda bulunabilir. Bu alanlar uygulamalarda sabit olarak kullanabilir. Çünkü arayüzün içerisinde tanımlanan bir alan (ilkel tipte veya sınıf tipinde olsun) otomatik olarak hem public erişim belirleyicisine hemde final ve static özelliğine sahip olur.

AyBul.java
interface Aylar {
int
OCAK = 1, SUBAT = 2, MART = 3,
NISAN = 4, MAYIS = 5, HAZIRAN = 6, TEMMUZ = 7,
AGUSTOS = 8, EYLUL = 9, EKIM = 10,
KASIM = 11, ARALIK = 12;
}

public class AyBul {
public static void main(String args[]) {

int ay = (int)(Math.random()*13) ;
System.out.println("Gelen ay = " + ay);
switch ( ay ) {
case Aylar.OCAK : System.out.println("Ocak");break;
case Aylar.SUBAT : System.out.println("Subat");break;
case Aylar.MART : System.out.println("Mart");break;
case Aylar.NISAN : System.out.println("Nisan");break;
case Aylar.MAYIS : System.out.println("Mayis");break;
case Aylar.HAZIRAN : System.out.println("Haziran");break;
case Aylar.TEMMUZ : System.out.println("Temmuz");break;
case Aylar.AGUSTOS : System.out.println("Agustos");break;
case Aylar.EYLUL : System.out.println("Eylul");break;
case Aylar.EKIM : System.out.println("Ekim");break;
case Aylar.KASIM : System.out.println("Kasim");break;
case Aylar.ARALIK : System.out.println("Aralik");break;
default:System.out.println("Tanimsiz Ay");
}
}
}

Arayüzün İçerisinde Tanımlanmış Alanlara İlk Değerlerinin Verilmesi

Arayüzlerin içerisinde tanımlanmış alanların ilk değerleri, çalışma anında da (run-time) verilebilir. Aşağıdaki örneği inceleyelim.

Test.java
interface A7 {

int devir_sayisi = (int) ( Math.random() *6 ) ;
String isim = "A7" ;
double t1 = ( Math.random() * 8 ) ;
}

public class Test {
public static void main(String args[] ) {

System.out.println("devir_sayisi = " + A7.devir_sayisi );
System.out.println("isim = " + A7.isim );
System.out.println("t1 = " + A7.t1 );
}
}

A7 arayüzünün içerisindeki ilkel (primitive) int tipindeki devir_sayisi ve t1 alanlarının değerlerini derleme anında bilebilmek imkânsızdır. Bu değerler ancak çalışma anında bilenebilir.

Dikkat edilmesi gereken bir başka husus ise A7 arayüzünün içerisindeki alanların ne zaman ilk değerlerini aldıklarıdır. Bir arayüzün içerisindeki alanlar final ve statik oldukları için, A7 arayüzüne ilk kez erişildiği zaman, A7 arayüzünün içerisindeki tüm alanlar ilk değerlerini alırlar.

Genel Bakış

Arayüzler ve soyut sınıfların bizlere sağlamak istediği fayda nedir? Aslında ulaşılmak istenen amaç çoklu yukarı çevirimdir (upcasting). Bir sınıfa ait nesnenin birçok tipteki sınıf referansına bağlanabilmesi, uygulama içerisinde büyük esneklik sağlar. Bir örnek üzerinde açıklayalım…

GenelBakis.java
interface Arayuz1 {
public void a1() ;
}

interface Arayuz2 {
public void a2() ;
}

abstract class Soyut1 {
public abstract void s1();
}

class A extends Soyut1 implements Arayuz1, Arayuz2 {
public void s1() { // iptal etti (override)
System.out.println("A.s1()");
}
public void a1() { // iptal etti (override)
System.out.println("A.a1()");
}
public void a2() { // iptal etti (override)
System.out.println("A.a2()");
}
}
public class GenelBakis {
public static void main(String args[]) {
Soyut1 soyut_1 = new A();
Arayuz1 arayuz_1 = (Arayuz1) soyut_1 ;
Arayuz2 arayuz_2 = (Arayuz2) soyut_1 ;
// Arayuz2 arayuz_2 = (Arayuz2) arayuz_1 ; // dogru

soyut_1.s1();
// soyut_1.a1(); // ! Hata !
// soyut_1.a2(); // ! Hata !

arayuz_1.a1();
// arayuz_1.a2(); // ! Hata !
// arayuz_1.s1(); // ! Hata !

arayuz_2.a2();
// arayuz_2.a1(); // ! Hata !
// arayuz_2.s1(); // ! Hata !
}
}

A sınıfı Soyut1 soyut sınıfından türetilmiştir, ayrıca A sınıfı Arayuz1 ve Arayuz2 arayüzlerine (interface) erişmektedir. Daha yakından incelenirse,

class A extends Soyut1 implements Arayuz1, Arayuz2 {

Yukarıdaki gösterim şunu der: A sınıfına ait bir nesne, Soyut1 sınıfı, Arayuz1 veya Arayuz2 arayüzü tipindeki referanslara bağlanabilir. Anlatılanlar UML diyagramına dönüştürülürse;


Bu örneğimizde görüldüğü üzere, A sınıfı ait tek bir nesne oluşturulmuştur. Oluşturulan nesne farklı referanslara bağlanabilmektir.

Soyut1 soyut_1 = new A();
Arayuz1 arayuz_1 = (Arayuz1) soyut_1 ; // tip degisimi
Arayuz2 arayuz_2 = (Arayuz2) soyut_1 ; // tip degisimi

Oluşturulan tek bir A sınıfına ait nesne, birçok farklı sınıf ve arayüz tipindeki referansa bağlanabilmektedir. Örneğin yukarıdaki gösterimde, A sınıfına ait nesnemiz ilk olarak Soyut1 sınıfı tipindeki referansa bağlanmıştır. Bağlanabilir çünkü A sınıfı Soyut1 sınıfından türemiştir. Soyut1 sınıfı tipindeki referansa bağlı olan A sınıfına ait nesnenin sadece s1() yordamına ulaşılabilir.

Daha sonra Soyut1 referansının bağlı olduğu A sınıfına ait nesneyi, Arayuz1 tipindeki referansa bağlanıyor ama bu bağlama sırasında A sınıfına ait nesneyi Arayuz1 tipindeki referansa bağlanacağını açık açık söylemek gerekir. A sınıfına ait nesnemiz Arayuz1 tipindeki referansa bağlanırsa bu nesnenin sadece a1() yordamına erişilebilir.

Aynı şekilde Soyut1 sınıfı tipindeki referansa bağlı olan A sınıfına ait nesne, Arayuz2 tipindeki referansa bağlınabilir. A sınıfına ait nesne, Arayuz2 tipindeki referansa bağlanırsa, bu nesnenin sadece a2() yordamına erişebilir.

Soyut1 soyut_1 = new A();
Arayuz1 arayuz_1 = new A();
Arayuz2 arayuz_2 = new A();

Görüldüğü üzere, A sınıfına ait üç adet nesne oluşturduk ve bu nesnelerin her birini farklı tipteki referanslara bağlayabildik. Bu olay nesneye yönelik tasarımlar yaparken işimize çokça yarayabilecek bir yaklaşımdır.

Dâhili Arayüzler (Nested Interface)

Bir arayüz, başka bir arayüzün veya sınıfın içerisinde tanımlanabilir. Bir arayüzün içerisinde tanımlanan dâhili arayüzler, protected, friendly veya private erişim belirleyicisine sahip olamaz. Örneğimize geçmeden evvel UML diyagramını inceleyelim.


UML diyagramımızdan anlaşılacağı üzere, ArayuzA arayüzünün içerisinde iki adet dâhili arayüz (nested interface) tanımlanmıştır. Dışarıdaki iki sınıfımız, dâhili olarak tanımlanmış bu iki arayüze erişebilmektedir.

DahiliArayuzTest.java

interface ArayuzA { //aslinda public erisim belirleyicisine sahip
public interface DahiliArayuz1 {
public void isYap1() ;
}

/* // ! Hata !
protected interface DahiliArayuz2 {
public void isYap2() ;
}
*/

interface DahiliArayuz3 { // aslinda public erisim belirleyicisine sahip
public void isYap3() ;
}

/* // ! Hata !
private interface DahiliArayuz4 {
public void isYap4() ;
}
*/
}

class Erisim1 implements ArayuzA.DahiliArayuz1 {
public void isYap1() {
System.out.println("Erisim1.isYap1()");
}
}

class Erisim2 implements ArayuzA.DahiliArayuz3 {
public void isYap3() {
System.out.println("Erisim1.isYap3()");
}
}

public class DahiliArayuzTest {
public static void main(String args[]) {
Erisim1 e1 = new Erisim1();
Erisim2 e2 = new Erisim2();
e1.isYap1();
e2.isYap3();
}
}

Dâhili arayüzlere erişen sınıflar açısından olaylar aynıdır. Yine bu dahili arayüzlerin içerisindeki gövdesiz yordamları iptal etmeleri gerekmektedir. Uygulamanın çıktısı aşağıdaki gibidir;

Erisim1.isYap1()
Erisim1.isYap3()

Sınıfların İçerisinde Tanımlanan Dahili Arayüzler (Nested Interface)

Bir arayüz diğer bir arayüzün içerisinde tanımlandığı gibi, bir sınıfın içerisinde de tanımlanabilir.

SinifA.java
public class SinifA {

public interface A1 {
public void ekranaBas();
} //arayüz

public class DahiliSinif1 implements A1 {
public void ekranaBas() {
System.out.println("DahiliSinif1.ekranaBas()");
}
} // class DahiliSinif1

protected class DahiliSinif2 implements A1 {
public void ekranaBas() {
System.out.println("DahiliSinif2.ekranaBas()");
}
} // class DahiliSinif2

class DahiliSinif3 implements A1 {
public void ekranaBas() {
System.out.println("DahiliSinif3.ekranaBas()");
}
} // class DahiliSinif3

private class DahiliSinif4 implements A1 {
public void ekranaBas() {
System.out.println("DahiliSinif4.ekranaBas()");
}
} // class DahiliSinif4

public static void main(String args[]) {
SinifA.DahiliSinif1 sad1 = new SinifA().new DahiliSinif1();
SinifA.DahiliSinif2 sad2 = new SinifA().new DahiliSinif2();
SinifA.DahiliSinif3 sad3 = new SinifA().new DahiliSinif3();
SinifA.DahiliSinif4 sad4 = new SinifA().new DahiliSinif4();

sad1.ekranaBas();
sad2.ekranaBas();
sad3.ekranaBas();
sad4.ekranaBas();

SinifB sb = new SinifB();
sb.ekranaBas();
}
}

class SinifB implements SinifA.A1{
public void ekranaBas() {
System.out.println("SinifB.ekranaBas()");
}
}

SinifA sınıfının içerisinde tanımlanan A1 arayüzüne, SinifB sınıfından ulaşılabilir. Bir sınıfın içerisinde dahili arayüz tanımlanabildiği gibi dahili sınıfta tanımlanabilir. Bu konu az sonra incelenecektir. Bu örneğimizdeki ana fikir, bir sınıfın içerisinde nasıl dahili arayüzün oluşturulduğu ve bu dahili arayüzün, dahili sınıf olsun veya dışarıdan başka bir sınıf tarafından olsun, nasıl erişilebildiğini göstermektir.

Toparlamak gerekirse bu makalemizde Java ise arayüz kavramını ayrıntılı bir biçimde incelemeye çalıştık. Bir sonraki Java makalemizde ise Java ile dahili sınıflar kavramına ayrıntılı bir biçimde değinmeye çalışacağız.

Umarım Yararlı olmuştur.

Kaynaklar
KTÜ İsbb ders notları