Mephistophelés
Bronz Üye
-
- Katılım
- Eylül 10, 2012
-
- Mesajlar
- 3,744
-
- Tepkime puanı
- 2
-
- Puanları
- 293
-
- Yaş
- 48
Kategorisi : J# ve Java dili
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 **** calis() ;
}
class mudur implements Calisan {
public **** calis() { // iptal etti (override)
System.out.println("Mudur Calisiyor");
}
}
class genelMudur extends Mudur {
public **** calis() { // iptal etti (override)
System.out.println("GenelMudur Calisiyor");
}
public **** toplantiYonet() {
System.out.println("GenelMudur toplanti yonetiyor");
}
}
class programci implements Calisan {
public **** calis() { // iptal etti (override)
System.out.println("Programci Calisiyor");
}
}
class AnalizProgramci extends Programci {
public **** analizYap() {
System.out.println("Analiz Yapiliyor");
}
}
class SistemProgramci extends Programci {
public **** sistemIncele() {
System.out.println("Sistem Inceleniyor");
}
}
class Pazarlamaci implements Calisan {
public **** calis() { // iptal etti (override)
System.out.println("Pazarlamaci Calisiyor");
}
}
class Sekreter implements Calisan {
public **** calis() { // iptal etti (override)
System.out.println("Sekreter Calisiyor");
}
}
public class BuyukIsYeri {
public static **** mesaiBasla(Calisan[] c ) {
for (int i = 0 ; i < c.length ; i++) {
c.calis(); // ! Dikkat !
}
}
public static **** 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 **** 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 **** 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 **** avlan() ;
}
abstract class Kedi implements Hayvan {
public abstract **** takipEt() ;
}
class Kaplan extends Kedi {
public **** avlan() { // iptal etti (override)
System.out.println("Kaplan avlaniyor...");
}
public **** 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 **** calis();
}
class Yazilimci extends Programci {
public **** calis() {
System.out.println("Yazilimci calisiyor....") ;
}
}
class Analist extends Programci {
public **** 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 **** programYaz();
}
interface AnalizYapabilme {
public **** analizYap();
}
class CoderTurhal implements ProgramYazabilme, AnalizYapabilme {
public **** programYaz() {
System.out.println("CoderTurhal program yaziyor");
}
public **** 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 **** kos();
}
interface DahaHizliKosabilme extends Kosabilme {
public **** dahaHizliKos();
}
interface Avlanabilme extends DahaHizliKosabilme {
public **** avlan();
}
class RoadRunner implements DahaHizliKosabilme {
public **** kos() {
System.out.println("RoadRunner kosuyor, bip ");
}
public **** dahaHizliKos() {
System.out.println("RoadRunner kosuyor, bip bippp");
}
}
public class Jaguar implements Avlanabilme {
public **** avlan() {
System.out.println("Juguar avlaniyor");
}
public **** dahaHizliKos() {
System.out.println("Juguar daha hizli kos");
}
public **** 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
rogram2.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 **** hesapla();
}
interface A2 {
public **** hesapla(int d);
}
interface A3 {
public int hesapla();
}
class S1 implements A1,A2 { // sorunsuz
public **** hesapla() { //adas yordamlar(overloaded)
System.out.println("S1.hesapla");
}
public **** 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 **** 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 : ****
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 **** 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 **** 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;
defaultystem.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 **** 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 **** a1() ;
}
interface Arayuz2 {
public **** a2() ;
}
abstract class Soyut1 {
public abstract **** s1();
}
class A extends Soyut1 implements Arayuz1, Arayuz2 {
public **** s1() { // iptal etti (override)
System.out.println("A.s1()");
}
public **** a1() { // iptal etti (override)
System.out.println("A.a1()");
}
public **** a2() { // iptal etti (override)
System.out.println("A.a2()");
}
}
public class GenelBakis {
public static **** 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;
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 **** calis() ;
}
class mudur implements Calisan {
public **** calis() { // iptal etti (override)
System.out.println("Mudur Calisiyor");
}
}
class genelMudur extends Mudur {
public **** calis() { // iptal etti (override)
System.out.println("GenelMudur Calisiyor");
}
public **** toplantiYonet() {
System.out.println("GenelMudur toplanti yonetiyor");
}
}
class programci implements Calisan {
public **** calis() { // iptal etti (override)
System.out.println("Programci Calisiyor");
}
}
class AnalizProgramci extends Programci {
public **** analizYap() {
System.out.println("Analiz Yapiliyor");
}
}
class SistemProgramci extends Programci {
public **** sistemIncele() {
System.out.println("Sistem Inceleniyor");
}
}
class Pazarlamaci implements Calisan {
public **** calis() { // iptal etti (override)
System.out.println("Pazarlamaci Calisiyor");
}
}
class Sekreter implements Calisan {
public **** calis() { // iptal etti (override)
System.out.println("Sekreter Calisiyor");
}
}
public class BuyukIsYeri {
public static **** mesaiBasla(Calisan[] c ) {
for (int i = 0 ; i < c.length ; i++) {
c.calis(); // ! Dikkat !
}
}
public static **** 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 **** 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 **** 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 **** avlan() ;
}
abstract class Kedi implements Hayvan {
public abstract **** takipEt() ;
}
class Kaplan extends Kedi {
public **** avlan() { // iptal etti (override)
System.out.println("Kaplan avlaniyor...");
}
public **** 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 **** calis();
}
class Yazilimci extends Programci {
public **** calis() {
System.out.println("Yazilimci calisiyor....") ;
}
}
class Analist extends Programci {
public **** 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 **** programYaz();
}
interface AnalizYapabilme {
public **** analizYap();
}
class CoderTurhal implements ProgramYazabilme, AnalizYapabilme {
public **** programYaz() {
System.out.println("CoderTurhal program yaziyor");
}
public **** 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 **** kos();
}
interface DahaHizliKosabilme extends Kosabilme {
public **** dahaHizliKos();
}
interface Avlanabilme extends DahaHizliKosabilme {
public **** avlan();
}
class RoadRunner implements DahaHizliKosabilme {
public **** kos() {
System.out.println("RoadRunner kosuyor, bip ");
}
public **** dahaHizliKos() {
System.out.println("RoadRunner kosuyor, bip bippp");
}
}
public class Jaguar implements Avlanabilme {
public **** avlan() {
System.out.println("Juguar avlaniyor");
}
public **** dahaHizliKos() {
System.out.println("Juguar daha hizli kos");
}
public **** 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
Ç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 **** hesapla();
}
interface A2 {
public **** hesapla(int d);
}
interface A3 {
public int hesapla();
}
class S1 implements A1,A2 { // sorunsuz
public **** hesapla() { //adas yordamlar(overloaded)
System.out.println("S1.hesapla");
}
public **** 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 **** 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 : ****
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 **** 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 **** 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;
defaultystem.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 **** 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 **** a1() ;
}
interface Arayuz2 {
public **** a2() ;
}
abstract class Soyut1 {
public abstract **** s1();
}
class A extends Soyut1 implements Arayuz1, Arayuz2 {
public **** s1() { // iptal etti (override)
System.out.println("A.s1()");
}
public **** a1() { // iptal etti (override)
System.out.println("A.a1()");
}
public **** a2() { // iptal etti (override)
System.out.println("A.a2()");
}
}
public class GenelBakis {
public static **** 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;