Bölüm 4: Sarmalama

Java ile Nesne Yönelimli Programlama
Bilgi saklamaÖn ve son koşullarErişim belirleyicilerGetter ve setter Arayüz ve gerçekleştirimPaketler ve importAd çakışmaları
Sercan KÜLCÜ · Giresun Üniversitesi · Elektrik-Elektronik Mühendisliği

Giriş

Öğrenme Çıktıları

Sarmalamayı açıklamak

Veriyi ve onu işleyen metotları tek sınıfta toplamayı, iç ayrıntıları gizlemeyi (bilgi saklama) ve “ne / nasıl” ayrımını örnekle anlatmak.

Metot sözleşmesi yazmak

Bir metodun ön koşulunu ve son koşulunu yorum satırıyla belirtmek ve kodda kontrol etmek.

Erişimi doğru seçmek

public, private, protected ve paket erişiminin kimlere açık olduğunu tablo ile söylemek.

Getter ve setter yazmak

Alanları private yapıp doğrulama yapan erişim metotları yazmak; this.alan = alan kalıbını kullanmak.

Arayüz ile gerçekleştirimi ayırmak

Sınıfın dış yüzünü değiştirmeden iç yapısını değiştirebilmek; interface ve implements ile tanışmak.

Paket kullanmak

package ve import ile sınıfları gruplamak, derleyip çalıştırmak; metot ve sınıf adı çakışmalarını çözmek.

Sarmalama ve Bilgi Saklama

Sarmalama (Encapsulation)

  • Sarmalama (encapsulation): bir nesnenin verisini (alanlar) ve bu veriyi işleyen metotları tek birimde, sınıfta toplamak.
  • Veri dışarıya kapatılır (private); dışarıdan yalnızca sınıfın sunduğu metotlar çağrılabilir.
  • Benzetme: ilaç kapsülü. İçindeki toz görünmez, yalnızca kapsülü yutarsınız. Sınıf da kendi verisinin bekçisidir: geçersiz bir değer (ör. 75. saniye) içeri giremez.
DijitalSaat nesnesi (kapsül)+ getSaniye()+ setSaniye(int)+ goster()private verisaat = 9dakika = 5saniye = 30Başka sınıf(kullanıcı)✓s.saniye = 75; → derleme hatası

Sarmalama, nesne yönelimli programlamanın dört temel ilkesinden biridir (diğerleri: kalıtım, çok biçimlilik, soyutlama).

Sarmalama ve Bilgi Saklama

Bilgi Saklama

  • Bilgi saklama (information hiding), soyutlama (abstraction) olarak da adlandırılır. Tasarım, ne yapıldığını nasıl yapıldığından ayırmalıdır.
  • Bir metot, iç ayrıntıları bilinmeden de kullanılabilmelidir: metodun ne işe yaradığını bilmek yeterli olmalıdır.
  • Arabada fren, gaz pedalı ve direksiyonu görür, kullanırız; bu parçaların iç mekaniğini bilmeyiz.
Arayüz: sürücünün gördüğüDireksiyonGaz pedalıFren pedalıGerçekleştirim: kaputun altı (gizli)Direksiyon kutusuMotor, yakıt enjeksiyonuŞanzımanFren hidroliği, ABS

Aynı fikir koddadır: System.out.println metodunu sürekli kullanırız ama ekrana yazmanın nasıl gerçekleştiğini bilmemiz gerekmez.

Sarmalama ve Bilgi Saklama

Bilgi Saklama: Kod

public class VeriIslemleri {
    private VeritabaniBaglantisi baglanti;
    // metodun iç yapısı bilinmeden de kullanılabilir.
    public void veriEkle(String veri) {
        baglanti = new VeritabaniBaglantisi();
        baglanti.baglan();
        baglanti.veriEkle(veri);
        baglanti.kapat();
    }
}
public class Ana {
    public static void main(String[] args) {
        VeriIslemleri islem = new VeriIslemleri();
        islem.veriEkle("Ayşe");
    }
}
Çıktı
Bağlantı açıldı
Eklendi: Ayşe
Bağlantı kapatıldı
  • Ana sınıfı yalnızca islem.veriEkle("Ayşe") der: ne istediğini söyler.
  • Bağlantıyı açmak, veriyi yazmak, kapatmak (nasıl) VeriIslemleri içinde gizlidir.
  • baglanti alanı private: dışarıdan görülemez, değiştirilemez.
  • VeritabaniBaglantisi burada ekrana yazan basit bir sınıftır; gerçek bir veri tabanı sürücüsü olsaydı bile Ana değişmezdi.

Sarmalama ve Bilgi Saklama · Kodu adım adım çalıştır

Tek Çağrının Arkası: Adım Adım

Yığın (stack)Yığın bellek (heap)main()islemVeriIslemleribaglantinull

new VeriIslemleri() heap'te bir nesne oluşturur; islem bu nesneyi gösterir (referans). baglanti henüz null.

Yığın (stack)Yığın bellek (heap)main()islemveriEkleveri"Ayşe"VeriIslemleribaglantinull

Ana'nın yaptığı tek şey bu çağrıdır. Yeni bir çerçeve açılır, veri parametresi "Ayşe" olur.

Yığın (stack)Yığın bellek (heap)main()islemveriEkleveri"Ayşe"VeriIslemleribaglantiVeritabaniBaglantisi(Ana bu nesneyi hiç görmez)

Gizli iş başlar: ikinci nesne oluşur, private baglanti onu gösterir.

Yığın (stack)Yığın bellek (heap)main()islemveriEkleveri"Ayşe"VeriIslemleribaglantiVeritabaniBaglantisi(Ana bu nesneyi hiç görmez)

baglan() çağrılır: yığında üç çerçeve var. Ana bundan habersiz.

Yığın (stack)Yığın bellek (heap)main()islemveriEkleveri"Ayşe"VeriIslemleribaglantiVeritabaniBaglantisi(Ana bu nesneyi hiç görmez)

