Java - Siniflarda kalitim, soyut (abstract) siniflar, interface siniflar

Programlama08/02/2012


Java deyince akla nesne, nesne degince de akla degiskenler, metotlar ve tabi ki siniflar gelir.
Bu siniflar Java kütüphanelerindeki siniflar da olabilir, kendi yazdigimiz siniflar da.
Simdilik bizi asil ilgilendiren kendi yazdigimiz siniflar olacak. Hemen bir sinif örnegi yazalim:
 
class BankaHesabi {
        String ad;
        String adres;
        int hesapNumarasi;
        double bakiye;
}
 
Sinifimizin adi BankaHesabi; bünyesinde ad, adres, hesapNumarasi ve bakiye adinda 4 adet degiskenimiz var.
Bir banka hesabinda ne gibi islemler yapilabilir? Para yatirabiliriz, çekebiliriz, baska bir hesaba transfer edebiliriz; bakiyemizi sorabiliriz...
Simdi bütün bu islemleri yapabilmek için gerekli metotlar yazalim.
 
        void paraYatir(double miktar) {
          bakiye = bakiye + miktar;
        }
 
        void paraCek(double miktar) {
          bakiye = bakiye - miktar;
        }
 
        double bakiyeBildir() {
          return bakiye;
        }
Gördügünüz gibi  metotlarimiz anlasilir bir sekilde isimlendirilmis ve yaptigi islemler de gayet açik.
Bir de baska bir hesaba para transferi yapmak için bir metot yazalim. Bu digerlerinden biraz farkli:
 
        void paraTransferiYap(double miktar, BankaHesabi alici) {
          paraCek(miktar);
          alici.paraYatir(miktar);
        }
 
Para transferi yapmak için ne lazim: Bir miktar para ve baska bir banka hesabi. Dolayisiyla bu metodumuzda iki degisken kullandik; biri double tipinde, digeri ise BankaHesabi  tipinde. Yani kendi olusturdugumuz sinifi degisken olarak kullandik.
 
Simdi sinifimizi bi test edelim mi?
 
class BankaHesabi {
        String ad;
        String adres;
        int hesapNumarasi;
        double bakiye;
       
        void paraYatir(double miktar) {
          bakiye = bakiye + miktar;
        }
        void paraCek(double miktar) {
          bakiye = bakiye - miktar;
        }
        double bakiyeBildir() {
          return bakiye;
        }
        void paraTransferiYap(double miktar, BankaHesabi alici) {
          paraCek(miktar);
          alici.paraYatir(miktar);
        }
      }
 
public class Main {
 
      public static void main(String[] args) {
            BankaHesabi b1 = new BankaHesabi();
            BankaHesabi b2 = new BankaHesabi();
            b1.paraYatir(100);
            System.out.println("b1 (para transferi yapmadan önce): " + b1.bakiyeBildir());
            System.out.println("b2 (para transferi yapmadan önce): " + b2.bakiyeBildir());
           
            //b1 adli hesaptan b2 adli hesaba para transferi yapiyoruz:
            b1.paraTransferiYap(20, b2);
            System.out.println("b1: " + b1.bakiyeBildir());
            System.out.println("b2: " + b2.bakiyeBildir());
      }
}
 
Sonuç:
b1 (para transferi yapmadan önce): 100.0
b2 (para transferi yapmadan önce): 0.0
b1: 80.0
b2: 20.0
 
 
Sinif yapilandiricilari (constructor):
Her sinifin bir de yapilandiricisi vardir, olmak zorundadir. Ama biz yukaridaki örnekte yapilandirici kullanmadik, diye düsünebilirsiniz. Dogru, biz kendimiz bir yapilandirici yazmadik fakar Java bunu kendisi yapiyor zaten.
 
class BankaHesabi {
        String ad;
        String adres;
        int hesapNumarasi;
        double bakiye;
       
        public BankaHesabi(){
             
        }
...
 
Eger kendimiz bir yapilandirici kullanmazsak Java bizim yerimize bos bir yapilandirici ekler.
 
Simdi yukaridaki örnegi kendimiz bir yapilandirici kullanarak yapalim.
 
class BankaHesabi {
        String ad;
        String adres;
        int hesapNumarasi;
        double bakiye;
       
        public BankaHesabi(String ad, String adres, int hesapNumarasi){
              this.ad = ad;
              this.adres = adres;
              this.hesapNumarasi = hesapNumarasi;
        }
 
Yapilandiricimiza 3 tane parametre verdik ve bu parametreleri sinifimizin basinda tanimladigimiz ad, adres ve hesapNumarasi adli degiskenlere atadik. Ben degisken isimlerini ayni yaptim ama siz karistirmamak için farkli isimler de kullanabilirsiniz veya parametre de ekleyebilirsiniz (mesela bakiye’yi ekliyebilirsiniz).
 
this.ad = ad;
Mavi renkli ad degiskeni yukaridaki sinif degiskeni, digeri ise yapilandiricimizdaki degisken. Zaten this. Seklinde yazilmasinin sebebi budur; yani sinif degiskeni olmasidir.
 
Sinifimizi bir de bu hâliyle test edelim:
 
