Bölüm 4.2: Sarmalama

Java ile Nesne Yönelimli Programlama
Bilgi saklamaÖn ve son koşullarErişim belirleyicilerGetter ve setter Arayüz ve gerçeklemeDeğiştirilemez sınıflarPaketler ve import
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çeklemeyi 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; 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çekleme: 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 · Adım adım kod izleme

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

Yığın (stack)Heapmain()islemVeriIslemleribaglantinull

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

Yığın (stack)Heapmain()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)Heapmain()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)Heapmain()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)Heapmain()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)Heapmain()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)Heapmain()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.

Ön ve Son Koşullar · Adım adım kod izleme

Sözleşme: Kod İzleme

YığınHeapmain()islemVeriIslemleri #1kayitSayisi0

Nesne yeni oluştu: kayitSayisi = 0. Sözleşme: veri null ve boş olmamalı.

YığınHeapveriEkle()thisveri"Ayşe"main()islemVeriIslemleri #1kayitSayisi0veri == nullfalse||veri.isEmpty()false

"Ayşe" null değil ve boş değil: koşul yanlış, if bloğu atlanır.

YığınHeapveriEkle()thisveri"Ayşe"main()islemVeriIslemleri #1kayitSayisi1veri == nullfalse||veri.isEmpty()falseön koşul sağlandı → kayıt eklenir, true

Son koşul yerine getirilir: sayaç 1 artar ve true döner.

YığınHeapveriEkle()thisverinullmain()islemVeriIslemleri #1kayitSayisi1veri == nulltrue||veri.isEmpty()değerlendirilmez

veri == null doğru; || kısa devre yapar. null.isEmpty() hiç çağrılmaz, NullPointerException olmaz.

YığınHeapveriEkle()thisverinullmain()islemVeriIslemleri #1kayitSayisi1veri == nulltrue||veri.isEmpty()değerlendirilmezön koşul bozuk → hiçbir şey eklenmez, false

Ön koşul bozuk: metot nesneye dokunmadan false döndürür.

YığınHeapveriEkle()thisveri""main()islemVeriIslemleri #1kayitSayisi1veri == nullfalse||veri.isEmpty()trueön koşul bozuk → hiçbir şey eklenmez, false

"" null değil ama boş: sağ taraf doğru, yine false. Sayaç değişmez.

YığınHeapveriEkle()thisveri"Can"main()islemVeriIslemleri #1kayitSayisi2veri == nullfalse||veri.isEmpty()falseön koşul sağlandı → kayıt eklenir, true

Geçerli veri: sayaç 2 olur, true.

YığınHeapmain()islemVeriIslemleri #1kayitSayisi2

Dört çağrıdan yalnızca sözleşmeye uyan ikisi kayıt ekledi: 2. Nesnenin durumu hiçbir çağrıda bozulmadı.

public boolean veriEkle(String veri) {
    if (veri == null || veri.isEmpty()) {
        return false;   // ön koşul bozuk
    }
    kayitSayisi++;
    return true;        // son koşul sağlandı
}
// main içinde:
VeriIslemleri islem = new VeriIslemleri();
System.out.println(islem.veriEkle("Ayşe"));
System.out.println(islem.veriEkle(null));
System.out.println(islem.veriEkle(""));
System.out.println(islem.veriEkle("Can"));
System.out.println(islem.getKayitSayisi());

Ön ve Son Koşullar

Ön Koşul Bozulunca Ne Yapmalı?

YaklaşımÖrnekÇağıran ne fark eder?
Sessizce yok saymaksetSaniye(75) hiçbir şey yapmazHiçbir şey; hata gözden kaçabilir
Durum döndürmekveriEkle(null) → falseDönüş değerine bakarsa fark eder
İstisna fırlatmakthrow new IllegalArgumentException(...)Yakalamazsa program durur; hata kaybolmaz
public void setSaniye(int saniye) {
    if (saniye < 0 || saniye > 59)
        throw new IllegalArgumentException(
            "saniye 0-59 olmalı: " + saniye);
    this.saniye = saniye;
}
// main içinde:
DijitalSaat s = new DijitalSaat();
try {
    s.setSaniye(30);
    s.setSaniye(75);
    System.out.println("buraya gelinmez");
} catch (IllegalArgumentException e) {
    System.out.println("Hata: " + e.getMessage());
}
System.out.println(s.getSaniye());
Hata: saniye 0-59 olmalı: 75
30
  • İstisna, hatalı değeri çağrının yapıldığı yerde görünür kılar; nesnenin durumu değişmez (30 kalır).
  • public metotlarda argüman hatası için Java'nın alışılmış seçimi IllegalArgumentException'dır.
  • Seçilen yaklaşım, sözleşmenin parçası olarak belgelenmelidir.

Ön ve Son Koşullar

Sözleşmeyi Javadoc ile Yazmak

/**
 * Hesaptan para çeker.
 * @param miktar çekilecek tutar; {@code 0 < miktar <= bakiye} olmalı
 * @return işlemden sonraki bakiye (eski bakiye - miktar)
 * @throws IllegalArgumentException miktar bu aralıkta değilse
 */
public double paraCek(double miktar) {
    if (miktar <= 0 || miktar > bakiye)
        throw new IllegalArgumentException("Geçersiz miktar: " + miktar);
    bakiye -= miktar;
    return bakiye;
}
Etiketler

@param: her parametre ve ön koşulu. @return: dönüş değeri, yani son koşul. @throws: sözleşme bozulunca ne olacağı.

Belge üretmek

javadoc -d belge BankaHesabi.java komutu bu yorumlardan HTML belgeler üretir. Java kütüphanesinin çevrim içi belgeleri de böyle oluşturulur.

Ne yazılır?

Metodun ne yaptığı ve kullanım koşulları. Nasıl yaptığı yazılmaz: o, gizlenen gerçeklemedir ve değişebilir.

Kimin için?

Gövdeyi hiç görmeyecek olan kullanıcı için. Sınıfın arayüzü, imzalar ve bu yorumların toplamıdır.

Ön ve Son Koşullar

assert ile Değişmezi Denetlemek

class Sayac {
    private int deger;   // değişmez: 0 <= deger <= 9
    void arttir() {
        deger = deger + 1;   // hata: 9'dan sonra 10
        assert deger >= 0 && deger <= 9 : "deger=" + deger;
    }
}
// main içinde:
Sayac s = new Sayac();
for (int i = 0; i < 10; i++) s.arttir();
System.out.println("bitti");

java Ana

bitti

java -ea Ana

Exception in thread "main" java.lang.AssertionError: deger=10
	at Sayac.arttir(Ana.java:5)
  • assert koşul : mesaj; koşul yanlışsa AssertionError fırlatır. Sınıf değişmezini ve son koşulları geliştirme sırasında denetlemek içindir.
  • Denetimler varsayılan olarak kapalıdır; -ea (enable assertions) seçeneğiyle açılır. Kapalıyken hata sessizce geçer: bitti.
  • public metodun argümanları assert ile denetlenmez: kapalıyken denetim hiç yapılmaz. Argüman için istisna, iç tutarlılık için assert.

Erişim Belirleyiciler

Erişim Belirleyiciler

  • Erişim belirleyici (access modifier): bir sınıfa, alana veya metoda kimin erişebileceğini belirleyen anahtar sözcük.
  • 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 alanları genellikle private tanımlanır.
  • Alanlara 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.

Ogrenci- ad : String (private)- not : int (private)+ getNot() : int (public)Başka sınıfo.getNot() ✓o.not ✗ derlenmez

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: başka paketteki alt sınıf da erişir.
  • Kural olarak en kısıtlayıcı düzey tercih edilir: ö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.
public: her yerprotected: + başka paketteki alt sınıflarpaket erişimi: aynı paketprivate: aynı sınıf

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.
  • 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.
TestDijitalSaat- saniye = 0+ setSaniye(int) 0..59 değilse reddeders.saniye = 75; derleme hatasıs.setSaniye(75); çalışır, değer reddedilir

Erişim Belirleyiciler

private Sınıf Düzeyindedir

class Kesir {
    private int pay, payda;
    Kesir(int pay, int payda) {
        this.pay = pay;
        this.payda = payda;
    }
    Kesir carp(Kesir diger) {
        // diger'in private alanları: izinli
        return new Kesir(pay * diger.pay,
                         payda * diger.payda);
    }
    @Override public String toString() {
        return pay + "/" + payda;
    }
}
// main içinde:
Kesir k = new Kesir(2, 3).carp(new Kesir(3, 4));
System.out.println(k);
6/12
  • Erişim denetimi nesneye göre değil, sınıfa göre yapılır: Kesir'in kodu, her Kesir nesnesinin private alanlarını okuyabilir.
  • carp, equals, compareTo gibi iki nesneyi birlikte işleyen metotlar bu sayede getter gerektirmez.
  • Başka bir sınıfta aynı satır derlenmez:
error: pay has private access in Kesir
Sarmalamanın sınırı sınıfın kaynak kodudur. Sınıfın içi tek bir yazarın sorumluluğundadır; korunan, dışarıdaki kullanıcılardır.

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 (erişimci): 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 · Adım adım kod izleme

Setter Koruması: Adım Adım

Yığın (stack)Heapmain()sDijitalSaatsaniye0

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

Yığın (stack)HeapsetSaniye(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)HeapsetSaniye(45)thissaniye45main()sDijitalSaatsaniye45

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

Yığın (stack)HeapsetSaniye(75)thissaniye75main()sDijitalSaatsaniye4575 < 60 → yanlış

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

Yığın (stack)HeapgetSaniye()thismain()sDijitalSaatsaniye45

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

Yığın (stack)Heapmain()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 erişimci 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ı/tipi 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ştirilemez sınıflar için record Nokta(int x, int y) { } yazılabilir; alanlar otomatik private final olur, erişimciler x() ve y() adını alır.

Getter ve Setter

Setter Koruması: Etkileşimli Deneme

 
saat9
dakika5
saniye30
09:05:30
Bir alan ve değer seçilince iki yol karşılaştırılabilir.

“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;
  }
  // ... para yatırma ve çekme metotları
  • Üç 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.
hesap1 nesnesibakiye2000.0 → 1700.0hesap2 nesnesibakiye1500.0 → 1800.0hedefHesap.paraYatir(300.0)transfer(hesap2, 300.0): bakiye -= 300paraYatir kendi kuralıyla: bakiye += 300

Örnek: Banka Hesabı · Adım adım kod izleme

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

Yığın (stack)Heapmain()hesap1hesap2BankaHesabihesapNumarasi123456bakiye2000.0hesapTuru"Vadeli"?

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

Yığın (stack)Heapmain()hesap1hesap2BankaHesabihesapNumarasi123456bakiye2000.0hesapTuru"Vadeli"BankaHesabihesapNumarasi789012bakiye1500.0hesapTuru"Vadesiz"

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

Yığın (stack)Heapmain()hesap1hesap2BankaHesabithishesapNumarasi123456bakiye2500.0hesapTuru"Vadeli"BankaHesabihesapNumarasi789012bakiye1500.0hesapTuru"Vadesiz"

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

Yığın (stack)Heapmain()hesap1hesap2BankaHesabihesapNumarasi123456bakiye2500.0hesapTuru"Vadeli"BankaHesabithishesapNumarasi789012bakiye1300.0hesapTuru"Vadesiz"

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

Yığın (stack)Heapmain()hesap1hesap2BankaHesabithishesapNumarasi123456bakiye2625.0hesapTuru"Vadeli"BankaHesabihesapNumarasi789012bakiye1300.0hesapTuru"Vadesiz"

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

Yığın (stack)Heapmain()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)Heapmain()hesap1hesap2BankaHesabihesapNumarasi123456bakiye2325.0hesapTuru"Vadeli"BankaHesabithishesapNumarasi789012bakiye1600.0hesapTuru"Vadesiz"

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

Yığın (stack)Heapmain()hesap1hesap2BankaHesabithishesapNumarasi123456bakiye2325.0hesapTuru"Vadeli"BankaHesabihedefHesaphesapNumarasi789012bakiye1600.0hesapTuru"Vadesiz"

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

Yığın (stack)Heapmain()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());

Örnek: Banka Hesabı

Yapıcı da Doğrulamalı

  • Setter'lar ve işlem metotları bakiyeyi korur; ama örnekteki yapıcı gelen değeri denetlemeden atar.
  • new BankaHesabi(1, -500.0, "Vadesiz") eksi bakiyeli bir hesap kurar; "vadeli" (küçük harf) yazılırsa hesap faiz alamaz ve kimse fark etmez.
  • Sınıf değişmezi nesnenin doğduğu anda sağlanmalıdır: yapıcı da bir ön koşul denetleyicisidir.
  • Yapıcı istisna fırlatırsa new ifadesi hiçbir referans üretmez: geçersiz nesne kullanıcının eline hiç geçmez.
