BÖLÜM 7

 

Altuğ B. Altıntaş
© 2004

 

Arayüzler ve Dahili Sınıflar

(Interface and 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. (yorum ekle)

 

7.1. Arayüz (Interface)

Arayüzler, soyut (abstract) sınıfların bir üst modeli gibi düşünelebilir, 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ıfdan (abstract class) türetilmiş  alt sınıfların içerisinde iptal edilmeleri (override) gerektiğini geçen bölümde 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; arayüzlere ait gövdesiz (soyut) yordamlar otomatik olarak public erişim belirleyicisine sahip olurlar ve sizin bunu değiştirme imkanı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. (yorum ekle)

 

7.1.1. Birleştiricilik

Bölüm-6’da verilen BüyükIsYeri.java örneğini, arayüzleri kullanarak  baştan yazmadan önce, yeni UML diyagramını inceleyelim; (yorum ekle)

Şekil-7.1. Birleştiricilik

 

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; (yorum ekle)

Örnek:  BuyukIsYeri.java  (yorum ekle)

 
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. (yorum ekle)

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

 

Gösterim-7.1:

 
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; (yorum ekle)

 

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

 

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

Eğer bir sınıf (soyut sınıflar dahil) 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ştı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; (yorum ekle)

Örnek:  Karisim.java (yorum ekle)

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; (yorum ekle)

 

Şekil-7.2.  Arayüzler ve Soyut Sınıflar

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; (yorum ekle)

Örnek:  Karisim2.java (yorum ekle)

 
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. (yorum ekle)

En baştaki sorumuzun cevabı olarak, Karisim.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) 8. bölümde detaylı bir şekilde anlatılmaktadır.) (yorum ekle)

 

7.1.3. 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 Sporcu soyut sınıfından türetilmiş iki adet sınıfımız bulunsun, BuzPatenci ve Basketbolcu sınıfları. Bu iki sınıftan türetilen yeni bir sınıf olamaz mı? Örneğin SportmenMehmet sınıfı; yani, SportmenMehmet sınıfı aynı anda hem BuzPatenci, hem de Basketbolcu sınıfından türetilebilir mi? Java programlama dilinde türetilemez. Bunun sebeplerini incelemeden evvel, hatalı yaklaşımı UML diyagramında ifade edilirse; (yorum ekle)

 

Şekil-7.3. Çoklu Kalıtımın (Multiple Inheritance) Sakıncaları

 

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;      (yorum ekle)       

 

Örnek:  Spor.java (yorum ekle)

 
abstract class Sporcu {
  public abstract void calis();
}
 
class BuzPatenci extends Sporcu {
  public void calis() {
      System.out.println("BuzPatenci calisiyor....") ;
  }
}
 
class Basketbolcu extends Sporcu {
  public void calis() {
      System.out.println("Basketbolcu calisiyor....") ;
  }
}
 
/*
Bu ornegimiz derleme aninda hata alicaktir.
Java, coklu kalitimi desteklemez
*/
class SportmenMehmet extends BuzPatenci, Basketbolcu {
}
 

 

Spor.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. (yorum ekle)

 

Gösterim-7.2:

 
Sporcu s = new SportmenMehmet(); // yukari dogru cevirim
s.calis(); // ??
 

 

Herhangi bir yerden, yukarıdaki gösterimde belirtildiği gibi bir ifade yazılmış olsa, sonuç nasıl olurdu? Sporcu tipinde olan referans, SportmenMehmet 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? BuzPatenci nesnesinin mi? Yoksa Basketbolcu nesnesinin mi? Sonuçta, calıs() yordamı, BuzPatenci ve Basketbolcu 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 dahili 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; (yorum ekle)

 

Şekil-7.4.Arayüzlerin kullanılışı

 

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

 

Örnek:  Spor2.java (yorum ekle)

 
interface BuzUstundeKayabilme {
  public void buzUstundeKay();
}
 
interface SutAtabilme {
  public void sutAt(); 
}
 
class SportmenMehmet implements BuzUstundeKayabilme, SutAtabilme {
  public void buzUstundeKay() {
      System.out.println("SportmenMehmet buz ustunde kayiyor");
  }
  public void sutAt() {
      System.out.println("SportmenMehmet sut atiyor");
 }
}
 

Bu örneğimizde SportmenMehmet, BuzUstundeKayabilme ve SutAtabilme özelliklerine sahip olmuştur. Arayüzler içerisindeki (BuzUstundeKayabilme,SutAtabilme)  gövdesiz (soyut) yordamları (buzUstundeKay(), sutAt()), 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. (yorum ekle)

Ö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. (yorum ekle)

 

7.1.4. 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. (