      BankaHesabi b1 = new BankaHesabi("Ahmet", "Yozgat", 123432);
      BankaHesabi b2 = new BankaHesabi("Mehmet", "Ankara", 657875);
 
Yapilandiricilar ile metotlar arasindaki farklar nelerdir?
 
·         Yapilandiricinin ismi sinifin ismiyle birebir ayni olmak zorundadir.
·         Bir yapilandirici hiç bir zaman bir deger geri döndürmez; hattâ void  seklinde de tanimlanmaz.
·         Bir yapilandirici çagirirken (yeni bir sinif nesnesi olusturulurken) mutlaka new anahtar sözcügü kullanilmalidir.
 
 
Siniflarda kalitim (inheritance):
Nesneye dayali programlama dillerinin bize sundugu en büyük avantajlardan biri, bir kere yazdigimiz nesneyi (sinifi) gerektiginde baska yerlerde defalarca kullanabilmemizdir.
Öyle ki bir sinifi kullanarak baska ve daha gelismis bir sinif olusturmamiz da mümkün.
Baska bir degisle öyle bir sinif yazacagiz ki, hem BankaHesabi adli sinifin bütün özelliklerine hem de arti bir takim özelliklere sahip olsun.
 
Yukarida yazdigimiz banka hesabini kontrol eden program para yatirmaya, para çekmeye ve baska bir hesaba para transferi yapmaya olanak sagliyor. Kisacasi bütün banka hesaplarinda olmasi gereken temel özellikler. Peki bizim hesabimiz vadeli bir hesapsa, yani yatirilan paraya faiz veriliyorsa? O zaman bize bir de faiz hesaplama metodu lazim olacak.
Ayri bir sinif yazip bütün bu temel metotlari tekrar yazmak pek akillica bir is olmaz degil mi?
O zaman yapacagimiz sey Java’nin kalitim özelligini kullanacagiz.
 
Yeni sinifimizin adi VadeliBankaHesabi olsun.
 
      public class VadeliBankaHesabi extends BankaHesabi{
     
            double faizOrani;
            public VadeliBankaHesabi(String ad, String adres, int hesapNumarasi, double faizOrani) {
                  super(ad, adres, hesapNumarasi);
                  this.faizOrani = faizOrani;
            }
      }
 
extends anahtar sözcügüne dikkat etmissinizdir. Bunun manasi sudur: VadeliBankaHesabi sinifi BankaHesabi sinifindan türemistir.
 
super(ad, adres, hesapNumarasi);
super anahtar sözcügü türenen sinifa, yani BankaHesabi sinifina atfediyor ve onun parametrelerini  (ad, adres, hesapNumarasi) aliyor.
 
Hiç bir metot yazmadigimiz hâlde para yatirma, çekme, transfer etme... islemlerini yapabilecegiz.
Hemen test edelim:
 
public class Main {
 
      public static void main(String[] args) {
            BankaHesabi b1 = new BankaHesabi("Ahmet", "Yozgat", 123432);
            VadeliBankaHesabi vbh = new VadeliBankaHesabi("Mehmet", "Ankara", 657875, 5.9);
            b1.paraYatir(100);
            System.out.println("b1 (para transferi yapmadan önce): " + b1.bakiyeBildir());
            System.out.println("vadeli hesap (para transferi yapmadan önce): " + vbh.bakiyeBildir());
           
            //b1 adli hesaptan vbh adli hesaba para transferi yapiyoruz:
            b1.paraTransferiYap(20, vbh);
            System.out.println("b1: " + b1.bakiyeBildir());
            System.out.println("vadeli hesap: " + vbh.bakiyeBildir());
      }
}
 
Sonuç:
b1 (para transferi yapmadan önce): 100.0
vadeli hesap (para transferi yapmadan önce): 0.0
b1: 80.0
vadeli hesap: 20.0
 
Bundan sonra artik VadeliBankaHesabi sinifinin içine faiz hesaplama metodu ya da daha baska degisken ya da metotlar ekleyebiliriz...
Sunu da unutmadan söyleyeyim, VadeliBankaHesabi sinifindan da baska siniflarin türemesini saglayabilir ve bütün önceki siniflarin özelliklerini kullanabiliriz.
Kod yazmadan örnek vermek istiyorum. Bizim bir adet ‘Canlilar’ sinifimiz olsun ve bu siniftan baska siniflar türetelim. Bunlar ne olabilir?
Bitkiler, Hayvanlar, Insanlar...
Hayvanlar sinifindan SudaYasayanlar, KaradaYasayanlar siniflari türetilebilir.
KaradaYasayanlar sinifindan Omurgalilar, Omurgasizlar siniflari,
Omurgalilar sinifindan da (mesela) Kedi sinifi türetilebilir.
Bu durumda ‘Kedi’ sinifi Omurgalilar, KaradaYasayanlar, Hayvanlar ve nihayet Canlilar siniflarinin özelliklerini almis olur.
Mantik bu sekilde.
 
Final sinifi:
Eger olusturdugumuz siniftan kalitim yoluyla yeni bir sinif olusmasin istiyorsak su sekilde tanimlamaliyiz:
 
 
public final class Kediler {
 
}
 
Bu sayede artik Kediler sinifindan baska bir sinif türeyemez.
 
Abstract (soyut) siniflar:
Yukaridaki örnegimizde Hayvanlar sinifini Canlilar sinifindan olusturduk.  Canlilarin ortak özellikleri nelerdir? Solunum yapmak, beslenmek... Evet bunlar bütün canlilarda var olan ortak özelliklerdir, fakat baliklar ile kedilerin solunum sekli farklidir; solucanla timsahin beslenmesi de farklidir.
Eger Canlilar sinifina söyle bir metot yazsaydik ne olurdu:
 