Veri, bağlantı nesnesinin veriEkle metoduna gider: aynı ad, başka sınıf.

Yığın (stack)Yığın bellek (heap)main()islemveriEkleveri"Ayşe"VeriIslemleribaglantiVeritabaniBaglantisi(Ana bu nesneyi hiç görmez)

Bağlantı kapatılır. Üç adımın hepsi tek çağrının arkasında saklı.

Yığın (stack)Yığın bellek (heap)main()islemVeriIslemleribaglantiVeritabaniBaglantisi(Ana bu nesneyi hiç görmez)

main'e dönülür. Kullanıcı tek satır yazdı, ayrıntıyı bilmedi: bilgi saklama.

public class VeriIslemleri {
  private VeritabaniBaglantisi baglanti;
  // metodun iç yapısı bilinmeden de kullanılabilir.
  public void veriEkle(String veri) {
    baglanti = new VeritabaniBaglantisi();
    baglanti.baglan();
    baglanti.veriEkle(veri);
    baglanti.kapat();
  }
}
public class Ana {
  public static void main(String[] args) {
    VeriIslemleri islem = new VeriIslemleri();
    islem.veriEkle("Ayşe");
  }
}

Ön ve Son Koşullar

Ön ve Son Koşullar

Ön koşul (precondition)

Metot çağrılmadan önce doğru olduğu varsayılan koşul. Sağlamak çağıranın sorumluluğudur.

public class VeriIslemleri {
    // Ön koşul: veri != null
    public void veriEkle(String veri) {
        // verinin null olup olmadığını kontrol et
        // ...
    }
}

Son koşul (postcondition)

Ön koşul sağlandıysa, metot bittikten sonra doğru olacağı garanti edilen durum. Sağlamak metodun sorumluluğudur.

public class VeriIslemleri {
    // Son koşul: veri tabanına veri eklendiyse true döner
    public boolean veriEkle(String veri) {
        // veriyi ekleyip eklemediğini kontrol et
        // ...
        return true;
    }
}
İkisi birlikte metodun sözleşmesidir (contract): “Sen bana boş olmayan bir veri ver, ben de onu ekleyip true döndüreyim.” Kullanıcı yalnızca sözleşmeyi bilir, gövdeyi bilmez.

Ön ve Son Koşullar

Sözleşme: Tam Örnek

public class VeriIslemleri {
  private int kayitSayisi = 0;

  /**
   * Veri tabanına bir kayıt ekler.
   * Ön koşul: veri null değil ve boş değil.
   * Son koşul: eklendiyse true döner,
   *            kayitSayisi 1 artar.
   */
  public boolean veriEkle(String veri) {
    if (veri == null || veri.isEmpty()) {
      return false;   // ön koşul bozuk
    }
    // ... veriyi veri tabanına ekle ...
    kayitSayisi++;
    return true;      // son koşul sağlandı
  }

  public int getKayitSayisi() {
    return kayitSayisi;
  }
}
// main içinde:
VeriIslemleri islem = new VeriIslemleri();
System.out.println(islem.veriEkle("Ali"));
System.out.println(islem.veriEkle(""));
System.out.println(islem.veriEkle(null));
System.out.println(islem.getKayitSayisi());
Çıktı
true
false
false
1
  • /** ... */ bir Javadoc yorumudur: metodun nasıl kullanılacağını anlatır.
  • Ön koşul bozulursa metot hiçbir şey eklemez, false döndürür.

Erişim Belirleyiciler

Erişim Belirleyiciler

  • Erişim belirleyici (access modifier): bir sınıfa, alana veya metoda kimin erişebileceğini belirleyen anahtar kelime.
  • public: diğer sınıflar tarafından doğrudan erişilebilir.
  • private: yalnızca sınıfın içinden erişilebilir; dışarıdan erişim derleyici tarafından engellenir.
  • Sınıflar genellikle public tanımlanır.
  • Sınıfın nitelikleri (alanları) genellikle private tanımlanır.
  • Niteliklere erişmek ve onları değiştirmek için public metotlar kullanılır.
public class Ogrenci {      // herkese açık sınıf
    private String ad;      // gizli alan
    private int not;        // gizli alan

    public int getNot() {   // açık metot
        return not;
    }
}

Kural: alanlar gizli, davranışlar açık.

Erişim Belirleyiciler

Dört Erişim Düzeyi

BelirleyiciAynı sınıfAynı paketAlt sınıf (başka paket)Her yer
public✓✓✓✓
protected✓✓✓✗
(yazılmazsa) paket erişimi✓✓✗✗
private✓✗✗✗
  • Belirleyici yazılmazsa paket erişimi (package-private, “default”) geçerlidir: yalnızca aynı paketteki sınıflar erişir.
  • protected alt sınıflar içindir; ayrıntısı Kalıtım bölümündedir.
  • En kısıtlayıcı olanı seçin: önce private, gerçekten gerekirse daha açık bir düzey.
  • Üst düzey bir sınıf yalnızca public ya da paket erişimli olabilir.

Erişim Belirleyiciler

private Alana Erişim Denemesi

public class Test {
    public static void main(String[] args) {
        DijitalSaat s = new DijitalSaat();
        s.saniye = 75;
    }
}
Test.java:4: error: saniye has private access in DijitalSaat
        s.saniye = 75;
         ^
1 error
  • saniye, DijitalSaat içinde private tanımlıdır (sonraki slayt).
  • Başka bir sınıftan s.saniye yazmak derleme hatasıdır: program hiç çalışmaz.
  • Alan public olsaydı s.saniye = 75; sessizce kabul edilir, saat geçersiz bir duruma düşerdi.
  • Çözüm: değişikliği sınıfın kendi metoduna yaptırmak: s.setSaniye(75); Metot geçersiz değeri reddeder.

Getter ve Setter

DijitalSaat Sınıfı

public class DijitalSaat {
    private int saat = 0;
    private int dakika = 0;
    private int saniye = 0;

    public int getSaniye() {
        return this.saniye;
    }

    public void setSaniye(int saniye) {
        if (saniye >= 0 && saniye < 60) {
            this.saniye = saniye;
        }
    }
}
DijitalSaat- saat: int = 0- dakika: int = 0- saniye: int = 0+ getSaniye(): int+ setSaniye(int): void
  • Getter (okuyucu): alanın değerini döndürür.
  • Setter (değiştirici): yeni değeri önce kontrol eder, geçerliyse atar.
  • UML'de - private, + public demektir.

Getter ve Setter · Kodu adım adım çalıştır

Setter Koruması: Adım Adım

Yığın (stack)Yığın bellek (heap)main()sDijitalSaatsaniye0

Nesne oluşturulur, saniye alanı 0 ile başlar. s nesneyi gösterir.

Yığın (stack)Yığın bellek (heap)setSaniye(45)thissaniye45main()sDijitalSaatsaniye045 ≥ 0 && 45 < 60 → doğru

setSaniye(45): parametre saniye = 45. this, metodu çağıran nesnedir. Koşul doğru.

Yığın (stack)Yığın bellek (heap)setSaniye(45)thissaniye45main()sDijitalSaatsaniye45

this.saniye = saniye; soldaki nesnenin alanı, sağdaki parametredir. Alan 45 olur.

Yığın (stack)Yığın bellek (heap)setSaniye(75)thissaniye75main()sDijitalSaatsaniye4575 < 60 → yanlış

setSaniye(75): koşul yanlış, atama satırı atlanır. Geçersiz değer nesneye giremez.

Yığın (stack)Yığın bellek (heap)getSaniye()thismain()sDijitalSaatsaniye45

getSaniye() alanın değerini döndürür: 45. Alan hâlâ geçerli.

Yığın (stack)Yığın bellek (heap)main()sDijitalSaatsaniye45

Ekrana 45 yazılır. Nesnenin durumu baştan sona geçerli kaldı: sarmalamanın amacı budur.

public class DijitalSaat {
  private int saniye = 0;
  public int getSaniye() {
    return this.saniye;
  }
  public void setSaniye(int saniye) {
    if (saniye >= 0 && saniye < 60) {
      this.saniye = saniye;
    }
  }
}
// SaatTest.main içinde:
DijitalSaat s = new DijitalSaat();
s.setSaniye(45);
s.setSaniye(75);
System.out.println(s.getSaniye());

Getter ve Setter

this ve Gölgeleme

Doğru

public void setSaniye(int saniye) {
    if (saniye >= 0 && saniye < 60) {
        this.saniye = saniye;
    }
}

Hatalı (ama derlenir!)

public class Kutu {
    private int en;
    public int getEn() {
        return en;
    }
    public void setEn(int en) {
        en = en;
    }
}
  • Parametrenin adı alanla aynıysa, metot içinde saniye adı parametreyi gösterir: parametre alanı gölgeler (shadowing).
  • this, metodu çağıran nesnenin kendisidir. this.saniye her zaman alandır.
  • Hatalı sürümde en = en; parametreyi kendisine atar; alan değişmez. k.setEn(5) sonrası k.getEn():
Çıktı
0

Derleyici hata vermez; hata ancak çalıştırınca fark edilir. Bu yüzden setter'larda this. alışkanlığı önemlidir.

Getter ve Setter

Erişim Metotları

public class DijitalSaat {
  private int saat = 0;      // 0..23
  private int dakika = 0;    // 0..59
  private int saniye = 0;    // 0..59

  public int getSaat() { return saat; }
  public int getDakika() { return dakika; }
  public int getSaniye() { return saniye; }

  public void setSaat(int saat) {
    if (saat >= 0 && saat < 24) this.saat = saat;
  }
  public void setDakika(int dakika) {
    if (dakika >= 0 && dakika < 60) this.dakika = dakika;
  }
  public void setSaniye(int saniye) {
    if (saniye >= 0 && saniye < 60) this.saniye = saniye;
  }

  public void goster() {
    System.out.printf("%02d:%02d:%02d%n",
        saat, dakika, saniye);
  }
}
DijitalSaat s = new DijitalSaat();
s.setSaat(9);
s.setDakika(5);
s.setSaniye(30);
s.goster();
s.setSaat(24);     // reddedilir
s.setDakika(-3);   // reddedilir
s.goster();
Çıktı
09:05:30
09:05:30
  • Adlandırma: dakika → getDakika(), setDakika(int).
  • boolean alanda okuyucu is ile başlar: isAktif().

Getter ve Setter

Neden public Alan Değil?

public alan

public class Saat {
    public int saniye;
}
// başka bir sınıfta:
s.saniye = -5;   // kabul edilir!
  • Her yerden, her değer yazılabilir.
  • Kural (0–59) her kullanıcıya bırakılır; biri unutursa nesne bozulur.
  • Alanın adı/türü değişirse onu kullanan tüm sınıflar değişir.

private alan + metot

public class Saat {
    private int saniye;
    public void setSaniye(int s) {
        if (s >= 0 && s < 60) saniye = s;
    }
}
  • Kural tek bir yerde, sınıfın içinde kontrol edilir.
  • Salt okunur alan mümkün: yalnızca getter yazılır.
  • İç yapı değişse de metotların imzası aynı kalabilir.

Java 16+: yalnızca veri taşıyan değişmez sınıflar için record Nokta(int x, int y) { } yazılabilir; alanlar otomatik private final olur, okuyucular x() ve y() adını alır.

Getter ve Setter

Deneyin: Setter Koruması

 
saat9
dakika5
saniye30
09:05:30
Bir alan ve değer seçip iki yolu karşılaştırın.

“setter ile ata” s.setSaniye(75) gibi çağırır: geçersiz değer reddedilir. “alan public olsaydı” s.saniye = 75 gibi doğrudan yazar: nesne geçersiz duruma düşer (kırmızı). Geçerli aralıklar: saat 0–23, dakika ve saniye 0–59.

Örnek: Banka Hesabı

BankaHesabi: Alanlar

public class BankaHesabi {
  private int hesapNumarasi;  // Hesap numarası
  private double bakiye;      // Hesap bakiyesi
  private String hesapTuru;   // "Vadesiz" / "Vadeli"

  public BankaHesabi(int hesapNumarasi,
      double bakiye, String hesapTuru) {
    this.hesapNumarasi = hesapNumarasi;
    this.bakiye = bakiye;
    this.hesapTuru = hesapTuru;
  }

  public int getHesapNumarasi() {
    return hesapNumarasi;
  }
  public double getBakiye() {
    return bakiye;
  }
  public String getHesapTuru() {
    return hesapTuru;
  }
  // ... işlemler sonraki slaytlarda
  • Üç alan da private: hesap numarasını veya bakiyeyi kimse doğrudan değiştiremez.
  • Yapıcı (constructor) nesneyi ilk değerleriyle kurar; this.bakiye = bakiye kalıbı.
  • Yalnızca getter var, setter yok: setBakiye olmadığı için bakiye yalnızca işlemlerle değişir.

Örnek: Banka Hesabı

BankaHesabi: Yatırma ve Çekme

  // ...
  // Para yatırma işlemi
  public void paraYatir(double miktar) {
    if (miktar > 0) {
      bakiye += miktar;
      System.out.println(miktar
          + " TL yatırıldı. Yeni bakiye: " + bakiye);
    } else {
      System.out.println("Yatırılan miktar geçersiz.");
    }
  }

  // Para çekme işlemi
  public void paraCek(double miktar) {
    if (miktar <= bakiye && miktar > 0) {
      bakiye -= miktar;
      System.out.println(miktar
          + " TL çekildi. Kalan bakiye: " + bakiye);
    } else if (miktar > bakiye) {
      System.out.println("Yetersiz bakiye.");
    } else {
      System.out.println("Çekilen miktar geçersiz.");
    }
  }
  • Her işlem önce ön koşulu kontrol eder: miktar > 0, çekmede ayrıca miktar ≤ bakiye.
  • Koşul bozuksa bakiye değişmez, yalnızca bir mesaj yazılır.
  • Bakiye hiçbir yoldan eksiye düşemez: bu, sınıfın değişmezidir.

Örnek: Banka Hesabı

BankaHesabi: Faiz

  // ...
  // Faiz ekleme (yalnızca vadeli hesap)
  public void faizEkle(double oran) {
    if (hesapTuru.equals("Vadeli") && oran > 0) {
      double faiz = bakiye * oran / 100;
      bakiye += faiz;
      System.out.println("Faiz ekleniyor: " + faiz
          + " TL. Yeni bakiye: " + bakiye);
    } else if (!hesapTuru.equals("Vadeli")) {
      System.out.println(
          "Faiz yalnızca vadeli hesaplarda uygulanabilir.");
    } else {
      System.out.println("Geçersiz faiz oranı.");
    }
  }
  • Faiz yalnızca vadeli hesapta ve pozitif oranla eklenir.
  • Metinler == ile değil, equals ile karşılaştırılır.
  • faiz bir yerel değişkendir: yalnızca metot çalışırken vardır, nesnenin parçası değildir.

Örnek: Banka Hesabı

BankaHesabi: Transfer

  // ...
  // Başka bir hesaba transfer
  public void transfer(BankaHesabi hedefHesap,
                       double miktar) {
    if (miktar <= bakiye && miktar > 0) {
      bakiye -= miktar;
      hedefHesap.paraYatir(miktar);
      System.out.println(miktar
          + " TL transfer edildi. Yeni bakiye: " + bakiye);
    } else {
      System.out.println(
          "Yetersiz bakiye veya geçersiz miktar.");
    }
  }
}
  • transfer, aynı sınıftan başka bir nesnenin metodunu çağırır: hedefHesap üzerinde paraYatir.
  • Hedef hesabın bakiye alanına doğrudan yazmak da mümkündü (aynı sınıf, private sınıf düzeyindedir), ama metot çağırmak kuralların tek yerde kalmasını sağlar.
  • Sonraki slayt bu çağrıları adım adım izler.

Örnek: Banka Hesabı · Kodu adım adım çalıştır

İki Hesap, Bir Transfer: Adım Adım

Yığın (stack)Yığın bellek (heap)main()hesap1hesap2BankaHesabihesapNumarasi123456bakiye2000.0hesapTuru"Vadeli"?

Yapıcı çalışır: ilk nesne heap'te 2000.0 bakiye ile kurulur.

Yığın (stack)Yığın bellek (heap)main()hesap1hesap2BankaHesabihesapNumarasi123456bakiye2000.0hesapTuru"Vadeli"BankaHesabihesapNumarasi789012bakiye1500.0hesapTuru"Vadesiz"

İkinci, bağımsız nesne. Her nesnenin kendi bakiye alanı vardır.

Yığın (stack)Yığın bellek (heap)main()hesap1hesap2BankaHesabithishesapNumarasi123456bakiye2500.0hesapTuru"Vadeli"BankaHesabihesapNumarasi789012bakiye1500.0hesapTuru"Vadesiz"

this = hesap1. miktar > 0 doğru: bakiye += miktar → 2500.0.

Yığın (stack)Yığın bellek (heap)main()hesap1hesap2BankaHesabihesapNumarasi123456bakiye2500.0hesapTuru"Vadeli"BankaHesabithishesapNumarasi789012bakiye1300.0hesapTuru"Vadesiz"

Bu kez this = hesap2. 200 ≤ 1500: bakiye 1300.0 olur; hesap1 etkilenmez.

Yığın (stack)Yığın bellek (heap)main()hesap1hesap2BankaHesabithishesapNumarasi123456bakiye2625.0hesapTuru"Vadeli"BankaHesabihesapNumarasi789012bakiye1300.0hesapTuru"Vadesiz"

Hesap vadeli: faiz = 2500.0 × 5 / 100 = 125.0, bakiye 2625.0.

Yığın (stack)Yığın bellek (heap)main()hesap1hesap2BankaHesabithishesapNumarasi123456bakiye2325.0hesapTuru"Vadeli"BankaHesabihedefHesaphesapNumarasi789012bakiye1300.0hesapTuru"Vadesiz"

transfer: koşul doğru, önce kendi bakiyesinden düşer: 2625.0 − 300.0 = 2325.0.

Yığın (stack)Yığın bellek (heap)main()hesap1hesap2BankaHesabihesapNumarasi123456bakiye2325.0hesapTuru"Vadeli"BankaHesabithishesapNumarasi789012bakiye1600.0hesapTuru"Vadesiz"

hedefHesap.paraYatir(miktar): yığında 3 çerçeve; şimdi this = hesap2, bakiye 1600.0.

Yığın (stack)Yığın bellek (heap)main()hesap1hesap2BankaHesabithishesapNumarasi123456bakiye2325.0hesapTuru"Vadeli"BankaHesabihedefHesaphesapNumarasi789012bakiye1600.0hesapTuru"Vadesiz"

paraYatir biter, transfer kaldığı yerden devam eder ve kendi mesajını yazar.

Yığın (stack)Yığın bellek (heap)main()hesap1hesap2BankaHesabihesapNumarasi123456bakiye2325.0hesapTuru"Vadeli"BankaHesabihesapNumarasi789012bakiye1600.0hesapTuru"Vadesiz"

Getter'lar son bakiyeleri okur. Toplam para korunur: 2325 + 1600 = 3925 = 2625 + 1300.

// main içinde:
BankaHesabi hesap1 =
    new BankaHesabi(123456, 2000.0, "Vadeli");
BankaHesabi hesap2 =
    new BankaHesabi(789012, 1500.0, "Vadesiz");
hesap1.paraYatir(500.0);
hesap2.paraCek(200.0);
hesap1.faizEkle(5.0);
hesap1.transfer(hesap2, 300.0);
System.out.println(hesap1.getBakiye());
System.out.println(hesap2.getBakiye());

Sınıf Tasarımı

Sınıf Tasarımı

  • Sınıf tanımı iki kısma ayrılır: arayüz (interface) ve gerçekleştirim (implementation).
  • Arayüz, sınıfın ne yaptığını ve nasıl kullanılacağını belirtir: public metotların adları, parametreleri, dönüş türleri ve yorumları.
  • Gerçekleştirim, sınıfın iç mekanik ayrıntılarını ve özel (private) niteliklerini içerir. Bunlar yalnızca sınıfın içinde önemlidir.
  • Gerçekleştirim değişse bile arayüz değişmeyebilir; öz nitelikler sınıfın dışına gizlidir.
KullanıcıKontrol paneli(arayüz)BaşlatSu miktarıDurdurİç mekanizma (gerçekleştirim)PompaKazan, ısıtıcıAkış valfiSolenoid valfDemleme grubu

Sınıf Tasarımı

Arayüz

public interface Araba {
    // Sınıf arayüzü: NE yapılır?
    public void hareketEt();

    public void dur();
}
  • Java'da interface, bir sınıfın ne yapacağını söyleyen sözleşmedir: metotların yalnızca imzası vardır, gövdesi yoktur.
  • Arabayı kullanan kişi yalnızca hareketEt() ve dur() metotlarını bilir.
  • Arayüzler ayrıntılı olarak Soyut Sınıflar bölümünde ele alınır.
«interface»Araba+ hareketEt(): void+ dur(): voidimplementsSporAraba- marka: String- hiz: int+ SporAraba(marka: String)+ hareketEt(): void+ dur(): void

Sınıf Tasarımı

Sınıf Gerçekleştirimi

public class SporAraba implements Araba {
  // Sınıf gerçekleştirimi: NASIL yapılır?
  private String marka;
  private int hiz;

  public SporAraba(String marka) {
    this.marka = marka;
  }

  public void hareketEt() {
    hiz = 100;
    System.out.println(marka + " hareket ediyor: "
        + hiz + " km/sa");
  }

  public void dur() {
    hiz = 0;
    System.out.println(marka + " durdu.");
  }
}
// main içinde:
Araba araba = new SporAraba("Rüzgar");
araba.hareketEt();
araba.dur();
Çıktı
Rüzgar hareket ediyor: 100 km/sa
Rüzgar durdu.
  • implements Araba: sınıf, arayüzdeki her metodu yazmak zorundadır.
  • Arayüz ile sınıf aynı pakette aynı adı taşıyamaz (duplicate class: Araba); bu yüzden sınıfın adı SporAraba.
  • Ana değişkeni Araba türünde tanımlar: yalnızca arayüze bağımlıdır.

Sınıf Tasarımı

Gerçekleştirimi Değiştirmek

Eski: iki ayrı alan

public class DijitalSaat {
  private int dakika = 0;
  private int saniye = 0;
  public int getDakika() {
    return dakika;
  }
  public int getSaniye() {
    return saniye;
  }
  public void setDakika(int dakika) {
    if (dakika >= 0 && dakika < 60) {
      this.dakika = dakika;
    }
  }
  public void setSaniye(int saniye) {
    if (saniye >= 0 && saniye < 60) {
      this.saniye = saniye;
    }
  }
}

Yeni: tek alan

public class DijitalSaat {
  // gece yarısından beri geçen saniye
  private int toplamSaniye = 0;
  public int getDakika() {
    return toplamSaniye / 60 % 60;
  }
  public int getSaniye() {
    return toplamSaniye % 60;
  }
  public void setDakika(int dakika) {
    if (dakika >= 0 && dakika < 60) {
      int fark = dakika - getDakika();
      toplamSaniye += fark * 60;
    }
  }
  public void setSaniye(int saniye) {
    if (saniye >= 0 && saniye < 60) {
      toplamSaniye += saniye - getSaniye();
    }
  }
}
Kullanan kod değişmez: setDakika(12); setSaniye(45); setSaniye(75); sonrası iki sürüm de 12:45 verir.