public BankaHesabi(int hesapNumarasi,
        double bakiye, String hesapTuru) {
    if (bakiye < 0)
        throw new IllegalArgumentException(
            "Başlangıç bakiyesi negatif olamaz");
    if (!hesapTuru.equals("Vadeli")
            && !hesapTuru.equals("Vadesiz"))
        throw new IllegalArgumentException(
            "Bilinmeyen hesap türü: " + hesapTuru);
    this.hesapNumarasi = hesapNumarasi;
    this.bakiye = bakiye;
    this.hesapTuru = hesapTuru;
}

Yapıcı, nesnenin kurulduğu tek kapıdır: kurallar orada bir kez uygulanırsa sınıfın öteki metotları gelen nesnenin geçerli olduğuna güvenebilir.

Örnek: Banka Hesabı · Adım adım kod izleme

Yapıcıda Doğrulama: Kod İzleme

Yığın (stack)Heapmain()ab?Sicaklik #1kelvin300.0300 ≥ 0: nesne kuruldu

300 geçerli: koşul yanlış, kelvin atanır ve a yeni nesneyi gösterir.

Yığın (stack)Heapmain()abnullSicaklik #1kelvin300.0b henüz hiçbir nesneyi göstermiyor

b açıkça null ile başlatılır; catch sonrasında da okunabilmesi için.

Yığın (stack)HeapSicaklik(kelvin)kelvin-5.0main()abnullSicaklik #1kelvin300.0Sicaklik #2kelvin0.0yarım kurulmuşnew yer ayırdı, yapıcı çalışıyor

new ikinci nesneye yer ayırır (kelvin varsayılan 0.0) ve yapıcıyı çağırır. Parametre −5.0.

Yığın (stack)HeapSicaklik(kelvin)kelvin-5.0main()abnullSicaklik #1kelvin300.0Sicaklik #2kelvin0.0hiçbir referans yokthrow: yapıcı yarıda kesilir

Ön koşul bozuk: throw yapıcıyı burada keser. Atama satırı hiç çalışmaz.

Yığın (stack)Heapmain()abnullSicaklik #1kelvin300.0Sicaklik #2kelvin0.0çöp toplayıcıya kaldıatama yapılmadı: b hâlâ null

Kontrol catch bloğuna geçer. b = ... ataması yarıda kaldığı için b değişmedi; yarım nesneye ulaşan hiçbir referans yok.

Yığın (stack)Heapmain()abnullSicaklik #1kelvin300.0geçersiz Sicaklik hiç doğmadı

Programda yalnızca geçerli Sicaklik nesneleri kullanılabilir: değişmez (kelvin ≥ 0) her nesnede doğrudur.

class Sicaklik {
    private final double kelvin;
    public Sicaklik(double kelvin) {
        if (kelvin < 0)
            throw new IllegalArgumentException(
                "Kelvin negatif olamaz");
        this.kelvin = kelvin;
    }
    public double getKelvin() { return kelvin; }
}
// main içinde:
Sicaklik a = new Sicaklik(300);
Sicaklik b = null;
try {
    b = new Sicaklik(-5);
} catch (IllegalArgumentException e) {
    System.out.println("Reddedildi: "
        + e.getMessage());
}
System.out.println(a.getKelvin() + " " + b);

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çekleme (implementation).
  • Arayüz, sınıfın ne yaptığını ve nasıl kullanılacağını belirtir: public metotların adları, parametreleri, dönüş tipleri ve yorumları.
  • Gerçekleme, sınıfın iç mekanik ayrıntılarını ve özel (private) alanlarını içerir. Bunlar yalnızca sınıfın içinde önemlidir.
  • Gerçekleme değişse bile arayüz değişmeyebilir; öz alanlar sınıfın dışına gizlidir.
  • Üç unsur: tanımlama (specification: yapıcı ve metotların imzaları), temsil (representation: veriyi tutan private alanlar) ve gerçekleme (implementation: yapıcı ve metot gövdeleri). Kullanıcı yalnızca tanımlamayı bilir; temsil ve gerçekleme değişse de kodu bozulmaz.