      public void beslen(){
            System.out.println("Fare yiyorum");
      }
 
Cevap basit: Hiç bir baligin isine yaramazdi bu metot.
Canlilar sinifinda bir beslen() metodu olmali fakat içerigi, yani nasil olacagi canlidan canliya degismeli.
O zaman nasil bir çözüm getirecegiz? Tabi ki abstract siniflar olusturarak.
 
public abstract class Canlilar {
 
      String isim;
     
      public abstract void beslen();
      public abstract void nefesAl();
     
      public void birseyYap(){
            System.out.println("bla bla");
      }    
}
 
Gördügünüz gibi sinifimizda beslen() ve nefesal() adli iki tane metot var; fakat bu metotlar simdiye kadar ögrendiklerimizden biraz farkli. Metotlar gövdesiz tanimlanmis. Yalnizca bu metotlarin varligi söz konusu; ne yaptiklari, nasil yaptiklari belirtilmemis. Iste bu sayede her canli bu metotlari kendi istedigi gibi sekillendirecektir.
Bu iki metodun digerlerinden baska bir özelligi daha var. Abstract metotlar, bu siniftan türeyen diger siniflarda mutlaka kullanilmalidir. Yani Canlilar sinifindan türeyen Hayvanlar sinifi bu iki metotdu bir sekilde kullanmak zorundadir.
 
abstract bir siniftan baska bir sinif türetirken extends sözcügünü kullaniyoruz:
public class Kediler extends Canlilar{
 
}
 
Kediler sinifi Canlilar abstract sinifindan türeyen bir altsinif.
Eger Eclipse ya da NetBeans gibi bir IDE kullaniyorsaniz yukaridaki kodlar hatâ verecektir.


Bu hatâ Canlilar sinifi bünyesindeki abstract metotlarin override (Türkçe’de iptal etmek deniyor) edilmemesinden kaynaklaniyor.  Iptal edelim:
 
public class Kediler extends Canlilar{
 
      public void beslen() {
            System.out.println("fare yiyorum");
      }
 
      public void nefesAl() {
            System.out.println("akcigerimi kullaniyorum");      
      }
}
 
Iste, beslen() ve nefesAl() metotlarinin içerigini Kediler sinifinda belirlemis olduk. Bunlar, Baliklar sinifinda daha degisik olacaktir elbette.
 
Soyut siniflardan yeni somut nesneler olusturulmaz
 
            Canlilar c = new Canlilar();
Hata verecektir.
 
Interface siniflar:
Interface’lerin bütün metotlari soyuttur ve kendinden türeyen bütün siniflarda iptal edilmesi (override) gerekir. Su sekilde olusturulur:
 
public interface InterfaceTestSinifi {
      public String konus();
}
Public class yerine public interface yaziyoruz.
 
Interface’lerden tipki abstract siniflarda oldugu gibi somut nesneler olusturulamaz.
 
InterfaceTestSinifi its = new InterfaceTestSinifi();
Hata verecektir.
 
Bir interface’den baska bir sinif türetirken implements sözcügünü kullaniyoruz:
public class TureyenSinif implements InterfaceTestSinifi{
 
      public String konus() {     
            return "Merhaba";
      }
}
 
ActionListener, MouseMotionListener gibi bir çok sinif Java’nin kendi kütüphanesinden interface örneklerindendir.

Not: Abstract siniflarda hem is yapan (bildigimiz metotlar) hem de is yapmayan, yalnizca adi konulmus metotlar vardir.  Fakat interface siniflarda ise bütün metotlar bostur; onlarin içerigini implemente ettigin sinifta kendin dolduracaksin. Isine nasil geliyorsa öyle doldurabilirsin. Fark bu.  (Soru:interface leri abstract sınıflardan ayıran özellik nedir ?)

Kaynak : KaynakAdresi

Etiketler: