BÖLÜM 7
|
|
Altuğ
B. Altıntaş
|
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)
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)
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 CalisiyorPazarlamaci CalisiyorMudur CalisiyorGenelMudur CalisiyorProgramci CalisiyorProgramci Calisiyor
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)
İ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)
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. (