KullanıcıKontrol paneli(arayüz)BaşlatSu miktarıDurdurİç mekanizma (gerçekleme)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.
«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çeklemesi

public class SporAraba implements Araba {
  // Sınıf gerçeklemesi: 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 tipinde tanımlar: yalnızca arayüze bağımlıdır.

Sınıf Tasarımı

Gerçeklemeyi 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ı · Adım adım kod izleme

Aynı Arayüz, Yeni Gerçekleme: Kod İzleme

YığınHeapmain()sYeni sürümtoplamSaniye0getDakika()0getSaniye()0Eski sürümdakika0saniye0

Yeni sürüm tek bir alan tutar; eski sürüm (karşılaştırma için) iki alan. Dışarıdan ikisi de 00:00.

YığınHeapmain()sYeni sürümtoplamSaniye720getDakika()12getSaniye()0Eski sürümdakika12saniye012 · 60 = 720

setDakika(12): fark 12 dakika, 12 · 60 = 720 saniye eklenir. Eski sürüm doğrudan dakika = 12 yazar.

YığınHeapmain()sYeni sürümtoplamSaniye720getDakika()12getSaniye()0Eski sürümdakika12saniye045 geçerli (0–59)

setSaniye(45): değer aralıkta, koşul doğru.

YığınHeapmain()sYeni sürümtoplamSaniye765getDakika()12getSaniye()45Eski sürümdakika12saniye45720 + (45 − 720 % 60) = 720 + 45 = 765

Önce getSaniye() = 720 % 60 = 0; fark 45 eklenir: 765. Dışarıdan bakan için 12:45.

YığınHeapmain()sYeni sürümtoplamSaniye765getDakika()12getSaniye()45Eski sürümdakika12saniye4575 < 60 false → değişiklik yok

75 geçersiz: iki sürüm de reddeder. Kural (0–59) sınıfın içinde kaldı.

YığınHeapmain()sYeni sürümtoplamSaniye729getDakika()12getSaniye()9Eski sürümdakika12saniye9765 + (9 − 45) = 729

Fark negatif olabilir: 9 − 45 = −36, toplam 729. Dakika yine 12.

YığınHeapmain()sYeni sürümtoplamSaniye729getDakika()12getSaniye()9Eski sürümdakika12saniye9729 / 60 % 60 = 12 · 729 % 60 = 9

Çıktı 12:9; eski sürüm de aynısını verirdi. İç temsil değişti, main tek harf değişmedi.

private int toplamSaniye = 0;  // tek alan
public int getSaniye() {
    return toplamSaniye % 60;
}
public void setSaniye(int saniye) {
    if (saniye >= 0 && saniye < 60)
        toplamSaniye += saniye - getSaniye();
}
// getDakika(): toplamSaniye / 60 % 60
DijitalSaat s = new DijitalSaat(); // main
s.setDakika(12);
s.setSaniye(45);
s.setSaniye(75);
s.setSaniye(9);
System.out.println(s.getDakika() + ":"
    + s.getSaniye());

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ı

Sor Değil, Söyle

Her alana bir getter ve bir setter yazmak sarmalama değildir: kurallar yine sınıfın dışında uygulanıyorsa veri yalnızca bir adım uzağa taşınmıştır.

Sormak: kural kullanan kodda

// her para çeken yerde aynı kontrol
if (h.getBakiye() >= 300) {
    h.setBakiye(h.getBakiye() - 300);
}
// ...ve not ekleyen her yerde
o.setNot(Math.min(100, o.getNot() + 5));
  • Kural her çağrı yerinde tekrar eder; biri unutursa nesne bozulur.
  • setBakiye yazıldığı anda herkes bakiyeyi istediği değere çekebilir.

Söylemek: kural nesnenin içinde

h.paraCek(300);   // kural BankaHesabi'nda
o.notEkle(5);     // 100 sınırı Ogrenci'de
  • Nesneye verisini sorup onun yerine karar vermek yerine, ne istendiği söylenir; kararı veriyi tutan nesne verir.
  • Setter yalnızca alanın gerçekten serbestçe değişebildiği durumlarda yazılır. BankaHesabi'nda bu yüzden setBakiye yoktur.

Sınama sorusu: “Bu kararı vermek için gereken veri kimde?” Cevap nesnenin kendisiyse karar da onun metodunda olmalıdır.

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çeklemeprivate alanlarbakiyeyardımcı metotlardeğişebilir
  • Arayüzler, gerçeklemeye göre çok daha nadir değişir: bu, kodun kararlı olmasını sağlar.
  • Gerçekleme değiştiğinde, ona doğrudan bağımlı olan kodların da değişmesi gerekir.
  • Gerçeklemeyi 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).