Sınıf Tasarımı

Tavsiyeler

  1. Sınıf içindeki tüm değişkenler (alanlar) private tanımlanmalı.
  2. Değişkenlere erişim için gerektiği kadar public erişim metodu sağlanmalı (her alana otomatik setter yazmak şart değildir).
  3. Her metodun nasıl kullanılacağını belirten yorum satırları eklenmeli: ne yapar, ön ve son koşulları neler.
  4. Yalnızca sınıf içinde kullanılan yardımcı metotlar private tanımlanmalı.
public class Ogrenci {
    private int not;
    private boolean gecerliMi(int deger) {
        return deger >= 0 && deger <= 100;
    }
    public void setNot(int not) {
        if (gecerliMi(not)) this.not = not;
    }
}

Başka sınıftan o.gecerliMi(50) çağrılırsa:

error: gecerliMi(int) has private access in Ogrenci

Sınıf Tasarımı

Sarmalamanın Faydaları

Soyutlama

Nesne ile kullanıcısı arasındaki ilişkiyi sadeleştirir: kullanıcı yalnızca metotları bilir.

Koruma

Nesneyi kullanıcının istenmeyen, hatalı erişimlerine karşı korur.

Değiştirilebilirlik

Arayüzü değiştirmeden sınıfın iç yapısı değiştirilebilir; kullanan kod etkilenmez.

Geçerli durum (değişmez)

Nesnenin durumu her zaman kurallara uyar (sınıf değişmezi, class invariant):

  • yalnızca pozitif koordinatlı noktalar,
  • saniye ve dakika 0–59 arasında,
  • saat negatif olamaz.
Bu bölümün örneklerinde: setSaniye(75) reddedilir, paraCek bakiyeyi asla eksiye düşürmez, BankaHesabi'nın iç yapısı değişse bile paraYatir çağıran kod aynı kalır.

Sınıf Tasarımı

Gözlemler

BankaRaporTestArayüzpublic metotlarparaYatir()paraCek()getBakiye()kararlıGerçekleştirimprivate alanlarbakiyeyardımcı metotlardeğişebilir
  • Arayüzler, gerçekleştirimden çok daha nadir değişir: bu, kodun kararlı olmasını sağlar.
  • Gerçekleştirim değiştiğinde, ona doğrudan bağımlı olan kodların da değişmesi gerekir.
  • Gerçekleştirimi gizlemek, diğer sınıfların bağımlılığını azaltır: değişiklik sınıfın içinde kalır (gevşek bağlılık, loose coupling).

Paketler

Paketler

  • Paket (package): bir klasör içinde gruplanmış sınıflar topluluğu. Klasörün adı paketin adıdır.
  • Her public sınıf ayrı bir dosyada, sınıfla aynı adlı dosyada tanımlanır.
  • Dosyanın ilk satırı ait olduğu paketi bildirir: package paket_adi;
  • Derleyici ve JVM paketleri sınıf yolu (classpath) köklerinden başlayarak arar; paket adı bu kökten itibaren klasör yolunu söyler. Varsayılan sınıf yolu bulunulan dizindir (.).
  • İç içe klasörlerde / veya \ yerine nokta kullanılır: okul/yonetim → okul.yonetim.
  • Paket adları küçük harfle yazılır; Java'nın kendi paketleri: java.lang, java.util, java.io…
proje/
├── src/
│   ├── banka/
│   │   ├── Hesap.java      // package banka;
│   │   └── Yardimci.java   // package banka;
│   └── uygulama/
│       └── Ana.java        // package uygulama;
└── out/                    // derleme çıktısı
    ├── banka/
    │   ├── Hesap.class
    │   └── Yardimci.class
    └── uygulama/
        └── Ana.class

Sınıfın tam adı paketini de içerir: banka.Hesap, uygulama.Ana.

Paketler

Paket Örneği

package banka;

public class Hesap {
    private double bakiye;

    public void yatir(double miktar) {
        if (miktar > 0) {
            bakiye += miktar;
        }
    }

    public double getBakiye() {
        return bakiye;
    }
}
package uygulama;

import banka.Hesap;

public class Ana {
    public static void main(String[] args) {
        Hesap h = new Hesap();
        h.yatir(250.0);
        System.out.println("Bakiye: " + h.getBakiye());
    }
}
Komut satırı (proje/ içinde)
> javac -d out src/banka/Hesap.java src/uygulama/Ana.java
> java -cp out uygulama.Ana
Bakiye: 250.0
  • -d out: .class dosyaları paket klasörleriyle out/ altına yazılır.
  • -cp out: sınıf yolu; çalıştırırken sınıfın tam adı verilir.

Paketler

İçe Aktarma (import)

  • Başka paketteki bir sınıfı kısa adıyla kullanmak için: import paket_adi.SinifAdi; Paketin tümü: import java.util.*; (alt paketler hariç).
  • import yazmadan tam ad da kullanılabilir: java.util.Random.
  • java.lang (String, Math, System) ve aynı paketteki sınıflar otomatik görünür.
  • Sıra: önce package, sonra import'lar, sonra sınıf. import static statik üyeyi sınıf adı olmadan kullandırır.
import java.util.Scanner;
import static java.lang.Math.sqrt;

public class ImportOrnek {
  public static void main(String[] args) {
    Scanner giris = new Scanner(System.in);
    int sayi = giris.nextInt();
    java.util.Random rastgele =
        new java.util.Random(42);
    System.out.println(sqrt(sayi) + " "
        + Math.max(sayi, 10) + " "
        + rastgele.nextInt(100));
  }
}
Çıktı (girdi: 49)
7.0 49 30
  • Scanner: import ile kısa ad.
  • java.util.Random: tam ad, import yok.
  • sqrt: import static; Math.max: java.lang.