Değiştirilemez Sınıflar

Sızan Referans

public class Karne {
    private final int[] notlar;
    public Karne(int[] notlar) {
        this.notlar = notlar; // aynı dizi
    }
    public int[] getNotlar() {
        return notlar;        // iç dizi
    }
    public double ortalama() {
        int t = 0;
        for (int n : notlar) t += n;
        return (double) t / notlar.length;
    }
}
int[] dizi = {70, 80, 90};
Karne k = new Karne(dizi);
dizi[0] = 0;                // dışarıdaki dizi
k.getNotlar()[1] = -100;    // getter'dan gelen
System.out.println(k.ortalama());
-3.3333333333333335
  • Alan private final, setter yok; yine de notlar dışarıdan değişti, hatta −100 gibi geçersiz bir not girdi.
  • Sebep: dizi bir nesnedir. Yapıcı ve getter yalnızca referansı paylaştı; nesneye üç ayrı yerden ulaşılıyor.
  • final yalnızca referansın başka diziyi göstermesini engeller, dizinin içeriğini korumaz.

Değiştirilemez Sınıflar · Adım adım kod izleme

Sızan Referans: Kod İzleme

Yığın (stack)Heapmain()dizik?int[3][0]70[1]80[2]90dizi bir nesne: dizi onu gösterir

Dizi heap'te bir nesnedir; dizi değişkeni yalnızca onun referansını tutar.

Yığın (stack)HeapKarne(notlar)notlarmain()dizik?int[3][0]70[1]80[2]90Karnenotlaryapıcı referansı kopyaladı, diziyi değil

Yapıcı parametredeki referansı alana yazar. Artık Karne'nin private alanı ile main'in dizi değişkeni aynı diziyi gösterir.

Yığın (stack)Heapmain()dizikint[3][0]0[1]80[2]90Karnenotlardışarıdan yazıldı: Karne habersiz

dizi[0] = 0 Karne'nin hiçbir metodundan geçmez, ama Karne'nin gördüğü not değişti.

Yığın (stack)Heapmain()dizikint[3][0]0[1]-100[2]90Karnenotlargetter iç dizinin referansını verdi

getNotlar() iç dizinin kendisini döndürür; dönen referansla −100 yazılır. Geçersiz not, hiçbir denetimden geçmeden nesneye girdi.

Yığın (stack)Heapmain()dizikint[3][0]0[1]-100[2]90Karnenotlar(0 − 100 + 90) / 3

Ortalama (0 − 100 + 90) / 3 ≈ −3,33. private yazmak yetmedi: iç nesneye giden referans dışarı sızdı.

public Karne(int[] notlar) {
    this.notlar = notlar;
}
public int[] getNotlar() {
    return notlar;
}
// main içinde:
int[] dizi = {70, 80, 90};
Karne k = new Karne(dizi);
dizi[0] = 0;
k.getNotlar()[1] = -100;
System.out.println(k.ortalama());

Değiştirilemez Sınıflar

Savunmacı Kopyalama

public class Karne {
    private final int[] notlar;
    public Karne(int[] notlar) {
        this.notlar = notlar.clone(); // girişte
    }
    public int[] getNotlar() {
        return notlar.clone();        // çıkışta
    }
    // ortalama() aynı
}

Aynı main ile

80.0
  • Savunmacı kopya (defensive copy): değiştirilebilir bir nesne sınıfa girerken ve sınıftan çıkarken kopyalanır. İç diziye ulaşan tek referans sınıfın kendi alanı kalır.
  • Yapıcıdaki kopya, çağıranın elindeki diziyi sonradan değiştirmesine karşı; getter'daki kopya, dönen diziyi değiştirmesine karşı korur.
  • Listeler için new ArrayList<>(liste) ya da salt okunur görünüm Collections.unmodifiableList(liste) kullanılır.
  • String, Integer gibi değiştirilemez nesneler kopyalanmaz: paylaşılmaları zararsızdır.

Kopyalamanın bir maliyeti vardır; büyük veride salt okunur görünüm ya da baştan değiştirilemez bir tip seçmek daha ucuzdur.

Değiştirilemez Sınıflar

Değiştirilemez Sınıf Tasarlamak

public final class Para {        // alt sınıf yok
    private final long kurus;     // bir kez atanır
    private final String birim;
    public Para(long kurus, String birim) {
        this.kurus = kurus;
        this.birim = birim;
    }
    public Para ekle(Para diger) {   // setter yerine
        if (!birim.equals(diger.birim))
            throw new IllegalArgumentException("Birim farklı");
        return new Para(kurus + diger.kurus, birim);
    }
    @Override public String toString() {
        return kurus / 100 + ","
            + String.format("%02d", kurus % 100) + " " + birim;
    }
}

Kurallar

  1. Setter yok; durum yalnızca yapıcıda kurulur.
  2. Alanlar private final.
  3. Sınıf final: değiştirilebilir bir alt sınıf yazılamasın.
  4. “Değiştiren” metotlar yeni bir nesne döndürür.
  5. Değiştirilebilir alanlar varsa savunmacı kopya.

Kazanç

  • Değişmez yalnızca yapıcıda denetlenir; sonra hiç bozulamaz.
  • Nesne kopyalanmadan paylaşılabilir, HashMap anahtarı olabilir, iş parçacıkları arasında güvenlidir.
  • String ve record bu kuralların hazır örnekleridir.

Değiştirilemez Sınıflar · Adım adım kod izleme

Değiştirilemez Para: Kod İzleme

Yığın (stack)Heapmain()ab?Para #1kurus1250birim"TL"a: 12,50 TL

İlk nesne kurulur. Alanları final: bundan sonra hiçbir metot onları değiştiremez.

Yığın (stack)Heapa.ekle(diger)digermain()ab?Para #1kurus1250birim"TL"Para #2kurus100Para #3kurus1350ekle yeni bir nesne kurar

ekle, a'yı değiştirmez; toplamı taşıyan yeni bir nesne (#3) kurup döndürür.

Yığın (stack)Heapmain()ab?Para #1kurus1250birim"TL"Para #2kurus100Para #3kurus1350sonuç atılmadı: kayıpa hâlâ 12,50 TL

Dönen değer bir değişkene atanmadı: #3 kaybolur. Değiştirilemez nesnelerde en sık yapılan hata budur; a değişmedi.

Yığın (stack)Heapmain()abPara #1kurus1250birim"TL"Para #5kurus1625birim"TL"b = 1250 + 375 = 1625 kuruş

Bu kez sonuç b'ye atanır: 1250 + 375 = 1625 kuruş. Argüman olarak kurulan #4 de artık kullanılmaz.

Yığın (stack)Heapmain()abPara #1kurus1250birim"TL"Para #5kurus1625birim"TL"iki ayrı, değişmeyen nesne

a ilk değerini korudu. String'in toUpperCase() ya da concat() metotları da aynı biçimde çalışır.

public Para ekle(Para diger) {
    if (!birim.equals(diger.birim))
        throw new IllegalArgumentException(
            "Birim farklı");
    return new Para(kurus + diger.kurus, birim);
}
// main içinde:
Para a = new Para(1250, "TL");
a.ekle(new Para(100, "TL"));  // sonuç atıldı
Para b = a.ekle(new Para(375, "TL"));
System.out.println(a + " | " + b);

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 static ü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.

Paketler

Paket Adlandırma ve Alt Paketler

  • Ad çakışmasını önlemek için kurumlar paket adına ters çevrilmiş alan adıyla başlar: giresun.edu.tr → tr.edu.giresun.banka.
  • Noktalı adlar klasörlere karşılık gelir: tr/edu/giresun/banka/.
  • Alt paket ayrı bir pakettir. tr.edu.giresun.banka.rapor, banka'nın paket erişimli üyelerini göremez; adların benzemesi bir erişim hakkı vermez.
  • import tr.edu.giresun.banka.*; alt paketleri içe aktarmaz: import java.util.*; da java.util.function'daki sınıfları getirmez.