Paketler

Paket Erişimi

package banka;

class Yardimci {     // public değil
    static double kdv(double tutar) {
        return tutar * 0.20;
    }
}
package uygulama;

import banka.Yardimci;

public class Ana2 {
}
Ana2.java:3: error: Yardimci is not public in banka;
cannot be accessed from outside package
import banka.Yardimci;
            ^
  • Belirleyicisi olmayan sınıf ve üyeler paket erişimlidir: yalnızca kendi paketinden görülür.
  • Yardimci, banka paketinin iç işidir; Hesap onu kullanabilir, uygulama paketi kullanamaz.
  • Paket de bir sarmalama düzeyidir: sınıflar dışarıya kapatılır, yalnızca public olanlar paketin arayüzünü oluşturur.

Ad Çakışmaları

Metot Adı Çakışmaları

public class Yazici {
  static void yazdir(int x)        { System.out.println("int: " + x); }
  static void yazdir(double x)     { System.out.println("double: " + x); }
  static void yazdir(String s)     { System.out.println("String: " + s); }
  static void yazdir(int a, int b) { System.out.println("iki int: " + a + ", " + b); }

  public static void main(String[] args) {
    yazdir(7);       // int: 7
    yazdir(7.5);     // double: 7.5
    yazdir("yedi");  // String: yedi
    yazdir(3, 4);    // iki int: 3, 4
    yazdir('A');     // int: 65       (char → int)
    yazdir(5L);      // double: 5.0   (long → double)
    yazdir(2.5f);    // double: 2.5   (float → double)
  }
}
  • Aynı adlı birden fazla metot olabilir (aşırı yükleme, overloading); parametre sayısı ve türleri ayırt eder. (Yorumlar gerçek çıktıdır.)
  • Tam eşleşme yoksa genişletme dönüşümü denenir: char → int, long → double.
  • Hiçbiri uymazsa derleme hatası: yazdir(true) → no suitable method found for yazdir(boolean)
  • Dönüş türü tek başına ayırt etmez; parametre listesi farklı olmalıdır.

Ad Çakışmaları

Sınıf Adı Çakışmaları

import java.util.*;
import java.sql.*;

public class Belirsiz {
    public static void main(String[] args) {
        Date tarih = null;
    }
}
Belirsiz.java:6: error: reference to Date is ambiguous
  both class java.sql.Date in java.sql and
  class java.util.Date in java.util match
import java.util.Date;

public class TarihOrnegi {
  public static void main(String[] args) {
    Date baslangic = new Date(0);  // java.util
    java.sql.Date kayit =          // tam ad
        java.sql.Date.valueOf("2024-09-16");
    System.out.println(baslangic.getTime());
    System.out.println(kayit);
  }
}
Çıktı
0
2024-09-16
  • İki farklı paketteki sınıflar aynı adı taşıyabilir: java.util.Date ve java.sql.Date.
  • Belirsizliği gidermek için paket adı kullanılır: birini import edin, diğerini tam adıyla yazın.

Özet

Özet

KavramAnlamıJava'da
SarmalamaVeri + metotları tek birimde toplamak, iç ayrıntıyı gizlemekprivate alanlar, public metotlar
Bilgi saklama“Ne” ile “nasıl”ı ayırmakKullanıcı yalnızca metot imzasını bilir
Ön / son koşulÇağrıdan önce varsayılan / sonra garanti edilen durumJavadoc yorumu + if kontrolü
Erişim belirleyiciKimin erişebileceğipublic, protected, (paket), private
Getter / setterAlanı okuyan / doğrulayarak değiştiren metotgetSaniye(), setSaniye(int), this.saniye = saniye
Arayüz / gerçekleştirimSınıfın dış yüzü / iç mekanizmasıinterface Araba, class SporAraba implements Araba
Paket, importKlasördeki sınıf grubu; başka paketten kısa adla kullanımpackage banka;, import java.util.Scanner;
Ad çakışmasıAynı adlı metot / sınıfParametre listesi / tam ad java.sql.Date

Özet

Sık Yapılan Hatalar (1)

private alana dışarıdan erişmekerror: saniye has private access in DijitalSaat

s.saniye = 75; yerine s.setSaniye(75); kullanın.

Setter'da this unutmaken = en; // derlenir, alan değişmez

Parametre alanı gölgeler; getEn() 0 döndürür. Doğrusu this.en = en;

Getter'ı void yazmakerror: incompatible types: unexpected return value

public void getEn() değer döndüremez; dönüş türü int olmalı.

Setter'ı doğrulamasız yazmakpublic void setSaniye(int s) { saniye = s; }

Bu, alanı public yapmaktan farksızdır; geçerlilik kuralını setter'a koyun.

Özet

Sık Yapılan Hatalar (2)

import unutmakerror: cannot find symbol symbol: class Scanner

Dosyanın başına import java.util.Scanner; ekleyin.

public sınıf ≠ dosya adıerror: class Hesap is public, should be declared in a file named Hesap.java

Dosya adı, public sınıfın adıyla birebir aynı olmalı.

Paket erişimli sınıfı dışarıdan kullanmakerror: Yardimci is not public in banka; cannot be accessed from outside package

Ya sınıfı public yapın ya da aynı pakette kullanın.

Arayüz metodunu yazmamakerror: SporAraba is not abstract and does not override abstract method dur() in Araba

implements eden sınıf arayüzdeki tüm metotları yazmalıdır.

Özet

Örnek Problemler (1)

DijitalSaat t = new DijitalSaat();
t.setSaat(25);
t.setDakika(30);
t.setSaniye(-1);
t.setSaniye(59);
t.goster();
DijitalSaat a = new DijitalSaat();
DijitalSaat b = a;
a.setDakika(15);
b.setDakika(45);
b = new DijitalSaat();
b.setDakika(10);
System.out.println(a.getDakika() + " " + b.getDakika());