// tr/edu/giresun/banka/Yardimci.java
package tr.edu.giresun.banka;
class Yardimci {                  // paket erişimli
    static double kdv(double t) { return t * 0.20; }
}
// tr/edu/giresun/banka/rapor/Rapor.java
package tr.edu.giresun.banka.rapor;
import tr.edu.giresun.banka.Yardimci;
public class Rapor {
    double hesapla() { return Yardimci.kdv(100); }
}
error: Yardimci is not public in tr.edu.giresun.banka; cannot be accessed from outside package

Paket adı ile klasör yolu birebir eşleşmezse derleyici sınıfı bulamaz; bu yüzden IDE'ler klasörleri paket adından oluşturur.

Paketler

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: biri import edilir, diğeri tam adıyla yazılır.

Ö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çeklemeSı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;
Sınıf adı çakışmasıFarklı paketlerde aynı adlı sınıfTam ad: java.sql.Date

Özet

İlgili Sayfalar

Erişim Belirleyiciler ve Sarmalama

Simülatör: public, protected, paket erişimi ve private üyelere hangi sınıftan erişilebildiği; setter ile korunan alan.

Çalışma Soruları

Bilgi saklama, sözleşme, erişim düzeyleri, getter ve setter, banka hesabı, gerçeklemeyi değiştirmek ve paket soruları.

Ö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); yazılır.

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üş tipi int olmalı.

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

Bu, alanı public yapmaktan farksızdır; geçerlilik kuralı setter'da denetlenir.

Özet

Sık Yapılan Hatalar (2)

import unutmakerror: cannot find symbol symbol: class Scanner

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

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

Sınıf ya public yapılır ya da yalnız aynı paketten kullanılır.

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

Sık Yapılan Hatalar (3)

İç nesneyi getter ile vermek

return notlar; değiştirilebilir diziyi ya da listeyi dışarı açar. Kopya ya da salt okunur görünüm döndürülür.

final alanı değiştirilemez sanmak

private final int[] notlar: referans sabittir, dizinin elemanları değil.

Yeni nesneyi atamayı unutmak

a.ekle(p); ya da s.toUpperCase(); tek başına hiçbir şeyi değiştirmez: a = a.ekle(p);

Yapıcıda doğrulamamak

Setter'lar denetlese de new BankaHesabi(1, -500.0, ...) geçersiz nesne kurar. Yapıcı da kuralı uygulamalı.

assert ile argüman denetlemek

-ea verilmezse denetim hiç çalışmaz. public metot argümanları için IllegalArgumentException.

Alt paketin içe aktarıldığını sanmak

import java.util.*; sonrası Function için: cannot find symbol. java.util.function ayrıca içe aktarılır.

Her alana setter yazmak

Kural kullanan koda kayar. Önce davranış (paraCek, notEkle) tasarlanır; setter gerekmeyebilir.

Hatayı sessizce yutmak

Geçersiz değeri hiçbir iz bırakmadan yok saymak hatayı gizler; ya sözleşmede belirtilir ya da istisna fırlatılı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

Örnek Problemler (4)

A) Kopyalamayan Karne ile

int[] d = {50, 60};
Karne k = new Karne(d);
int[] g = k.getNotlar();
g[0] = 100;
d[1] = 100;
System.out.println(k.ortalama());

B) Para sınıfıyla

Para p = new Para(100, "TL");
p.ekle(p);
p = p.ekle(p);
p.ekle(new Para(1, "TL"));
System.out.println(p);
  1. A ne yazdırır? Savunmacı kopyalı Karne ile ne yazdırır?
  2. B ne yazdırır?
  3. On kez arttir() çağıran Sayac programı -ea olmadan ve -ea ile çalıştırılırsa ne olur? Hangisi hatayı gösterir?
  4. Kesir dışındaki bir sınıfta new Kesir(1, 2).pay derlenir mi?
Cevapları göster
  1. 100.0: d, g ve alan aynı diziyi gösterir; iki atama da nesneye ulaşır. Kopyalı sürümde 55.0: iki atama da kopyalara yapılır.
  2. 2,00 TL: yalnızca p = p.ekle(p) sonucu saklanır (100 + 100); öteki iki çağrının sonucu atılır.
  3. -ea olmadan bitti yazar, hata görünmez. -ea ile onuncu artırmada AssertionError: deger=10.
  4. Hayır: pay has private access in Kesir. private erişim yalnızca Kesir'in kendi kodunda serbesttir.

Ö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.2: Sarmalama
Sonraki bölüm: Bölüm 4.3: Kalıtım · Terim sözlüğü
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.