“Erişim Metotları” slaytındaki DijitalSaat sınıfı kullanılıyor (saat 0–23, dakika/saniye 0–59).

  1. Soldaki kod ne yazdırır?
  2. Sağdaki kod ne yazdırır? Kaç tane DijitalSaat nesnesi oluşturuldu?
Cevapları göster
  1. 00:30:59. 25 ve −1 setter'larca reddedilir; saat başlangıç değeri 0'da kalır.
  2. 45 10. b = a yeni nesne oluşturmaz, iki değişken aynı nesneyi gösterir; 45 onun dakikasıdır. Sonra b ikinci nesneye bağlanır. Toplam 2 nesne.

Özet

Örnek Problemler (2)

public class Test {
    public static void main(String[] args) {
        DijitalSaat s = new DijitalSaat();
        s.setSaniye(30);                    // A
        int x = s.saniye;                   // B
        System.out.println(s.getSaniye());  // C
        s.getSaniye() = 10;                 // D
        s.setSaniye("40");                  // E
    }
}

A–E satırlarından hangileri derleme hatası verir? Neden?

Cevapları göster
  • B: saniye has private access in DijitalSaat. Okumak için s.getSaniye().
  • D: unexpected type, required: variable, found: value. Metot çağrısı bir değerdir, atama yapılamaz; s.setSaniye(10) yazılmalı.
  • E: incompatible types: String cannot be converted to int.
  • A ve C doğrudur: public metotlar çağrılıyor.

Özet

Örnek Problemler (3)

BankaHesabi h = new BankaHesabi(111, 1000.0, "Vadesiz");
h.paraCek(5000.0);
h.paraYatir(-50.0);
h.faizEkle(3.0);
h.paraCek(250.0);
BankaHesabi v = new BankaHesabi(222, 100.0, "Vadeli");
h.transfer(v, 1000.0);
h.transfer(v, 750.0);
System.out.println(h.getBakiye() + " " + v.getBakiye());

BankaHesabi sınıfıyla bu kod ne yazdırır? Son satırda hangi bakiyeler görülür?

Cevabı göster
Yetersiz bakiye.
Yatırılan miktar geçersiz.
Faiz yalnızca vadeli hesaplarda uygulanabilir.
250.0 TL çekildi. Kalan bakiye: 750.0
Yetersiz bakiye veya geçersiz miktar.
750.0 TL yatırıldı. Yeni bakiye: 850.0
750.0 TL transfer edildi. Yeni bakiye: 0.0
0.0 850.0

Özet

Alıştırmalar (1)

  1. Ogrenci: private ad ve not; not yalnızca 0–100 arası kabul edilsin; gectiMi() not ≥ 50 ise true döndürsün.
  2. Nokta: yalnızca negatif olmayan koordinatları kabul etsin; uzaklik() orijine uzaklığı döndürsün.
  3. Termometre: santigrat değeri saklasın (−273.15 altını reddetsin); getFahrenheit() ayrı bir alan olmadan hesaplasın.
Cevaplar / ipuçları
  1. if (not >= 0 && not <= 100) this.not = not; → setNot(120) sonrası “0 false”, setNot(75) sonrası “75 true”.
  2. Yapıcı setter'ları çağırsın; return Math.sqrt(x * x + y * y); → new Nokta(3, 4): 5.0; setX(-7) reddedilir, x = 3 kalır.
  3. return santigrat * 9 / 5 + 32; → 25 °C = 77.0 °F. Türetilen değer ayrı alanda tutulmaz; setSantigrat(-300) sonrası değer 25.0 kalır.

Özet

Alıştırmalar (2)

  1. Sayac: yalnızca arttir(), sifirla(), getDeger() metotları olsun. Neden setter yok?
  2. Ogrenci'yi okul paketine taşıyın, uygulama.Ana'dan kullanın; derleme ve çalıştırma komutlarını yazın.
  3. BankaHesabi'na yalnızca getter'ı olan islemSayisi ekleyin; başarılı her yatırma/çekmede 1 artsın. Banka örneğinin (izleme slaytı) sonunda iki hesabın değeri ne olur?
Cevaplar / ipuçları
  1. Değer yalnızca 1'er artmalı veya sıfırlanmalı; setDeger bu kuralı delerdi. Üç arttir() → 3, sifirla() → 0.
  2. package okul; ve import okul.Ogrenci; → javac -d out src/okul/Ogrenci.java src/uygulama/Ana.java, java -cp out uygulama.Ana.
  3. hesap1: 1 (yalnızca paraYatir; faiz ve transfer kendi paraCek'ini çağırmaz), hesap2: 2 (paraCek + transferdeki paraYatir).

Özet

Kaynaklar

  • Oracle, The Java Tutorials: "Controlling Access to Members of a Class", "Using the this Keyword", "Packages", "Interfaces".
  • J. Gosling ve diğ., The Java Language Specification, Java SE 21 Edition (6.6 Access Control, 7 Packages and Modules, 7.5 Import Declarations, 15.12.2 metot seçimi).
  • P. Deitel ve H. Deitel, Java: How to Program, Early Objects, 11. baskı, Pearson, 2017 (Bölüm 3 ve 8: Classes and Objects).
  • K. Sierra ve B. Bates, Head First Java, 3. baskı, O'Reilly, 2022 (Bölüm 4: encapsulation).
  • C. S. Horstmann, Core Java, Volume I: Fundamentals, 12. baskı, 2022 (Bölüm 4: Objects and Classes).
  • J. Bloch, Effective Java, 3. baskı, Addison-Wesley, 2018 (Madde 15–16: erişimi en aza indirmek, public alan yerine erişim metodu).
  • A. B. Downey ve C. Mayfield, Think Java, 2. baskı, O'Reilly, 2019 (Bölüm 11: Designing Classes).

Son

Bölüm 4: Sarmalama
Sonraki bölüm: Bölüm 4: Kalıtım
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.