---
title: 'Nesneye Dayalı Java'
source: 'https://academia.sh/tr/kurslar/java-nesne-modeli'
language: tr
updated: '2026-08-17T04:41:20+00:00'
license: 'CC BY-SA 4.0'
lessons: 13
outcomes:
    - 'Sınıf, arayüz ve kalıtım ilişkilerini doğru modelleyerek tasarım kurar'
    - 'Genellikleri tip güvenliği için kullanır'
    - 'İstisna hiyerarşisini denetlenen/denetlenmeyen ayrımıyla yönetir'
---

# Nesneye Dayalı Java

Java'da bir ad iki tip taşır ve kurs her dilsel kararı tek bir soruyla ölçer: sonucu bildirilen tip mi verdi, çalışma zamanı tipi mi? Sınıf, kalıtım, arayüz, iç sınıf ve kayıtlardan genelliklerin tip silmesine ve istisna hiyerarşisine kadar her konu bu ayrımın hangi tarafında durduğuyla kurulur.

## Sınıflar ve Arayüzler

Nesne yaşam döngüsü ve yapıcılar, görünürlük düzeyleriyle kapsülleme, yeniden tanımlama ile gizlemenin ayrıldığı yer, soyut sınıfla arayüz arasındaki seçim, sınıf düzeyi durum ve başlatma sırası, genişletme kısıtı, iç içe sınıf türleri ve veri taşıyıcı tipler.

### Sınıf, Nesne ve Yapıcı

Source: https://academia.sh/tr/kurslar/java-nesne-modeli/sinif-nesne-ve-yapici

Yapıcı zinciri üstten alta çalışır ve zincirin üst halkasından çağrılan yeniden tanımlı bir yöntem çalışma zamanı tipinden gelir: alt sınıfın alanları henüz atanmadığı için sonuç `alt: null x0` çıkar, kırılgan bir sürümde nesne hiç teslim edilmez — özel ve `final` yöntemde ise aynı tuzak doğmaz.

Java Temelleri kursu sınıfı yalnız bir **kap** olarak kullandı: ölçülen şey kaynağın sınıf
dosyasına neyi bıraktığıydı, sınıfın kendisi ölçüme girmedi. Kurs kapanışında aktarımın tek
modeli okunurken bile sınıf bir çerçeveydi — bir alan taşıyordu, bir yöntem barındırıyordu,
o kadar.

Bu kursta kap konu oluyor. Ve sınıf konu olur olmaz kaynakta yeni bir şey belirir: **bir ad
iki tip taşımaya başlar.** Bir değişken bildirildiğinde derleyicinin gördüğü tip yazılır;
o değişkene atanan nesnenin gerçek sınıfı bambaşka bir şey olabilir. Bu kursun her dersi tek
bir soruyu sorar: **bu kararı hangisi verdi?** İlk ders soruyu nesnenin kurulduğu ana sorar,
çünkü kurulum sırasında iki tip bir süre boyunca birbirini tutmaz.

#### Bir Ad, İki Tip

Bir bildirimde iki ayrı bilgi vardır. `Ust d = new Alt();` satırında `Ust` **bildirilen
tiptir (declared type)**: derleyicinin adı çözerken baktığı tip budur ve hangi üyenin
yazılabileceğini o belirler. `Alt` ise **çalışma zamanı tipidir (runtime type)**: nesnenin
gerçek sınıfı, çöp toplanana kadar değişmez. Çoğu programda ikisi aynı yazılır ve fark
görünmez.

Kurs bu ikisini bilerek ayırır. Çekirdek bir üst sınıf (`Ust`) ile onu genişleten bir alt
sınıftan (`Alt`) oluşur. `Alt`, `Ust`'un bir yöntemini **yeniden tanımlar (overriding)** —
yani aynı imzayla kendi sürümünü koyar — ve aynı adlı bir alan da bildirir. Tek bir referans
kurulur: bildirilen tipi `Ust`, çalışma zamanı tipi `Alt`. Ölçümün kâhini kurgunun
kendisidir; iki tipi biz ayırdığımız için gelen yanıtın hangisinden geldiği baştan bellidir.

Bu dersin sorusu şu: nesne henüz **kuruluyorken** iki tip ne durumda? Yapıcı bir yöntem
değildir, ama gövdesinde yöntem çağrısı bulunabilir. O çağrı çalıştığında ortada hangi
nesne vardır?

Nesneye dayalı programlamanın kavramları — nesne, sınıf, kalıtım, arayüz — Programlama
Temelleri kursunda kuruldu ve burada tekrarlanmaz. Ölçülen şey kavram değil, **kararın
tarafıdır**.

#### Ölçüm Çekirdeği

Ölçüm bir **kurulum izidir**: her başlatma adımı çalıştığında adını ve o anda gördüğü sonucu ortak
bir listeye yazar ve liste sonda tek seferde basılır.

- **SN1** — Bildirilen tip `Ust`, çalışma zamanı tipi `Alt` olarak kurgu tarafından
  ayrılmıştır; her satırın hangi tipten geldiği kurgudan okunur, çıkarımdan değil.
- **SN2** — İz ortak bir listeye yazılır ve program sonunda basılır; basma işi ölçülen
  sıraya karışmaz.
- **SN3** — `Alt` sınıfının `adet` alanının başlangıç değeri yoktur ve yalnız yapıcı
  gövdesinde atanır; atanmamış hâli **0** göründüğü için alan başlatma ile yapıcı gövdesi
  ayırt edilebilir.
- **SN4** — Ölçüm ortama bağlı hiçbir veri okumaz: süre, bellek adresi, kimlik karması ve
  yol yazılmaz. Basılan tek şey sıra ve değerdir.

```java
// Ust.java — kursun çekirdek ikilisi ve kurulum izi
import java.util.*;

class Ust {
    static final List<String> IZ = new ArrayList<>();
    static void iz(String s) { IZ.add(s); }
    static void bas() { IZ.forEach(System.out::println); IZ.clear(); }

    String etiket = "ust alan";

    Ust() {
        iz("2. Ust yapicisi       | ad() -> " + ad());
        iz("                      | etiketle() -> " + etiketle());
    }

    String ad() { return "ust yontem"; }
    String etiketle() { return "ust: " + etiket; }
}

class Alt extends Ust {
    String etiket = "alt alan";
    int adet;
    { iz("3. Alt alan baslatici | etiketle() -> " + etiketle()); }

    Alt(int adet) {
        iz("4. Alt yapici govdesi | etiketle() -> " + etiketle());
        this.adet = adet;
        iz("5. atamadan sonra     | etiketle() -> " + etiketle());
    }

    @Override String ad() { return "alt yontem"; }
    @Override String etiketle() { return "alt: " + etiket + " x" + adet; }
}

class Kirilgan extends Ust {
    String etiket = "kirilgan alan";
    @Override String etiketle() { return "kirilgan: " + etiket.length(); }
}
```

`Alt` sınıfının yapıcısında `super()` çağrısı **yazılı değildir**. Yazılmadığında derleyici
onu zincirin başına kendisi koyar; bir yapıcının ilk işi her zaman üst sınıfın yapıcısını
çalıştırmaktır. Zincir `Ust`'ta da bitmez, taban sınıfa kadar sürer.

#### Yapıcı Zinciri Hangi Sırayla Çalışıyor

```java
// Zincir.java — nesne kurulurken kararı hangi tip veriyor
public class Zincir {
    public static void main(String[] args) {
        Ust.iz("1. new Alt(3) cagrildi");
        Ust d = new Alt(3);
        Ust.iz("6. kurulum bitti      | etiketle() -> " + d.etiketle());
        System.out.println("bildirilen tip Ust, calisma zamani tipi "
                + d.getClass().getSimpleName());
        System.out.println();
        Ust.bas();

        System.out.println();
        Kirilgan k = null;
        try {
            k = new Kirilgan();
        } catch (NullPointerException e) {
            System.out.println("kirilgan kurulum   : " + e.getClass().getSimpleName());
        }
        System.out.println("teslim edilen nesne: " + k);
    }
}
```

```
bildirilen tip Ust, calisma zamani tipi Alt

1. new Alt(3) cagrildi
2. Ust yapicisi       | ad() -> alt yontem
                      | etiketle() -> alt: null x0
3. Alt alan baslatici | etiketle() -> alt: alt alan x0
4. Alt yapici govdesi | etiketle() -> alt: alt alan x0
5. atamadan sonra     | etiketle() -> alt: alt alan x3
6. kurulum bitti      | etiketle() -> alt: alt alan x3

kirilgan kurulum   : NullPointerException
teslim edilen nesne: null
```

Sıra dört adımdır ve yönü tektir: **üst yapıcı, alt alan başlatıcıları, alt yapıcı gövdesi,
teslim.** `new Alt(3)` yazılmış olsa da çalışan ilk gövde `Ust`'un yapıcısıdır. Bu, kalıtımın
yapıcı tarafındaki karşılığıdır — bir nesne, kendi sınıfının katmanından önce üstündeki
katmanların hepsi kurulmuş olarak var olur.

İkinci satır bu dersin ölçümüdür. `Ust`'un yapıcısı `ad()` yazıyor ve karşılığında **`alt
yontem`** alıyor. Çağrıyı yazan `Ust`'tur, çalışan `Alt`'ın sürümüdür: yeniden tanımlanmış
bir yöntemin kararını **çalışma zamanı tipi** verir ve bu kural yapıcı zincirinin ortasında
da askıya alınmaz. Nesne henüz yarı kurulmuşken bile gerçek sınıfı bellidir.

Üçüncü satır bunun ödettiğidir. Aynı noktada `etiketle()` çağrısı **`alt: null x0`**
veriyor. `Alt.etiketle()` kendi `etiket` alanını okuyor, ama o alan henüz atanmamıştır:
alan başlatıcıları **üst yapıcı bittikten sonra** çalışır. Yani çalışma zamanı tipi
doğru yöntemi seçmiştir, seçilen yöntem ise henüz var olmayan bir duruma bakmaktadır. İki
karar ayrı taraflardan gelir ve arada bir aralık kalır.

Aralığın genişliği üçüncü ile beşinci satır arasında okunuyor. Üçüncü satırda `etiket` atanmış,
`adet` hâlâ **0**; beşinci satırda ikisi de yerindedir. `adet` alanı yapıcı gövdesinde atandığı
için (**SN3**) iki aşama birbirinden ayrılabiliyor: bildirimde verilen değerler bir yerde, yapıcı
gövdesinde verilenler daha sonra oturur. Kaynağa bakan biri `etiket` ile `adet`'i aynı sınıfın iki
alanı olarak görür; kurulum sırası onları üç ayrı ana dağıtır.

#### Yarım Kurulmuş Nesnenin Bedeli

Çıktının son iki satırı aralığın gerçek maliyetidir. `Kirilgan` sınıfı `Alt` ile aynı işi yapar —
tek farkı yeniden tanımladığı yöntemin alanı yalnız okumakla kalmayıp üzerinde bir çağrı
yapmasıdır. Sonuç bir **`NullPointerException`**'dır ve önemli olan nerede doğduğudur: istisna
`Kirilgan.etiketle()` içinde düşer, ama o yöntemi çağıran satır `Ust`'un yapıcısında durur. Kusuru
yazan sınıf ile kusurun göründüğü sınıf ayrıdır.

Son satır bunun sonucunu ölçüyor: `k` referansı **`null`**. Yapıcı tamamlanmadığı için
`new` ifadesi hiçbir değer üretmemiştir. Yarı kurulmuş nesne teslim edilmez, ama teslim
edilmeden önce onun üzerinde bir yöntem çalışmıştır — hem de alt sınıfın yöntemi.

Buradan çıkan kural, bu kursun ilk somut sözleşmesidir: **bir yapıcı, yeniden
tanımlanabilecek bir yöntemi çağırmamalıdır.** Çağırdığında yazdığı kod alt sınıfın
sürümünü çalıştırır ve alt sınıfın yazarı, alanlarının atanmış olduğunu varsayarak yazmıştır.
İki taraf da kendi içinde tutarlıdır; kusur ikisinin birleştiği yerde doğar ve ne üst sınıfın
ne de alt sınıfın kaynağına bakarak görünür.

#### Sınırlayıcı Ölçüm: Özel ve Kapalı Yöntemde Tuzak Yok

Kural her yeriyle geçerli olsaydı bir şey açıklamazdı. Tuzağın doğmadığı iki durum vardır ve
ikisi de aynı nedenden gelir: o yöntemler için ortada bir **yeniden tanımlama** yoktur.

- **SN5** — Sınır ölçümündeki üç yöntem aynı gövde biçimini taşır ve yalnız
  belirteçleriyle ayrılır: biri özel (`private`), biri `final`, biri belirteçsiz. Alt
  sınıfta üçünün de aynı adla bir karşılığı yazılmıştır, yalnız `final` olan hariç — o
  yazılamaz.

```java
// Sinir.java — özel ve final yöntemde tuzak doğmuyor
public class Sinir {
    public static void main(String[] args) {
        SinirUst s = new SinirAlt();
        System.out.println("kurulan nesnenin sinifi   : " + s.getClass().getSimpleName());
    }
}

class SinirUst {
    SinirUst() {
        System.out.println("yapicidan ozel()          : " + ozel());
        System.out.println("yapicidan sabit()         : " + sabit());
        System.out.println("yapicidan acik()          : " + acik());
    }

    private String ozel() { return "ust ozel"; }
    final String sabit()  { return "ust final"; }
    String acik()         { return "ust acik"; }
}

class SinirAlt extends SinirUst {
    String etiket = "alt alan";

    private String ozel() { return "alt ozel: " + etiket; }
    @Override String acik() { return "alt acik: " + etiket; }
}
```

```
yapicidan ozel()          : ust ozel
yapicidan sabit()         : ust final
yapicidan acik()          : alt acik: null
kurulan nesnenin sinifi   : SinirAlt
```

Üç satır, üç ayrı taraf. Kurulan nesnenin sınıfı üçünde de aynıdır — dördüncü satır bunu
söylüyor — ama yanıtı yalnız sonuncusu çalışma zamanı tipinden alıyor.

`ozel()` çağrısı **`ust ozel`** veriyor. Özel bir yöntem alt sınıfa hiç miras kalmaz; alt
sınıftaki aynı adlı yöntem onun yeniden tanımlaması değil, **ayrı bir yöntemdir**. Ortada
yeniden tanımlanmış bir sürüm olmadığı için çağrının gidebileceği tek hedef `SinirUst`'un
kendi yöntemidir; çalışma zamanı tipinin katacağı bir şey yoktur.

`sabit()` çağrısı **`ust final`** veriyor. `final` bir yöntem yeniden tanımlanamaz;
`SinirAlt` içine aynı imzayla bir sürüm yazılsaydı derleyici kaynağı reddederdi. Alt sınıfta
başka bir sürüm bulunamayacağına göre çağrının gidebileceği tek yer üst sınıftır. Kısıtın
kendisi bu kursun ileriki bir dersinde ayrıca ölçülür; burada görünen, kısıtın kurulum
sırasındaki sonucudur.

`acik()` çağrısı ise **`alt acik: null`** veriyor. Belirteçsiz yöntem yeniden
tanımlanabilir, tanımlanmıştır ve kararı çalışma zamanı tipi verir — arkasından da tanıdık
`null` gelir.

Sınırın anlamı şudur: yapıcıdan yapılan bir çağrının tehlikeli olup olmadığı çağrının
yazımından okunmaz. Üç satır da aynı biçimde yazılmıştır. Ayrımı yapan şey **yöntemin
belirtecidir**, yani üst sınıfın alt sınıfa ne kadar açık bıraktığıdır. Yapıcıdan çağrılacak
bir yardımcı yöntemi özel ya da `final` bildirmek, tuzağı bir uyarıya değil, dilin
kuralına dönüştürür.

#### Çağrı Sınıf Dosyasında Nereye Bağlanıyor

Üç çağrının kaynakta ayırt edilemediği görüldü. Sınıf dosyasında ayırt edilebiliyor mu?
Ölçüm için `SinirUst` derlenir ve yapıcısının gövdesindeki çağrı komutları ile o çağrıların
hedeflerinin belirteçleri okunur.

- **SN6** — Ölçüm yalnız `SinirUst` sınıfını derler; alt sınıf ortada yoktur. Okunan şey bir
  koşum değil, derleyicinin tek bir sınıf için ürettiği dosyadır.

```java
// Olcek.java — sınıf dosyasını okur: yapıcıdaki çağrılar ve hedeflerinin belirteçleri
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.instruction.*;
import java.lang.reflect.AccessFlag;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Olcek {
    record Cagri(String ad, String komut, String hedef, String belirtec, boolean acik) {}

    static List<Cagri> yapiciCagrilari(String kaynak, String sinif) throws Exception {
        Path d = Files.createTempDirectory("olcek");
        Path k = d.resolve(sinif + ".java");
        Files.writeString(k, kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, "-d", d.toString(), k.toString()) != 0)
            throw new IllegalStateException("derlenmedi: " + sinif);
        ClassModel cm = ClassFile.of().parse(d.resolve(sinif + ".class"));
        Map<String, String> belirtec = new HashMap<>();
        for (MethodModel m : cm.methods()) {
            var f = m.flags();
            belirtec.put(m.methodName().stringValue(),
                    f.has(AccessFlag.PRIVATE) ? "private"
                            : f.has(AccessFlag.FINAL) ? "final" : "-");
        }
        List<Cagri> cagri = new ArrayList<>();
        for (MethodModel m : cm.methods()) {
            if (!m.methodName().stringValue().equals("<init>") || m.code().isEmpty()) continue;
            for (CodeElement e : m.code().get())
                if (e instanceof InvokeInstruction iv) {
                    String ad = iv.name().stringValue();
                    String hedef = kisa(iv.owner().asInternalName());
                    String b = hedef.equals(sinif) ? belirtec.getOrDefault(ad, "-") : "-";
                    cagri.add(new Cagri(ad, iv.opcode().name().toLowerCase(Locale.ROOT),
                            hedef, b, hedef.equals(sinif) && b.equals("-")));
                }
        }
        return cagri;
    }

    static String kisa(String ic) { return ic.substring(ic.lastIndexOf('/') + 1); }
}
```

```java
// Komut.java — yapıcıdaki çağrılar sınıf dosyasında hangi komuta bağlanıyor
public class Komut {
    static final String KAYNAK = """
        class SinirUst {
            SinirUst() { ozel(); sabit(); acik(); }
            private String ozel() { return "ust ozel"; }
            final String sabit()  { return "ust final"; }
            String acik()         { return "ust acik"; }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-8s %-14s %-9s %-10s %s%n",
                "cagri", "komut", "hedef", "belirtec", "yeniden tanimlanabilir");
        for (Olcek.Cagri c : Olcek.yapiciCagrilari(KAYNAK, "SinirUst"))
            System.out.printf("%-8s %-14s %-9s %-10s %s%n",
                    c.ad(), c.komut(), c.hedef(), c.belirtec(), c.acik() ? "evet" : "hayir");
    }
}
```

```
cagri    komut          hedef     belirtec   yeniden tanimlanabilir
<init>   invokespecial  Object    -          hayir
ozel     invokevirtual  SinirUst  private    hayir
sabit    invokevirtual  SinirUst  final      hayir
acik     invokevirtual  SinirUst  -          evet
```

İlk satır kaynakta yazılmayan bir çağrıdır. `SinirUst`'un yapıcısında `super()` yazılmamıştır,
buna karşın sınıf dosyasında bir `Object` yapıcısı çağrısı durmaktadır ve gövdenin **ilk**
komutudur. Yapıcı zincirinin üstten alta çalıştığı iddiası burada okunuyor: zincirin halkası
derleyici tarafından takılmıştır.

Asıl gözlem kalan üç satırdadır. Üç çağrı da **aynı komuta** derlenmiştir. Kaynakta ayırt
edilemeyen fark, sınıf dosyasının çağrı yerinde de ayırt edilemiyor: `ozel`, `sabit` ve
`acik` için üretilen komut birebir aynıdır ve üçünün de hedefi `SinirUst` olarak yazılıdır.
Çağrı yerine bakan biri hangisinin alt sınıfa gidebileceğini söyleyemez.

Fark **hedefin belirtecindedir.** Özel bir yöntem miras kalmaz, `final` bir yöntem yeniden
tanımlanamaz; ikisinde de sanal makinenin arayabileceği ikinci bir gerçekleştirim yoktur, bu
yüzden dinamik seçim çalışsa bile tek bir aday bulur. Yalnız belirteçsiz yöntemde ikinci bir
aday olabilir ve `Sinir` koşumunda olan da budur.

Bu, kursun ikinci iddiasının ilk örneğidir: **fark sözdiziminde görünmez.** Şimdi bir adım
daha ileri gidildi — fark çağrının yazıldığı yerde de görünmüyor. Kararın hangi taraftan
geleceği çağrıda değil, **çağrılanın bildiriminde** yazılıdır.

#### Özet

- Bir ad iki tip taşır: derleyicinin gördüğü **bildirilen tip** ve nesnenin gerçek sınıfı
  olan **çalışma zamanı tipi**. Kursun her ölçümü kararın hangisinden geldiğini sorar.
- Yapıcı zinciri üstten alta çalışır: üst yapıcı, alt sınıfın alan başlatıcıları, alt
  yapıcının gövdesi, sonra teslim. `super()` yazılmasa da sınıf dosyasında durur ve
  yapıcının **ilk** komutudur.
- Yapıcıdan çağrılan yeniden tanımlı bir yöntemin kararını **çalışma zamanı tipi** verir:
  `Ust`'un yapıcısı `ad()` yazar, **`alt yontem`** alır.
- Seçilen yöntem henüz atanmamış alanlara bakar: aynı noktada `etiketle()` **`alt: null x0`**
  döner, çünkü alan başlatıcıları üst yapıcı bittikten sonra çalışır.
- Alan yalnız okunmayıp üzerinde çağrı yapıldığında kurulum bir **`NullPointerException`** ile
  düşer ve `new` hiçbir değer üretmez: referans **`null`** kalır, yarı kurulmuş nesne teslim
  edilmez.
- Tuzak özel ve `final` yöntemde doğmaz: üç çağrı sınıf dosyasında **aynı komuta** derlenir
  ve aynı hedefi yazar; farkı çağrı yeri değil, çağrılanın belirteci taşır.

#### Sonraki Adım

Bu derste `Ust` ile `Alt` arasındaki bütün üyeler birbirine açıktı; hangi yöntemin
görülebildiği hiç sorulmadı. Oysa sınır ölçümünde ilk kez bir belirteç işe karıştı: `private`
yazılan yöntem alt sınıfa hiç miras kalmadı ve kararı bildirilen tipe bıraktı. O tek sözcüğün
neyi kapattığı ölçülmedi. Sıradaki ders adın **kimden görüldüğünü** sorar: dört görünürlük
düzeyi ile dört ayrı çağrı konumu çaprazlanır ve on altı çiftin kaçının derlendiği sayılır.
Ölçümün ikinci yarısı görünürlüğün nerede yaşadığını gösterir — sınıf dosyasında bir bayrak
olarak durur, çalışma zamanında ise üye yerinde durmaya devam eder.

### Erişim Belirteçleri

Source: https://academia.sh/tr/kurslar/java-nesne-modeli/erisim-belirtecleri

Dört görünürlük düzeyi ile dört çağrı konumu çaprazlandığında on altı çiftin onu derlenir; kararın tamamı derleme zamanında verilir ve çalışma zamanında üye yerinde durur — sınıf dosyasında dört alanın dördü de vardır, ayrımı yalnız bir bayrak taşır. Korumalı düzey paket içini de açar.

Önceki ders bir nesnenin kuruluş anını ölçtü ve sonunda beklenmedik bir yerden bir belirteç
çıktı: `private` yazılan yöntem alt sınıfa hiç miras kalmadığı için yapıcı tuzağı orada
doğmadı. O tek sözcük bir kararın tarafını değiştirdi, ama sözcüğün kendisi ölçülmedi.

Bu ders onu ölçer. Sorusu şudur: bir üyenin **kimden görüldüğüne** kim karar veriyor ve bu
karar ne zaman veriliyor? Yanıt bu kursta beklenenden daha kesindir. Görünürlük, kursun
ölçtüğü sekiz kararın en uçtaki örneğidir: kararın tamamı **bildirilen tipe** ve derleme
zamanına aittir, çalışma zamanının hiçbir katkısı yoktur. Ölçüm bunu iki yönden gösterir —
derlenen ve derlenmeyen çiftleri sayarak, sonra üyenin çalışma zamanında nerede durduğuna
bakarak.

#### Görünürlük Kimin Sorusu

Java dört görünürlük düzeyi tanır ve üçünün adı vardır: `private`, `protected`, `public`.
Dördüncüsü adsızdır — hiçbir belirteç yazılmadığında geçerli olan düzeydir ve **paket
düzeyi** olarak anılır. Bu adsızlık bir ayrıntı değildir: bir üyeyi belirteçsiz bırakmak bir
seçim yapmamak değil, paket içine açık bırakmayı seçmektir.

Düzeylerin tek başına anlamı yoktur; anlam **erişimin nereden denendiğiyle** birlikte doğar.
Bu yüzden ölçüm bir çaprazlamadır: dört düzeyin her biri dört ayrı konumdan denenir. Konumlar
şunlardır — üyeyi bildiren sınıfın kendi içi, aynı paketteki ilgisiz bir sınıf, başka
paketteki bir alt sınıf, başka paketteki ilgisiz bir sınıf. On altı çift çıkar ve her çift
tek bir soruya indirgenir: bu kaynak derleniyor mu?

Nesneye Dayalı Python ve Tipler kursu kapsüllemeyi **ad düzeyinde bir sözleşme** olarak kurmuştu; orada
görünürlük bir anlaşmaydı ve tekrarlanmaz. Buradaki ayrım yapısaldır: kararı veren dilin
kendi derleyicisidir ve reddi bir uyarı değil, derlenmemiş bir kaynaktır.

#### Ölçüm Çekirdeği

Ölçüm hiçbir programı çalıştırmaz. Her çift için bir kaynak kümesi üretilir, derleyici
program içinden çağrılır ve yalnız çıkış durumu okunur.

- **SN7** — Ölçümün kâhini kurgunun kendisidir: her senaryoda üye tek bir yerde bildirilir ve
  tek bir yerden okunur; derleme başarısızlığının başka bir nedeni yoktur.
- **SN8** — Derleyicinin ürettiği bütün iletiler yutulur; ölçülen tek şey çıkış durumudur.
  Kaynağın neden reddedildiği metinden değil, kurgudan okunur.
- **SN9** — Bütün senaryolarda aynı üye kullanılır: `Ust` sınıfının `etiket` adlı alanı.
  Düzeyler arasında değişen tek şey o alanın belirtecidir.

```java
// Derle.java — kaynak kümesini derler; sonuç yalnız derlendi ya da derlenmedi
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.reflect.AccessFlag;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Derle {
    static boolean olur(Map<String, String> dosya) throws Exception {
        Path d = Files.createTempDirectory("erisim");
        List<String> arg = new ArrayList<>(List.of("-d", d.resolve("cikti").toString()));
        for (var g : dosya.entrySet()) {
            Path p = d.resolve(g.getKey());
            Files.createDirectories(p.getParent());
            Files.writeString(p, g.getValue());
            arg.add(p.toString());
        }
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        return ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, arg.toArray(String[]::new)) == 0;
    }

    static List<String> alanBayraklari(String kaynak, String sinif) throws Exception {
        Path d = Files.createTempDirectory("bayrak");
        Path k = d.resolve(sinif + ".java");
        Files.writeString(k, kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, "-d", d.toString(), k.toString()) != 0)
            throw new IllegalStateException("derlenmedi: " + sinif);
        List<String> satir = new ArrayList<>();
        for (FieldModel f : ClassFile.of().parse(d.resolve(sinif + ".class")).fields()) {
            var b = f.flags();
            satir.add(f.fieldName().stringValue() + " " + (b.has(AccessFlag.PRIVATE) ? "ACC_PRIVATE"
                    : b.has(AccessFlag.PROTECTED) ? "ACC_PROTECTED"
                    : b.has(AccessFlag.PUBLIC) ? "ACC_PUBLIC" : "(bayrak yok)"));
        }
        return satir;
    }
}
```

#### On Altı Çiftin Kaçı Derleniyor

```java
// Erisim.java — dört görünürlük düzeyi × dört çağrı konumu
import java.util.*;

public class Erisim {
    static final String[] DUZEY = { "private", "", "protected", "public" };
    static final String[] DUZEY_ADI = { "private", "(varsayilan)", "protected", "public" };
    static final String BICIM = "%-13s %-11s %-11s %-11s %s%n";

    static String ust(String duzey) {
        return "package depo;\npublic class Ust {\n"
                + "    " + duzey + " String etiket = \"ust alan\";\n"
                + "    public String kendi() { return etiket; }\n}\n";
    }

    static Map<String, String> senaryo(String duzey, int konum) {
        Map<String, String> d = new LinkedHashMap<>();
        d.put("depo/Ust.java", ust(duzey));
        switch (konum) {
            case 1 -> d.put("depo/Komsu.java",
                    "package depo;\nclass Komsu { String oku(Ust u) { return u.etiket; } }\n");
            case 2 -> d.put("dis/AltDis.java", "package dis;\nimport depo.Ust;\n"
                    + "public class AltDis extends Ust { public String oku() { return this.etiket; } }\n");
            case 3 -> d.put("dis/Yabanci.java", "package dis;\nimport depo.Ust;\n"
                    + "class Yabanci { String oku(Ust u) { return u.etiket; } }\n");
            default -> { }
        }
        return d;
    }

    public static void main(String[] args) throws Exception {
        System.out.printf(BICIM, "duzey", "ayni sinif", "ayni paket", "alt sinif", "dis paket");
        int derlenen = 0;
        for (int i = 0; i < DUZEY.length; i++) {
            String[] h = new String[4];
            for (int j = 0; j < 4; j++) {
                boolean ok = Derle.olur(senaryo(DUZEY[i], j));
                h[j] = ok ? "derlendi" : "derlenmedi";
                if (ok) derlenen++;
            }
            System.out.printf(BICIM, DUZEY_ADI[i], h[0], h[1], h[2], h[3]);
        }
        System.out.println("16 ciftin derlenen sayisi: " + derlenen);
    }
}
```

```
duzey         ayni sinif  ayni paket  alt sinif   dis paket
private       derlendi    derlenmedi  derlenmedi  derlenmedi
(varsayilan)  derlendi    derlendi    derlenmedi  derlenmedi
protected     derlendi    derlendi    derlendi    derlenmedi
public        derlendi    derlendi    derlendi    derlendi
16 ciftin derlenen sayisi: 10
```

On altı çiftin **onu** derleniyor, altısı derlenmiyor. Tablonun biçimi bir merdivendir: her
satır bir öncekinin açtıklarını açar ve üzerine bir konum ekler. Düzeyler bu yüzden dört ayrı
kural değil, **iç içe geçmiş dört halkadır**; bir üyeyi bir düzeyden ötekine taşımak yeni bir
kapı açar, hiçbirini kapatmaz.

İlk sütun dört satırda da aynıdır. Bir sınıf kendi üyesini her düzeyde görür; `private` bile
sınıfın kendi içinde hiçbir şey kapatmaz. Görünürlük bir üyeyi sahibinden saklamaz, **çağıran
tarafı** sınırlar.

İlk satır ile ikinci satır arasındaki tek fark bir sözcüğün yokluğudur ve o yokluk aynı
paketteki bütün sınıfları içeri alır. Belirteçsiz bir alan `Komsu` sınıfından okunabiliyor;
`Komsu`, `Ust` ile hiçbir kalıtım ilişkisi taşımıyor, yalnız aynı pakette duruyor. Paket bu
tabloda bir düzenleme birimi değil, bir **erişim sınırıdır**.

Son satırın söylediği ise dördüncü sütunun ilk kez dolmasıdır. `public`, üyeyi bildiren
paketin dışına açılan tek düzeydir; kalan üçü şu ya da bu genişlikte pakete bağlıdır.

Bütün bu kararlar tek bir yerde verilir: derleyicide. On altı senaryonun hiçbirinde program
çalıştırılmadı. Altı çift için ortada bir sınıf dosyası bile yoktur — kusur, çalışan bir
programda bir hata olarak değil, **hiç var olmayan bir program** olarak ortaya çıkar. Kursun
ölçtüğü sekiz karar arasında görünürlük, çalışma zamanı tipinin payının sıfır olduğu uçtur.

#### Sınırlayıcı Ölçüm: Korumalı Düzey Paketi de Açar

Tabloda bir satır ilk okumaya direniyor. `protected` düzeyi çoğunlukla "yalnız alt sınıflara
açık" diye okunur; oysa üçüncü satırın ikinci sütunu **derlendi** diyor. Alt sınıf olmayan,
yalnız aynı pakette duran bir sınıf korumalı alanı okuyabiliyor.

Bu tek ölçüm bir kuralı sınırlamakla kalmaz, `protected` düzeyinin biçimini de değiştirir.
Ayrıntıyı görmek için aynı korumalı alan beş ayrı yerden denenir.

- **SN10** — Beş denemede de `Ust` sınıfı ve `etiket` alanı birebir aynıdır; değişen tek şey
  erişimi deneyen sınıfın paketi, kalıtım ilişkisi ve kullandığı **referansın bildirilen
  tipidir**.

```java
// Koruma.java — korumalı düzey tam olarak neyi açıyor
import java.util.*;

public class Koruma {
    static final String UST = "package depo;\npublic class Ust {\n"
            + "    protected String etiket = \"ust alan\";\n}\n";
    static final String ONEK = "package dis;\nimport depo.Ust;\n";

    static final String[][] DENEME = {
        { "ayni paket, ilgisiz sinif", "depo/Komsu.java",
          "package depo;\nclass Komsu { String oku(Ust u) { return u.etiket; } }\n" },
        { "dis paket, alt sinif, this uzerinden", "dis/A.java",
          ONEK + "public class A extends Ust { String oku() { return this.etiket; } }\n" },
        { "dis paket, alt sinif, kendi tipi uzerinden", "dis/B.java",
          ONEK + "public class B extends Ust { String oku(B b) { return b.etiket; } }\n" },
        { "dis paket, alt sinif, Ust tipi uzerinden", "dis/C.java",
          ONEK + "public class C extends Ust { String oku(Ust u) { return u.etiket; } }\n" },
        { "dis paket, ilgisiz sinif", "dis/D.java",
          ONEK + "class D { String oku(Ust u) { return u.etiket; } }\n" },
    };

    public static void main(String[] args) throws Exception {
        System.out.printf("%-44s %s%n", "erisim yeri", "sonuc");
        for (String[] d : DENEME) {
            Map<String, String> k = new LinkedHashMap<>();
            k.put("depo/Ust.java", UST);
            k.put(d[1], d[2]);
            System.out.printf("%-44s %s%n", d[0], Derle.olur(k) ? "derlendi" : "derlenmedi");
        }
    }
}
```

```
erisim yeri                                  sonuc
ayni paket, ilgisiz sinif                    derlendi
dis paket, alt sinif, this uzerinden         derlendi
dis paket, alt sinif, kendi tipi uzerinden   derlendi
dis paket, alt sinif, Ust tipi uzerinden     derlenmedi
dis paket, ilgisiz sinif                     derlenmedi
```

Beş denemenin üçü derleniyor. İlk satır sınırlayıcı ölçümün kendisidir: korumalı üye paketin
tamamına açıktır ve bunun için kalıtım gerekmez. "Korumalı, yalnız alt sınıfa açık demektir"
cümlesi ölçümle yanlışlanıyor.

Üçüncü ile dördüncü satır arasındaki fark ise `protected` düzeyinin en kolay gözden kaçan
yanıdır. İki sınıf da `Ust`'un alt sınıfıdır, ikisi de dış pakettedir, ikisi de aynı alanı
okumaya çalışır. Ayrım okumanın hangi referans üzerinden yapıldığındadır: `B` kendi tipinden
bir referans kullanıyor ve derleniyor, `C` bildirilen tipi `Ust` olan bir referans kullanıyor
ve derlenmiyor. Nesne aynı nesne olabilir; karar nesneye değil, **referansın bildirilen
tipine** bakılarak veriliyor.

Bu, dersin başındaki iddianın en keskin biçimidir. Aynı alan, aynı alt sınıf, aynı satır
biçimi — ve iki ayrı sonuç. Farkı yaratan tek şey, derleyicinin o adı çözerken gördüğü tiptir.
Dış paketteki bir alt sınıf korumalı üyeyi **kendi kalıtım hattı üzerinden** kullanabilir,
üst tipin genel bir aracı olarak kullanamaz.

#### Üye Nerede Duruyor

Derleme zamanında reddedilen erişimlerin çalışma zamanında bir karşılığı var mı? Soru,
üyenin sınıf dosyasında ne hâlde durduğuna bakılarak yanıtlanır.

```java
// Bayrak.java — dört düzey sınıf dosyasında ne bırakıyor
public class Bayrak {
    static final String KAYNAK = """
        class Uye {
            private   String gizli   = "";
                      String paket   = "";
            protected String korumali = "";
            public    String acik    = "";
        }
        """;

    public static void main(String[] args) throws Exception {
        var alanlar = Derle.alanBayraklari(KAYNAK, "Uye");
        System.out.println("sinif dosyasindaki alan sayisi: " + alanlar.size());
        for (String s : alanlar) System.out.println("  " + s);
    }
}
```

```
sinif dosyasindaki alan sayisi: 4
  gizli ACC_PRIVATE
  paket (bayrak yok)
  korumali ACC_PROTECTED
  acik ACC_PUBLIC
```

Dört alanın dördü de sınıf dosyasındadır. `private` bildirilen alan silinmemiş, gizlenmemiş,
başka bir yere taşınmamıştır; ötekilerle aynı listede, aynı biçimde durur. Aralarındaki tek
fark bir **bayraktır**.

Paket düzeyinin karşılığı da burada görünür. Öteki üç düzeyin her birinin sınıf dosyasında bir
adı vardır; belirteçsiz alanın hiçbir bayrağı yoktur. Kaynaktaki adsızlık sınıf dosyasında da
adsızdır: paket düzeyi, üç bayrağın hiçbirinin konmamış olmasıdır.

Buradan dersin ikinci yarısı çıkıyor. Görünürlük bir **saklama düzeneği değildir**; üyeyi
kimsenin ulaşamayacağı bir yere koymaz. Yaptığı şey, derleyiciye hangi kaynakları
reddedeceğini söylemektir. Kapsülleme bir engel değil, bir sözleşmedir — ve o sözleşmenin
yaptırımı, çalışan programda değil, hiç doğmayan programda görünür.

#### Kusur Kimin Derlemesinde Doğuyor

Bir düzeyi daraltmak, sahibinin kaynağında tek bir sözcük değiştirmektir. Bu değişikliğin
kimi vurduğu ölçülebilir: aynı `Ust` sınıfı iki düzeyle, önce tek başına, sonra dış paketteki
bir çağıranla birlikte derlenir.

```java
// Bedel.java — bir düzey daraltıldığında kusur kimin derlemesinde doğuyor
import java.util.*;

public class Bedel {
    static final String CAGIRAN = "package dis;\nimport depo.Ust;\n"
            + "class Yabanci { String oku(Ust u) { return u.etiket; } }\n";

    static String ust(String duzey) {
        return "package depo;\npublic class Ust {\n    " + duzey
                + " String etiket = \"ust alan\";\n}\n";
    }

    public static void main(String[] args) throws Exception {
        for (String duzey : new String[] { "public", "protected" }) {
            Map<String, String> ikisi = new LinkedHashMap<>();
            ikisi.put("depo/Ust.java", ust(duzey));
            ikisi.put("dis/Yabanci.java", CAGIRAN);
            System.out.printf("%-10s | Ust tek basina: %-9s | cagiranla birlikte: %s%n", duzey,
                    Derle.olur(Map.of("depo/Ust.java", ust(duzey))) ? "derlendi" : "derlenmedi",
                    Derle.olur(ikisi) ? "derlendi" : "derlenmedi");
        }
    }
}
```

```
public     | Ust tek basina: derlendi  | cagiranla birlikte: derlendi
protected  | Ust tek basina: derlendi  | cagiranla birlikte: derlenmedi
```

Orta sütun iki satırda da **derlendi** diyor. `Ust` sınıfının kaynağı korumalı düzeyde de
kusursuzdur; kendi başına derlenir, sınıf dosyası üretilir, hiçbir uyarı çıkmaz. Sahibinin
tarafında görünen bir şey yoktur.

Sağ sütun ise ayrılıyor. Aynı `Yabanci` sınıfı, aynı satır, iki ayrı sonuç — ve `Yabanci`
sınıfının kaynağında hiçbir şey değişmemiştir. Kusur, değişikliği yapan sınıfın derlemesinde
değil, **ona bakan sınıfın derlemesinde** doğuyor.

Bu, kursun her derste sorduğu "bedel nerede görünür" sorusunun buradaki yanıtıdır.
Görünürlüğü genişletmek geriye dönük olarak sorunsuzdur: yeni bir kapı açar, açık olanı
kapatmaz. Daraltmak ise sahibinin göremeyeceği bir yerde kırılma üretir. Bir üyeyi `public`
bildirmenin maliyeti, o üyeyi sonradan geri almanın maliyetidir.

#### Özet

- Dört görünürlük düzeyi dört çağrı konumuyla çaprazlandığında on altı çiftin **onu**
  derlenir; tablo iç içe geçmiş dört halka biçimindedir, her düzey bir öncekinin üstüne
  konum ekler.
- Görünürlük üyeyi sahibinden saklamaz — ilk sütun dört düzeyde de derlenir — sınırlanan şey
  **çağıran taraftır**; belirteçsiz düzey de bir seçimsizlik değildir, aynı paketteki ilgisiz
  sınıfları içeri alır ve paketi bir erişim sınırına çevirir.
- Korumalı düzey paket içini de açar: alt sınıf olmayan bir komşu sınıf korumalı alanı okur,
  yani "yalnız alt sınıfa açık" okuması yanlıştır.
- Dış paketteki bir alt sınıf korumalı üyeye **kendi tipinden** bir referansla erişebilir,
  bildirilen tipi üst sınıf olan bir referansla erişemez: karar nesneye değil, referansın
  bildirilen tipine bakar.
- Sınıf dosyasında dört alanın dördü de durur; ayrımı yalnız bir bayrak taşır ve paket
  düzeyinin bayrağı yoktur. Görünürlük saklama değil, derleyiciye verilen bir kuraldır.
- Bir düzey daraltıldığında sahibinin kaynağı yine derlenir; kusur **çağıranın derlemesinde**
  doğar. Genişletmek geriye dönük olarak sorunsuzdur, daraltmak başka bir yerde kırılır.

#### Sonraki Adım

İki derstir aynı ikili kullanılıyor ve ikisinde de kararın bir tarafı ölçüldü: yapıcıdan
çağrılan yöntemi çalışma zamanı tipi seçti, erişilebilirliği bildirilen tip belirledi. İkisi
yan yana konmadı. Sıradaki ders bu kursun çekirdek ölçümüdür: tek bir referans üzerinde sekiz
ayrı karar okunur ve kaçının hangi taraftan geldiği sayılır. Aynı satırda `d.ad()` ile
`d.etiket` iki karşıt yanıt verir — alanların aynı adla yeniden bildirilmesi yeniden
tanımlama değil **gizlemedir** ve gizlenen alan yok olmaz. Aynı derste `@Override` işaretinin
derleme zamanında neyi yakaladığı ve imza değiştiğinde kararın neden taraf değiştirdiği
ölçülür.

### Kalıtım ve Yeniden Tanımlama

Source: https://academia.sh/tr/kurslar/java-nesne-modeli/kalitim-ve-yeniden-tanimlama

Tek bir referans üzerinde sekiz karar okunuyor ve yanıt yarı yarıya bölünüyor: dördünü çalışma zamanı tipi, dördünü bildirilen tip veriyor. Alanların aynı adla yeniden bildirilmesi yeniden tanımlama değil gizlemedir; imza değiştiğinde ise yeniden tanımlama hiç doğmaz ve karar taraf değiştirir.

İki derstir kararın bir tarafı ayrı ayrı ölçüldü. Yapıcıdan çağrılan yeniden tanımlı yöntemi
**çalışma zamanı tipi** seçti; bir üyenin görülüp görülmediğine **bildirilen tip** karar
verdi. İkisi hiç yan yana konmadı, dolayısıyla aradaki kuralın ne olduğu da sorulmadı.

Bu ders kursun çekirdek ölçümüdür. Tek bir referans kurulur — bildirilen tipi `Ust`, çalışma
zamanı tipi `Alt` — ve o referans üzerinden sekiz ayrı dilsel karar okunur. Her karar için tek
soru sorulur: sonucu hangi tip verdi? Ölçümün kâhini kurgunun kendisidir; iki tipi bilerek
ayırdığımız için her yanıtın kaynağı baştan bellidir.

Programlama Paradigmaları kursu kalıtımın iki vaadini ve alt tip sözleşmesinin nasıl ihlal
edildiğini ölçmüştü; orada sayılan şey **sözleşme ihlaliydi**. Burada sayılan şey **kararın
tarafıdır** ve ölçüm çalıştırılmış bir koşumdan gelir.

#### Ölçüm Çekirdeği

Çekirdek üç tipten oluşur: bir arayüz (`Tasiyici`), bir üst sınıf (`Ust`) ve onu genişleten
bir alt sınıf (`Alt`). `Alt`, `Ust`'un yöntemini **yeniden tanımlar**, alanını ise aynı adla
**yeniden bildirir** — ikisinin ayrı şeyler olduğu bu dersin ikinci ölçümüdür.

- **SN11** — Her üye kendi kaynağını metin olarak döndürür (`ust alan`, `alt yontem`); dönen
  değer hangi bildirimden geldiğini kendisi söyler, çıkarım gerekmez.
- **SN12** — Sekiz kararın yedisi **aynı** referans üzerinden okunur; statik yöntem, kararı
  zaten bildirilen tip verdiği için doğrudan sınıf üzerinden çağrılır. Değişen şey okunan
  üyenin cinsidir.
- **SN13** — Ölçüm ortama bağlı hiçbir veri okumaz: kimlik karması, süre ve yol yazılmaz;
  kimlik gerekirse `==` ile karşılaştırılır.

```java
// Ust.java — kursun çekirdeği: bir arayüz, bir üst sınıf, onu genişleten bir alt sınıf
interface Tasiyici {
    default String tasi() { return "arayuz varsayilani"; }
}

class Ust implements Tasiyici {
    String etiket = "ust alan";
    String ad() { return "ust yontem"; }
    String karsila(Ust u) { return "Ust.karsila(Ust)"; }
    static String durum() { return "ust statik"; }
    @Override public String toString() { return "Ust nesnesi"; }
}

class Alt extends Ust {
    String etiket = "alt alan";
    @Override String ad() { return "alt yontem"; }
    String karsila(Alt a) { return "Alt.karsila(Alt)"; }
    static String durum() { return "alt statik"; }
    @Override public String tasi() { return "alt yeniden tanim"; }
    @Override public String toString() { return "Alt nesnesi"; }
}
```

Derste iki ölçüm koşum değil **derleme** okur: birinde bir kaynağın kabul edilip edilmediği,
ötekinde üretilen sınıf dosyasındaki alanlar sayılır. İkisi de aynı yardımcıdan gelir.

```java
// Derle.java — kaynağı derler; ya derleme sonucunu ya da bir sınıfın alanlarını verir
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Derle {
    static Path derle(String kaynak) throws Exception {
        Path d = Files.createTempDirectory("kalitim");
        Path k = d.resolve("Deneme.java");
        Files.writeString(k, kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        return ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, "-d", d.toString(), k.toString()) == 0 ? d : null;
    }

    static String olur(String kaynak) throws Exception {
        return derle(kaynak) == null ? "derlenmedi" : "derlendi";
    }

    static List<String> alanlar(String kaynak, String sinif) throws Exception {
        Path d = derle(kaynak);
        List<String> ad = new ArrayList<>();
        for (FieldModel f : ClassFile.of().parse(d.resolve(sinif + ".class")).fields())
            ad.add(f.fieldName().stringValue());
        return ad;
    }
}
```

#### Sekiz Karar, Tek Referans

```java
// Karar.java — tek referans üzerinde sekiz karar
public class Karar {
    static String secim(Ust u) { return "Ust imzasi"; }
    static String secim(Alt a) { return "Alt imzasi"; }

    static void bildir(String karar, String sonuc, String veren) {
        System.out.printf("%-24s %-22s %s%n", karar, sonuc, veren);
    }

    public static void main(String[] args) {
        Ust d = new Alt();
        System.out.println("bildirilen tip Ust, calisma zamani tipi "
                + d.getClass().getSimpleName());
        System.out.println();
        System.out.printf("%-24s %-22s %s%n", "karar", "sonuc", "karari veren");
        bildir("yeniden tanimli yontem", d.ad(), "calisma zamani tipi");
        bildir("arayuz varsayilani", d.tasi(), "calisma zamani tipi");
        bildir("metne cevirme", d.toString(), "calisma zamani tipi");
        bildir("alan okumasi", d.etiket, "bildirilen tip");
        bildir("asiri yukleme secimi", secim(d), "bildirilen tip");
        bildir("statik yontem", Ust.durum(), "bildirilen tip");
        bildir("tip sinamasi", String.valueOf(d instanceof Alt), "calisma zamani tipi");
        bildir("asagi donusum", ((Alt) d).etiket, "bildirilen tip (donusum sonrasi)");
    }
}
```

```
bildirilen tip Ust, calisma zamani tipi Alt

karar                    sonuc                  karari veren
yeniden tanimli yontem   alt yontem             calisma zamani tipi
arayuz varsayilani       alt yeniden tanim      calisma zamani tipi
metne cevirme            Alt nesnesi            calisma zamani tipi
alan okumasi             ust alan               bildirilen tip
asiri yukleme secimi     Ust imzasi             bildirilen tip
statik yontem            ust statik             bildirilen tip
tip sinamasi             true                   calisma zamani tipi
asagi donusum            alt alan               bildirilen tip (donusum sonrasi)
```

Sekiz kararın **dördü** çalışma zamanı tipinden, **dördü** bildirilen tipten geliyor. "Java
nesneye dayalıdır, çağrı gerçek nesneye gider" cümlesi kararların yalnız yarısını açıklar.

Ayrım rastgele değildir. Üstteki üç satır ile yedinci satır **çalışmayı** ilgilendirir:
hangi gövdenin yürütüleceği, nesnenin gerçekten ne olduğu. Bunlar çalışma zamanı tipine
bakar, çünkü yanıt ancak nesne ortadayken bilinebilir. Kalan dördü **adı çözme** işidir:
hangi alan okunacak, hangi imza seçilecek, hangi sınıfın statik yöntemi çağrılacak. Bunlar
derleyicinin masasında biter ve derleyicinin elinde yalnız bildirilen tip vardır.

Kural tek cümlede toplanabilir: **çağrılan davranış çalışma zamanı tipine, seçilen ad
bildirilen tipe bakar.** İkinci satır bunun en görünmez örneğidir — arayüzden gelen bir
varsayılan yöntem de yeniden tanımlanabilir ve kararı yine çalışma zamanı tipi verir.

Son satır dördüncü bir okuma ekliyor. `((Alt) d).etiket` ifadesi **alt alan** veriyor, oysa
nesne baştan beri aynı nesnedir. Aşağı dönüşüm nesneyi değiştirmez, **bildirilen tipi**
değiştirir; derleyiciye verilen bir sözdür ve sözün doğruluğu çalışma zamanında sınanır.

#### Yeniden Tanımlama ile Gizlemenin Ayrımı

Dördüncü satır dersin en sessiz sonucudur. `d.ad()` **alt yontem** verirken `d.etiket` **ust
alan** veriyor; ikisi de aynı `d` üzerinden, iki karakterlik bir fark ile yazılıyor. Alanların
aynı adla yeniden bildirilmesi yeniden tanımlama değildir; **gizlemedir**. Gizlenen alanın ne
olduğunu görmek için tek nesneye iki ayrı referans bağlanır.

```java
// Gizleme.java — gizlenen alan yok olmuyor
public class Gizleme {
    public static void main(String[] args) {
        Ust u = new Alt();
        Alt a = (Alt) u;
        System.out.println("iki referans ayni nesne mi : " + (u == a));
        System.out.println("Ust referansindan etiket   : " + u.etiket);
        System.out.println("Alt referansindan etiket   : " + a.etiket);
        System.out.println("Ust referansindan ad()     : " + u.ad());
        System.out.println("Alt referansindan ad()     : " + a.ad());
        a.etiket = "yazildi";
        System.out.println("a.etiket yazildiktan sonra u.etiket: " + u.etiket);
    }
}
```

```
iki referans ayni nesne mi : true
Ust referansindan etiket   : ust alan
Alt referansindan etiket   : alt alan
Ust referansindan ad()     : alt yontem
Alt referansindan ad()     : alt yontem
a.etiket yazildiktan sonra u.etiket: ust alan
```

İlk satır ölçümün dayanağıdır: ortada tek bir nesne vardır. Buna karşın o tek nesne `etiket`
sorusuna **iki ayrı yanıt** veriyor, `ad()` sorusuna **tek yanıt**. Yeniden tanımlanan yöntem
üsttekinin yerini alır; yeniden bildirilen alan üsttekinin yerini almaz, **üstüne konur.**

Son satır bunun bellek karşılığıdır. `a.etiket` alanına yazıldıktan sonra `u.etiket` hâlâ
**ust alan** diyor. Aynı nesnenin içinde iki ayrı yer vardır ve biri yazıldığında öteki
değişmez. Gizlenen alan yok olmamıştır; yalnız bildirilen tipi `Alt` olan bir referanstan
görünmez olmuştur.

İki yerin gerçekten iki yer olduğu sınıf dosyasından da okunabilir. Aynı ikili derlenir ve
her iki sınıfın bildirdiği alanlar listelenir.

```java
// Yer.java — gizlenen alan sınıf dosyasında nerede duruyor
public class Yer {
    static final String KAYNAK = """
        class Ust { String etiket = "ust alan"; String ad() { return "ust yontem"; } }
        class Alt extends Ust { String etiket = "alt alan";
                                @Override String ad() { return "alt yontem"; } }
        """;

    public static void main(String[] args) throws Exception {
        System.out.println("Ust sinif dosyasindaki alanlar: " + Derle.alanlar(KAYNAK, "Ust"));
        System.out.println("Alt sinif dosyasindaki alanlar: " + Derle.alanlar(KAYNAK, "Alt"));
    }
}
```

```
Ust sinif dosyasindaki alanlar: [etiket]
Alt sinif dosyasindaki alanlar: [etiket]
```

İki ayrı sınıf dosyası, her birinde bir `etiket` bildirimi. Yeniden tanımlanan `ad()` yöntemi
için de iki gövde vardır, ama çağrı yalnız birine gider; alanda böyle bir seçim yoktur, ikisi
de okunabilir durumdadır. Kalıtım yöntemde **birini ötekinin yerine koyar**, alanda **ikisini
yan yana bırakır**.

Bedeli buradan okunur. Bir alt sınıfa üstündekiyle aynı adı taşıyan bir alan yazan
programcı, üstteki alanı değiştirdiğini düşünür. Değiştirmemiştir — iki alan da yaşamaya
devam eder ve hangisinin okunacağı, o satırdaki referansın bildirilen tipine bakılarak
belirlenir. Kusur yazan yerde görünmez; okuyan yerde, hem de yanlış değeri sessizce
döndürerek görünür.

#### Derleyicinin Yakaladığı ve Kaçırdığı

Yeniden tanımlama bir bildirim değildir; iki imza denk düştüğünde kendiliğinden doğar. Bu
yüzden **doğmaması** da kendiliğinden olur ve sessizdir. `@Override` işareti tam olarak bu
sessizliği bozmak içindir: alt sınıfa "bu yöntem gerçekten bir yeniden tanımlama mı" sorusunu
derleyiciye sordurur.

- **SN14** — Beş denemede de üst sınıf birebir aynıdır; değişen tek şey alt sınıfın tek
  satırlık bildirimidir. Ölçülen yalnız derleme sonucudur.

```java
// Isaret.java — @Override ne yakalıyor, imza ne kadar oynayabiliyor
public class Isaret {
    static final String TABAN = """
        class A { String ad() { return "ust"; } A uret() { return this; }
                  String karsila(A a) { return "A"; } }
        """;

    static final String[][] DENEME = {
        { "isaretsiz, ad yanlis yazilmis", "class B extends A { String adi() { return \"alt\"; } }" },
        { "isaretli, ad yanlis yazilmis", "class B extends A { @Override String adi() { return \"alt\"; } }" },
        { "isaretli, donus tipi daraltilmis", "class B extends A { @Override B uret() { return this; } }" },
        { "isaretli, donus tipi genisletilmis", "class B extends A { @Override Object uret() { return this; } }" },
        { "isaretli, parametre tipi daraltilmis", "class B extends A { @Override String karsila(B b) { return \"B\"; } }" },
    };

    public static void main(String[] args) throws Exception {
        System.out.printf("%-38s %s%n", "alt sinif", "sonuc");
        for (String[] d : DENEME)
            System.out.printf("%-38s %s%n", d[0], Derle.olur(TABAN + d[1] + "\n"));
    }
}
```

```
alt sinif                              sonuc
isaretsiz, ad yanlis yazilmis          derlendi
isaretli, ad yanlis yazilmis           derlenmedi
isaretli, donus tipi daraltilmis       derlendi
isaretli, donus tipi genisletilmis     derlenmedi
isaretli, parametre tipi daraltilmis   derlenmedi
```

İlk iki satır işaretin bütün değerini taşıyor. Adı yanlış yazılmış bir yöntem, işaret
konmadığında **derleniyor**: derleyici için ortada bir kusur yoktur, alt sınıfa yeni bir
yöntem eklenmiştir. Program çalışır, hiçbir uyarı çıkmaz ve üst sınıfın sürümü çağrılmaya
devam eder. Aynı kaynak `@Override` ile **derlenmiyor**. İşaret bir davranış eklemez; yalnız
bir varsayımı derleyiciye bildirir ve varsayım tutmadığında kaynağı reddettirir.

Üçüncü ile dördüncü satır dönüş tipinin ne kadar oynayabildiğini ölçüyor. Yeniden tanımlayan
yöntem dönüş tipini **daraltabilir** — üstteki `A` döndürüyorsa alttaki `B` döndürebilir — ama
genişletemez. Nedeni alt tip sözleşmesidir: `A` bekleyen çağıran taraf bir `B` aldığında
sözleşme bozulmaz, ama `Object` aldığında bozulur. Daraltma çağırana verilen sözü güçlendirir,
genişletme zayıflatır.

Beşinci satır sıradaki bölümün kapısını açıyor. Parametre tipi daraltıldığında işaretli kaynak
**derlenmiyor**: ortada bir yeniden tanımlama yoktur. Dönüş tipinde serbest olan daraltma,
parametrede yasaktır — ve bunun sonucu yalnız bir derleme hatası değildir.

#### Sınırlayıcı Ölçüm: İmza Değişince Karar Taraf Değiştirir

`@Override` yazılmadığında aynı bildirim derlenir. O zaman ne olur? Çekirdeğe eklenen
`karsila` çifti tam olarak bu durumdadır: `Ust` bir `Ust` alır, `Alt` bir `Alt` alır, imzalar
denk düşmez, dolayısıyla ortada yeniden tanımlama değil **aşırı yükleme** vardır.

- **SN15** — İki çağrıda da nesne aynı nesnedir ve argüman aynı argümandır; değişen tek şey
  çağrının yapıldığı referansın bildirilen tipidir.

```java
// Imza.java — imza değişince yeniden tanımlama yerine aşırı yükleme oluyor
public class Imza {
    public static void main(String[] args) {
        Ust d = new Alt();
        Alt gercek = new Alt();
        System.out.println("Ust referansindan karsila : " + d.karsila(gercek));
        System.out.println("Alt referansindan karsila : " + ((Alt) d).karsila(gercek));
        System.out.println("Ust referansindan ad()    : " + d.ad());
        System.out.println("Alt referansindan ad()    : " + ((Alt) d).ad());
    }
}
```

```
Ust referansindan karsila : Ust.karsila(Ust)
Alt referansindan karsila : Alt.karsila(Alt)
Ust referansindan ad()    : alt yontem
Alt referansindan ad()    : alt yontem
```

Dört satır ikişer ikişer okunur. Alt iki satır yeniden tanımlamanın imzasıdır: iki ayrı
referans, **tek yanıt**. Referansın bildirilen tipini değiştirmek sonucu değiştirmiyor, çünkü
kararı veren taraf o değil.

Üst iki satır ise aşırı yüklemenin imzasıdır: aynı nesne, aynı argüman, **iki ayrı yanıt**.
Bir tek karakter bile değişmeden yazılabilecek iki çağrı, yalnız referansın bildirilen tipi
yüzünden ayrı gövdelere gidiyor. Karar tarafı yer değiştirmiştir.

Sınırın anlamı budur. Bir alt sınıfa yöntem yazan programcı çoğu zaman yeniden tanımladığını
sanır; imzada bir harf ya da bir parametre tipi kayarsa yeniden tanımlama hiç doğmaz ve
yerine, çalışma zamanı tipini hiç dinlemeyen bir seçim geçer. Program derlenir, çalışır ve
üst sınıfın sürümünü çağırır. Bu yüzden `@Override`, kaynağa eklenen bir açıklama değil,
kararın hangi taraftan geleceğini derleme zamanında dondurmanın tek yoludur.

#### Özet

- Tek referans üzerinde sekiz karar okunur ve yanıt yarı yarıya bölünür: **dördü** çalışma
  zamanı tipinden, **dördü** bildirilen tipten gelir.
- Ayrım kuralı tektir: **çağrılan davranış** çalışma zamanı tipine, **seçilen ad** bildirilen
  tipe bakar. Arayüzden gelen varsayılan yöntem de yeniden tanımlanabilir ve ilk gruba girer.
- Aşağı dönüşüm nesneyi değiştirmez, bildirilen tipi değiştirir: `((Alt) d).etiket` **alt
  alan** verirken nesne baştan beri aynı nesnedir.
- Alanların aynı adla yeniden bildirilmesi **gizlemedir**: tek nesne `etiket` sorusuna iki
  yanıt verir ve birine yazmak ötekini değiştirmez — gizlenen alan yok olmaz.
- `@Override` bir davranış eklemez, bir varsayımı bildirir: adı yanlış yazılmış yöntem
  işaretsiz **derlenir**, işaretli **derlenmez**. Dönüş tipi daraltılabilir, genişletilemez.
- İmza denk düşmediğinde yeniden tanımlama doğmaz, aşırı yükleme olur ve karar taraf
  değiştirir: `karsila` iki referanstan iki ayrı yanıt verirken `ad()` tek yanıt verir.

#### Sonraki Adım

Bu derste `Ust` bir gövde taşıyordu: her yöntemin bir gerçekleştirimi vardı ve alt sınıf
onları isterse yeniden tanımlıyordu. Arayüzden gelen varsayılan yöntem de tabloya bir satır
olarak girdi, ama arayüzün ne işe yaradığı sorulmadı. Sıradaki ders soyutlamanın iki aracını
yan yana koyar: bir yetenek soyut sınıfla da arayüzle de kurulabilir, biri tek üstle
sınırlıdır, öteki çoklu. Ölçüm iki arayüzün aynı varsayılan yöntemi verdiği duruma bakar —
derleyici kaç durumda karar vermeyi reddediyor, kaç durumda sessizce bir taraf kazanıyor — ve
durum taşıyan bir yeteneğin arayüzle neden kurulamadığını gösterir.

### Soyut Sınıflar ve Arayüzler

Source: https://academia.sh/tr/kurslar/java-nesne-modeli/soyut-siniflar-ve-arayuzler

Aynı yetenek sekiz ayrı yoldan kurulduğunda derleme üçünde duruyor; kalan beşte derleyici kararı kendisi veriyor ve tarafı hiç sormuyor — sınıf gövdesi arayüz varsayılanını yener, alt arayüz üsttekini yener, yalnız iki bağımsız arayüz aynı varsayılanı verdiğinde reddeder. Durum taşıyan bir yetenek arayüzle kurulamaz.

Önceki ders sekiz kararı tek bir referans üzerinde okudu ve tablodaki ikinci satır bir
arayüzden geliyordu: `Tasiyici` arayüzünün varsayılan yöntemi de yeniden tanımlanabiliyor ve
kararı çalışma zamanı tipi veriyordu. Arayüzün kendisi ise yalnız bir satır olarak geçti.

Bu ders onu konu yapar. Java'da bir yetenek iki ayrı araçla kurulabilir: gövdesi eksik bir
**soyut sınıf** ya da bir **arayüz**. İkisi de aynı işi görüyor gibi durur ve seçim
çoğunlukla bir tasarım tercihi diye anlatılır. Ölçüm başka bir şeye bakar: **derleyici hangi
kurulumu reddediyor ve reddetmediğinde kararı kime bırakıyor?**

Programlama Paradigmaları kursu arayüz ile soyut sınıf arasındaki seçimi tasarım gerekçeleriyle
karşılaştırmıştı; o gerekçeler tekrarlanmaz. Burada sayılan şey derleyicinin durduğu
noktalardır.

#### İki Araç, İki Ayrı Kısıt

Soyut sınıf bir sınıftır: alan taşır, yapıcısı vardır, gövdeli ve gövdesiz yöntemleri yan
yana koyabilir. Karşılığında Java'nın tek üstlü kalıtımına bağlıdır — bir sınıf yalnız bir
sınıfı genişletebilir.

Arayüz tersini yapar. Bir sınıf istediği kadar arayüzü gerçekleştirebilir, dolayısıyla
yetenekler bir sınıfa yan yana takılabilir. Karşılığında arayüz **örnek durumu tutamaz**:
alan bildirimleri örtük olarak sabittir. Varsayılan yöntemler arayüze gövde koyma imkânı
verir, ama üzerinde çalışacak bir durum vermez.

Bu iki kısıt bir araya geldiğinde bir soru doğar. Çoklu gerçekleştirme serbestse, iki ayrı
arayüz **aynı imzayla** gövdeli birer yöntem verdiğinde ne olur? Kalıtımda böyle bir durum
yoktur, çünkü üst yalnız bir tanedir. Ölçüm buraya bakar.

#### Ölçüm Çekirdeği

Ölçümün birinci yarısı hiçbir şey çalıştırmaz: her kurulum bir kaynak metnidir, derleyici
program içinden çağrılır ve yalnız çıkış durumu okunur.

- **SN16** — Sekiz kurulumun hepsi aynı yeteneği kurar: `tasi` adlı, dizgi döndüren tek bir
  yöntem. Değişen tek şey yeteneğin hangi tip biçimiyle verildiğidir.
- **SN17** — Derleyicinin ürettiği iletiler yutulur; okunan tek şey kaynağın kabul edilip
  edilmediğidir.

```java
// Derle.java — verilen kaynağın derlenip derlenmediğini söyler
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.file.*;
import java.util.spi.ToolProvider;

class Derle {
    static String olur(String kaynak) throws Exception {
        Path d = Files.createTempDirectory("soyut");
        Path k = d.resolve("Deneme.java");
        Files.writeString(k, kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        return ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, "-d", d.toString(), k.toString()) == 0
                ? "derlendi" : "derlenmedi";
    }
}
```

#### Sekiz Kurulum, Derleme Kaçında Duruyor

```java
// Kurulum.java — aynı yetenek sekiz ayrı yoldan kuruluyor, kaçı derleniyor
public class Kurulum {
    static final String[][] DENEME = {
        { "tek soyut sinifi genisletme",
          "abstract class A { abstract String tasi(); }\nclass C extends A { public String tasi() { return \"c\"; } }" },
        { "iki soyut sinifi genisletme",
          "abstract class A { }\nabstract class B { }\nclass C extends A, B { }" },
        { "soyut yontemi gerceklestirmeme",
          "abstract class A { abstract String tasi(); }\nclass C extends A { }" },
        { "iki arayuzu gerceklestirme",
          "interface A { default String tasi() { return \"a\"; } }\ninterface B { default String ad() { return \"b\"; } }\nclass C implements A, B { }" },
        { "iki arayuz ayni varsayilan, cozumsuz",
          "interface A { default String tasi() { return \"a\"; } }\ninterface B { default String tasi() { return \"b\"; } }\nclass C implements A, B { }" },
        { "iki arayuz ayni varsayilan, sinif cozer",
          "interface A { default String tasi() { return \"a\"; } }\ninterface B { default String tasi() { return \"b\"; } }\nclass C implements A, B { public String tasi() { return \"c\"; } }" },
        { "arayuz ile soyut sinif ayni yontem",
          "interface A { default String tasi() { return \"a\"; } }\nabstract class S implements A { public String tasi() { return \"s\"; } }\nclass C extends S { }" },
        { "alt arayuz ustteki varsayilani ezer",
          "interface A { default String tasi() { return \"a\"; } }\ninterface B extends A { default String tasi() { return \"b\"; } }\nclass C implements B { }" },
    };

    public static void main(String[] args) throws Exception {
        System.out.printf("%-40s %s%n", "kurulum", "sonuc");
        int durdu = 0;
        for (String[] d : DENEME) {
            String s = Derle.olur(d[1] + "\n");
            if (s.equals("derlenmedi")) durdu++;
            System.out.printf("%-40s %s%n", d[0], s);
        }
        System.out.println("derlemenin durdugu kurulum sayisi: " + durdu + " / " + DENEME.length);
    }
}
```

```
kurulum                                  sonuc
tek soyut sinifi genisletme              derlendi
iki soyut sinifi genisletme              derlenmedi
soyut yontemi gerceklestirmeme           derlenmedi
iki arayuzu gerceklestirme               derlendi
iki arayuz ayni varsayilan, cozumsuz     derlenmedi
iki arayuz ayni varsayilan, sinif cozer  derlendi
arayuz ile soyut sinif ayni yontem       derlendi
alt arayuz ustteki varsayilani ezer      derlendi
derlemenin durdugu kurulum sayisi: 3 / 8
```

Sekiz kurulumun **üçünde** derleme duruyor, **beşinde** sürüyor. Duran üçü ayrı nedenlerle
duruyor ve üçü de bu dersin iki aracını tanımlıyor.

İkinci satır soyut sınıfın kısıtıdır: iki sınıfı birden genişletmek yazılamıyor bile. Tek
üstlü kalıtım bir tasarım önerisi değil, dilin reddettiği bir yazımdır. Üçüncü satır soyut
sınıfın sözleşmesidir: gövdesiz bırakılan yöntem alt sınıfta doldurulmadığında sınıf somut
olamaz. Soyut sınıf bir eksiklik bildirir ve eksikliğin kapatılmasını derleme zamanında
zorlar.

Beşinci satır ise arayüzün karşılığıdır ve bu dersin çekirdek ölçümüdür. İki bağımsız arayüz
aynı imzayla gövdeli birer yöntem verdiğinde derleyici **karar vermeyi reddediyor.** Ortada
bir belirsizlik yoktur ki çözülsün — iki aday da eşit uzaklıktadır ve dil aralarında bir
öncelik tanımlamak yerine kaynağı geri çeviriyor. Kalıtımda bu durum hiç doğmaz; çoklu
gerçekleştirmenin bedeli tam olarak budur.

Altıncı satır bedelin nasıl ödendiğini gösteriyor. Aynı iki arayüz, aynı çakışma — ama sınıf
kendi gövdesini yazdığında kaynak **derleniyor**. Derleyicinin istediği şey adaylardan birini
seçmek değil, seçimin **kaynakta yazılmasıdır.**

Derlenen satırların en sade ikisi, iki aracın neden yan yana durduğunu söylüyor. Birinci satır
soyut sınıfın verdiğidir: eksik bırakılan yöntem alt sınıfta doldurulunca sınıf somut olur ve
zincir kurulur. Dördüncü satır arayüzün verdiğidir: `C` sınıfı iki ayrı yeteneği aynı anda
üstleniyor ve ikisi de imzalarıyla çakışmadığı için ortada soru yoktur. Çakışma bir kural
değil, bir **kesişme** durumudur; yalnız iki bağımsız arayüz aynı imzayı verdiğinde doğar.
Ölçümün geri kalanı o kesişmenin nasıl kapatıldığını sayar.

#### Derleme Durmadığında Kim Kazanıyor

Kalan beş kurulumun üçünde ortada birden fazla aday vardır ve derleme yine de sürüyor. Bu
durumlarda kararı kim veriyor?

- **SN18** — Dört kurulumda da çağrı, bildirilen tipi `Tasiyici` olan bir referans üzerinden
  yapılır; değişen tek şey çalışma zamanı tipini kuran tip hiyerarşisidir.
- **SN19** — Her gövde kendi kaynağını metin olarak döndürür; kazanan taraf çıkarımla değil,
  dönen değerden okunur.

```java
// Tasiyici.java — aynı yeteneği iki yoldan veren tipler
interface Tasiyici {
    default String tasi() { return "arayuz varsayilani"; }
}

interface Etiketli extends Tasiyici {
    @Override default String tasi() { return "alt arayuz varsayilani"; }
}

interface Ikinci {
    default String tasi() { return "ikinci arayuz varsayilani"; }
}

abstract class SoyutTasiyici implements Tasiyici {
    @Override public String tasi() { return "soyut sinif govdesi"; }
    abstract String ad();
}

class Ozgul implements Etiketli { }

class SinifKazanir extends SoyutTasiyici implements Tasiyici {
    @Override String ad() { return "sinif"; }
}

class Cozen implements Tasiyici, Ikinci {
    @Override public String tasi() { return "sinif kendi cozdu"; }
}

class Secen implements Tasiyici, Ikinci {
    @Override public String tasi() { return Ikinci.super.tasi(); }
}
```

```java
// Kazanan.java — derleme durmadığında hangi taraf kazanıyor
public class Kazanan {
    public static void main(String[] args) {
        Tasiyici a = new Ozgul();
        Tasiyici b = new SinifKazanir();
        Tasiyici c = new Cozen();
        Tasiyici e = new Secen();
        System.out.printf("%-36s %s%n", "kaynak", "tasi() sonucu");
        System.out.printf("%-36s %s%n", "arayuz, ustteki ve alttaki", a.tasi());
        System.out.printf("%-36s %s%n", "arayuz varsayilani ve sinif govdesi", b.tasi());
        System.out.printf("%-36s %s%n", "iki bagimsiz arayuz, sinif cozer", c.tasi());
        System.out.printf("%-36s %s%n", "iki bagimsiz arayuz, sinif secer", e.tasi());
    }
}
```

```
kaynak                               tasi() sonucu
arayuz, ustteki ve alttaki           alt arayuz varsayilani
arayuz varsayilani ve sinif govdesi  soyut sinif govdesi
iki bagimsiz arayuz, sinif cozer     sinif kendi cozdu
iki bagimsiz arayuz, sinif secer     ikinci arayuz varsayilani
```

Dört satır, dört ayrı seçim ve yalnız son ikisi kaynakta yazılıdır.

İlk satırda iki arayüz vardır, ama biri ötekini genişletiyor. Bu bir çakışma değildir:
`Etiketli`, `Tasiyici`'nin varsayılanını **yeniden tanımlar** ve daha özgül olan kazanır. Kural
kalıtımdaki kuralın aynısıdır; iki tip arasında bir alt–üst ilişkisi varsa alttaki seçilir.

İkinci satır dilin sessiz önceliğidir. `SinifKazanir` hem soyut sınıftan bir gövde, hem
arayüzden bir varsayılan alıyor. Kaynakta bir seçim yazılı değildir, buna karşın derleme
duruyor ve sonuç **soyut sınıf gövdesi** çıkıyor. Sınıf hattından gelen gövde arayüz
varsayılanını her zaman yener. Bu kural bir belirsizliği çözer, ama çözdüğünü kimseye
sormaz — arayüzü yazan taraf gövdesinin çağrılmadığını hiçbir yerde göremez.

Üçüncü satır ise beşinci kurulumun derlenen biçimidir. `Cozen` iki bağımsız arayüzü
gerçekleştiriyor ve kendi gövdesini yazıyor; kazanan **kaynakta yazılan** taraftır. Burada
çakışma çözülmemiş, yalnız aşılmıştır: iki varsayılan gövdenin ikisi de çağrılmıyor.

Dördüncü satır çakışmanın asıl çözümüdür. `Secen` de kendi gövdesini yazıyor, ama gövdesinde
adaylardan birini adıyla çağırıyor — sonuç **ikinci arayuz varsayilani**. Derleyicinin istediği
şey yeni bir davranış değildi; **hangi adayın seçildiğinin yazılmasıydı** ve dil bu seçimi
yazmak için bir biçim veriyor. Reddedilen kaynak ile kabul edilen kaynak arasındaki fark bir
gerçekleştirim değil, bir imzadır.

Buradan dersin ana okuması çıkıyor. Derleyici üç ayrı davranış gösteriyor: bir hiyerarşi varsa en
özgülü seçer; sınıf ile arayüz karşılaştığında sınıfı seçer; iki eşit arayüz karşılaştığında
seçmez. Yalnız sonuncusu programcıya görünür. İlk ikisi de birer karardır ve ikisi de bildirilen
tiplerin hiyerarşisine bakılarak **derleme zamanında** verilir; çalışma zamanı tipinin buradaki
payı, hangi gövdenin çağrılacağını değil, o gövdeyi taşıyan sınıfın ne olduğunu belirlemekle
sınırlıdır.

#### Sınırlayıcı Ölçüm: Durum Taşıyan Yetenek Arayüzle Kurulamaz

Tablo arayüzü çoklu gerçekleştirme yüzünden daha esnek gösteriyor. Sınır, arayüzün ne
tutamadığındadır.

- **SN20** — Üç kurulumun üçü de aynı yeteneği kurar: bir sayacı bir artıran tek bir işlem.
  Değişen tek şey sayacın nerede tutulduğudur.

```java
// Durum.java — durum taşıyan bir yetenek hangi araçla kurulabiliyor
public class Durum {
    static final String[][] DENEME = {
        { "arayuzde sabit bildirimi",
          "interface Sayac { int BASLANGIC = 0; }" },
        { "arayuzde ornek alani ve artirma",
          "interface Sayac { int deger = 0; default void artir() { deger++; } }" },
        { "soyut sinifta alan ve artirma",
          "abstract class Sayac { int deger = 0; void artir() { deger++; } }" },
    };

    public static void main(String[] args) throws Exception {
        System.out.printf("%-36s %s%n", "yetenek nasil kuruluyor", "sonuc");
        for (String[] d : DENEME)
            System.out.printf("%-36s %s%n", d[0], Derle.olur(d[1] + "\n"));
        System.out.println();
        Adet x = new Adet(), y = new Adet();
        x.artir(); x.artir(); y.artir();
        System.out.println("soyut siniftan turetilen iki ornek : " + x.deger + " ve " + y.deger);
        System.out.println("arayuzdeki sabit ornek olmadan okunur: " + Sabitli.BASLANGIC);
    }
}

abstract class SoyutSayac {
    int deger = 0;
    void artir() { deger++; }
}

class Adet extends SoyutSayac { }

interface Sabitli { int BASLANGIC = 0; }
```

```
yetenek nasil kuruluyor              sonuc
arayuzde sabit bildirimi             derlendi
arayuzde ornek alani ve artirma      derlenmedi
soyut sinifta alan ve artirma        derlendi

soyut siniftan turetilen iki ornek : 2 ve 1
arayuzdeki sabit ornek olmadan okunur: 0
```

İlk satır yanıltıcıdır: arayüzde bir alan bildirilebiliyor. İkinci satır onun ne olduğunu
söylüyor — o alan artırılamaz, çünkü örtük olarak sabittir. Arayüzde yazılan her alan
bildirimi, yazılmasa da, sınıf düzeyinde ve değiştirilemezdir. Son satırın gösterdiği gibi
hiçbir örnek kurulmadan okunabiliyor, yani bir örneğe ait değildir.

Üçüncü satır ile alttaki koşum karşı tarafı ölçüyor. Aynı yetenek soyut sınıfla kurulduğunda
derleniyor ve iki örnek **2** ile **1** sayıyor: her nesnenin kendi sayacı var. Yeteneğin
gerektirdiği şey bir gövde değil, gövdenin üzerinde çalışacağı **örneğe ait bir yerdi** ve
arayüz o yeri veremiyor. İki sayının ayrı çıkması ölçümün kendisidir: tek bir yer paylaşılsaydı
her iki referans da **3** okurdu.

Sınırın anlamı, dersin ilk yarısındaki esnekliği tersine çeviriyor. Arayüz çoklu takılabilir
olduğu için daha geniş görünür; oysa taşıyabildiği şey yalnız davranıştır. Bir yetenek durum
tutuyorsa çoklu gerçekleştirme zaten sorulamaz, çünkü tek aday vardır. İki aracın seçimi bir
üslup sorusu değildir: yetenek durumsuzsa iki yol da açıktır ve arayüz çoklu takılmayı
kazandırır; yetenek durum tutuyorsa yol tektir ve onunla birlikte tek üstlü kalıtımın kısıtı
da gelir.

#### Özet

- Aynı yeteneğin sekiz kurulumundan **üçünde** derleme durur: iki sınıfı birden genişletmek,
  soyut yöntemi doldurmamak ve iki bağımsız arayüzün aynı varsayılanı vermesi.
- İki bağımsız arayüz aynı imzayla gövde verdiğinde derleyici **karar vermeyi reddeder**;
  aynı kaynak, sınıf kendi gövdesini yazdığında derlenir. İstenen şey yeni bir davranış
  değil, seçimin kaynakta yazılmasıdır — sınıf adaylardan birini adıyla da çağırabilir.
- Derleme durmadığında karar yine derleyicinindir ve sessizdir: hiyerarşi varsa **en özgül**
  arayüz kazanır; sınıf ile arayüz karşılaştığında **sınıf gövdesi** kazanır.
- Sessiz kararın bedeli görünmezliğidir: arayüz varsayılanını yazan taraf, gövdesinin hiç
  çağrılmadığını kendi kaynağına bakarak göremez.
- Arayüzde bildirilen alan örtük olarak sabittir ve örnek olmadan okunur; artırılmaya
  çalışıldığında kaynak **derlenmez**.
- Aynı sayaç soyut sınıfla kurulduğunda iki örnek **2** ile **1** sayar: durum taşıyan bir
  yetenek arayüzle kurulamaz ve o yolu seçmek tek üstlü kalıtımın kısıtını da getirir.

#### Sonraki Adım

Bu derste iki soyutlama aracı da **örnek** düzeyinde çalışıyordu: soyut sınıfın gövdesi de,
arayüzün varsayılan yöntemi de bir nesne üzerinden çağrıldı ve hangi gövdenin yürütüleceği
sorusu hep bir örneğin çalışma zamanı tipiyle birlikte soruldu. Sıradaki ders sınıfın
kendisine ait üyelere bakar ve kararın tarafının orada ters döndüğünü ölçer.

### Statik Üyeler ve Başlatma Blokları

Source: https://academia.sh/tr/kurslar/java-nesne-modeli/statik-uyeler-ve-baslatma-bloklari

Statik yöntem yeniden tanımlanmaz, gizlenir: aynı çağrı yazımı bildirilen tipten seçilir ve boş referans üzerinden yapılan statik çağrı bile çalışır. Sınıf düzeyi durumun tek kopyası, sınıf başlatmasını hangi erişimin tetiklediği ve aynı satırın iki ayrı tarafa gittiği durum ölçülür.

Önceki ders soyutlamanın iki aracını yan yana koydu: soyut sınıf tek üstle, arayüz çokluyla
çalışıyordu ve iki bağımsız arayüz aynı varsayılanı verdiğinde derleyici karar vermeyi
reddediyordu. İki aracın da ortak yanı vardı — ikisi de **örnek** düzeyinde iş görüyordu.
Çağrılan her yöntem bir nesneye aitti, dolayısıyla kararı çoğunlukla çalışma zamanı tipi
veriyordu.

Bu derste konu sınıfın kendisine ait üyelerdir: **statik yöntem**, **sınıf değişkeni** ve
**başlatma bloğu**. Nesne olmadan çağrılan bir yöntemin arkasında çalışma zamanı tipi yoktur;
geriye yalnız derleyicinin gördüğü ad kalır. Sorular üç tanedir: aynı satır bir örnek yöntemi
ile bir statik yöntem için farklı taraflara mı gidiyor, sınıf düzeyi durum kaç kopya taşıyor
ve bir sınıfın başlatma bloğunu hangi erişim tetikliyor?

#### Ölçüm Çekirdeği

Kursun çekirdeği aynı ikilidir: bildirilen tip `Ust`, çalışma zamanı tipi `Alt`. Bu derste
ikiliye iki üye eklenir — bir sınıf değişkeni (`uretilen`) ve bir örnek alanı (`sira`) — ama
var olan üyelerin davranışı değişmez.

- **SN21** — İki tip bilerek ayrılmıştır; kâhin kurgunun kendisidir. `Ust d = new Alt()`
  yazıldığında bir yanıtın hangi tipten geldiğini yanıta bakmadan biliriz.
- **SN22** — `Ust` ve `Alt` ikilisine yalnız üye eklenir; ortak tanımdaki üyelerin ürettiği
  değerler değişmez.
- **SN23** — Başlatma ölçümünde her senaryo **ayrı bir yükleyicide** çalışır; bir senaryonun
  başlattığı sınıf ötekini etkilemez.
- **SN24** — Başlatma adımları çalıştıklarında adlarını basar; sıra bir çıkarım değil dökümdür.
- **SN25** — Sayılan tek şey kararın tarafıdır. Komut sayısı ve eklenen adım bu kursta
  sayılmaz; sınıf dosyası yalnız kararın nerede donduğunu göstermek için okunur ve okuma
  derleyiciyi program içinden çağırıp ürettiği dosyayı standart kitaplıkla ayrıştırır.

```java
// Ust.java — kursun çekirdeği: örnek yöntemi ile statik yöntem yan yana
class Ust {
    static int uretilen = 0;
    final int sira;
    Ust() { sira = ++uretilen; }
    String ad() { return "ust yontem"; }
    static String durum() { return "ust statik"; }
}
```

```java
// Alt.java — aynı iki adı yeniden bildiren alt sınıf
class Alt extends Ust {
    @Override String ad() { return "alt yontem"; }
    static String durum() { return "alt statik"; }
}
```

`ad` yönteminde `@Override` işareti vardır ve derleyici onu kabul eder: bu bir **yeniden
tanımlamadır**. `durum` yönteminde o işaret yoktur — konsaydı derleme dururdu. Statik bir
yöntem yeniden tanımlanamaz; aynı adla yeniden bildirildiğinde olan şeyin adı **gizlemedir**
ve gizlemenin alanlarda gördüğümüz davranışı burada yöntemlerde tekrarlanır.

#### Aynı Yazım, İki Taraf

İlk ölçüm beş çağrıyı yan yana koyar. Üçü `Ust` bildirilmiş bir referans üzerinden, biri `Alt`
bildirilmiş bir referans üzerinden, sonuncusu ise hiçbir nesneye bağlanmamış bir referans
üzerinden yapılır.

```java
// Taraf.java — aynı çağrı yazımı hangi tipten seçiliyor
public class Taraf {
    @SuppressWarnings("static-access")
    public static void main(String[] args) {
        Ust d = new Alt();
        Alt a = new Alt();
        Ust bos = null;
        System.out.printf("%-30s %-12s %s%n", "yazilan cagri", "sonuc", "karari veren");
        System.out.printf("%-30s %-12s %s%n", "d.ad()      [bildirilen Ust]", d.ad(), "calisma zamani tipi");
        System.out.printf("%-30s %-12s %s%n", "d.durum()   [bildirilen Ust]", d.durum(), "bildirilen tip");
        System.out.printf("%-30s %-12s %s%n", "a.ad()      [bildirilen Alt]", a.ad(), "calisma zamani tipi");
        System.out.printf("%-30s %-12s %s%n", "a.durum()   [bildirilen Alt]", a.durum(), "bildirilen tip");
        System.out.printf("%-30s %-12s %s%n", "bos.durum() [bos referans]", bos.durum(), "bildirilen tip");
        System.out.println();
        System.out.println("ornek alani -> d.sira " + d.sira + " | a.sira " + a.sira);
        System.out.println("sinif alani -> Ust.uretilen " + Ust.uretilen + " | Alt.uretilen " + Alt.uretilen);
        Alt.uretilen = 99;
        System.out.println("Alt.uretilen = 99 sonrasi Ust.uretilen: " + Ust.uretilen);
    }
}
```

```
yazilan cagri                  sonuc        karari veren
d.ad()      [bildirilen Ust]   alt yontem   calisma zamani tipi
d.durum()   [bildirilen Ust]   ust statik   bildirilen tip
a.ad()      [bildirilen Alt]   alt yontem   calisma zamani tipi
a.durum()   [bildirilen Alt]   alt statik   bildirilen tip
bos.durum() [bos referans]     ust statik   bildirilen tip

ornek alani -> d.sira 1 | a.sira 2
sinif alani -> Ust.uretilen 2 | Alt.uretilen 2
Alt.uretilen = 99 sonrasi Ust.uretilen: 99
```

İlk iki satır aynı `d` referansı üzerinden yazılmıştır ve karşıt yanıt verir. `d.ad()` **alt
yontem** döner: nesne bir `Alt`tır ve yöntem oradan seçilmiştir. `d.durum()` **ust statik**
döner: nesne yine aynı nesnedir, ama seçim `d`nin bildirilen tipinden yapılmıştır. Aradaki
tek fark, `Alt` içindeki iki bildirimden birinin başında `static` sözcüğünün bulunmasıdır.

Beşinci satır bu okumanın kanıtıdır. `bos` hiçbir nesneye bağlı değildir; buna rağmen
`bos.durum()` çağrısı bir sonuç verir ve hiçbir hata düşmez. Referans **okunmamıştır** bile.
Statik bir çağrının yazımında bir referans bulunması yalnızca bir sözdizim kolaylığıdır; derleyici
o referansın **tipini** alır ve değerini atar.

Alt bölümdeki üç satır ikinci soruyu yanıtlar. İki nesne üretildi ve her biri kendi `sira`
değerini taşıyor: **1** ve **2**. Buna karşılık `uretilen` iki ad üzerinden okunduğunda da
aynı sayıyı, **2**yi veriyor. `Alt.uretilen` diye yazılan ad ayrı bir sayaç değildir;
`Alt.uretilen = 99` yazıldığında `Ust.uretilen` de 99 olur. Örnek alanı nesne başına bir
kopya taşır, sınıf değişkeni ise kalıtım zinciri boyunca **tek** kopyadır.

#### Sınıf Dosyasındaki Kanıt

Davranış ölçüldü; şimdi kararın nerede donduğuna bakılabilir. Ölçüm çekirdeği bir kaynak
metnini derler, ürettiği sınıf dosyasını okur ve bir yöntemin gövdesindeki her çağrının iki
şeyini döndürür: çağrının sınıf dosyasına yazılan **sahibi** ve çağrının **türü**.

```java
// Olcek.java — sınıf dosyasındaki çağrının sahibi ve türü
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.instruction.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Olcek {
    record Cagri(String sahip, String ad, String tur) {}

    static Path derle(String kaynak, String sinif) throws Exception {
        Path d = Files.createTempDirectory("olcek");
        Path k = d.resolve(sinif + ".java");
        Files.writeString(k, kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, "-d", d.toString(), k.toString()) != 0)
            throw new IllegalStateException("derlenmedi: " + sinif);
        return d;
    }

    static List<Cagri> cagrilar(String kaynak, String sinif, String yontem) throws Exception {
        ClassModel cm = ClassFile.of().parse(derle(kaynak, sinif).resolve(sinif + ".class"));
        List<Cagri> c = new ArrayList<>();
        for (MethodModel m : cm.methods()) {
            if (!m.methodName().stringValue().equals(yontem) || m.code().isEmpty()) continue;
            for (CodeElement e : m.code().get())
                if (e instanceof InvokeInstruction iv)
                    c.add(new Cagri(kisa(iv.owner().asInternalName()),
                            iv.name().stringValue(), tur(iv.opcode())));
        }
        return c;
    }

    static String tur(Opcode o) {
        return switch (o) {
            case INVOKESTATIC -> "statik cagri";
            case INVOKEVIRTUAL -> "sanal cagri";
            default -> "dogrudan cagri";
        };
    }

    static String kisa(String ic) { return ic.substring(ic.lastIndexOf('/') + 1); }
}
```

```java
// Sahip.java — çağrının sahibi sınıf dosyasına ne olarak yazıldı
public class Sahip {
    static final String KAYNAK = """
        class Ust {
            String ad() { return "ust yontem"; }
            static String durum() { return "ust statik"; }
        }
        class Alt extends Ust {
            @Override String ad() { return "alt yontem"; }
            static String durum() { return "alt statik"; }
        }
        class Cagiran {
            static String ustOrnek(Ust d) { return d.ad(); }
            static String altOrnek(Alt a) { return a.ad(); }
            @SuppressWarnings("static-access")
            static String ustStatik(Ust d) { return d.durum(); }
            @SuppressWarnings("static-access")
            static String altStatik(Alt a) { return a.durum(); }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-11s %-24s %s%n", "yontem", "sinif dosyasindaki cagri", "cagri turu");
        for (String y : new String[] {"ustOrnek", "altOrnek", "ustStatik", "altStatik"})
            for (Olcek.Cagri c : Olcek.cagrilar(KAYNAK, "Cagiran", y))
                System.out.printf("%-11s %-24s %s%n", y, c.sahip() + "." + c.ad(), c.tur());
    }
}
```

```
yontem      sinif dosyasindaki cagri cagri turu
ustOrnek    Ust.ad                   sanal cagri
altOrnek    Alt.ad                   sanal cagri
ustStatik   Ust.durum                statik cagri
altStatik   Alt.durum                statik cagri
```

Dört satırın hepsinde sahip olarak **bildirilen tip** yazılıdır; bu, tablonun kolay yanlış
okunan yeridir. `ustOrnek` yönteminde de sahip `Ust`tur, oysa çalışan yöntem `Alt`ınkidir.
Ayrım sahipte değil **türdedir**. Sanal çağrıda sahip yalnızca bir başlangıç noktasıdır:
sanal makine çağrıyı nesnenin gerçek sınıfından başlayarak yeniden çözer, dolayısıyla sınıf
dosyasındaki `Ust.ad` yazısı sonucu belirlemez. Statik çağrıda ise çözme yoktur; sahip
sonucun kendisidir ve `Alt.durum` ile `Ust.durum` **iki ayrı hedeftir**.

Bu, dersin ana ölçümünü tek cümleye indirir: statik çağrının hedefi derleme sırasında
yazılmıştır ve çalışma zamanında değiştirilecek bir yeri yoktur.

#### Başlatmayı Hangi Erişim Tetikler

Sınıf düzeyi durum bir yerde kurulmalıdır ve o yer **başlatma bloğudur**. Bu bloğun ne zaman
çalıştığı — programın açılışında değil, sınıfa ilk kez ihtiyaç duyulduğunda ve bir kez — Java
Temelleri kursunda ölçüldü ve burada tekrarlanmaz. Buradaki soru ötekidir: ihtiyaç duyulmasını
**hangi yazım** sayıyor?

Aşağıdaki ölçüm dört ayrı erişimi dört ayrı yükleyicide çalıştırır, böylece her senaryo temiz
bir başlangıçtan başlar. Başlatma blokları çalıştıklarında adlarını basar.

```java
// Tetik.java — sınıf başlatmasını hangi erişim tetikler
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;

public class Tetik {
    static final String KAYNAK = """
        class Ust {
            static { System.out.println("  Ust baslatildi"); }
            static int sayac = 0;
            static final int TAVAN = 100;
        }
        class Alt extends Ust {
            static { System.out.println("  Alt baslatildi"); }
            static int altSayac = 0;
        }
        class S1 { static void calis() { System.out.println("  okunan deger: " + Alt.sayac); } }
        class S2 { static void calis() { System.out.println("  okunan deger: " + Ust.TAVAN); } }
        class S3 { static void calis() { System.out.println("  okunan deger: " + Alt.altSayac); } }
        class S4 { static void calis() { new Alt(); System.out.println("  bir Alt uretildi"); } }
        """;

    static void senaryo(Path d, String surucu, String yazim) throws Exception {
        System.out.println("yazilan erisim: " + yazim);
        try (URLClassLoader cl = new URLClassLoader(new URL[] {d.toUri().toURL()}, null)) {
            var m = cl.loadClass(surucu).getDeclaredMethod("calis");
            m.setAccessible(true);
            m.invoke(null);
        }
        System.out.println();
    }

    public static void main(String[] args) throws Exception {
        Path d = Olcek.derle(KAYNAK, "Ust");
        senaryo(d, "S1", "Alt.sayac     [alan Ust icinde bildirildi]");
        senaryo(d, "S2", "Ust.TAVAN     [derleme zamani sabiti]");
        senaryo(d, "S3", "Alt.altSayac  [alan Alt icinde bildirildi]");
        senaryo(d, "S4", "new Alt()");
    }
}
```

```
yazilan erisim: Alt.sayac     [alan Ust icinde bildirildi]
  Ust baslatildi
  okunan deger: 0

yazilan erisim: Ust.TAVAN     [derleme zamani sabiti]
  okunan deger: 100

yazilan erisim: Alt.altSayac  [alan Alt icinde bildirildi]
  Ust baslatildi
  Alt baslatildi
  okunan deger: 0

yazilan erisim: new Alt()
  Ust baslatildi
  Alt baslatildi
  bir Alt uretildi
```

Birinci senaryoda kaynakta yazılan ad `Alt`tır, ama başlatılan sınıf **`Ust`**tur ve `Alt` hiç
başlatılmaz. Yazılan ad tetikleyici değildir; alanın **bildirildiği** sınıf tetikleyicidir ve
bu bildirim derleme sırasında bulunur. Üçüncü senaryo aynı yazımla ama `Alt` içinde bildirilen
bir alanla yapılır ve bu kez iki sınıf da başlatılır, üstelik önce üst sınıf. Aynı yazım, iki
ayrı sonuç; ayrımı yapan şey adın kendisi değil, adın hangi sınıfa çözüldüğüdür.

İkinci senaryo daha da ileri gider: **hiçbir sınıf başlatılmaz**, ama değer okunur ve doğru
gelir. `TAVAN` bir `static final` alandır ve değeri derleme sırasında hesaplanabilen bir
sabittir; derleyici o değeri okuyan tarafa yazmıştır, dolayısıyla `Ust` sınıfına çalışma
zamanında hiç dokunulmamıştır. Sınıf değişkeni okumak sınıfı başlatır, sabit okumak
başlatmaz — ve iki satır kaynakta birbirinden ayırt edilemez.

#### Sınırlayıcı Ölçüm: Aynı Satır İki Tarafa Gider

Şimdiye kadarki ölçümler bir örnek yöntemiyle bir statik yöntemi **ayrı satırlarda** çağırdı.
Asıl kusur ikisi yan yana konduğunda doğar: tek bir satır, tek bir referans, iki ayrı karar.

```java
// Yanyana.java — aynı satır iki ayrı tarafa gider
public class Yanyana {
    @SuppressWarnings("static-access")
    static String satir(Ust d) { return d.ad() + " / " + d.durum(); }

    @SuppressWarnings("static-access")
    public static void main(String[] args) {
        for (Ust d : new Ust[] {new Ust(), new Alt()})
            System.out.printf("Ust d = new %-6s -> %s%n", d.getClass().getSimpleName() + "()", satir(d));
        Alt a = new Alt();
        System.out.printf("Alt a = new %-6s -> %s%n", "Alt()", a.ad() + " / " + a.durum());
    }
}
```

```
Ust d = new Ust()  -> ust yontem / ust statik
Ust d = new Alt()  -> alt yontem / ust statik
Alt a = new Alt()  -> alt yontem / alt statik
```

`satir` yöntemi hiç değişmedi; üç satırın üçünde de aynı gövde çalıştı. Buna karşılık üç
sonuç üç ayrı bileşim veriyor. Birinci satırda iki yanıt uyuşuyor, çünkü bildirilen tip ile
çalışma zamanı tipi aynı. Üçüncü satırda da uyuşuyor, aynı nedenle. **Ayrım yalnız ikinci
satırda görünüyor** — ve tam da orada bir programcının beklediği okuma bozuluyor: nesne bir
`Alt` olduğu hâlde durum bilgisi `Ust`tan geliyor.

Buradan iki sınır birden çıkar. Birincisi, kuralın tersine döndüğü durum yoktur; statik üye
her zaman bildirilen tipten seçilir. İkincisi ve daha önemlisi, **ayrım çoğu programda
gözlenemez**: bildirilen tip ile çalışma zamanı tipi ayrılmadıkça iki taraf aynı yanıtı verir
ve fark hiç ortaya çıkmaz. Bir sınıfın bir alt sınıfı yazıldığı gün, o güne kadar doğru
çalışan bir satır sessizce yön değiştirebilir. Kusurun bedeli yazan yerde değil, alt sınıfın
kullanıldığı yerde ödenir; ve orada görünen şey bir hata değil, **yanlış bir değerdir**.

#### Özet

- Statik yöntem yeniden tanımlanmaz, **gizlenir**: `d.ad()` çalışma zamanı tipinden **alt
  yontem**, `d.durum()` bildirilen tipten **ust statik** verir; iki satır arasındaki tek fark
  bildirimdeki `static` sözcüğüdür.
- Statik çağrı referansın değerini hiç okumaz; boş referans üzerinden yapılan `bos.durum()`
  çağrısı sonuç verir ve hata düşmez.
- Örnek alanı nesne başına bir kopya taşır (`sira` 1 ve 2), sınıf değişkeni kalıtım zinciri
  boyunca tektir: `Alt.uretilen = 99` yazıldığında `Ust.uretilen` de 99 olur.
- Sınıf dosyasında dört çağrının da sahibi bildirilen tiptir; ayrım türdedir. Sanal çağrıda
  sahip bir başlangıç noktasıdır, statik çağrıda sonucun kendisidir.
- Sınıf başlatmasını yazılan ad değil, alanın bildirildiği sınıf tetikler: `Alt.sayac` yalnız
  `Ust`u başlatır, `Ust.TAVAN` ise hiçbir sınıfı başlatmaz.
- Ayrım yalnız bildirilen tip ile çalışma zamanı tipi ayrıldığında görünür; üç satırlık
  ölçümün ikisinde iki taraf aynı yanıtı verir ve fark gözlenemez.

#### Sonraki Adım

Bu dersin son ölçümünde bir değer sınıf başlatmasını hiç tetiklemeden okundu: `TAVAN` bir
`static final` alandı ve değeri okuyan tarafa geçmişti. O tek sözcük, `final`, burada yalnız
bir yan etki olarak göründü; oysa üç ayrı işi birden yapar ve üçünde de kararı **derleme
zamanına** taşır. Bir yerel değişkene yeniden atama yapılmasını, bir yöntemin yeniden
tanımlanmasını ve bir sınıfın genişletilmesini aynı sözcük engeller. Sonraki ders bu üç
kısıtı ayrı ayrı ölçer, sabit değerin okuyan sınıfa gömülmesinin sonucunu gösterir ve `final`
bir referansın nesneyi dondurmadığını — yani değiştirilemezlik olmadığını — aynı ölçümle
sınırlar.

### `final` Anahtar Sözcüğü

Source: https://academia.sh/tr/kurslar/java-nesne-modeli/final-anahtar-sozcugu

Tek bir anahtar sözcük üç ayrı kısıt koyar ve üçünü de derleme zamanında uygular: yeniden atama, yeniden tanımlama ve genişletme. Sabit bir final alanın değeri okuyan sınıfa yazılır ve kaynak değiştiğinde eski değer yerinde kalır; final bir referans ise nesneyi dondurmaz.

Önceki dersin son ölçümünde bir değer, ait olduğu sınıfı hiç başlatmadan okundu: `Ust.TAVAN`
bir `static final` alandı ve sınıf başlatma bloğu çalışmadan doğru sayı geldi. O sonucun
nedeni tek bir sözcükte, `final` sözcüğünde duruyordu ve orada yalnız bir yan etki olarak
göründü.

Bu ders o sözcüğü doğrudan ölçer. `final` üç ayrı yerde yazılabilir — bir değişkenin, bir
yöntemin ve bir sınıfın başında — ve üç ayrı kısıt koyar. Üç kısıtın ortak yanı, hepsinin
kararının **derleme zamanında** verilmesidir: hiçbiri çalışma zamanında sınanan bir koşul
değildir. Sorular üç tanedir: bu kısıtların kaçı derleyicide durur, sabit bir alanın değeri
nereye yazılır ve `final` bir referans neyi korur?

#### Ölçüm Çekirdeği

Ölçüm üç şey yapar: bir kaynağı derler ve derlenip derlenmediğini bildirir, ürettiği sınıf
dosyasındaki `final` bayraklarını okur ve derlenmiş sınıfları ayrı bir yükleyicide
çalıştırır.

- **SN26** — Derleme denemelerinde kâhin kurgunun kendisidir: her denemede tek bir satır
  değişir, dolayısıyla derlemenin durması yalnız o satıra bağlanabilir. Sonuç tek bir çıkış
  koduyla okunur; hata metni okunmaz, çünkü metin yerel ayara bağlıdır.
- **SN27** — Her deneme kendi geçici dizininde derlenir; denemeler birbirinin ürettiği sınıf
  dosyasını görmez.
- **SN28** — Gömme ölçümünde `Sabit` iki kez derlenir, `Kullanan` **bir kez**; ikinci koşumda
  okunan sınıf dosyası birincisiyle aynı dosyadır.
- **SN29** — Koşum ayrı bir yükleyicide yapılır, böylece ilk koşumda yüklenen sınıf ikincisine
  taşınmaz.
- **SN30** — Ortama bağlı hiçbir veri okunmaz; sayılan şey derlenen deneme sayısı, sınıf
  dosyasındaki bayraklar ve okunan değerdir.

```java
// Olcek.java — derleme sonucu, sınıf dosyasındaki final bayrağı ve okuyan tarafın izi
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.instruction.*;
import java.lang.reflect.AccessFlag;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Olcek {
    static Path yeniDizin() throws Exception { return Files.createTempDirectory("olcek"); }

    static boolean derle(Path d, String ad, String kaynak) throws Exception {
        Path k = d.resolve(ad + ".java");
        Files.writeString(k, kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        return ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, "-d", d.toString(), "-cp", d.toString(), k.toString()) == 0;
    }

    static String finalBayraklari(Path d, String sinif) throws Exception {
        ClassModel cm = ClassFile.of().parse(d.resolve(sinif + ".class"));
        List<String> f = new ArrayList<>();
        if (cm.flags().has(AccessFlag.FINAL)) f.add("sinif " + sinif);
        for (FieldModel x : cm.fields())
            if (x.flags().has(AccessFlag.FINAL)) f.add("alan " + x.fieldName().stringValue());
        for (MethodModel x : cm.methods())
            if (x.flags().has(AccessFlag.FINAL)) f.add("yontem " + x.methodName().stringValue());
        return String.join(", ", f);
    }

    static List<String> izler(Path d, String sinif, String yontem) throws Exception {
        ClassModel cm = ClassFile.of().parse(d.resolve(sinif + ".class"));
        List<String> iz = new ArrayList<>();
        for (MethodModel m : cm.methods()) {
            if (!m.methodName().stringValue().equals(yontem) || m.code().isEmpty()) continue;
            for (CodeElement e : m.code().get()) {
                if (e instanceof FieldInstruction f)
                    iz.add("alan okumasi " + kisa(f.owner().asInternalName())
                            + "." + f.name().stringValue());
                else if (e instanceof ConstantInstruction c)
                    iz.add("sabit yukleme " + c.constantValue());
                else if (e instanceof InvokeInstruction iv)
                    iz.add(switch (iv.opcode()) {
                        case INVOKEVIRTUAL -> "sanal cagri ";
                        case INVOKESTATIC -> "statik cagri ";
                        default -> "dogrudan cagri ";
                    } + kisa(iv.owner().asInternalName()) + "." + iv.name().stringValue());
            }
        }
        return iz;
    }

    static Object calistir(Path d, String sinif, String yontem) throws Exception {
        try (URLClassLoader cl = new URLClassLoader(new URL[] {d.toUri().toURL()}, null)) {
            var m = cl.loadClass(sinif).getDeclaredMethod(yontem);
            m.setAccessible(true);
            return m.invoke(null);
        }
    }

    static String kisa(String ic) { return ic.substring(ic.lastIndexOf('/') + 1); }
}
```

#### Üç Kısıt, Üçü de Derleme Zamanında

İlk ölçüm dokuz kaynak derler. Denemeler çiftler hâlinde kurulmuştur: her `final` denemesinin
yanında, tek farkı o sözcüğün yokluğu olan bir eşi vardır.

```java
// Kisit.java — final üç ayrı kısıt koyar, üçü de derleme zamanında
import java.nio.file.Path;

public class Kisit {
    static final String[][] DENEME = {
        {"yerel degiskene yeniden atama",  "class T { void f() { int y = 1; y = 2; } }"},
        {"final yerele yeniden atama",     "class T { void f() { final int y = 1; y = 2; } }"},
        {"alana yeniden atama",            "class T { int a; T() { a = 1; } void f() { a = 2; } }"},
        {"final alana yeniden atama",      "class T { final int a; T() { a = 1; } void f() { a = 2; } }"},
        {"final alana yapicida atama",     "class T { final int a; T() { a = 1; } }"},
        {"yontemi yeniden tanimlama",      "class A { int f() { return 1; } } class T extends A { @Override int f() { return 2; } }"},
        {"final yontemi yeniden tanimlama","class A { final int f() { return 1; } } class T extends A { int f() { return 2; } }"},
        {"sinifi genisletme",              "class A { } class T extends A { }"},
        {"final sinifi genisletme",        "final class A { } class T extends A { }"},
    };

    public static void main(String[] args) throws Exception {
        System.out.printf("%-33s %s%n", "denenen yazim", "derleniyor mu");
        for (String[] d : DENEME)
            System.out.printf("%-33s %s%n", d[0], Olcek.derle(Olcek.yeniDizin(), "T", d[1]));

        String kaynak = """
            final class Bayrak {
                final int sabitAlan = 1;
                int digerAlan = 2;
                final int kilitli() { return 1; }
                int serbest() { final int y = 1; return y; }
            }
            """;
        Path d = Olcek.yeniDizin();
        Olcek.derle(d, "Bayrak", kaynak);
        System.out.println();
        System.out.println("sinif dosyasinda final bayragi tasiyanlar:");
        System.out.println("  " + Olcek.finalBayraklari(d, "Bayrak"));

        Path e = Olcek.yeniDizin();
        Olcek.derle(e, "A", """
            class A { final int kilitli() { return 1; } int serbest() { return 2; } }
            class Cagiran {
                static int a(A x) { return x.kilitli(); }
                static int b(A x) { return x.serbest(); }
            }
            """);
        System.out.println();
        System.out.println("final yontem cagrisi        : " + Olcek.izler(e, "Cagiran", "a"));
        System.out.println("final olmayan yontem cagrisi: " + Olcek.izler(e, "Cagiran", "b"));
    }
}
```

```
denenen yazim                     derleniyor mu
yerel degiskene yeniden atama     true
final yerele yeniden atama        false
alana yeniden atama               true
final alana yeniden atama         false
final alana yapicida atama        true
yontemi yeniden tanimlama         true
final yontemi yeniden tanimlama   false
sinifi genisletme                 true
final sinifi genisletme           false

sinif dosyasinda final bayragi tasiyanlar:
  sinif Bayrak, alan sabitAlan, yontem kilitli

final yontem cagrisi        : [sanal cagri A.kilitli]
final olmayan yontem cagrisi: [sanal cagri A.serbest]
```

Dokuz denemenin **dördü** durdu ve dördü de bir `final` denemesiydi. Eşleri sorunsuz derlendi;
tek fark o sözcüktür. Beşinci satır kısıtın tam yerini gösterir: `final` bir alana atama
yasak değildir, **yeniden** atama yasaktır. Alan yapıcıda bir kez atanabilir ve orada
atanmazsa derleme yine durur; kısıt "hiç yazılmasın" değil, "tam bir kez yazılsın" biçimindedir.

Yedinci satır kursun sorusuna bağlanır. Bir yöntem `final` bildirildiğinde ona alt sınıftan
başka bir yanıt yazmak olanaksızdır. Önceki derste bir çağrının **iki** olası yanıtı vardı ve
hangisinin geleceğini çalışma zamanı tipi belirliyordu. `final` bu sayıyı **bire** indirir:
çalışma zamanı tipi ne olursa olsun tek bir gövde kalır, dolayısıyla soru anlamını yitirir.
`final` çalışma zamanı tipinin kararını bildirilen tipe taşımaz; kararı **ortadan kaldırır**.

Çıktının son iki satırı bu okumayı bir adım keskinleştiriyor. `final` bir yöntemin çağrısı da,
`final` olmayanınki de sınıf dosyasına **sanal çağrı** olarak yazılıyor; iki satır arasında
hiçbir fark yok. Yani derleyici çağrının düzeneğini değiştirmiyor, çağrının seçebileceği
gövde kümesini bire indiriyor. Önceki dersteki statik çağrı ile arasındaki fark budur: orada
yazım değişmeden **hedef** değişiyordu, burada hedef seçimi yerinde duruyor ama seçilecek
tek bir aday kalıyor.

Ortadaki blok kısıtların nerede durduğunu ayırır. Sınıf dosyasında `final` bayrağını üç şey
taşıyor: sınıfın kendisi, `sabitAlan` ve `kilitli` yöntemi. `serbest` yönteminin gövdesindeki
`final int y` bildirimi listede **yok**. Yerel değişkenin kısıtı sınıf dosyasına hiç
yazılmaz; o kısıt yalnız derleyicinin belleğinde yaşar ve derleme bittiğinde ortadan kalkar.
Öteki ikisi bayrak olarak kalır, çünkü onları **başka bir derleme** okuyacaktır: bir alt sınıf
yazan derleyici `kilitli` yönteminin bayrağını görmek zorundadır.

#### Sabit Değer Nereye Yazılıyor

Önceki dersin `TAVAN` ölçümü bir sorun bırakmıştı: sınıf başlatılmadığı hâlde değer nereden
geldi? Aşağıdaki ölçüm iki alanı yan yana koyar. `TAVAN` değerini doğrudan bir sayıdan alır;
`TABAN` aynı sayıyı bir yöntem çağrısından alır. İkisi de `static final`dır.

```java
// Gomme.java — sabit final alanın değeri okuyan sınıfa yazılır
import java.nio.file.Path;

public class Gomme {
    static String sabit(int n) {
        return """
            class Sabit {
                static final int TAVAN = %d;
                static final int TABAN = hesapla();
                static int hesapla() { return %d; }
            }
            """.formatted(n, n);
    }

    static final String KULLANAN = """
        class Kullanan {
            static int tavan() { return Sabit.TAVAN; }
            static int taban() { return Sabit.TABAN; }
            static String deger() { return "TAVAN=" + tavan() + " TABAN=" + taban(); }
        }
        """;

    public static void main(String[] args) throws Exception {
        Path d = Olcek.yeniDizin();
        Olcek.derle(d, "Sabit", sabit(100));
        Olcek.derle(d, "Kullanan", KULLANAN);
        for (String y : new String[] {"tavan", "taban"})
            System.out.println("Kullanan." + y + "() icindeki iz: " + Olcek.izler(d, "Kullanan", y));
        System.out.println();
        System.out.println("ilk kosum    : " + Olcek.calistir(d, "Kullanan", "deger"));
        Olcek.derle(d, "Sabit", sabit(200));
        System.out.println("Sabit 200 ile yeniden derlendi, Kullanan derlenmedi");
        System.out.println("ikinci kosum : " + Olcek.calistir(d, "Kullanan", "deger"));
    }
}
```

```
Kullanan.tavan() icindeki iz: [sabit yukleme 100]
Kullanan.taban() icindeki iz: [alan okumasi Sabit.TABAN]

ilk kosum    : TAVAN=100 TABAN=100
Sabit 200 ile yeniden derlendi, Kullanan derlenmedi
ikinci kosum : TAVAN=100 TABAN=200
```

İlk iki satır soruyu yanıtlıyor. `tavan` yönteminin sınıf dosyasında `Sabit` sınıfına hiçbir
referans yok; yalnız **100 sayısının kendisi** var. `taban` yönteminde ise bir alan okuması
duruyor ve o okumanın sahibi `Sabit`tir. Kaynakta iki satır birbirinin aynıdır — ikisi de bir
noktadan sonra bir alan adı yazar — ama biri değeri okuyan sınıfa **kopyalamış**, öteki bir
bağ **bırakmıştır**.

Kopyalamanın bedeli ikinci koşumda görünüyor. `Sabit` 200 değeriyle yeniden derlendi; `Kullanan`
derlenmedi ve diskte eski hâliyle duruyor. Çalıştırıldığında `TABAN` yeni değeri, **200**'ü
veriyor: bağ duruyordu ve bağ üzerinden okundu. `TAVAN` ise hâlâ **100** diyor. Kaynağa bakan biri
`Sabit` içinde 200 görecek, çalışan programda 100 okuyacaktır.

Bu, kursun sorduğu sorunun en sert biçimidir. Karar burada bildirilen tiple çalışma zamanı
tipi arasında değil, **derleme zamanıyla çalışma zamanı** arasında bölünmüştür — ama sonucu
aynıdır: iki taraftan hangisinin yanıt verdiğini bilmeyen bir programcı, kaynakta gördüğü
değerin çalıştığını sanır. Kusur `Sabit`i değiştiren kişide de değildir; `Kullanan`ın yeniden
derlenmemesindedir ve hiçbir yerde bir hata iletisi üretmez.

#### Sınırlayıcı Ölçüm: `final` Referans Nesneyi Dondurmaz

Üç kısıt da ölçüldü ve hepsi derleme zamanında duruyor. Şimdi bu tezin sınırı: `final` bir ada
bağlı nesnenin **içi** ne oluyor?

```java
// Dondur.java — final referans nesneyi dondurmaz
import java.util.Arrays;

public class Dondur {
    static class Kayit {
        String ad;
        int adet;
        Kayit(String ad, int adet) { this.ad = ad; this.adet = adet; }
        @Override public String toString() { return ad + ":" + adet; }
    }

    static final String GOVDE = """
        class Deneme {
            static class Kayit { int adet; }
            void f() { final Kayit k = new Kayit(); %s }
        }
        """;

    public static void main(String[] args) throws Exception {
        final Kayit k = new Kayit("civata", 12);
        final int[] agirlik = {10, 20, 30};
        Kayit ilk = k;
        System.out.println("baslangic        : " + k + " " + Arrays.toString(agirlik));
        k.ad = "somun";
        k.adet = 99;
        agirlik[0] = 99;
        System.out.println("degisiklikten son: " + k + " " + Arrays.toString(agirlik));
        System.out.println("referans ayni mi : " + (k == ilk));
        System.out.println();
        for (String[] y : new String[][] {{"k.adet = 1;", "alani degistirme"},
                                          {"k = new Kayit();", "referansi yeniden baglama"}})
            System.out.printf("%-26s -> derleniyor mu: %s%n", y[1],
                    Olcek.derle(Olcek.yeniDizin(), "Deneme", GOVDE.formatted(y[0])));
    }
}
```

```
baslangic        : civata:12 [10, 20, 30]
degisiklikten son: somun:99 [99, 20, 30]
referans ayni mi : true

alani degistirme           -> derleniyor mu: true
referansi yeniden baglama  -> derleniyor mu: false
```

`k` bir `final` yerel değişkendir ve iki alanının ikisi de değişti; `agirlik` de `final`dır ve
ilk öğesi değişti. Buna karşılık `k == ilk` hâlâ **doğru**: referans hiç değişmedi. Korunan
şey nesne değil, **ad ile nesne arasındaki bağdır**.

Son iki satır ayrımı tek ölçüye indiriyor. Aynı `final` bildirimi üzerinde alanı değiştiren
satır derleniyor, referansı yeniden bağlayan satır derlenmiyor. `final`, Java Temelleri
kursunda ölçülen aktarım modelinin üstüne bir kısıt koyar ve o kısıt yalnız **bağlamaya**
değer: değerin kendisi bir referans olduğunda, referansın gösterdiği nesne bu kısıtın dışında
kalır.

Buradan iki sonuç çıkar. Birincisi, `final` bir alan **değiştirilemezlik** değildir; bir tipin
değiştirilemez olması için alanlarının `final` olması yetmez, o alanların gösterdiği nesnelerin
de değişmemesi gerekir. İkincisi, ölçümün gözlenemediği durum vardır: `agirlik` yerine bir
sayı ya da bir dizgi konsaydı ayrım hiç görünmezdi, çünkü o değerlerin değiştirilebilecek bir
içi yoktur. Kısıtın yetip yetmediği, `final` sözcüğüne değil **tipe** bağlıdır.

#### Özet

- `final` üç ayrı kısıt koyar ve dokuz denemenin dördü tam bu sözcük yüzünden durur: yeniden
  atama, yeniden tanımlama ve genişletme.
- Kısıt "hiç atanmasın" değil, "tam bir kez atansın" biçimindedir; `final` bir alan yapıcıda
  atanabilir.
- `final` bir yöntem çalışma zamanı tipinin verebileceği yanıt sayısını ikiden **bire**
  indirir; kararı öteki tarafa taşımaz, ortadan kaldırır.
- Sınıf dosyasında `final` bayrağını sınıf, alan ve yöntem taşır; yerel değişkenin kısıtı hiç
  yazılmaz, çünkü onu başka bir derlemenin okumasına gerek yoktur.
- Sabit bir `static final` alanın değeri okuyan sınıfa yazılır: kaynak 200 olduğu hâlde
  yeniden derlenmeyen okuyucu **100** okumayı sürdürür, hesaplanan alan ise **200** verir.
- `final` bir referans nesneyi dondurmaz; alanları ve dizinin öğeleri değişir, yalnız ad ile
  nesne arasındaki bağ korunur.

#### Sonraki Adım

Bu dersin ölçümünde bir kısıt sınıf dosyasına hiç yazılmadı: `final` bir yerel değişkenin
kısıtı yalnız derleyicide durdu ve derleme bittiğinde ortadan kalktı. Öyleyse o kısıt neye
yarıyor? Java'da derleyicinin bu sözü **yazılmadığı hâlde kendiliğinden istediği** bir yer
vardır: bir yöntemin gövdesi içinde tanımlanan bir sınıf, dışarıdaki yerel değişkeni
kullandığında o değişkenin bir daha atanmamasını şart koşar. Şartın nedeni, o değişkenin
oraya kopyalanmasıdır — ve kopyayı taşımak için ortaya kaynakta hiç yazılmamış yeni sınıf
dosyaları çıkar. Sonraki ders bu dosyaları sayar: iç içe yazılan bir sınıf kaç ayrı dosya
üretiyor, hangileri dış örneğe gizli bir alan taşıyor ve o gizli alanla kopyalanan değer
arasındaki fark ne zaman görünür oluyor?

### İç ve Yerel Sınıflar

Source: https://academia.sh/tr/kurslar/java-nesne-modeli/ic-ve-yerel-siniflar

Tek bir kaynak dosyası altı sınıf dosyası üretir ve üretilenlerin dördü kaynakta hiç yazılmamış gizli alanlar taşır. İç sınıf dış örneğe bir alan üzerinden bağlanır ve o alanı canlı okur; yerel sınıf yakaladığı değişkeni yapıcı parametresiyle kopyalar, bu yüzden değişken bir daha atanamaz.

Önceki dersin ölçümünde bir kısıt sınıf dosyasına hiç yazılmadan yok oldu: `final` bir yerel
değişkenin kısıtı yalnız derleyicide durdu. Derleme bittiğinde o söz ortadan kalkıyorsa neye
yarıyor? Java'da derleyicinin aynı sözü **yazılmadığı hâlde kendiliğinden istediği** bir yer
vardır ve orada sözün karşılığı görünür hâle gelir.

O yer bir sınıfın başka bir sınıfın içine yazıldığı yerdir. Java'da bir sınıf bir başkasının
gövdesinde, bir yöntemin gövdesinde ya da doğrudan bir `new` ifadesinin içinde tanımlanabilir.
Kaynakta bunların hepsi tek bir dosyada, iç içe durur; sınıf dosyası ise iç içe olamaz. Sorular
üç tanedir: bu iç içelik kaç ayrı dosyaya açılıyor, üretilen dosyalar dışarıyla nasıl
bağlanıyor ve bağlanma biçimi kararı hangi tarafa bırakıyor?

#### Ölçüm Çekirdeği

Ölçüm bir kaynağı derler ve üretilen bütün sınıf dosyalarını okur. Her dosya için iki şey
sayılır: alanlar — kaynakta yazılmış olanlarla derleyicinin eklediği **gizli** olanlar birlikte
— ve yapıcının imzası.

- **SN31** — Kâhin kaynağın kendisidir: hangi alanı yazdığımızı biliriz, dolayısıyla listede
  fazladan görünen her alan derleyicinin eklediğidir. Gizli alan tahminle değil, sınıf
  dosyasındaki bayrağıyla ayrılır.
- **SN32** — Bütün sınıf dosyaları **tek** bir kaynak dosyasından üretilir; sayım o dizindeki
  bütün dosyaları kapsar.
- **SN33** — Yakalama ölçümünde nesne, dış alanın değeri değiştirilmeden **önce** üretilir;
  böylece sonradan okunan değer canlı okumanın kanıtı olur.
- **SN34** — Derleme denemelerinde tek bir satır değişir ve her deneme kendi geçici dizininde
  derlenir.
- **SN35** — Ortama bağlı hiçbir veri okunmaz; sayılan şey dosya sayısı, alan sayısı ve
  yapıcı parametreleridir.

```java
// Olcek.java — üretilen sınıf dosyaları, taşıdıkları gizli alanlar ve yapıcı imzaları
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.reflect.AccessFlag;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Olcek {
    static Path yeniDizin() throws Exception { return Files.createTempDirectory("olcek"); }

    static boolean derle(Path d, String ad, String kaynak) throws Exception {
        Path k = d.resolve(ad + ".java");
        Files.writeString(k, kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        return ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, "-d", d.toString(), "-cp", d.toString(), k.toString()) == 0;
    }

    static List<String> sinifDosyalari(Path d) throws Exception {
        try (var s = Files.list(d)) {
            return s.map(p -> p.getFileName().toString())
                    .filter(n -> n.endsWith(".class")).sorted().toList();
        }
    }

    static String alanlar(Path d, String dosya) throws Exception {
        ClassModel cm = ClassFile.of().parse(d.resolve(dosya));
        List<String> a = new ArrayList<>();
        for (FieldModel f : cm.fields())
            a.add(f.fieldName().stringValue()
                    + (f.flags().has(AccessFlag.SYNTHETIC) ? " [gizli]" : ""));
        return a.isEmpty() ? "-" : String.join(", ", a);
    }

    static String yapici(Path d, String dosya) throws Exception {
        ClassModel cm = ClassFile.of().parse(d.resolve(dosya));
        for (MethodModel m : cm.methods())
            if (m.methodName().stringValue().equals("<init>")) {
                List<String> p = new ArrayList<>();
                for (var a : m.methodTypeSymbol().parameterList()) p.add(a.displayName());
                return "(" + String.join(", ", p) + ")";
            }
        return "-";
    }
}
```

#### Bir Kaynak Dosyası, Altı Sınıf Dosyası

Ölçülen kaynak tek bir dış sınıf içerir ve içine beş ayrı biçim yerleştirir: bir iç sınıf, bir
statik iç içe sınıf, bir örnek yönteminde tanımlanan yerel sınıf, bir statik yöntemde
tanımlanan yerel sınıf ve bir adsız sınıf.

```java
// Uretim.java — iç içe yazılan sınıflar kaç dosya üretir
import java.nio.file.Path;

public class Uretim {
    static final String KAYNAK = """
        class Dis {
            int disAlan = 1;
            class Ic { int oku() { return disAlan; } }
            static class Yuva { int oku() { return 2; } }
            Runnable ornekYerel() {
                int y = 3;
                class Yerel implements Runnable { public void run() { say(disAlan + y); } }
                return new Yerel();
            }
            static Runnable statikYerel() {
                int y = 4;
                class SYerel implements Runnable { public void run() { say(y); } }
                return new SYerel();
            }
            Runnable isimsiz() {
                int y = 5;
                return new Runnable() { public void run() { say(disAlan + y); } };
            }
            static void say(int n) { }
        }
        """;

    public static void main(String[] args) throws Exception {
        Path d = Olcek.yeniDizin();
        Olcek.derle(d, "Dis", KAYNAK);
        System.out.printf("%-18s %-28s %s%n", "sinif dosyasi", "alanlar", "yapici imzasi");
        for (String f : Olcek.sinifDosyalari(d))
            System.out.printf("%-18s %-28s %s%n", f, Olcek.alanlar(d, f), Olcek.yapici(d, f));
        System.out.println("uretilen dosya sayisi: " + Olcek.sinifDosyalari(d).size());
    }
}
```

```
sinif dosyasi      alanlar                      yapici imzasi
Dis$1.class        val$y [gizli], this$0 [gizli] (Dis, int)
Dis$1SYerel.class  val$y [gizli]                (int)
Dis$1Yerel.class   val$y [gizli], this$0 [gizli] (Dis, int)
Dis$Ic.class       this$0 [gizli]               (Dis)
Dis$Yuva.class     -                            ()
Dis.class          disAlan                      ()
uretilen dosya sayisi: 6
```

Tek bir kaynak dosyasından **altı** sınıf dosyası çıktı. İç içelik sınıf dosyası düzeyinde
yoktur; her biçim kendi dosyasını alır ve adları dış sınıfın adından türetilir. Adsız sınıfın
kaynakta hiç adı yoktu, dosyası yine de vardır.

İkinci sütun asıl ölçümü taşıyor. Kaynakta yazılan tek alan `disAlan`dır ve yalnız `Dis`
dosyasında görünür. Öteki dosyalarda görünen dört alan **kaynakta yazılmamıştır**: `this$0` ve
`val$y`. Beş biçimin dördü en az bir gizli alan taşıyor; taşımayan tek biçim statik iç içe
sınıftır.

Üçüncü sütun gizli alanların nasıl dolduğunu gösterir. `Dis$Ic` sınıfının yapıcısı bir `Dis`
parametresi alır; kaynakta bu sınıfın yapıcısı hiç yazılmamıştır ve `new` yazan taraf da
parametre vermez. `Dis$1Yerel` iki parametre alır: dış örnek ve yakalanan sayı. `Dis$1SYerel`
yalnız sayıyı alır, çünkü statik bir yöntemde dış örnek yoktur. Derleyici hem alanı hem de onu
dolduran parametreyi eklemiştir; iç içe yazma bir görünüm değil, **bir bağlama düzeneğidir**.

Tablo beş biçimi bu düzeneğe göre üç kümeye ayırıyor. Dış örneği de yakalanan değeri de
taşıyanlar: örnek yönteminde tanımlanan yerel sınıf ile adsız sınıf. Yalnız birini taşıyanlar:
dış örneği taşıyan iç sınıf ve yalnız yakalanan değeri taşıyan statik yöntemdeki yerel sınıf.
Hiçbirini taşımayan: statik iç içe sınıf. Kaynakta bu beş biçim birbirine çok benzer biçimde
yazılır — hepsi bir sınıf bildirimidir ve aralarındaki fark bir sözcük ya da bir konumdur —
ama sınıf dosyasında üç ayrı yapıya dönüşürler. Bir biçimi seçmek, bir yazım kolaylığı seçmek
değildir.

#### Paylaşılan Alan, Kopyalanan Yerel

Gizli alanlar iki ayrı şeyi taşıyor ve ikisi aynı biçimde davranmıyor. `this$0` bir
**referanstır**; `val$y` ise bir **değerin kopyasıdır**. Aradaki farkı görmek için ikisi de
aynı nesne üzerinden okunur.

```java
// Yakala.java — dış alan paylaşılır, yakalanan yerel kopyalanır
public class Yakala {
    int alan = 1;

    Runnable uret() {
        int yerel = 1;
        class Iz implements Runnable {
            @Override public void run() { System.out.println("  alan=" + alan + " yerel=" + yerel); }
        }
        Runnable r = new Iz();
        alan = 2;
        return r;
    }

    static final String GOVDE = """
        class Deneme {
            int alan = 1;
            Runnable f() {
                int yerel = 1;
                class Iz implements Runnable { public void run() { say(alan + yerel); } }
                Runnable r = new Iz();
                %s
                return r;
            }
            static void say(int n) { }
        }
        """;

    public static void main(String[] args) throws Exception {
        Yakala y = new Yakala();
        Runnable r = y.uret();
        System.out.println("nesne uretildikten sonra alan 2 yapildi, yerel yok oldu");
        r.run();
        y.alan = 3;
        System.out.println("disaridan alan 3 yazildi");
        r.run();
        System.out.println();
        for (String[] s : new String[][] {{"alan = 2;", "dis alana yeniden atama"},
                                          {"yerel = 2;", "yakalanan yerele yeniden atama"}})
            System.out.printf("%-32s -> derleniyor mu: %s%n", s[1],
                    Olcek.derle(Olcek.yeniDizin(), "Deneme", GOVDE.formatted(s[0])));
    }
}
```

```
nesne uretildikten sonra alan 2 yapildi, yerel yok oldu
  alan=2 yerel=1
disaridan alan 3 yazildi
  alan=3 yerel=1

dis alana yeniden atama          -> derleniyor mu: true
yakalanan yerele yeniden atama   -> derleniyor mu: false
```

`Iz` nesnesi üretildiği anda `alan` 1, `yerel` 1 idi. Nesne üretildikten **sonra** `alan` iki
kez değişti ve iki değişiklik de okunan değere yansıdı: önce 2, sonra 3. `yerel` ise her iki
okumada **1** kaldı. Kaynakta iki ad da aynı biçimde, hiçbir niteleyici olmadan yazılmıştır;
buna karşılık biri dışarıdaki değeri izliyor, öteki üretim anında donmuş bir kopyayı
okuyor.

Fark gizli alanların taşıdığı şeyden çıkıyor. `this$0` dış nesneyi gösterir ve alan her
okunduğunda o nesneden alınır; dolayısıyla dışarıdaki değişiklik görünür. `val$y` ise sayının
kendisini tutar ve yapıcıda bir kez doldurulmuştur; dışarıdaki değişkenle arasında hiçbir bağ
kalmamıştır. Yöntemin yerel değişkeni zaten yöntem bittiğinde ortadan kalkar — nesne ise
yaşamayı sürdürür, bu yüzden izleyecek bir şey de yoktur.

Son iki satır derleyicinin bu kopyayı nasıl koruduğunu gösteriyor. Dış alana yeniden atama
yapan sürüm derleniyor; yakalanan yerele yeniden atama yapan sürüm **derlenmiyor**. Kaynakta
`final` sözcüğü hiçbir yerde yazılmamıştır; kısıtı derleyici kendiliğinden koyar, çünkü
kopyanın aslıyla anlaşmazlığa düşebileceği tek durum aslın değişmesidir. Önceki dersin
"yerel değişkende `final` sınıf dosyasına hiç yazılmaz" ölçümü burada karşılığını buluyor: o
kısıt bir bayrak bırakmaz, ama bir **kopyayı** korur.

#### Hangi Dış Örnek — Üçüncü Bir Kaynak

Kararın tarafı bu bölümde ikiye ayrılır. Yakalanan değerin ne olacağı **yapıcının çağrıldığı
anda** kesinleşir ve bir daha sorulmaz. Hangi dış nesnenin okunacağı ise her okumada yeniden
sorulur — ve yanıtı ne bildirilen tip ne de çalışma zamanı tipi verir.

```java
// Baglama.java — hangi dış örneğe bağlandığı çalışma zamanında belirlenir
public class Baglama {
    static class Dis {
        String ad;
        Dis(String ad) { this.ad = ad; }
        class Ic { String oku() { return ad; } }
    }

    public static void main(String[] args) throws Exception {
        Dis a = new Dis("birinci");
        Dis b = new Dis("ikinci");
        Dis.Ic x = a.new Ic();
        Dis.Ic y = b.new Ic();
        System.out.println("x ile y ayni sinif mi : " + (x.getClass() == y.getClass()));
        System.out.println("x.oku()               : " + x.oku());
        System.out.println("y.oku()               : " + y.oku());
        System.out.println();
        for (String[] s : new String[][] {
                {"I yap() { return new D().new I(); }", "dis ornek verilerek"},
                {"static I yap() { return new I(); }",  "dis ornek olmadan"}})
            System.out.printf("%-20s -> derleniyor mu: %s%n", s[1],
                    Olcek.derle(Olcek.yeniDizin(), "D",
                            "class D { class I { } " + s[0] + " }"));
    }
}
```

```
x ile y ayni sinif mi : true
x.oku()               : birinci
y.oku()               : ikinci

dis ornek verilerek  -> derleniyor mu: true
dis ornek olmadan    -> derleniyor mu: false
```

`x` ile `y` aynı sınıftır — çıktının ilk satırı bunu doğruluyor — ve bildirilen tipleri de
aynıdır. Buna karşılık aynı yöntem çağrısı iki ayrı sonuç veriyor. Bu kursta şimdiye kadar
ölçülen iki kaynak da burada yanıt veremez: iki nesnenin çalışma zamanı tipi bir, bildirilen
tipi bir. Sonucu belirleyen şey **gizli alanın değeridir** — hangi dış örneğin `this$0` alanına
yazıldığıdır.

Bu, kursun sorusuna bir üçüncü yanıt eklemez; onu daha kesin kılar. Yeniden tanımlanmış bir
yöntemde nesnenin sınıfı yanıtı verirken burada nesnenin **bir alanı** verir; ikisi de çalışma
zamanına aittir, ama biri tipe biri veriye bağlıdır. Aradaki farkın pratik sonucu şudur: alt
sınıf eklenmeyen bir programda ilk kaynak hiç değişmez, ikinci kaynak ise her `new` ifadesinde
yeniden belirlenir.

Alttaki iki satır bu alanın zorunluluğunu gösteriyor. Bir iç sınıf ancak bir dış örnek
üzerinden üretilebilir; dış örneği olmayan bir bağlamdan yazılan `new` derlemeyi durdurur.
Gizli alanın doldurulamayacağı bir yerde nesne hiç kurulamaz.

#### Sınırlayıcı Ölçüm: Gizli Alanın Bulunmadığı Yer

Tezin sınırı ilk tablonun iki satırında duruyor. `Dis$Yuva` hiçbir alan taşımıyor ve yapıcısı
parametresizdir; `Dis$1SYerel` ise `this$0` taşımıyor, yalnız `val$y` taşıyor.

Statik iç içe sınıf, dış sınıfın gövdesinde yazılmış olmasına karşın dış **örnekle** hiçbir
bağ kurmaz. Kaynaktaki iç içelik yalnız ad alanı ve görünürlük içindir; üretilen sınıf, dış
sınıfla aynı düzeyde duran bağımsız bir sınıftır. Bu yüzden "hangi dış nesne" sorusunun
çalışma zamanında bir yanıtı yoktur, çünkü soru hiç sorulmaz. Aynı gerekçe statik bir yöntemde
tanımlanan yerel sınıf için de geçerlidir: o yöntem çalışırken ortada bir dış örnek
bulunmadığından taşınacak bir referans da yoktur, ama yakalanan yerel değişken orada da
kopyalanır.

Buradan iki sonuç çıkar. Birincisi, gizli alanın varlığını belirleyen şey sınıfın nereye
yazıldığı değil, **dışarıdan neye eriştiğidir**: dış örneğe erişmeyen bir biçim referans
taşımaz. İkincisi, ölçüm gözlenemez hâle gelebilir. Dış alanı hiç okumayan bir iç sınıf
yazıldığında `this$0` yine eklenir ama davranışta hiçbir izi görünmez; iki biçim arasındaki
fark, ancak dış nesne yaşamaya devam ettiği için bir daha serbest bırakılamadığında ortaya
çıkar. Bir bağ kurulduğunu bilmeyen bir programcı, kaldırıldığını sandığı nesnenin hâlâ
tutulduğunu ancak dolaylı olarak fark eder.

#### Özet

- Tek bir kaynak dosyası **altı** sınıf dosyası üretir; iç içelik sınıf dosyası düzeyinde
  yoktur, adsız sınıfın bile kendi dosyası vardır ve beş biçimin dördü kaynakta yazılmamış
  gizli alan taşır: dış örneği tutan `this$0`, yakalanan değeri tutan `val$y`.
- İki iç sınıf örneği aynı sınıftan ve aynı bildirilen tipten olduğu hâlde aynı çağrıya iki
  ayrı yanıt verir; yanıtı veren şey tip değil, `this$0` alanına yazılan dış örnektir.
- Gizli alanları dolduran şey yapıcının gizli parametreleridir: iç sınıfın yapıcısı `(Dis)`,
  örnek yönteminde tanımlanan yerel sınıfınki `(Dis, int)`, statik yöntemdekinin ise `(int)`
  imzasını taşır.
- Dış alan **paylaşılır** ve canlı okunur — nesne üretildikten sonra 2 ve 3 değerleri okundu;
  yakalanan yerel **kopyalanır** ve iki okumada da 1 kaldı.
- Kaynakta `final` yazılmadığı hâlde yakalanan değişkene yeniden atama derlemeyi durdurur;
  kısıt kopyanın aslıyla anlaşmazlığa düşmesini engeller.
- Statik iç içe sınıf gizli alan taşımaz ve statik yöntemdeki yerel sınıf `this$0` taşımaz:
  gizli alanı belirleyen şey yazıldığı yer değil, dışarıdan neye eriştiğidir.

#### Sonraki Adım

Bu derste derleyici kaynakta hiç yazılmamış **sınıflar** üretti ve onlara kaynakta hiç
yazılmamış alanlar ile yapıcı parametreleri koydu. Üretilen her şeyin ortak yanı, bir
programcının elle yazması gereken bir bağlama işini üstlenmesiydi. Aynı üretim yeteneğinin çok
daha görünür bir kullanımı vardır: tek satırlık bir bildirimden yola çıkıp bir veri
taşıyıcının bütün yüzeyini — alan okuyucularını, eşitliği, karma değerini ve metne çevirmeyi —
derleyicinin üretmesi. Sonraki ders bu üyeleri sayar ve her birinin kararı hangi tarafa
bıraktığını sorar; sonra da üretilen eşitliğin nereye kadar koruduğunu, yani bir veri
taşıyıcının içindeki değişebilir bir parçanın dışarıdan nasıl değiştirilebildiğini ölçer.

### Kayıtlar ve Numaralandırmalar

Source: https://academia.sh/tr/kurslar/java-nesne-modeli/kayitlar-ve-numaralandirmalar

Tek satırlık bir kayıt bildirimi sınıf dosyasında on üye üretir ve üretilen sınıf genişletilemez, yani bildirilen tiple çalışma zamanı tipi hiçbir zaman ayrılamaz. Numaralandırmada ise gövde yazılan bir sabit kendi sınıfını alır; kaydın ürettiği eşitlik ise yalnız sığ korur.

Önceki derste derleyici kaynakta hiç yazılmamış sınıflar üretti ve onlara gizli alanlarla
gizli yapıcı parametreleri koydu. Üretilen her şeyin ortak yanı, bir programcının elle
yazacağı bir bağlama işini üstlenmesiydi — ama üretilenler görünmezdi: hiçbiri kaynaktan
çağrılabilecek bir yüzey değildi.

Bu derste aynı üretim yeteneği görünür bir işe koşulur. Java'da bir veri taşıyıcının bütün
yüzeyi — alan okuyucuları, eşitlik, karma değeri ve metne çevirme — tek bir satırdan
üretilebilir; bu satırın adı **kayıt (record)** bildirimidir. Sabit bir kümeyi tanımlayan
**numaralandırma** da benzer bir üretim yapar. Sorular üç tanedir: bir satır kaç üye
üretiyor, üretilen üyeler kararı hangi tarafa bırakıyor ve üretilen eşitlik nereye kadar
koruyor?

#### Ölçüm Çekirdeği

Ölçüm bir kaynağı derler, ürettiği sınıf dosyasındaki alan ve yöntem adlarını okur, sınıfın
genişletilebilir olup olmadığını bildirir ve ayrıca bir kaynağın derlenip derlenmediğini
döndürür.

- **SN36** — Kâhin kaynağın kendisidir: kayıt bildiriminin gövdesi boştur, dolayısıyla sınıf
  dosyasında görünen her üye derleyicinin ürettiğidir.
- **SN37** — Üye listesi sınıf dosyasından okunur; sayım kaynaktaki yazımı değil, üretilen
  dosyayı temel alır.
- **SN38** — Derleme denemeleri kendi geçici dizinlerinde yapılır ve sonuç tek bir çıkış
  koduyla okunur.
- **SN39** — Eşitlik ölçümünde iki nesne ayrı ayrı kurulur; kimlik yalnız `==` ile
  karşılaştırılır ve hiçbir kimlik sayısı basılmaz.
- **SN40** — Karma değeri yalnız iki nesnenin karması **birbirine eşit mi** sorusuyla anılır ve
  bu soru yalnız değer tabanlı karma üreten bileşenler için sorulur.

```java
// Olcek.java — sınıf dosyasındaki üyeler ve genişletme denemesi
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.reflect.AccessFlag;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Olcek {
    record Sinif(boolean sonlanmis, List<String> alan, List<String> yontem) {}

    static Path yeniDizin() throws Exception { return Files.createTempDirectory("olcek"); }

    static boolean derle(Path d, String ad, String kaynak) throws Exception {
        Path k = d.resolve(ad + ".java");
        Files.writeString(k, kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        return ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, "-d", d.toString(), "-cp", d.toString(), k.toString()) == 0;
    }

    static Sinif oku(String kaynak, String ad) throws Exception {
        Path d = yeniDizin();
        if (!derle(d, ad, kaynak)) throw new IllegalStateException("derlenmedi: " + ad);
        ClassModel cm = ClassFile.of().parse(d.resolve(ad + ".class"));
        List<String> alan = new ArrayList<>();
        for (FieldModel f : cm.fields())
            alan.add(f.fieldName().stringValue()
                    + (f.flags().has(AccessFlag.FINAL) ? " [final]" : ""));
        List<String> yontem = new ArrayList<>();
        for (MethodModel m : cm.methods()) yontem.add(m.methodName().stringValue());
        return new Sinif(cm.flags().has(AccessFlag.FINAL), alan, yontem);
    }
}
```

#### Tek Satır, On Üye

Ölçülen kaynak tek bir satırdır ve gövdesi boştur. Kursun somut ekseni burada da sürer: üç
alanlı bir depo kaydı — ad, adet ve gram cinsinden ağırlık.

```java
// Uretilen.java — tek satırlık bir kayıt bildiriminden kaç üye çıkıyor
import java.util.TreeSet;

public class Uretilen {
    static final String ELLE = """
        class Elle {
            private final String ad; private final int adet; private final long agirlik;
            Elle(String ad, int adet, long agirlik) {
                this.ad = ad; this.adet = adet; this.agirlik = agirlik;
            }
            public String ad() { return ad; }
            public int adet() { return adet; }
            public long agirlik() { return agirlik; }
            @Override public boolean equals(Object o) {
                return o instanceof Elle e
                        && ad.equals(e.ad) && adet == e.adet && agirlik == e.agirlik;
            }
            @Override public int hashCode() {
                return java.util.Objects.hash(ad, adet, agirlik);
            }
            @Override public String toString() {
                return "Elle[ad=" + ad + ", adet=" + adet + ", agirlik=" + agirlik + "]";
            }
        }
        """;

    public static void main(String[] args) throws Exception {
        String kaynak = "record Kayit(String ad, int adet, long agirlik) { }";
        Olcek.Sinif k = Olcek.oku(kaynak, "Kayit");
        System.out.println("kaynak: " + kaynak);
        System.out.println("alanlar (" + k.alan().size() + "): " + k.alan());
        System.out.println("yontemler (" + k.yontem().size() + "): " + k.yontem());
        System.out.println("sinif final mi: " + k.sonlanmis());
        System.out.println();
        Olcek.Sinif e = Olcek.oku(ELLE, "Elle");
        System.out.println("kaynak satiri: kayit 1, elle yazilan " + ELLE.strip().lines().count());
        System.out.println("uye sayisi   : kayit " + (k.alan().size() + k.yontem().size())
                + ", elle yazilan " + (e.alan().size() + e.yontem().size()));
        System.out.println("yontem adlari ayni mi: "
                + new TreeSet<>(k.yontem()).equals(new TreeSet<>(e.yontem())));
        System.out.println();
        System.out.printf("%-34s -> derleniyor mu: %s%n", "kaydi genisletme",
                Olcek.derle(Olcek.yeniDizin(), "T", kaynak + " class T extends Kayit { }"));
        System.out.printf("%-34s -> derleniyor mu: %s%n", "kaydin arayuz gerceklestirmesi",
                Olcek.derle(Olcek.yeniDizin(), "T", "interface T { }"
                        + " record Kayit(String ad) implements T { }"));
        System.out.printf("%-34s -> derleniyor mu: %s%n", "alanina yeniden atama",
                Olcek.derle(Olcek.yeniDizin(), "T",
                        "record Kayit(String ad) { } class T { void f(Kayit k) { k.ad = \"x\"; } }"));
    }
}
```

```
kaynak: record Kayit(String ad, int adet, long agirlik) { }
alanlar (3): [ad [final], adet [final], agirlik [final]]
yontemler (7): [<init>, toString, hashCode, equals, ad, adet, agirlik]
sinif final mi: true

kaynak satiri: kayit 1, elle yazilan 19
uye sayisi   : kayit 10, elle yazilan 10
yontem adlari ayni mi: true

kaydi genisletme                   -> derleniyor mu: false
kaydin arayuz gerceklestirmesi     -> derleniyor mu: true
alanina yeniden atama              -> derleniyor mu: false
```

Kaynakta bir bildirim satırı yazıldı; sınıf dosyasında **üç alan** ve **yedi yöntem**, toplam
**on üye** var. Üç alanın üçü de `final` bayrağını taşıyor. Yedi yöntemin üçü **bileşen
okuyucusudur** (`ad`, `adet`, `agirlik`), üçü Java'nın her nesnesinden beklediği yüzeydir
(`toString`, `hashCode`, `equals`) ve biri bileşenleri alan **asıl yapıcıdır**. Bir önceki
dersin gizli alanlarından farkı buradadır: bunlar gizli değildir, çağrılabilir bir yüzeydir.

Ortadaki üç satır bu yüzeyin elle yazıldığında neye mal olduğunu ölçüyor. Aynı on üyeyi elle
kuran sınıf **19 satırdır** ve sınıf dosyasında yine **10 üye** bırakır; iki sınıfın yöntem
adları birebir aynıdır. Kayıt yeni bir düzenek getirmez — sınıf dosyası düzeyinde ortaya çıkan
şey sıradan bir sınıftır. Getirdiği şey, bu on üyenin **elle yazılmasında yapılabilecek
hataların ortadan kalkmasıdır**: bir bileşen eklendiğinde elle yazılan eşitlikte onu unutmak
mümkündür, üretilen eşitlikte değildir.

Java Temelleri kursunda numaralandırma için de aynı ölçüm yapılmış ve kaynakta yalnız iki ad
yazılan bir bildirimin sınıf dosyasında kaynakta bulunmayan alanlar ve yöntemler taşıdığı
gösterilmişti. İki biçim aynı düzeneği kullanır; farkları ne ürettikleridir, üretip üretmedikleri
değil.

Alt bölümdeki üç deneme üretilenin bir yanını daha gösteriyor. Kayıt **genişletilemez**:
sınıf `final` bayrağını taşır ve onu genişleten bir sınıf derlenmez. Bir arayüz
gerçekleştirmesi ise serbesttir. Alanına yeniden atama da derlenmez, çünkü alanların hepsi
`final`dır — önceki dersin ölçtüğü kısıt burada kaynakta hiç yazılmadan uygulanır.

#### Üretilen Üyeler Kararı Kime Bırakıyor

Kaydın genişletilememesi kursun sorusunu tuhaf bir yere getirir. Bir kayıt tipinin alt tipi
olamayacağına göre, o tiple bildirilmiş bir adın çalışma zamanı tipi her zaman aynı olacaktır.
Öyleyse üretilen üyeler için soru anlamını mı yitirir?

```java
// Taraf.java — üretilen üyeler kararı hangi tarafa bırakıyor
import java.util.*;

public class Taraf {
    interface Tasiyici { }
    record Kayit(String ad, int adet) implements Tasiyici { }

    enum Islem {
        EKLE { @Override int uygula(int a) { return a + 1; } },
        TUT;
        int uygula(int a) { return a; }
    }

    public static void main(String[] args) {
        Tasiyici t = new Kayit("civata", 12);
        Object n = t;
        System.out.println("bildirilen tip Object, metne cevirme : " + n);
        Kayit a = new Kayit("civata", 12), b = new Kayit("civata", 12);
        System.out.println("a == b                               : " + (a == b));
        System.out.println("a.equals(b)                          : " + a.equals(b));
        System.out.println("karma degerleri esit mi              : " + (a.hashCode() == b.hashCode()));
        Map<Kayit, String> m = new HashMap<>();
        m.put(a, "birinci");
        System.out.println("b anahtariyla okunan                 : " + m.get(b));
        System.out.println();
        for (Islem i : Islem.values())
            System.out.printf("%-5s sinifi Islem'in kendisi mi: %-5s | bildiren tip: %s | uygula(1)=%d%n",
                    i, i.getClass() == Islem.class, i.getDeclaringClass().getSimpleName(), i.uygula(1));
        System.out.println("Islem.EKLE ile valueOf(\"EKLE\") ayni nesne mi: "
                + (Islem.EKLE == Islem.valueOf("EKLE")));
    }
}
```

```
bildirilen tip Object, metne cevirme : Kayit[ad=civata, adet=12]
a == b                               : false
a.equals(b)                          : true
karma degerleri esit mi              : true
b anahtariyla okunan                 : birinci

EKLE  sinifi Islem'in kendisi mi: false | bildiren tip: Islem | uygula(1)=2
TUT   sinifi Islem'in kendisi mi: true  | bildiren tip: Islem | uygula(1)=1
Islem.EKLE ile valueOf("EKLE") ayni nesne mi: true
```

İlk satır soruyu yanıtlıyor. Bildirilen tip `Object`tir ve `Object` sınıfının metne çevirme
yöntemi kaydın verdiğini vermez; buna karşılık basılan metin kaydın ürettiği biçimdir. Üretilen
`toString` bir **yeniden tanımlamadır** ve her yeniden tanımlama gibi kararı çalışma zamanı
tipine bırakır. Kayıt genişletilemez olduğu için o tip **bilinen tek tiptir**, ama karar yine
de o taraftan gelir. Genişletilememe kararın tarafını değiştirmez; yanıtın **önceden
bilinebilir** olmasını sağlar.

Sonraki dört satır üretilen eşitliğin ne olduğunu gösteriyor. `a` ile `b` ayrı ayrı kurulmuş
iki nesnedir ve `==` **yanlış** verir: kimlik ayrıdır. `equals` ise **doğru** verir, çünkü
üretilen gerçekleştirim bileşenleri karşılaştırır. Karma değerleri de eşittir ve bunun
gözlenebilir sonucu son satırdadır: `a` ile eklenen bir eşleme girişi `b` ile okunabiliyor.
Kimlik ile eşitliğin ayrıldığı bu üç satır, bir veri taşıyıcının nasıl davranması gerektiğinin
tanımıdır ve kaynakta tek bir karakteri yazılmamıştır.

Numaralandırmada aynı soru ters uçtan sorulur. `EKLE` ile `TUT` aynı bildirilen tiptedir, ama
`EKLE`nin çalışma zamanı sınıfı **numaralandırmanın kendisi değildir**: gövde yazılan bir sabit
kendi sınıfını alır. `TUT` içinse ikisi aynıdır. İki sabit aynı yöntem çağrısına ayrı yanıtlar
veriyor — **2** ve **1** — ve yanıtı veren şey yine çalışma zamanı tipidir. Bildiren tip her
ikisinde de `Islem` olarak kalır; bu, bildirilen tiple çalışma zamanı tipinin ayrıldığı yerde
sabitin hangi kümeye ait olduğunun kaybolmadığını gösterir. Son satır ise kümenin sabit
oluşunu ölçer: adla bulunan sabit `==` ile aynı nesnedir, çünkü her sabitten bir tane vardır.

İki biçimin bu derste yan yana durmasının nedeni buradan okunur. Kayıt **kaç tane olduğu
bilinmeyen** taşıyıcılar için yazılır ve her birinin kimliğini değerine bırakır; numaralandırma
**kaç tane olduğu bilinen** bir küme tanımlar ve her birinin kimliğini kendinde tutar. Birinde
`equals` doğruyken `==` yanlış olabilir, ötekinde ikisi hep birlikte yanıt verir. Ortak yanları
üretilen üyelerin kaynağıdır: iki bildirim de derleyiciye bir yüzey ürettirir ve üretilen
yüzeyin kararı çalışma zamanı tipinden gelir.

#### Sınırlayıcı Ölçüm: Kayıt Sığ Değiştirilemezdir

Üretilen alanların hepsi `final`dı ve üretilen eşitlik bileşenleri karşılaştırıyordu. Bu iki
gerçek birlikte okunduğunda bir kayıt "değiştirilemez" görünür. Sınır tam buradadır.

```java
// Sig.java — kayıt sığ değiştirilemezdir
import java.util.Arrays;

public class Sig {
    record Parti(String ad, int[] adetler) { }

    record Guvenli(String ad, int[] adetler) {
        Guvenli { adetler = adetler.clone(); }
    }

    public static void main(String[] args) {
        int[] disari = {1, 2, 3};
        Parti p = new Parti("civata", disari);
        Guvenli g = new Guvenli("civata", disari);
        System.out.println("kurulustan sonra    : " + Arrays.toString(p.adetler())
                + " | kopyalayan: " + Arrays.toString(g.adetler()));
        disari[0] = 99;
        System.out.println("disaridan yazildi   : " + Arrays.toString(p.adetler())
                + " | kopyalayan: " + Arrays.toString(g.adetler()));
        System.out.println();
        Parti q = new Parti("civata", new int[] {99, 2, 3});
        System.out.println("ad alanlari esit mi : " + p.ad().equals(q.ad()));
        System.out.println("dizi icerigi esit mi: " + Arrays.equals(p.adetler(), q.adetler()));
        System.out.println("p.equals(q)         : " + p.equals(q));
    }
}
```

```
kurulustan sonra    : [1, 2, 3] | kopyalayan: [1, 2, 3]
disaridan yazildi   : [99, 2, 3] | kopyalayan: [1, 2, 3]

ad alanlari esit mi : true
dizi icerigi esit mi: true
p.equals(q)         : false
```

`p` kurulduktan sonra hiçbir üyesine yazılmadı; yine de `adetler` bileşeni değişti. Değişikliği
yapan taraf kaydı hiç görmedi bile — elindeki dizi üzerine yazdı ve kayıt aynı diziyi tutuyordu.
`final` alan, önceki derste ölçüldüğü gibi yalnız bağlamayı korur; bağın ucundaki nesne bu
kısıtın dışındadır. Kayıt bu kısıtı bileşen başına uygular, dolayısıyla koruması bileşenlerin
tipi kadardır — **sığdır**.

Alt üç satır aynı sınırı eşitlik tarafında gösteriyor. `p` ile `q` artık aynı adı ve aynı
içerikte bir diziyi taşıyor; iki karşılaştırma da doğru veriyor. Buna karşılık `p.equals(q)`
**yanlış**tır. Üretilen eşitlik her bileşeni o bileşenin kendi eşitliğiyle karşılaştırır; bir
dizinin kendi eşitliği ise kimliktir. Aynı kayıt tipi içinde bir bileşen değere, öteki kimliğe
bakıyor ve bu ayrım kaynakta hiçbir yerde yazılı değil.

İki kusurun da çaresi aynı yerdedir. `Guvenli` kaydı asıl yapıcısında diziyi kopyalar ve
dışarıdaki değişiklikten etkilenmez; çıktının ikinci satırı bunu gösteriyor. Derleyicinin
ürettiği şey bir başlangıç noktasıdır, bir güvence değil: üretilen üyeler bileşenlerin
davranışını devralır, düzeltmek yazan tarafa kalır.

#### Özet

- Gövdesi boş, tek satırlık bir kayıt bildirimi sınıf dosyasında **üç alan** ve **yedi
  yöntem** üretir; üç alanın üçü de `final` bayrağını taşır.
- Üretilen yedi yöntem üç bileşen okuyucusu, üç ortak yüzey yöntemi (`toString`, `hashCode`,
  `equals`) ve bileşenleri alan asıl yapıcıdır.
- Kayıt genişletilemez, arayüz gerçekleştirebilir ve alanına yeniden atama derlemeyi durdurur.
- Üretilen üyeler kararı **çalışma zamanı tipine** bırakır: bildirilen tip `Object` olduğunda
  bile basılan metin kaydın ürettiği biçimdir. Genişletilememe kararın tarafını değiştirmez,
  yanıtı önceden bilinebilir kılar.
- Ayrı ayrı kurulan iki kayıt için `==` yanlış, `equals` doğrudur ve karma değerleri eşittir;
  numaralandırmada ise gövde yazılan sabit kendi sınıfını alır ve aynı çağrıya ayrı yanıt verir.
- Kaydın koruması sığdır: dizi bileşeni dışarıdan değişir ve üretilen eşitlik o bileşende
  değere değil kimliğe bakar; asıl yapıcıda kopyalamak ikisini de düzeltir.

#### Sonraki Adım

Bu konu boyunca ölçülen şey hep aynı soruydu: kararı bildirilen tip mi verdi, çalışma zamanı
tipi mi. Son iki derste soruya bir taraf daha katıldı — derleyicinin kendisi. Gizli alanları,
yapıcı parametrelerini ve bir veri taşıyıcının bütün yüzeyini o üretti; ürettiklerinin hepsi
sınıf dosyasında durdu ve çalışma zamanında okunabildi. Öyleyse derleyicinin elindeki bir
başka bilgiye, **tip bilgisinin kendisine** ne oluyor? Bir kabın hangi tipte öğe tuttuğu
kaynakta yazılıdır ve derleyici o yazıya bakarak denetim yapar. Sıradaki konu bu bilginin
çalışma zamanında ne kadarının kaldığını ölçer: iki ayrı tip parametresiyle yazılmış iki kap
çalışma zamanında ayırt edilebiliyor mu ve denetimi yapan taraf ortadan kalktığında yanlış
yazmanın bedelini kim ödüyor?

## Genellikler ve İstisnalar

Tip parametrelerinin derleme zamanında yaptığı iş ve tip silmenin çalışma zamanına bıraktığı boşluk, sınırlı joker karakterlerle okuma ve yazma yönünün ayrılması, denetlenen ile denetlenmeyen istisna ayrımı, otomatik kaynak kapatma, ve bir hatanın istisnayla mı dönüş değeriyle mi bildirileceği kararı.

### Genel Tipler

Source: https://academia.sh/tr/kurslar/java-nesne-modeli/genel-tipler

Tip parametresi sınıf dosyasına iki ayrı yerde yazılır: sanal makinenin okuduğu tanımda silinmiştir, imza özniteliğinde durur. Ölçüm bunun sonucunu gösterir — denetim yalnız derleme zamanındadır, iki genel tip çalışma zamanında aynı sınıftır ve ham tiple yapılan yazma okuyan yerde düşer.

Önceki ders bir kayıt için derleyicinin ürettiği üyeleri saydı: erişimciler, eşitlik, karma
ve metne çevirme kaynakta yazılmadan sınıf dosyasına girdi. Derleyici üye üretebiliyorsa,
elindeki tip bilgisiyle ne yapıyor — onu da bir yere yazıyor mu, yoksa kullanıp bırakıyor mu?

Genellikler (generics) TypeScript kursunda tip parametreleri ve kısıtlarla, ifade gücü
açısından kuruldu; bu ders onu tekrarlamaz. Buradaki soru kursun sorusudur: `Depo<Ust>`
yazımındaki `Ust` bilgisiyle kararı **kim ve ne zaman** veriyor? Yanıt bu konuda uçtadır —
kararın tamamı bildirilen tiptedir ve çalışma zamanı tipinin söyleyecek hiçbir şeyi yoktur.
Ölçüm bunun üç sonucunu gösterecek: iki ayrı tip parametresiyle yazılmış iki kap çalışma
zamanında ayırt edilebiliyor mu, denetim nereye konuyor ve denetimi yapan taraf ortadan
kalktığında yanlış yazmanın bedelini kim ödüyor?

#### Tip Parametresi Sınıf Dosyasına İki Kez Yazılır

- **GN1** — Ölçülen kaynak metni dersin içinde yazılır. Ne yazdığımızı bildiğimiz için, sınıf
  dosyasında bulunan her fark derleyicinin kararıdır.
- **GN2** — `Olcek` çekirdeği ortak tanımdan gelir. Bu ders ona tek bir ölçüm ekler: bir
  öğenin **imza özniteliğini** döndüren `imza`. Var olan ölçümlerin davranışı değişmez.
- **GN3** — Tanımlayıcı ve imza metinleri sınıf dosyasındaki ham biçimleriyle basılır;
  kısaltılmaz ve çevrilmez.

```java
// Olcek.java — ortak ölçüm çekirdeği: sınıf dosyasını derleyip okur
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Olcek {
    static ClassModel derle(String kaynak, String sinif) throws Exception {
        Path d = Files.createTempDirectory("olcek");
        Path k = d.resolve(sinif + ".java");
        Files.writeString(k, kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, "-d", d.toString(), k.toString()) != 0)
            throw new IllegalStateException("derlenmedi: " + sinif);
        return ClassFile.of().parse(d.resolve(sinif + ".class"));
    }

    static List<String> komutlar(String kaynak, String sinif, String yontem) throws Exception {
        List<String> adlar = new ArrayList<>();
        for (MethodModel m : derle(kaynak, sinif).methods())
            if (m.methodName().stringValue().equals(yontem))
                for (CodeElement e : m.code().orElseThrow())
                    if (e instanceof Instruction i)
                        adlar.add(i.opcode().name().toLowerCase(Locale.ROOT));
        return adlar;
    }

    static String imza(AttributedElement e) {
        return e.findAttribute(Attributes.signature())
                .map(a -> a.signature().stringValue()).orElse("-");
    }
}
```

```java
// Imza.java — tip parametresi sınıf dosyasında nerede duruyor
import java.lang.classfile.*;

public class Imza {
    static final String DEPO = """
        class Depo<T> {
            private T oge;
            void koy(T oge) { this.oge = oge; }
            T al() { return oge; }
        }
        """;

    public static void main(String[] args) throws Exception {
        ClassModel cm = Olcek.derle(DEPO, "Depo");
        System.out.printf("%-14s %-26s %s%n", "uye", "sanal makinenin gordugu", "imza ozniteligi");
        System.out.printf("%-14s %-26s %s%n", "Depo sinifi", "-", Olcek.imza(cm));
        for (FieldModel f : cm.fields())
            System.out.printf("%-14s %-26s %s%n", "alan " + f.fieldName().stringValue(),
                    f.fieldType().stringValue(), Olcek.imza(f));
        for (MethodModel m : cm.methods())
            System.out.printf("%-14s %-26s %s%n", "yontem " + m.methodName().stringValue(),
                    m.methodType().stringValue(), Olcek.imza(m));
    }
}
```

```
uye            sanal makinenin gordugu    imza ozniteligi
Depo sinifi    -                          <T:Ljava/lang/Object;>Ljava/lang/Object;
alan oge       Ljava/lang/Object;         TT;
yontem <init>  ()V                        -
yontem koy     (Ljava/lang/Object;)V      (TT;)V
yontem al      ()Ljava/lang/Object;       ()TT;
```

Orta sütun **tip silmedir** (type erasure). Kaynakta `T` yazan her yer sınıf dosyasının
tanımlayıcısında `Object` olmuştur: alan `Ljava/lang/Object;`, `koy` yöntemi bir `Object`
alır, `al` yöntemi bir `Object` döndürür. Sanal makine bir yöntemi çağırırken bu sütuna
bakar; onun için `Depo` tek parametreli sıradan bir sınıftır ve `T` diye bir şey yoktur.

Sağ sütun ise **silinmemiştir**. Sınıfın imzası `T`'nin var olduğunu ve üst sınırının
`Object` olduğunu yazıyor; alanın imzası `TT;`, yani "tip değişkeni `T`"; `koy` ile `al`
imzalarında da `T` adıyla duruyor. Kaynakta yazılan bilgi kaybolmadı, **ikinci bir yere**
yazıldı. Yapıcının imzası yok, çünkü yapıcının tanımında tip parametresi geçmiyor.

Buradan kursun sorusunun bu konudaki yanıtı çıkıyor. Sağ sütunu **yalnız derleyici** okur;
sol sütunu sanal makine yürütür. Tip parametresiyle ilgili her karar, çalışma zamanı tipinin
hiç bakmadığı bir metne dayanır.

#### Denetim Okuyan Yere Konur, Yazana Konmaz

- **GN4** — Aynı yazma üç ayrı kaynakta denenir; ikisi derlenir, biri derlenmez. Derleyicinin
  ileti metni basılmaz, yalnız derlenip derlenmediği bildirilir.

```java
// Denetim.java — genel çağrı sınıf dosyasında hangi komutu bırakıyor
public class Denetim {
    static final String TIPLER = """
        class Ust { String ad() { return "ust yontem"; } }
        class Alt extends Ust { @Override String ad() { return "alt yontem"; } }
        class Depo<T> { private T oge; void koy(T o) { oge = o; } T al() { return oge; } }
        """;

    static final String KULLANIM = TIPLER + """
        class Kullanim {
            static Ust oku(Depo<Ust> d) { return d.al(); }
            @SuppressWarnings("rawtypes")
            static Object hamOku(Depo d) { return d.al(); }
            static void yaz(Depo<Ust> d, Alt a) { d.koy(a); }
        }
        """;

    static final String YAZMA =
            TIPLER + "class Yazma { static void y(%s d) { d.koy(new Alt()); } }";

    static void dene(String ad, String kaynak) {
        try {
            Olcek.derle(kaynak, "Yazma");
            System.out.printf("%-30s -> derlendi%n", ad);
        } catch (Exception e) {
            System.out.printf("%-30s -> %s%n", ad, e.getMessage());
        }
    }

    public static void main(String[] args) throws Exception {
        System.out.println("okuma  Depo<Ust> : " + Olcek.komutlar(KULLANIM, "Kullanim", "oku"));
        System.out.println("okuma  ham Depo  : " + Olcek.komutlar(KULLANIM, "Kullanim", "hamOku"));
        System.out.println("yazma  Depo<Ust> : " + Olcek.komutlar(KULLANIM, "Kullanim", "yaz"));
        System.out.println();
        dene("Depo<Ust> d; d.koy(Alt)", YAZMA.formatted("Depo<Ust>"));
        dene("Depo<String> d; d.koy(Alt)", YAZMA.formatted("Depo<String>"));
        dene("Depo d (ham); d.koy(Alt)", YAZMA.formatted("Depo"));
    }
}
```

```
okuma  Depo<Ust> : [aload_0, invokevirtual, checkcast, areturn]
okuma  ham Depo  : [aload_0, invokevirtual, areturn]
yazma  Depo<Ust> : [aload_0, aload_1, invokevirtual, return]

Depo<Ust> d; d.koy(Alt)        -> derlendi
Depo<String> d; d.koy(Alt)     -> derlenmedi: Yazma
Depo d (ham); d.koy(Alt)       -> derlendi
```

İlk iki satır tek bir komutla ayrılıyor. `Depo<Ust>` üzerinden yapılan okuma **dört**
komuttur ve dördüncüsü `checkcast`; ham tip üzerinden yapılan aynı okuma **üç** komuttur ve
denetim yoktur. Çağrılan yöntem ikisinde de aynı yöntemdir — sınıf dosyasında `Object`
döndüren tek bir `al` vardır. Farkı yapan şey çağrılan taraf değil, **çağıran taraftır**:
derleyici okunan değeri bildirilen tipe uydurmak için bir denetim koyar.

Üçüncü satır dersin en sessiz ölçümüdür. Yazma **üç** komuttur ve içinde hiçbir denetim
yoktur: referansı yükle, değeri yükle, çağır. Okuma yerine denetim konur, yazma yerine
konmaz. Bunun nedeni alt tablodadır: yazma zaten derleme zamanında denetlenmiştir.
`Depo<String>` olarak adlandırılmış bir depoya `Alt` yazan kaynak **derlenmiyor**. Karar
tümüyle bildirilen tipten geliyor ve derleme durduğu için çalışma zamanına hiçbir iş
kalmıyor.

Alt tablonun üçüncü satırı ise ödenen bedeli yazıyor. Aynı depo **ham tiple** (raw type)
adlandırıldığında aynı yazma **derleniyor**. Derleyici burada bir uyarı üretir, ama uyarı
derlemeyi durdurmaz; bildirilen tip ortadan kalkınca denetleyecek taraf da kalmaz.

#### Kısıt Silinen Tipi Değiştirir

- **GN5** — Beş denemenin hepsi aynı kısıtlı sınıf üzerine kurulur; yalnız denenen satır
  değişir, çevresindeki tipler sabit kalır.

```java
// Sinir.java — kısıt silinen tipi değiştirir
import java.lang.classfile.*;

public class Sinir {
    static final String SINIRLI = """
        class Ust { String ad() { return "ust yontem"; } }
        class Alt extends Ust { @Override String ad() { return "alt yontem"; } }
        class Sinirli<T extends Ust> {
            private T oge;
            void koy(T oge) { this.oge = oge; }
            T al() { return oge; }
        }
        """;

    static final String OKUMA = SINIRLI + """
        class Okuma {
            static Ust ustOku(Sinirli<Ust> d) { return d.al(); }
            static Alt altOku(Sinirli<Alt> d) { return d.al(); }
        }
        """;

    static void dene(String ad, String bildirim, String govde) {
        try {
            Olcek.derle(SINIRLI + "class Yasak" + bildirim + " { " + govde + " }", "Yasak");
            System.out.printf("%-38s -> derlendi%n", ad);
        } catch (Exception e) {
            System.out.printf("%-38s -> %s%n", ad, e.getMessage());
        }
    }

    public static void main(String[] args) throws Exception {
        ClassModel cm = Olcek.derle(SINIRLI, "Sinirli");
        System.out.printf("%-14s %-26s %s%n", "uye", "sanal makinenin gordugu", "imza ozniteligi");
        System.out.printf("%-14s %-26s %s%n", "Sinirli sinifi", "-", Olcek.imza(cm));
        for (FieldModel f : cm.fields())
            System.out.printf("%-14s %-26s %s%n", "alan " + f.fieldName().stringValue(),
                    f.fieldType().stringValue(), Olcek.imza(f));
        for (MethodModel m : cm.methods())
            if (!m.methodName().stringValue().equals("<init>"))
                System.out.printf("%-14s %-26s %s%n", "yontem " + m.methodName().stringValue(),
                        m.methodType().stringValue(), Olcek.imza(m));

        System.out.println();
        System.out.println("Sinirli<Ust> okuma : " + Olcek.komutlar(OKUMA, "Okuma", "ustOku"));
        System.out.println("Sinirli<Alt> okuma : " + Olcek.komutlar(OKUMA, "Okuma", "altOku"));

        System.out.println();
        dene("<T> icinde o.ad()", "<T>", "String s(T o) { return o.ad(); }");
        dene("<T extends Ust> icinde o.ad()", "<T extends Ust>", "String s(T o) { return o.ad(); }");
        dene("new T[3]", "<T extends Ust>", "Object[] d() { return new T[3]; }");
        dene("x instanceof Sinirli<Ust>", "",
                "boolean t(Object x) { return x instanceof Sinirli<Ust>; }");
        dene("x instanceof Sinirli<?>", "",
                "boolean t(Object x) { return x instanceof Sinirli<?>; }");
    }
}
```

```
uye            sanal makinenin gordugu    imza ozniteligi
Sinirli sinifi -                          <T:LUst;>Ljava/lang/Object;
alan oge       LUst;                      TT;
yontem koy     (LUst;)V                   (TT;)V
yontem al      ()LUst;                    ()TT;

Sinirli<Ust> okuma : [aload_0, invokevirtual, areturn]
Sinirli<Alt> okuma : [aload_0, invokevirtual, checkcast, areturn]

<T> icinde o.ad()                      -> derlenmedi: Yasak
<T extends Ust> icinde o.ad()          -> derlendi
new T[3]                               -> derlenmedi: Yasak
x instanceof Sinirli<Ust>              -> derlenmedi: Yasak
x instanceof Sinirli<?>                -> derlendi
```

Tanımlayıcı sütunu değişti: kısıtlı bir tip parametresi `Object`'e değil, **sınırına**
siliniyor. Alan `LUst;`, `koy` bir `Ust` alıyor, `al` bir `Ust` döndürüyor. İmza sütunu ise
aynı kaldı — orada hâlâ `TT;` yazıyor ve sınıf imzası sınırı da taşıyor.

Bunun ilk sonucu alt tablonun ilk iki satırında. Sınırsız bir tip parametresi üzerinden
`o.ad()` çağıran kaynak **derlenmiyor**, çünkü silinen tip `Object`'tir ve `Object` üzerinde
böyle bir yöntem yoktur. Aynı satır sınır konduğunda **derleniyor**. Kısıt bir belgelendirme
değildir; sınıf dosyasındaki tanımlayıcıyı değiştirir ve çağrının bağlanabileceği yöntem
kümesini genişletir.

İkinci sonuç okuma satırlarında ve önceki bölümün ölçümünü tamamlıyor. `Sinirli<Ust>`
üzerinden yapılan okumada `checkcast` **yok**, `Sinirli<Alt>` üzerinden yapılanda **var**.
Denetim her genel okumaya eklenmez; yalnız bildirilen tip ile silinen tip **ayrıştığında**
eklenir. İlk ölçümde `Depo<T>` `Object`'e siliniyordu ve `Ust` okuması ayrışıyordu; burada
silinen tip zaten `Ust` olduğu için eklenecek bir şey kalmıyor.

Son üç satır silmenin doğrudan yasaklarıdır. `new T[3]` derlenmiyor: bir dizi öğe tipini
kendi içinde taşır ve her yazmada onu denetler — Java Temelleri kursunda ölçülmüştü — ama
çalışma zamanında `T` diye bir tip bulunmadığı için taşınacak bir şey yoktur. `x instanceof
Sinirli<Ust>` de derlenmiyor, oysa `x instanceof Sinirli<?>` derleniyor. Sanal makineye
sorulabilecek tek soru ham sınıftır; joker karakter (wildcard) tam olarak "tip parametresini
sorma" demektir.

#### İki Genel Tip Çalışma Zamanında Aynı Sınıftır

- **GN6** — Kimlik yalnız `==` ile sorulur, hiçbir kimlik sayısı basılmaz.
- **GN7** — Ham tipli yazmanın sonucu iddia olarak yazılmaz, `instanceof` ile ölçülür;
  yakalanan istisnadan yalnız sınıf adı basılır.

```java
// Silme.java — tip silme çalışma zamanında ne bırakıyor
import java.util.Arrays;

class Ust {
    String ad() { return "ust yontem"; }
}

class Alt extends Ust {
    @Override String ad() { return "alt yontem"; }
}

class Depo<T> {
    private T oge;
    void koy(T oge) { this.oge = oge; }
    T al() { return oge; }
}

public class Silme {
    static Depo<Ust> alan;

    @SuppressWarnings({"unchecked", "rawtypes"})
    public static void main(String[] args) throws Exception {
        Depo<Ust> tasiyicilar = new Depo<>();
        Depo<String> metinler = new Depo<>();
        System.out.println("Depo<Ust> ile Depo<String> ayni sinif mi : "
                + (tasiyicilar.getClass() == metinler.getClass()));
        System.out.println("calisma zamaninda tip parametreleri      : "
                + Arrays.toString(Depo.class.getTypeParameters()));

        tasiyicilar.koy(new Alt());
        System.out.println("okunan ogenin calisma zamani tipi        : "
                + tasiyicilar.al().getClass().getSimpleName());

        Depo ham = tasiyicilar;
        ham.koy("kuzey");
        System.out.println("ham tiple yazma durduruldu mu            : "
                + (tasiyicilar.al() instanceof Ust ? "evet" : "hayir"));
        try {
            Ust u = tasiyicilar.al();
            System.out.println("okuma sonucu                             : " + u.ad());
        } catch (ClassCastException e) {
            System.out.println("hata yazan yerde degil, OKUYAN yerde     : "
                    + e.getClass().getSimpleName());
        }
        System.out.println("alanin imza ozniteliginden okunan tipi   : "
                + Silme.class.getDeclaredField("alan").getGenericType());
    }
}
```

```
Depo<Ust> ile Depo<String> ayni sinif mi : true
calisma zamaninda tip parametreleri      : [T]
okunan ogenin calisma zamani tipi        : Alt
ham tiple yazma durduruldu mu            : hayir
hata yazan yerde degil, OKUYAN yerde     : ClassCastException
alanin imza ozniteliginden okunan tipi   : Depo<Ust>
```

İlk iki satır tanımlayıcı sütununun sonucudur. `Depo<Ust>` ile `Depo<String>` çalışma
zamanında **aynı** sınıftır ve o sınıfın tip parametresi yalnız `T` adıyla, bir değeri
olmadan durur. Kaynakta iki ayrı tip yazılmıştı; öbekte tek bir sınıf var.

Üçüncü satır ayrımı keskinleştiriyor. Depodan okunan nesnenin çalışma zamanı tipi `Alt` —
yani nesnenin kendi sınıfı bilinir ve gizli değildir. Bilinmeyen şey **kabın** ne tuttuğudur.
Nesnenin tipi çalışma zamanında vardır, kabın tip parametresi yoktur.

Dördüncü ve beşinci satırlar bedeli ölçüyor. Ham tiple yazılan dizgi hiçbir yerde
durdurulmadı: depo onu kabul etti ve depodaki öğe artık bir `Ust` değil. Hata, yazmanın
yapıldığı satırda değil, **okumanın yapıldığı** satırda düştü — çünkü tek denetim oradaki
`checkcast` komutudur. Yanlış tarafa bakan bir programcının ödediği bedel budur: kusuru
üreten satır ile kusurun göründüğü satır ayrı yerlerdedir ve aradaki uzaklık programın
uzunluğu kadar olabilir.

#### Silinen Bilgi Değil, Denetimdir

Son satır dersin sınırlayıcı ölçümüdür ve tezi tersine çeviriyor. `Depo<Ust>` diye
bildirilmiş bir alanın genel tipi çalışma zamanında **okunabiliyor**: `Depo<Ust>`. Bilgi
silinmemiştir, imza özniteliğinde durmaktadır ve oradan geri alınabilir.

O halde silinen nedir? İlk ölçümün iki sütunu yanıtı veriyor. Sanal makinenin yürüttüğü
tanımlayıcıda `Object` yazar; sanal makine bir çağrıyı denetlerken imza özniteliğine
bakmaz. Tip bilgisi sınıf dosyasında **veri** olarak durur, ama **denetim** olarak
kullanılmaz. Bu yüzden "tip silme" adı, bilginin değil, o bilgiye dayanan denetimin
kaybolmasını anlatır. Bir alanın imzasını okumakla bir yazmanın engellenmesi ayrı şeylerdir:
birincisi bir metni geri okumaktır, ikincisi ise çalışma zamanında hiç yapılmayan bir iştir.

#### Özet

- Tip parametresi sınıf dosyasına iki ayrı yere yazılır: sanal makinenin okuduğu
  tanımlayıcıda `Object` olarak silinmiş, imza özniteliğinde `T` adıyla yazılı durur.
- Genel bir okuma çağrısı sınıf dosyasında bir `checkcast` komutu ekler; aynı okuma ham tip
  üzerinden yapıldığında o komut yoktur. Denetim çağrılan tarafta değil, çağıran tarafta
  bulunur.
- Genel bir yazma çağrısı hiçbir denetim komutu bırakmaz, çünkü yazma derleme zamanında
  denetlenmiştir: `Depo<String>` deposuna `Alt` yazan kaynak derlenmez.
- Kısıtlı bir tip parametresi `Object`'e değil sınırına silinir; bu hem `T` üzerinden yöntem
  çağrılmasını açar hem de bildirilen tiple silinen tip çakıştığında `checkcast` komutunu
  ortadan kaldırır.
- Çalışma zamanında `Depo<Ust>` ile `Depo<String>` aynı sınıftır; ham tiple yapılan yazma
  durdurulmaz ve hata yazan yerde değil, okuyan yerde düşer.
- Sınırlayıcı ölçüm: alanın genel tipi çalışma zamanında imza özniteliğinden geri
  okunabilir. Silinen şey bilgi değil, o bilgiye dayanan denetimdir.

#### Sonraki Adım

Bu derste bir depo tek bir tiple adlandırıldı ve o tipe yazmakla o tipten okumak arasında
denetim bakımından bir fark çıktı: okumaya bir komut eklendi, yazmaya eklenmedi. Aynı fark
tip düzeyinde de sorulabilir. `Depo<Ust>` yerine "`Ust` ya da altındaki herhangi bir tipi
tutan depo" yazmak istersek, o depodan okumak ile ona yazmak aynı ölçüde açık kalır mı?
Sıradaki ders dört ayrı kap biçimini iki işlemle deneyip kaç çiftin derlendiğini sayar.

### Sınırlı Joker Karakterler

Source: https://academia.sh/tr/kurslar/java-nesne-modeli/sinirli-joker-karakterler

Dört kap biçimi ve iki işlem sekiz çift verir; bunların yalnız dördü derlenir. Kovaryans okumayı, kontravaryans yazmayı açar ve ikisi bir arada açılmaz; sınırsız joker karakter ise yalnız boş değer yazmaya izin verir. Yön kısıtının tamamı imza özniteliğindedir ve çalışma zamanında hiçbir izi yoktur.

Önceki ders bir depoyu tek bir tiple adlandırdı ve okumaya bir denetim komutu eklendiğini,
yazmaya eklenmediğini ölçtü. Yazma zaten derleme zamanında kapatılmıştı. Aynı soru tip
düzeyinde de sorulabilir: `Depo<Ust>` yerine "`Ust` ya da altındaki herhangi bir tipi tutan
depo" yazmak istersek, o depodan okumakla ona yazmak aynı ölçüde açık kalır mı?

Bu ders dört kap biçimini iki işlemle deneyip kaç çiftin derlendiğini sayar. Kursun sorusu
burada da geçerlidir, ama yanıtın tarafı önceden bellidir: joker karakterin koyduğu kısıt
yalnız bildirilen tipe aittir. Ölçüm hem bu kısıtın kaç kapıyı kapattığını hem de çalışma
zamanında ondan geriye ne kaldığını gösterir.

#### Dört Kap Biçimi, İki İşlem

- **GN8** — Ölçülen kaynak dersin içinde yazılır; yalnız kap biçimi ve denenen satır
  değişir, çevresindeki tipler her denemede aynıdır.
- **GN9** — `Olcek` çekirdeği ortak tanımdan gelir; önceki dersteki iki ölçüm, komut adları
  ile imza özniteliği, olduğu gibi kullanılır.
- **GN10** — Bir deneme ya derlenir ya derlenmez; derleyicinin ileti metni basılmaz, çünkü
  ölçülen şey iletinin içeriği değil, kapının açık olup olmadığıdır.

```java
// Olcek.java — ortak ölçüm çekirdeği: sınıf dosyasını derleyip okur
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Olcek {
    static ClassModel derle(String kaynak, String sinif) throws Exception {
        Path d = Files.createTempDirectory("olcek");
        Path k = d.resolve(sinif + ".java");
        Files.writeString(k, kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, "-d", d.toString(), k.toString()) != 0)
            throw new IllegalStateException("derlenmedi: " + sinif);
        return ClassFile.of().parse(d.resolve(sinif + ".class"));
    }

    static List<String> komutlar(String kaynak, String sinif, String yontem) throws Exception {
        List<String> adlar = new ArrayList<>();
        for (MethodModel m : derle(kaynak, sinif).methods())
            if (m.methodName().stringValue().equals(yontem))
                for (CodeElement e : m.code().orElseThrow())
                    if (e instanceof Instruction i)
                        adlar.add(i.opcode().name().toLowerCase(Locale.ROOT));
        return adlar;
    }

    static String imza(AttributedElement e) {
        return e.findAttribute(Attributes.signature())
                .map(a -> a.signature().stringValue()).orElse("-");
    }
}
```

```java
// Yon.java — dört kap biçimi, iki işlem: kaç çift derleniyor
import java.lang.classfile.*;

public class Yon {
    static final String TIPLER = """
        class Ust { String ad() { return "ust yontem"; } }
        class Alt extends Ust { @Override String ad() { return "alt yontem"; } }
        class Depo<T> { private T oge; void koy(T o) { oge = o; } T al() { return oge; } }
        """;

    static final String KALIP = TIPLER + "class Deneme { static void y(%s d) { %s } }";

    static final String[] KAPLAR =
            {"Depo<Ust>", "Depo<? extends Ust>", "Depo<? super Alt>", "Depo<?>"};

    static final String OKU = "Ust u = d.al();";
    static final String YAZ = "d.koy(new Alt());";

    static boolean derlenirMi(String kap, String islem) {
        try {
            Olcek.derle(KALIP.formatted(kap, islem), "Deneme");
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public static void main(String[] args) throws Exception {
        System.out.printf("%-22s %-18s %s%n", "kap bicimi", "Ust u = d.al()", "d.koy(new Alt())");
        int gecen = 0;
        for (String kap : KAPLAR) {
            boolean o = derlenirMi(kap, OKU), y = derlenirMi(kap, YAZ);
            gecen += (o ? 1 : 0) + (y ? 1 : 0);
            System.out.printf("%-22s %-18s %s%n", kap,
                    o ? "derlendi" : "derlenmedi", y ? "derlendi" : "derlenmedi");
        }
        System.out.println("sekiz ciftin derlenen sayisi: " + gecen);
    }
}
```

```
kap bicimi             Ust u = d.al()     d.koy(new Alt())
Depo<Ust>              derlendi           derlendi
Depo<? extends Ust>    derlendi           derlenmedi
Depo<? super Alt>      derlenmedi         derlendi
Depo<?>                derlenmedi         derlenmedi
sekiz ciftin derlenen sayisi: 4
```

Sekiz çiftin **dördü** derleniyor, dördü derlenmiyor. İlk satır ölçüt satırıdır: tam olarak
adlandırılmış bir depoda iki kapı da açıktır. Sonraki üç satır kapıları teker teker kapatıyor.

`Depo<? extends Ust>` yazımı **kovaryans** (covariance) kurar: `Ust`'un altındaki her tipin
deposu bu ada geçer. Okuma açıktır, çünkü kapta ne varsa en azından bir `Ust`'tur. Yazma
kapalıdır, çünkü kabın gerçekte hangi tipin deposu olduğu bilinmez; `Depo<Alt>` da olabilir,
`Ust`'un başka bir alt tipinin deposu da. Bir `Alt` yazmak ikinci durumda yanlış olurdu ve
derleyici hangisi olduğunu bilmediği için ikisini birden kapatır.

`Depo<? super Alt>` yazımı **kontravaryans** (contravariance) kurar ve iki kapıyı **yer
değiştirir**. Yazma açıktır: kap `Alt`'ın bir üst tipinin deposudur ve bir `Alt` her üst
tipin deposuna yazılabilir. Okuma kapalıdır, çünkü kap `Depo<Object>` de olabilir ve okunan
değerin bir `Ust` olduğu güvence altında değildir.

Buradan ölçülmüş bir kural çıkıyor: **kaptan okuyacaksanız üst sınır, kaba yazacaksanız alt
sınır yazılır; ikisi bir arada açılmaz.** Joker karakterin kazandırdığı şey, kabul edilen
tip kümesinin genişlemesidir; ödettiği şey, iki işlemden birinin kapanmasıdır.

#### Yön Kısıtı Yalnız İmzada Durur

```java
// Iz.java — joker karakter sınıf dosyasında iz bırakıyor mu
import java.lang.classfile.*;

public class Iz {
    static final String TIPLER = """
        class Ust { String ad() { return "ust yontem"; } }
        class Alt extends Ust { @Override String ad() { return "alt yontem"; } }
        class Depo<T> { private T oge; void koy(T o) { oge = o; } T al() { return oge; } }
        """;

    static final String KALIP = TIPLER + "class Deneme { static void y(%s d) { %s } }";

    static final String KOVARYANT = KALIP.formatted("Depo<? extends Ust>", "Ust u = d.al();");
    static final String SINIRSIZ = KALIP.formatted("Depo<?>", "Object o = d.al();");

    public static void main(String[] args) throws Exception {
        for (MethodModel m : Olcek.derle(KOVARYANT, "Deneme").methods())
            if (m.methodName().stringValue().equals("y"))
                System.out.println("y tanimi : " + m.methodType().stringValue()
                        + "   imzasi: " + Olcek.imza(m));
        System.out.println("okuma Depo<? extends Ust> : "
                + Olcek.komutlar(KOVARYANT, "Deneme", "y"));
        System.out.println("okuma Depo<?>             : "
                + Olcek.komutlar(SINIRSIZ, "Deneme", "y"));
    }
}
```

```
y tanimi : (LDepo;)V   imzasi: (LDepo<+LUst;>;)V
okuma Depo<? extends Ust> : [aload_0, invokevirtual, checkcast, astore_1, return]
okuma Depo<?>             : [aload_0, invokevirtual, astore_1, return]
```

Bu ölçüm kısıtın nerede yaşadığını söylüyor. Sanal makinenin okuduğu
tanımlayıcı `(LDepo;)V`: parametrede ne tip parametresi var ne de joker karakter. İmza
özniteliğinde ise kısıtın tamamı yazılı — `+LUst;` işaretindeki artı, üst sınırlı joker
karakteri gösteriyor. Önceki dersin ayrımı burada bir kez daha çıkıyor: bilgi sınıf
dosyasında durur, ama sanal makine ona bakmaz.

Komut satırları da aynı yöne bakıyor. Kovaryant görünümden yapılan okuma bir `checkcast`
komutu taşıyor, çünkü bildirilen tip `Ust` ile silinen tip `Object` ayrışıyor. Sınırsız
görünümden `Object` olarak yapılan okumada ise denetim yok. Yön kısıtına ait tek bir komut
bulunmuyor; sınıf dosyasında "buraya yazılamaz" diyen bir işaret yoktur, çünkü o karar
derlemede verilmiş ve orada bitmiştir.

#### Sınırsız Joker Karakter Yalnız Boş Değer Kabul Eder

- **GN11** — İkinci tabloda okuma `Object` olarak yapılır ve yazılan değer boş değerdir;
  kap biçimleri birinci tablodakiyle aynıdır.

```java
// Bos.java — sınırsız joker karakter neye izin veriyor
public class Bos {
    static final String TIPLER = """
        class Ust { String ad() { return "ust yontem"; } }
        class Alt extends Ust { @Override String ad() { return "alt yontem"; } }
        class Depo<T> { private T oge; void koy(T o) { oge = o; } T al() { return oge; } }
        """;

    static final String KALIP = TIPLER + "class Deneme { static void y(%s d) { %s } }";

    static final String[] KAPLAR =
            {"Depo<Ust>", "Depo<? extends Ust>", "Depo<? super Alt>", "Depo<?>"};

    static String dene(String kap, String islem) {
        try {
            Olcek.derle(KALIP.formatted(kap, islem), "Deneme");
            return "derlendi";
        } catch (Exception e) {
            return "derlenmedi";
        }
    }

    public static void main(String[] args) {
        System.out.printf("%-24s %-18s %s%n", "kap bicimi", "Object o = d.al()", "d.koy(null)");
        for (String kap : KAPLAR)
            System.out.printf("%-24s %-18s %s%n", kap,
                    dene(kap, "Object o = d.al();"), dene(kap, "d.koy(null);"));
        System.out.println();
        System.out.printf("%-24s %s%n", "Depo<?>   d.koy(d.al())", dene("Depo<?>", "d.koy(d.al());"));
        System.out.printf("%-24s %s%n", "Depo<Ust> d.koy(d.al())", dene("Depo<Ust>", "d.koy(d.al());"));
    }
}
```

```
kap bicimi               Object o = d.al()  d.koy(null)
Depo<Ust>                derlendi           derlendi
Depo<? extends Ust>      derlendi           derlendi
Depo<? super Alt>        derlendi           derlendi
Depo<?>                  derlendi           derlendi

Depo<?>   d.koy(d.al())  derlenmedi
Depo<Ust> d.koy(d.al())  derlendi
```

İkinci tabloda sekiz çiftin **sekizi** derleniyor ve bu dersin sınırlayıcı ölçümüdür. İlk
tablodaki dört kapalı kapının hiçbiri gerçekte kilitli değildi; ikisi yalnız **daraltılmış**,
ikisi de yalnız **belirli bir değer** için kapanmıştı.

Okuma hiçbir biçimde kapanmaz. `Depo<? super Alt>` ve `Depo<?>` üzerinden okuma ilk tabloda
düşmüştü, çünkü sonuç bir `Ust`'a atanıyordu. `Object` olarak okunduğunda dördü de
derleniyor. Sınırsız joker karakter okumayı yasaklamaz, okunan değerin bildirilen tipini
`Object`'e indirir.

Yazma da tümüyle kapanmaz. Dört kap biçiminin dördü de boş değer kabul eder, çünkü boş değer
her referans tipine uyar ve hangi tipin deposu olduğu sorusunu doğurmaz. Bu yüzden `Depo<?>`
biçiminin ölçülmüş tanımı şudur: **okuma yalnız `Object` olarak, yazma yalnız boş değerle.**
Sınırsız joker karakter bir tip söylemez, tip sorusunu sormayacağını söyler.

Son iki satır aynı kuralın en dar noktasıdır. `Depo<Ust>` üzerinde `d.koy(d.al())` yazımı,
yani kaptan okuyup aynı kaba geri yazmak, derleniyor. Aynı satır `Depo<?>` üzerinde
**derlenmiyor** — okunan değerin tipi bilinmediği için, aynı kaptan gelmiş olması bile
yazmayı geçerli kılmıyor. Derleyici burada tipin **kimliğini** değil, yalnız adını izler.

#### Yönü Ayırmanın Çağıran Tarafta Karşılığı

- **GN12** — İki imza için dokuzar çağrı denenir; deneme kümesi ikisinde de aynıdır ve
  yalnız çağrılan yöntemin parametre biçimi değişir.

```java
// Kopya.java — yönü ayırmak çağıran tarafta kaç çifti açıyor
public class Kopya {
    static final String TIPLER = """
        class Ust { String ad() { return "ust yontem"; } }
        class Alt extends Ust { @Override String ad() { return "alt yontem"; } }
        class Depo<T> { private T oge; void koy(T o) { oge = o; } T al() { return oge; } }
        """;

    static final String TAM = "static void kopyala(Depo<Ust> h, Depo<Ust> k) { h.koy(k.al()); }";
    static final String JOKER =
            "static void kopyala(Depo<? super Ust> h, Depo<? extends Ust> k) { h.koy(k.al()); }";

    static final String[] DEPOLAR = {"Depo<Object>", "Depo<Ust>", "Depo<Alt>"};

    static int say(String imza) {
        int gecen = 0;
        System.out.printf("%-14s %-12s %-12s %s%n", "hedef \\ kaynak",
                DEPOLAR[0], DEPOLAR[1], DEPOLAR[2]);
        for (String h : DEPOLAR) {
            String[] hucre = new String[DEPOLAR.length];
            for (int i = 0; i < DEPOLAR.length; i++) {
                String kaynak = TIPLER + "class C { " + imza + " static void c("
                        + h + " h, " + DEPOLAR[i] + " k) { kopyala(h, k); } }";
                try {
                    Olcek.derle(kaynak, "C");
                    hucre[i] = "derlendi";
                    gecen++;
                } catch (Exception e) {
                    hucre[i] = "-";
                }
            }
            System.out.printf("%-14s %-12s %-12s %s%n", h, hucre[0], hucre[1], hucre[2]);
        }
        return gecen;
    }

    public static void main(String[] args) {
        System.out.println("kopyala(Depo<Ust>, Depo<Ust>)");
        System.out.println("dokuz ciftin derlenen sayisi: " + say(TAM));
        System.out.println();
        System.out.println("kopyala(Depo<? super Ust>, Depo<? extends Ust>)");
        System.out.println("dokuz ciftin derlenen sayisi: " + say(JOKER));
    }
}
```

```
kopyala(Depo<Ust>, Depo<Ust>)
hedef \ kaynak Depo<Object> Depo<Ust>    Depo<Alt>
Depo<Object>   -            -            -
Depo<Ust>      -            derlendi     -
Depo<Alt>      -            -            -
dokuz ciftin derlenen sayisi: 1

kopyala(Depo<? super Ust>, Depo<? extends Ust>)
hedef \ kaynak Depo<Object> Depo<Ust>    Depo<Alt>
Depo<Object>   -            derlendi     derlendi
Depo<Ust>      -            derlendi     derlendi
Depo<Alt>      -            -            -
dokuz ciftin derlenen sayisi: 4
```

Aynı gövde, iki ayrı parametre biçimi: birincisi dokuz çağrı çiftinin **birini** kabul
ediyor, ikincisi **dördünü**. Kapatılan iki işlem burada geri ödeniyor. Kaynaktan yalnız
okunduğu için ona üst sınır, hedefe yalnız yazıldığı için ona alt sınır konabiliyor ve
yöntem dört ayrı çağrıya açılıyor.

Boş kalan hücreler de aynı kuralın ölçüsüdür. `Depo<Alt>` hedef olamıyor, çünkü oraya bir
`Ust` yazılabilirdi; `Depo<Object>` kaynak olamıyor, çünkü oradan gelen değerin bir `Ust`
olduğu bilinmiyor. Genişleme rastgele değil, işlemin yönüyle tam olarak eşleşiyor.

#### Kısıt Çalışma Zamanında Yoktur

- **GN13** — Nesne kimliği yalnız `==` ile sorulur; hiçbir kimlik sayısı basılmaz.
- **GN14** — Ham tiple yazmanın sonucu iddia edilmez, `instanceof` ile ölçülür.

```java
// Akis.java — yön kısıtı çalışma zamanında duruyor mu
class Ust {
    String ad() { return "ust yontem"; }
}

class Alt extends Ust {
    @Override String ad() { return "alt yontem"; }
}

class Depo<T> {
    private T oge;
    void koy(T oge) { this.oge = oge; }
    T al() { return oge; }
}

public class Akis {
    @SuppressWarnings({"unchecked", "rawtypes"})
    public static void main(String[] args) {
        Depo<Alt> altDepo = new Depo<>();
        altDepo.koy(new Alt());

        Depo<? extends Ust> okunur = altDepo;
        Depo<? super Alt> yazilir = altDepo;
        Depo<?> kapali = altDepo;
        System.out.println("dort bildirilen tip, kac nesne : "
                + ((okunur == yazilir) && (yazilir == kapali) && (kapali == altDepo) ? 1 : 2));
        System.out.println("nesnenin calisma zamani sinifi : "
                + altDepo.getClass().getSimpleName());

        System.out.println("kovaryant gorunumden okuma     : " + okunur.al().ad());
        yazilir.koy(new Alt());
        System.out.println("kontravaryant gorunume yazma   : " + altDepo.al().ad());

        Depo ham = okunur;
        ham.koy(new Ust());
        System.out.println("ham tiple Ust yazildi mi       : "
                + (altDepo.al() instanceof Alt ? "hayir" : "evet"));
        try {
            Alt a = altDepo.al();
            System.out.println("okuma sonucu                   : " + a.ad());
        } catch (ClassCastException e) {
            System.out.println("hata OKUYAN yerde              : "
                    + e.getClass().getSimpleName());
        }
    }
}
```

```
dort bildirilen tip, kac nesne : 1
nesnenin calisma zamani sinifi : Depo
kovaryant gorunumden okuma     : alt yontem
kontravaryant gorunume yazma   : alt yontem
ham tiple Ust yazildi mi       : evet
hata OKUYAN yerde              : ClassCastException
```

İlk iki satır dersin bütün tablosunu tek bir cümleye indiriyor: dört ayrı bildirilen tip,
**tek** bir nesne. Kabın çalışma zamanı sınıfı yalnız `Depo`; ne tip parametresi ne de yön
kısıtı orada. Okuma ve yazma kapıları nesnenin üzerinde değil, **ona bakan adın** üzerinde
açılıp kapanıyor.

Üçüncü ve dördüncü satırlar açık kapıların çalıştığını doğruluyor. Beşinci satır ise kapalı
kapının neyle kapatıldığını gösteriyor. Kovaryant görünüme bir `Ust` yazmak derlenmiyordu;
aynı görünüm ham tiple adlandırıldığında yazma **geçiyor** ve depodaki öğe artık bir `Alt`
değil. Yön kısıtını uygulayan tek taraf derleyicidir; o taraf devre dışı bırakıldığında
kısıtı yeniden uygulayacak kimse yoktur.

Son satır bedelin nereye düştüğünü yazıyor. Kusur, yanlış tipin yazıldığı satırda değil,
`Alt` bekleyen okumada göründü — ve bu kez arada bir de görünüm değişimi var: yazan kod kabı
kovaryant görünümüyle tanıyordu, düşen kod ise gerçek tipiyle.

#### Özet

- Dört kap biçimi ve iki işlemin sekiz çiftinden **dördü** derlenir. Joker karakter kabul
  edilen tip kümesini genişletir, karşılığında iki işlemden birini kapatır.
- Kovaryans (`? extends`) okumayı açar, yazmayı kapatır; kontravaryans (`? super`) yazmayı
  açar, okumayı `Object` düzeyine indirir. İkisi bir arada açılmaz.
- Sınırlayıcı ölçüm: hiçbir kapı gerçekte kilitli değildir. Dört biçimin dördünden de
  `Object` olarak okunabilir ve dördüne de boş değer yazılabilir; sınırsız joker karakterin
  yazmaya izin verdiği tek değer budur.
- `Depo<?>` üzerinde kaptan okunanı aynı kaba geri yazmak derlenmez, oysa `Depo<Ust>`
  üzerinde derlenir: derleyici tipin kimliğini değil adını izler.
- Yön kısıtının tamamı imza özniteliğindedir. Tanımlayıcıda joker karakterin izi yoktur, ona
  ait tek bir komut üretilmez ve dört ayrı bildirilen tip çalışma zamanında tek bir nesnedir.

#### Sonraki Adım

İki ders boyunca derleyicinin kapattığı kapılar sayıldı: yanlış tipe yazma derlenmedi,
kovaryant görünüme yazma derlenmedi, kaptan okunanı geri yazma derlenmedi. Bunların hepsinde
derleyici bir **yasak** koydu. Java'da derleyicinin kaynağa dayattığı bir de **zorunluluk**
vardır: kimi olayları bildirmeden ya da işlemeden bir yöntem yazılamaz. Sıradaki ders o
zorunluluğu ölçer — denetlenen ile denetlenmeyen istisna arasındaki ayrım derleme zamanında
tam olarak neyi zorluyor ve bu zorunluluğun sınıf dosyasındaki karşılığını sanal makine
denetliyor mu?

### İstisna Hiyerarşisi

Source: https://academia.sh/tr/kurslar/java-nesne-modeli/istisna-hiyerarsisi

Denetlenen ile denetlenmeyen istisna ayrımının derleme zamanında ne zorladığı ölçülür: dokuz yazımın yalnız biri reddedilir, bildirim sınıf dosyasında komut değil öznitelik olarak durur ve sanal makine onu denetlemez. Yakalamanın hangi dalı çalıştıracağına ise çalışma zamanı tipi karar verir.

Önceki iki ders derleyicinin kapattığı kapıları saydı: yanlış tipe yazma derlenmedi,
kovaryant görünüme yazma derlenmedi, kaptan okunan değeri aynı kaba geri yazmak derlenmedi.
Hepsinde derleyici bir **yasak** koydu. Java'da derleyicinin kaynağa dayattığı bir de
**zorunluluk** vardır: kimi olaylar bildirilmeden ya da işlenmeden bir yöntem yazılamaz.

İstisnanın bir nesne olduğu, yakalamanın bir sınıf aldığı ve hiyerarşinin kaç olayı tuttuğu
Python Temelleri kursunda ölçüldü; o aritmetik burada tekrarlanmıyor. Buradaki soru kursun
sorusudur ve iki yanıtı vardır: bir istisnayı **bildirme** zorunluluğuna bildirilen tip karar
verir, **hangi yakalama dalının çalışacağına** ise çalışma zamanı tipi. Bu ders ikisini de
aynı yerde ölçer.

#### Derleyicinin Zorladığı Tek Şey

- **GN15** — Üç istisna türü kaynakta dersin içinde tanımlanır: `Exception` altında bir
  denetlenen istisna, `RuntimeException` altında bir denetlenmeyen istisna, `Error` altında
  bir ağır hata.
- **GN16** — Üç yazım biçimi aynı gövdeyi taşır; yalnız yöntemin bildirimi ve gövdenin
  çevresi değişir.
- **GN17** — Derleyicinin ileti metni basılmaz; ölçülen şey yazımın kabul edilip
  edilmediğidir.

```java
// Olcek.java — ortak ölçüm çekirdeği: sınıf dosyasını derleyip okur
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Olcek {
    static ClassModel derle(String kaynak, String sinif) throws Exception {
        Path d = Files.createTempDirectory("olcek");
        Path k = d.resolve(sinif + ".java");
        Files.writeString(k, kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, "-d", d.toString(), k.toString()) != 0)
            throw new IllegalStateException("derlenmedi: " + sinif);
        return ClassFile.of().parse(d.resolve(sinif + ".class"));
    }

    static List<String> komutlar(String kaynak, String sinif, String yontem) throws Exception {
        List<String> adlar = new ArrayList<>();
        for (MethodModel m : derle(kaynak, sinif).methods())
            if (m.methodName().stringValue().equals(yontem))
                for (CodeElement e : m.code().orElseThrow())
                    if (e instanceof Instruction i)
                        adlar.add(i.opcode().name().toLowerCase(Locale.ROOT));
        return adlar;
    }

    static String kisa(String ic) { return ic.substring(ic.lastIndexOf('/') + 1); }
}
```

```java
// Zorunlu.java — üç istisna türü, üç yazım biçimi
public class Zorunlu {
    static final String TIPLER = """
        class UstIstisna extends Exception { UstIstisna(String m) { super(m); } }
        class SessizIstisna extends RuntimeException { SessizIstisna(String m) { super(m); } }
        class AgirHata extends Error { AgirHata(String m) { super(m); } }
        """;

    static final String[] TURLER = {"UstIstisna", "SessizIstisna", "AgirHata"};

    static final String BILDIRMEDEN = "class C { static void f() { throw new %1$s(\"olay\"); } }";
    static final String BILDIREREK =
            "class C { static void f() throws %1$s { throw new %1$s(\"olay\"); } }";
    static final String YAKALAYARAK =
            "class C { static void f() { try { throw new %1$s(\"olay\"); } catch (%1$s e) { } } }";

    static String dene(String kalip, String tur) {
        try {
            Olcek.derle(TIPLER + kalip.formatted(tur), "C");
            return "derlendi";
        } catch (Exception e) {
            return "derlenmedi";
        }
    }

    public static void main(String[] args) {
        System.out.printf("%-16s %-12s %-12s %s%n",
                "istisna turu", "bildirmeden", "bildirerek", "yakalayarak");
        int gecen = 0;
        for (String t : TURLER) {
            String a = dene(BILDIRMEDEN, t), b = dene(BILDIREREK, t), c = dene(YAKALAYARAK, t);
            for (String s : new String[] {a, b, c}) if (s.equals("derlendi")) gecen++;
            System.out.printf("%-16s %-12s %-12s %s%n", t, a, b, c);
        }
        System.out.println("dokuz yazimin derlenen sayisi: " + gecen);
    }
}
```

```
istisna turu     bildirmeden  bildirerek   yakalayarak
UstIstisna       derlenmedi   derlendi     derlendi
SessizIstisna    derlendi     derlendi     derlendi
AgirHata         derlendi     derlendi     derlendi
dokuz yazimin derlenen sayisi: 8
```

Dokuz yazımın **sekizi** derleniyor. Reddedilen tek yazım tablonun sol üst köşesindedir:
denetlenen bir istisnayı fırlatıp ne bildiren ne yakalayan yöntem. Denetlenen istisnanın
derleme zamanında zorladığı şeyin tamamı budur — **bir seçim yapmak**. Bildirmek ile
yakalamak arasında derleyici için hiçbir fark yoktur; ikisi de aynı kapıyı kapatır ve iki
sütun da eşit derecede geçerlidir.

İkinci ve üçüncü satırlar ayrımın karşı yüzüdür. Denetlenmeyen istisna ile ağır hata için üç
yazımın **üçü** de derleniyor: bildirilebilirler, yakalanabilirler, ama hiçbiri zorunlu
değildir. Denetlenmeyen bir istisna kaynağın hiçbir yerinde iz bırakmadan bir yöntemden
çıkabilir.

Buradan iki cümle çıkıyor. Birincisi: denetlenen/denetlenmeyen ayrımı bir davranış ayrımı
değildir; ikisi de fırlatılır, ikisi de yakalanır, ikisi de çağrı yığıtını yukarı doğru
katlar. Ayrım yalnız **derleyicinin bir yazımı kabul edip etmemesinde** görünür. İkincisi:
bu zorunluluk bildirilen tipe bakar. Derleyici gövdenin gerçekte ne fırlatacağını
çalıştırarak öğrenmez; çağrılan yöntemlerin **bildirimlerini** okur.

#### Bildirim Bir Özniteliktir, Komut Değil

- **GN18** — İki kaynak da tek bir yöntem taşır ve gövdeleri aynı biçimdedir; yalnız
  fırlatılan istisnanın türü değişir.

```java
// Bildirim.java — denetlenen istisna bildirimi sınıf dosyasının neresinde
import java.lang.classfile.*;
import java.lang.classfile.attribute.ExceptionsAttribute;
import java.util.List;

public class Bildirim {
    static final String TIPLER = """
        class UstIstisna extends Exception { UstIstisna(String m) { super(m); } }
        class SessizIstisna extends RuntimeException { SessizIstisna(String m) { super(m); } }
        """;

    static final String BILDIREN = TIPLER
            + "class C { static void f() throws UstIstisna { throw new UstIstisna(\"olay\"); } }";
    static final String BILDIRMEYEN = TIPLER
            + "class C { static void f() { throw new SessizIstisna(\"olay\"); } }";

    static void yaz(String ad, String kaynak) throws Exception {
        for (MethodModel m : Olcek.derle(kaynak, "C").methods())
            if (m.methodName().stringValue().equals("f"))
                System.out.printf("%-14s tanim %-6s bildirim ozniteligi %s%n", ad,
                        m.methodType().stringValue(),
                        m.findAttribute(Attributes.exceptions())
                                .map(ExceptionsAttribute::exceptions).orElse(List.of())
                                .stream().map(e -> Olcek.kisa(e.asInternalName())).toList());
    }

    public static void main(String[] args) throws Exception {
        yaz("denetlenen", BILDIREN);
        yaz("denetlenmeyen", BILDIRMEYEN);
        System.out.println();
        System.out.println("denetlenen firlatma   : " + Olcek.komutlar(BILDIREN, "C", "f"));
        System.out.println("denetlenmeyen firlatma: " + Olcek.komutlar(BILDIRMEYEN, "C", "f"));
    }
}
```

```
denetlenen     tanim ()V    bildirim ozniteligi [UstIstisna]
denetlenmeyen  tanim ()V    bildirim ozniteligi []

denetlenen firlatma   : [new, dup, ldc, invokespecial, athrow]
denetlenmeyen firlatma: [new, dup, ldc, invokespecial, athrow]
```

İki yöntemin tanımı **aynıdır**: `()V`. Yöntemin sanal makineye görünen kimliği parametre
tipleriyle dönüş tipinden oluşur; fırlatabileceği istisna oraya girmez. Bildirim ayrı bir
**öznitelikte** durur ve denetlenmeyen istisnada o öznitelik boştur.

Alt satırlar bunun kod tarafındaki karşılığıdır: iki fırlatma **birebir aynı beş komuttur**.
Nesneyi üret, çoğalt, iletiyi yükle, yapıcıyı çağır, fırlat. Sınıf dosyasının yürütülen
bölümünde denetlenen ile denetlenmeyen istisna arasında hiçbir fark yoktur. Fark yalnız
derleyicinin okuduğu öznitelikte durur — tıpkı tip parametresinin yalnız imza özniteliğinde
durması gibi. Bu kursun ilk dersinde ölçülen kalıp burada bir kez daha çıkıyor: **bilgi sınıf
dosyasında veri olarak yazılıdır, denetim olarak değil.**

#### Sanal Makine Bildirime Bakmaz

- **GN19** — Ölçüm hiçbir dış araç kullanmadan, dilin kendi içinde kurulur; istisna nesnesi
  ile ilgili tek bilgi olarak sınıf adı basılır.

```java
// Gizli.java — sanal makine bildirimi denetliyor mu
class UstIstisna extends Exception {
    UstIstisna(String m) { super(m); }
}

public class Gizli {
    @SuppressWarnings("unchecked")
    static <T extends Throwable> void firlat(Throwable t) throws T {
        throw (T) t;
    }

    static void bildirmeyen() {
        Gizli.<RuntimeException>firlat(new UstIstisna("olay"));
    }

    public static void main(String[] args) {
        try {
            bildirmeyen();
            System.out.println("istisna cikmadi");
        } catch (Throwable t) {
            System.out.println("bildirmeyen yontemden cikan   : " + t.getClass().getSimpleName());
            System.out.println("denetlenen istisna mi         : "
                    + (t instanceof RuntimeException || t instanceof Error ? "hayir" : "evet"));
            System.out.println("sanal makine yolu kesti mi    : hayir");
        }
    }
}
```

```
bildirmeyen yontemden cikan   : UstIstisna
denetlenen istisna mi         : evet
sanal makine yolu kesti mi    : hayir
```

`bildirmeyen` yöntemi hiçbir istisna bildirmiyor ve bildirim özniteliği boş. Buna karşın o
yöntemden **denetlenen** bir istisna çıkıyor ve çalışma boyunca hiçbir şey bu yolu kesmiyor.

Bunun nasıl olduğu bu konunun iki yarısını birleştiriyor. `firlat` yöntemi `T` tip
parametresiyle bildirilmiş, çağrı yerinde `T` yerine `RuntimeException` konmuş, yani
derleyici için bu yöntem denetlenmeyen bir istisna fırlatır. Gövdedeki dönüşüm ise `T`'ye
yapılıyor ve **tip silme yüzünden o dönüşüm sınıf dosyasında hiçbir denetim bırakmıyor**.
Derleyicinin bildirimle kurduğu güvence, tip parametresinin silinmesiyle açılan boşluktan
geçip gidiyor.

Ölçümün söylediği kural şudur: denetlenen istisna bildirimi **derleyiciler arası bir
sözleşmedir**, çalışma zamanı güvencesi değildir. Sanal makine bir yöntemden çıkan istisnanın
o yöntemin bildiriminde yazıp yazmadığını sınamaz. Bu, kursun ölçüsüne göre saf bir
"bildirilen tip" kararıdır ve çalışma zamanı tipinin bu konuda söyleyecek hiçbir şeyi yoktur.

#### Bildirim Daraltılabilir, Genişletilemez

- **GN20** — Beş denemede yalnız alt sınıfın bildirimi değişir; üst sınıfın bildirimi,
  istisna tipleri ve çağıran kod her denemede aynıdır.

```java
// Devir.java — yeniden tanımlayan yöntem bildirimi genişletebilir mi
public class Devir {
    static final String KALIP = """
        class UstIstisna extends Exception { UstIstisna(String m) { super(m); } }
        class AltIstisna extends UstIstisna { AltIstisna(String m) { super(m); } }
        class BagimsizIstisna extends Exception { BagimsizIstisna(String m) { super(m); } }
        class Ust { void f() throws UstIstisna { } }
        class Alt extends Ust { @Override void f() %s { } }
        class C { static void c(Ust u) { try { u.f(); } catch (Exception e) { } } }
        """;

    static void dene(String bildirim) {
        String ad = bildirim.isEmpty() ? "(bildirimsiz)" : bildirim;
        try {
            Olcek.derle(KALIP.formatted(bildirim), "C");
            System.out.printf("Alt.f() %-28s -> derlendi%n", ad);
        } catch (Exception e) {
            System.out.printf("Alt.f() %-28s -> derlenmedi%n", ad);
        }
    }

    public static void main(String[] args) throws Exception {
        for (String b : new String[] {"throws UstIstisna", "throws AltIstisna", "",
                "throws BagimsizIstisna", "throws RuntimeException"})
            dene(b);

        System.out.println();
        String cagri = """
            class UstIstisna extends Exception { UstIstisna(String m) { super(m); } }
            class Ust { void f() throws UstIstisna { } }
            class Alt extends Ust { @Override void f() { } }
            class C { static void c() { %s.f(); } }
            """;
        for (String bildirilen : new String[] {"new Ust()", "new Alt()"}) {
            String etiket = bildirilen.equals("new Ust()") ? "Ust" : "Alt";
            try {
                Olcek.derle(cagri.formatted(bildirilen), "C");
                System.out.printf("bildirilen tip %-4s cagrisi islemeden -> derlendi%n", etiket);
            } catch (Exception e) {
                System.out.printf("bildirilen tip %-4s cagrisi islemeden -> derlenmedi%n", etiket);
            }
        }
    }
}
```

```
Alt.f() throws UstIstisna            -> derlendi
Alt.f() throws AltIstisna            -> derlendi
Alt.f() (bildirimsiz)                -> derlendi
Alt.f() throws BagimsizIstisna       -> derlenmedi
Alt.f() throws RuntimeException      -> derlendi

bildirilen tip Ust  cagrisi islemeden -> derlenmedi
bildirilen tip Alt  cagrisi islemeden -> derlendi
```

Üst tablonun kuralı tek yönlüdür. Yeniden tanımlayan yöntem üst sınıfın bildirdiği istisnayı
aynen bildirebilir, onun bir alt tipini bildirebilir, hiçbir şey bildirmeyebilir — ama
**başka bir denetlenen istisna ekleyemez**. Denetlenmeyen istisna bildirmek her durumda
serbesttir, çünkü onun için zaten hiçbir zorunluluk yoktu.

Alt iki satır bu kuralın gerekçesini ölçüyor ve kursun sorusunu tam ortasından yanıtlıyor.
`Alt` sınıfının yöntemi hiçbir istisna bildirmiyor. Aynı nesne `Alt` olarak adlandırıldığında
çağrı işlemeden **derleniyor**; `Ust` olarak adlandırıldığında **derlenmiyor**. Nesne aynı,
çalıştırılacak gövde aynı, sonuç aynı — değişen tek şey adın bildirilen tipi. Derleyici
işleme zorunluluğunu yalnız oradan okuduğu için, alt sınıfın bildirimi genişletmesine izin
verilseydi `Ust` üzerinden yazılmış hiçbir çağrı güvenilir olmazdı.

#### Yakalamanın İki Tarafı

```java
// Yakala.java — yakalamanın iki tarafı: hangi dal yazılabilir, hangi dal çalışır
class UstIstisna extends Exception {
    UstIstisna(String m) { super(m); }
}

class AltIstisna extends UstIstisna {
    AltIstisna(String m) { super(m); }
}

public class Yakala {
    static final String KALIP = """
        class UstIstisna extends Exception { UstIstisna(String m) { super(m); } }
        class AltIstisna extends UstIstisna { AltIstisna(String m) { super(m); } }
        class BagimsizIstisna extends Exception { BagimsizIstisna(String m) { super(m); } }
        class C {
            static void firlat() throws UstIstisna { throw new AltIstisna("olay"); }
            static void y() throws UstIstisna { try { firlat(); } catch (%s e) { } }
        }
        """;

    static void dene(String dal) {
        try {
            Olcek.derle(KALIP.formatted(dal), "C");
            System.out.printf("catch (%-19s -> derlendi%n", dal + " e)");
        } catch (Exception e) {
            System.out.printf("catch (%-19s -> derlenmedi%n", dal + " e)");
        }
    }

    static void firlat() throws UstIstisna {
        throw new AltIstisna("olay");
    }

    public static void main(String[] args) {
        for (String dal : new String[]
                {"UstIstisna", "AltIstisna", "BagimsizIstisna", "RuntimeException", "Exception"})
            dene(dal);

        System.out.println();
        try {
            firlat();
        } catch (UstIstisna e) {
            System.out.println("UstIstisna dali yakaladi, nesnenin sinifi: "
                    + e.getClass().getSimpleName());
        }
        try {
            firlat();
        } catch (AltIstisna e) {
            System.out.println("iki dal varken calisan dal               : AltIstisna");
        } catch (UstIstisna e) {
            System.out.println("iki dal varken calisan dal               : UstIstisna");
        }
    }
}
```

```
catch (UstIstisna e)       -> derlendi
catch (AltIstisna e)       -> derlendi
catch (BagimsizIstisna e)  -> derlenmedi
catch (RuntimeException e) -> derlendi
catch (Exception e)        -> derlendi

UstIstisna dali yakaladi, nesnenin sinifi: AltIstisna
iki dal varken calisan dal               : AltIstisna
```

Üst tablo yakalamanın **derleme zamanı** yüzüdür ve reddedilen tek dal ilginç olanıdır.
`BagimsizIstisna` denetlenen bir istisnadır ve `try` bloğunun içindeki çağrının bildiriminde
geçmiyor; derleyici o dalı **yazdırmıyor** bile. Buradaki karar tümüyle bildirilen tiplerden
gelir: derleyici bloğun içinden hangi denetlenen istisnaların çıkabileceğini bildirimlerden
hesaplar ve o kümenin dışında kalan bir dalı ölü kabul eder. `RuntimeException` dalının
geçmesi aynı kuralın diğer yüzüdür — denetlenmeyen istisna her yerden çıkabileceği için hiçbir
zaman ölü sayılmaz.

Alt satırlar aynı yakalamanın **çalışma zamanı** yüzüdür ve bu dersin sınırlayıcı ölçümüdür.
Fırlatan yöntem `UstIstisna` bildiriyor, ama fırlattığı nesnenin sınıfı `AltIstisna`.
`UstIstisna` dalı bu nesneyi yakalıyor ve yakalanan nesnenin sınıfı `AltIstisna` olarak
okunuyor. İki dal yan yana konduğunda çalışan dal `AltIstisna` — yani seçim bildirilen tipten
değil, **çalışma zamanı tipinden** yapılıyor.

Aynı yapıda iki karşıt yanıt bulunması bu kursun temel iddiasının bu konudaki biçimidir. Bir
`catch` dalının **yazılabilir olması** bildirilen tipe, **çalışması** çalışma zamanı tipine
bağlıdır. Yanlış tarafa bakan bir programcı bunu iki ayrı biçimde ödeyebilir: dar bir dalı
üst tipe göre yazıp hiç çalışmadığını görmek, ya da geniş bir dalın altına yazdığı dar dalın
derlenmediğini görmek.

#### Özet

- Denetlenen istisnanın derleme zamanında zorladığı tek şey bir seçimdir: bildirmek ya da
  yakalamak. Dokuz yazımdan yalnız biri reddedilir; denetlenmeyen istisna ile ağır hatanın üç
  yazımı da geçerlidir.
- Denetlenen ile denetlenmeyen istisna arasında davranış farkı yoktur; fark yalnız
  derleyicinin bir yazımı kabul edip etmemesindedir.
- Bildirim yöntemin tanımına girmez, ayrı bir öznitelikte durur; iki türün fırlatma kodu
  birebir aynı beş komuttur.
- Sanal makine bildirimi denetlemez: tip parametresi üzerinden fırlatılan denetlenen bir
  istisna, hiçbir şey bildirmeyen bir yöntemden çıkabilir. Bildirim bir derleme zamanı
  sözleşmesidir.
- Yeniden tanımlayan yöntem bildirimi daraltabilir, genişletemez; aynı nesneye `Ust` diye
  bakan çağrı işlemek zorundadır, `Alt` diye bakan çağrı değildir.
- Sınırlayıcı ölçüm: yakalamanın hangi dalının yazılabileceğine bildirilen tip, hangi dalın
  çalışacağına çalışma zamanı tipi karar verir. Aynı yapıda iki karşıt yanıt bir arada durur.

#### Sonraki Adım

Bu derste bir istisna fırlatıldı, bildirildi ve yakalandı; her ölçümde tek bir olay vardı ve
onun nereye gittiği izlendi. Oysa gerçek bir gövdede istisna genellikle yalnız değildir: bir
kaynak açılmışsa, gövde düşerken o kaynağın kapatılması gerekir ve kapatma da düşebilir. Bu
durumda iki istisna aynı anda ortaya çıkar ve biri diğerinin üstünü örtebilir. Sıradaki ders
elle yazılan kapatma ile derleyicinin ürettiği kapatmayı yan yana koyup kaç istisnanın
kaybolduğunu, kaçının korunduğunu ve kapatmanın hangi sırayla yapıldığını sayar.

### Kaynakla Deneme

Source: https://academia.sh/tr/kurslar/java-nesne-modeli/kaynakla-deneme

Gövde ile kapatma aynı anda düştüğünde elle yazılan biçimde iki olaydan birine ulaşılamaz; kaynakla denemede ikisine de ulaşılır. Ölçüm kaybolan ve korunan istisna sayısını, kapatmanın ters sırasını ve derleyicinin ürettiği fazladan beş komutu birlikte gösterir.

Önceki derste her ölçümde tek bir olay vardı: bir istisna fırlatıldı, bildirildi ve
yakalandı. Gerçek bir gövdede olay genellikle yalnız değildir. Bir kaynak açılmışsa gövde
düşerken o kaynağın kapatılması gerekir ve kapatma da düşebilir. Bu durumda iki istisna
aynı anda ortaya çıkar ve ikisi de aynı yoldan yukarı gitmek zorundadır.

Bağlam yöneticisiyle kaynağın serbest bırakılması Python Temelleri kursunda kuruldu; o düzenek
burada tekrarlanmıyor. Buradaki soru sayısaldır: iki olay çıktığında kaça ulaşılabiliyor,
hangisi ulaşılamaz oluyor ve kapatma hangi sırayla yapılıyor? Kursun sorusu bu derste
biçim değiştirir: kararı veren taraf bir tip değil, **kodu yazan taraftır** — elle yazılan
kapatma ile derleyicinin ürettiği kapatma aynı gövdede ayrı sonuçlar verir.

#### Kaybolan İstisna ile Taşınan İstisna

- **GN21** — Kaynak sınıfı dersin içinde tanımlanır; her açılış ve kapanış bir günlüğe
  yazılır ve günlük her ölçümden önce boşaltılır.
- **GN22** — Her ölçümün kaç olay ürettiği kurgudan bilinir ve tabloda payda olarak durur;
  ulaşılan sayı, çıkan istisnadan ve onun taşıdığı bastırılan istisnalardan sayılır.
- **GN23** — İstisnalardan yalnız ileti metni basılır; yığıt izi ve konum basılmaz.

```java
// Kapatma.java — elle yazılan kapatma ile kaynakla deneme yan yana
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Olay extends Exception {
    Olay(String m) { super(m); }
}

class Kaynak implements AutoCloseable {
    static final List<String> gunluk = new ArrayList<>();
    private final String ad;
    private final boolean kapanistaDuser;

    Kaynak(String ad, boolean kapanistaDuser) {
        this.ad = ad;
        this.kapanistaDuser = kapanistaDuser;
        gunluk.add("acildi " + ad);
    }

    @Override public void close() throws Olay {
        gunluk.add("kapandi " + ad);
        if (kapanistaDuser) throw new Olay("kapanis " + ad);
    }
}

interface Deneme {
    void calistir() throws Olay;
}

public class Kapatma {
    static void elle() throws Olay {
        Kaynak k = new Kaynak("A", true);
        try {
            throw new Olay("govde");
        } finally {
            k.close();
        }
    }

    static void kaynakla() throws Olay {
        try (Kaynak k = new Kaynak("A", true)) {
            throw new Olay("govde");
        }
    }

    static void yalnizKapanis() throws Olay {
        try (Kaynak k = new Kaynak("A", true)) {
            Kaynak.gunluk.add("govde saglam");
        }
    }

    static void ikiKapanis() throws Olay {
        try (Kaynak a = new Kaynak("A", true); Kaynak b = new Kaynak("B", true)) {
            Kaynak.gunluk.add("govde saglam");
        }
    }

    static void ucKaynak() throws Olay {
        try (Kaynak a = new Kaynak("A", false);
             Kaynak b = new Kaynak("B", false);
             Kaynak c = new Kaynak("C", false)) {
            Kaynak.gunluk.add("govde saglam");
        }
    }

    static void bildir(String ad, int olay, Deneme d) {
        Kaynak.gunluk.clear();
        String cikan = "-";
        int ulasilan = 0;
        try {
            d.calistir();
        } catch (Olay e) {
            cikan = e.getMessage();
            ulasilan = 1 + e.getSuppressed().length;
            List<String> bastirilan =
                    Arrays.stream(e.getSuppressed()).map(Throwable::getMessage).toList();
            if (!bastirilan.isEmpty()) cikan += " + " + bastirilan;
        }
        System.out.printf("%-16s %-26s %-9s %s%n", ad, cikan, ulasilan + " / " + olay,
                Kaynak.gunluk);
    }

    public static void main(String[] args) {
        System.out.printf("%-16s %-26s %-9s %s%n", "olcum", "cikan (+ bastirilan)",
                "ulasilan", "gunluk");
        bildir("elle", 2, Kapatma::elle);
        bildir("kaynakla", 2, Kapatma::kaynakla);
        bildir("yalniz kapanis", 1, Kapatma::yalnizKapanis);
        bildir("iki kapanis", 2, Kapatma::ikiKapanis);
        bildir("uc kaynak", 0, Kapatma::ucKaynak);
    }
}
```

```
olcum            cikan (+ bastirilan)       ulasilan  gunluk
elle             kapanis A                  1 / 2     [acildi A, kapandi A]
kaynakla         govde + [kapanis A]        2 / 2     [acildi A, kapandi A]
yalniz kapanis   kapanis A                  1 / 1     [acildi A, govde saglam, kapandi A]
iki kapanis      kapanis B + [kapanis A]    2 / 2     [acildi A, acildi B, govde saglam, kapandi B, kapandi A]
uc kaynak        -                          0 / 0     [acildi A, acildi B, acildi C, govde saglam, kapandi C, kapandi B, kapandi A]
```

İlk iki satır dersin ölçümüdür. Aynı gövde, aynı kaynak, aynı iki olay: gövdede bir istisna,
kapatmada bir istisna. Elle yazılan biçimde çağırana ulaşan tek istisna **kapanış**
istisnasıdır; gövdedeki istisna hiçbir yerde durmaz ve **kaybolur**. Ulaşılan sayı ikide
birdir. Kaynakla denemede ise ulaşılan sayı ikide ikidir: çıkan istisna **gövdedeki** ve
kapanış istisnası onun bastırılan istisnası olarak taşınıyor.

Kaybın nedeni `finally` bloğunun anlamındadır. `finally` içinden bir istisna çıkarsa o
istisna, o sırada yolda olan istisnanın **yerini alır**; iki istisna aynı anda yukarı
gidemez ve dilin elle yazılan biçiminde ikincisini saklayacak bir yer yoktur. Kaynakla
deneme tam olarak bu yeri açar: gövdenin istisnası yolda kalır, kapatmanınki ona iliştirilir.

Kaybın ölçülen bedeli, kusurun **hangi olayla** görüneceğidir. Elle yazılan biçimde çağıran
"kapanis A" iletisini görür; oysa programı durduran asıl olay gövdededir ve o olayın izi
hiçbir yerde yoktur. Kapatma sırasında düşen bir kaynak, kendisinden önce düşen her şeyin
üstünü örter.

#### Kapatma Sırası

Üçüncü, dördüncü ve beşinci satırların günlükleri kapatmanın sırasını yazıyor. Üç kaynaklı
ölçümde açılış sırası `A, B, C`, kapanış sırası `C, B, A`. Kaynaklar **açılışın tersi
sırayla** kapatılır ve bu keyfi değildir: sonra açılan kaynak önce açılana dayanmış olabilir,
tersi olamaz.

Dördüncü satır aynı kuralı iki olayla birlikte gösteriyor. `A` ile `B` açılıyor, gövde sağlam
bitiyor, ikisi de kapanışta düşüyor. Çağırana ulaşan istisna **`B`'nin** kapanış istisnasıdır
— yani ilk kapatılanın, sonra açılanın. `A`'nınki ona bastırılan olarak yazılıyor. Sıra
burada kimin çıkacağını da belirliyor: yolda olan ilk istisna kim ise sonrakiler ona
iliştirilir.

Beşinci satırda hiçbir kaynak kapanışta düşmüyor ve ulaşılan olay sayısı sıfır; günlük yine
de üç açılış ve üç kapanış gösteriyor. Kapatma, istisna olsun olmasın her durumda yapılır.

#### Kaynak Olma Kararını Bildirilen Tip Verir

- **GN24** — `Olcek` çekirdeğinin bu derste kullanılan üç parçası yazılır: derleme, bir
  yöntemin komut sayısı ve çağırdığı yöntemlerin adları.
- **GN25** — Altı denemede tipler ve gövdeler sabittir; yalnız kaynağın bildirildiği tip,
  gövdedeki satır ve çevreleyen yöntemin bildirimi değişir.

```java
// Olcek.java — ortak ölçüm çekirdeği: sınıf dosyasını derleyip okur
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.instruction.InvokeInstruction;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Olcek {
    static ClassModel derle(String kaynak, String sinif) throws Exception {
        Path d = Files.createTempDirectory("olcek");
        Path k = d.resolve(sinif + ".java");
        Files.writeString(k, kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, "-d", d.toString(), k.toString()) != 0)
            throw new IllegalStateException("derlenmedi: " + sinif);
        return ClassFile.of().parse(d.resolve(sinif + ".class"));
    }

    static int komutSayisi(String kaynak, String sinif, String yontem) throws Exception {
        int sayi = 0;
        for (MethodModel m : derle(kaynak, sinif).methods())
            if (m.methodName().stringValue().equals(yontem))
                for (CodeElement e : m.code().orElseThrow())
                    if (e instanceof Instruction) sayi++;
        return sayi;
    }

    static List<String> cagrilar(String kaynak, String sinif, String yontem) throws Exception {
        List<String> adlar = new ArrayList<>();
        for (MethodModel m : derle(kaynak, sinif).methods())
            if (m.methodName().stringValue().equals(yontem))
                for (CodeElement e : m.code().orElseThrow())
                    if (e instanceof InvokeInstruction iv)
                        adlar.add(kisa(iv.owner().asInternalName()) + "." + iv.name().stringValue());
        return adlar;
    }

    static String kisa(String ic) { return ic.substring(ic.lastIndexOf('/') + 1); }
}
```

```java
// Uygun.java — bir ad ne zaman kaynak olabilir
public class Uygun {
    static final String TIPLER = """
        class Olay extends Exception { Olay(String m) { super(m); } }
        class Kapanan implements AutoCloseable { @Override public void close() throws Olay { } }
        class Sessiz implements AutoCloseable { @Override public void close() { } }
        class Kapanmayan { public void close() { } }
        """;

    static final String BILDIREN = TIPLER + "class C { static void y() throws Olay { %s } }";
    static final String BILDIRMEYEN = TIPLER + "class C { static void y() { %s } }";

    static void dene(String kalip, String ad, String govde) {
        try {
            Olcek.derle(kalip.formatted(govde), "C");
            System.out.printf("%-42s -> derlendi%n", ad);
        } catch (Exception e) {
            System.out.printf("%-42s -> derlenmedi%n", ad);
        }
    }

    public static void main(String[] args) {
        System.out.println("cevreleyen yontem Olay bildiriyor");
        dene(BILDIREN, "try (Kapanan k = new Kapanan())", "try (Kapanan k = new Kapanan()) { }");
        dene(BILDIREN, "try (Object k = new Kapanan())", "try (Object k = new Kapanan()) { }");
        dene(BILDIREN, "try (Kapanmayan k = new Kapanmayan())",
                "try (Kapanmayan k = new Kapanmayan()) { }");
        dene(BILDIREN, "govde icinde k = new Kapanan()",
                "try (Kapanan k = new Kapanan()) { k = new Kapanan(); }");
        System.out.println();
        System.out.println("cevreleyen yontem hicbir sey bildirmiyor");
        dene(BILDIRMEYEN, "try (Kapanan k = new Kapanan())", "try (Kapanan k = new Kapanan()) { }");
        dene(BILDIRMEYEN, "try (Sessiz k = new Sessiz())", "try (Sessiz k = new Sessiz()) { }");
    }
}
```

```
cevreleyen yontem Olay bildiriyor
try (Kapanan k = new Kapanan())            -> derlendi
try (Object k = new Kapanan())             -> derlenmedi
try (Kapanmayan k = new Kapanmayan())      -> derlenmedi
govde icinde k = new Kapanan()             -> derlenmedi

cevreleyen yontem hicbir sey bildirmiyor
try (Kapanan k = new Kapanan())            -> derlenmedi
try (Sessiz k = new Sessiz())              -> derlendi
```

İkinci satır bu dersi kursun eksenine bağlıyor. Aynı nesne — çalışma zamanı tipi `Kapanan`,
yani kapatılabilir bir nesne — `Object` olarak adlandırıldığında **kaynak olamıyor**. Nesne
kapatılabilir olmasına rağmen derleyici kapatma çağrısını üretmiyor, çünkü üretilecek çağrının
var olup olmadığına bakarken **bildirilen tipe** bakıyor. Kaynakla denemenin bütün düzeneği
derleme zamanında kurulur ve nesnenin gerçek sınıfına hiç sorulmaz.

Üçüncü satır aynı kuralın alt sınırıdır: `close` adında bir yöntemi olan ama kapatılabilirlik
sözleşmesini üstlenmeyen bir sınıf kaynak olamıyor. Ad yeterli değildir; gereken şey
bildirilen tipin o sözleşmeyi taşımasıdır.

Dördüncü satır kaynak değişkeninin gövde içinde yeniden bağlanamadığını gösteriyor. Bunun
gerekçesi üretilen kodun kendisidir: derleyici kapatma çağrısını başlangıçta bağlanan nesne
için üretir ve değişken yeniden bağlanabilseydi kapatılan nesne ile açılan nesne ayrı
düşerdi.

Son iki satır ise önceki dersin daraltma kuralının buradaki karşılığıdır. `close` yöntemi
denetlenen bir istisna bildirdiğinde kaynakla deneme bloğu o istisnayı çağırana taşır ve
çevreleyen yöntem bir seçim yapmak zorunda kalır. Aynı blok, `close` hiçbir şey bildirmediğinde
hiçbir zorunluluk üretmiyor. Kaynakla denemenin çağırana yazdığı bedel, kaynağın kapatma
bildirimiyle **birebir** aynıdır.

#### Derleyicinin Ürettiği Kod

- **GN26** — Ölçülen kaynak dersin içinde yazılır; iki yöntemin gövdesi aynı işi yapar,
  yalnız kapatmanın yazılış biçimi değişir.

```java
// Uretilen.java — kaynakla deneme sınıf dosyasında ne üretiyor
public class Uretilen {
    static final String KAYNAK = """
        class Olay extends Exception { Olay(String m) { super(m); } }
        class Kaynak implements AutoCloseable {
            void is() { }
            @Override public void close() throws Olay { }
        }
        class C {
            static void elle() throws Olay {
                Kaynak k = new Kaynak();
                try { k.is(); } finally { k.close(); }
            }
            static void kaynakla() throws Olay {
                try (Kaynak k = new Kaynak()) { k.is(); }
            }
        }
        """;

    public static void main(String[] args) throws Exception {
        for (String y : new String[] {"elle", "kaynakla"})
            System.out.printf("%-10s komut %2d  cagri %s%n", y,
                    Olcek.komutSayisi(KAYNAK, "C", y), Olcek.cagrilar(KAYNAK, "C", y));
    }
}
```

```
elle       komut 15  cagri [Kaynak.<init>, Kaynak.is, Kaynak.close, Kaynak.close]
kaynakla   komut 20  cagri [Kaynak.<init>, Kaynak.is, Kaynak.close, Kaynak.close, Throwable.addSuppressed]
```

Kaynakta iki satır daha kısa olan biçim sınıf dosyasında **beş komut daha uzundur**: elle
yazılan 15, kaynakla yazılan 20. Kaynakla deneme bir kısaltmadır, ama kısalttığı şey yazılan
metindir; ürettiği kod fazladan iş yapar.

Çağrı listelerinde iki `close` görünüyor: biri gövde düzgün bittiğinde çalışan yol, biri
gövde düştüğünde çalışan yol. Bu ikisi elle yazılan biçimde de vardır, çünkü derleyici
`finally` bloğunu iki yola da yerleştirir. Farkı yapan beşinci çağrıdır ve yalnız kaynakla
denemede görünür: bastırma çağrısı. Fazladan beş komut da bunun içindir — gövdeden çıkan
istisnanın tutulması, kapatmanın ayrı bir kolda denenmesi ve ikinci istisnanın birincisine
iliştirilmesi.

Ölçümün söylediği şudur: bastırılan istisna dilin çalışma zamanında kendiliğinden olan bir
şey değildir. Kaynağı kapatan kodun içinde bastırmayı isteyen bir çağrı vardır ve o çağrıyı
derleyici koymuştur; aynı çağrıyı elle yazan biri aynı sonucu alırdı. Kaynakla denemenin
kazandırdığı şey yeni bir yetenek değil, o kodun **her seferinde ve aynı biçimde**
yazılmasıdır.

#### Kapatma İstisnası Gövdedekini Bastırmaz

Bu dersin sınırlayıcı ölçümü ikinci ile üçüncü satırın karşılaştırılmasındadır ve bastırma
yönünün tek olduğunu gösterir.

İkinci satırda hem gövde hem kapatma düşüyor ve çıkan istisna **gövdedekidir**; kapatmanınki
bastırılan olarak taşınıyor. Yön hiçbir zaman tersine dönmez: kapatma sırasında düşen bir
istisna gövdedekini bastıramaz, çünkü gövdedeki istisna zaten yoldadır ve yerini kimseye
bırakmaz.

Üçüncü satır bu kuralın sınırını çiziyor. Gövde sağlam bittiğinde ortada bastırılacak bir şey
yoktur; kapatma istisnası **doğrudan çıkar** ve bastırılan listesi boştur. Yani bastırma bir
gizleme düzeneği değil, yalnız **ikinci** olayı taşıyan bir yerdir. Kapatmadaki kusur tek
başınayken görünürlüğünden hiçbir şey kaybetmez.

Bu iki satırın birlikte söylediği kural şudur: kaynakla deneme hiçbir olayı yok etmez ve
hiçbir olayın önceliğini değiştirmez. Elle yazılan biçimde kaybolan istisna, öncelik sırası
yanlış olduğu için değil, **ikinci olayı koyacak bir yer olmadığı** için kayboluyordu.

#### Özet

- Gövde ile kapatma aynı anda düştüğünde elle yazılan biçimde iki olaydan yalnız birine
  ulaşılır; kaynakla denemede ikisine de ulaşılır.
- Elle yazılan biçimde `finally` içinden çıkan istisna yoldaki istisnanın yerini alır ve
  gövdedeki olayın izi kalmaz; çağıran, programı durduran olayı değil kapatma olayını görür.
- Kaynaklar açılışın tersi sırayla kapatılır; birden çok kapanış düştüğünde ilk kapatılanın
  istisnası çıkar, sonrakiler ona bastırılan olarak yazılır.
- Bir adın kaynak olup olamayacağına bildirilen tip karar verir: kapatılabilir bir nesne
  `Object` olarak adlandırıldığında kapatılmaz ve blok çağırana ancak `close` bildiriminin
  taşıdığı kadar bedel yazar.
- Kaynakla deneme sınıf dosyasında elle yazılan biçimden **beş komut** daha uzundur; kısalan
  şey yazılan metindir, üretilen kod değil.
- Sınırlayıcı ölçüm: bastırma yönü tektir. Kapatma istisnası gövdedekini bastırmaz, tersi
  olur; gövde sağlam bittiğinde kapatma istisnası doğrudan ve tek başına çıkar.

#### Sonraki Adım

Üç ders boyunca istisna hep verili bir şey oldu: fırlatıldı, bildirildi, yakalandı,
bastırıldı. Hiçbirinde bir olayın **istisnayla mı bildirileceği** sorulmadı. Oysa aynı iş üç
ayrı sözleşmeyle yazılabilir: denetlenen istisna, denetlenmeyen istisna, ya da hiç istisna
kullanmayıp sonucu bir tiple döndürmek. Kursun son dersi üçünü aynı işle kurup çağıran
tarafta derleyicinin zorladığı karar noktası sayısını sayar ve sözleşme kırıldığında kusurun
nerede göründüğünü ölçer.

### İstisna Tasarımı

Source: https://academia.sh/tr/kurslar/java-nesne-modeli/istisna-tasarimi

Aynı iş üç sözleşmeyle kurulur: denetlenen istisna, denetlenmeyen istisna ve sonuç tipi. Ölçüm çağıran tarafta derleyicinin zorladığı karar noktası sayısını ve boş depoda dikkatsiz çağıranın ne aldığını gösterir; üç sözleşmenin hiçbiri kusuru yok etmez, yalnız göründüğü yeri değiştirir.

Önceki dört ders istisnayı verili aldı: fırlatıldı, bildirildi, yakalandı, bastırıldı.
Hiçbirinde bir olayın **istisnayla mı bildirileceği** sorulmadı. Aynı iş üç ayrı sözleşmeyle
yazılabilir — denetlenen istisna, denetlenmeyen istisna, ya da hiç istisna kullanmayıp sonucu
bir tiple döndürmek.

Bu ders üçünü aynı iş üzerinde kurar: boş olabilen bir depodan öğe almak. Kursun sorusu
burada tasarım sorusuna dönüşür, ama ölçü aynı kalır. Sözleşmeyi seçen, çağıranın ne yapmak
**zorunda** olduğunu seçer; ve zorlamayı yapan taraf her zaman derleyicidir.

#### Derleyicinin Zorladığı Karar Noktası

- **GN27** — Üç sözleşme aynı depo üzerinde, aynı üç katmanlı çağrı zinciriyle ölçülür:
  `ic` işi yapar, `orta` onu çağırır, `dis` da `orta`yı.
- **GN28** — Sekiz deneme, bildirimin üç noktadan hangilerine konduğu kümesidir. Sonuç tipi
  için konulacak bir bildirim olmadığından o satırın alt küme sayımı boş bırakılır.

```java
// Olcek.java — ortak ölçüm çekirdeği: verilen kaynağı derler
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.nio.file.*;
import java.util.spi.ToolProvider;

class Olcek {
    static ClassModel derle(String kaynak, String sinif) throws Exception {
        Path d = Files.createTempDirectory("olcek");
        Path k = d.resolve(sinif + ".java");
        Files.writeString(k, kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, "-d", d.toString(), k.toString()) != 0)
            throw new IllegalStateException("derlenmedi: " + sinif);
        return ClassFile.of().parse(d.resolve(sinif + ".class"));
    }
}
```

```java
// Zorlama.java — üç sözleşme çağıran zincirinde kaç noktayı zorluyor
public class Zorlama {
    static final String TIPLER = """
        class Olay extends Exception { Olay(String m) { super(m); } }
        class SessizOlay extends RuntimeException { SessizOlay(String m) { super(m); } }
        record Sonuc(String deger, String hata) { }
        class Depo {
            String denetlenen() throws Olay { throw new Olay("bos"); }
            String denetlenmeyen() { throw new SessizOlay("bos"); }
            Sonuc sonuc() { return new Sonuc(null, "bos"); }
        }
        """;

    static String zincir(String tip, String cagri, String[] bildirim) {
        return TIPLER + "class C {\n"
                + "    static " + tip + " ic(Depo d) " + bildirim[0]
                + " { return d." + cagri + "(); }\n"
                + "    static " + tip + " orta(Depo d) " + bildirim[1] + " { return ic(d); }\n"
                + "    static " + tip + " dis(Depo d) " + bildirim[2] + " { return orta(d); }\n}\n";
    }

    static boolean derlenirMi(String kaynak) {
        try {
            Olcek.derle(kaynak, "C");
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    static void olc(String ad, String tip, String cagri, String bildirim) {
        boolean dokunulmamis = derlenirMi(zincir(tip, cagri, new String[] {"", "", ""}));
        int enAz = -1, gecen = 0;
        for (int m = 0; m < 8; m++) {
            String[] b = new String[3];
            int nokta = 0;
            for (int i = 0; i < 3; i++) {
                boolean var = (m >> i & 1) == 1;
                b[i] = var ? bildirim : "";
                if (var) nokta++;
            }
            if (derlenirMi(zincir(tip, cagri, b))) {
                gecen++;
                if (enAz < 0 || nokta < enAz) enAz = nokta;
            }
        }
        System.out.printf("%-16s %-20s %-10d %s%n", ad,
                dokunulmamis ? "derlendi" : "derlenmedi", enAz,
                bildirim.isEmpty() ? "-" : gecen + " / 8");
    }

    public static void main(String[] args) {
        System.out.printf("%-16s %-20s %-10s %s%n",
                "sozlesme", "dokunulmamis zincir", "en az nokta", "derlenen alt kume");
        olc("denetlenen", "String", "denetlenen", "throws Olay");
        olc("denetlenmeyen", "String", "denetlenmeyen", "throws SessizOlay");
        olc("sonuc tipi", "Sonuc", "sonuc", "");
    }
}
```

```
sozlesme         dokunulmamis zincir  en az nokta derlenen alt kume
denetlenen       derlenmedi           3          1 / 8
denetlenmeyen    derlendi             0          8 / 8
sonuc tipi       derlendi             0          -
```

İlk satır denetlenen istisnanın çağırana yazdığı bedeli sayıyor. Hiçbir yere dokunulmamış
zincir derlenmiyor ve zinciri derlenir kılan en küçük bildirim kümesinin büyüklüğü **üç** —
yani her katman. Sekiz alt kümeden yalnız **biri** derleniyor: hepsinin bildirdiği küme.
Zorunluluk aradaki bir katmanda durdurulamaz, çünkü bir katman bildirmezse onu çağıran
katman istisnanın varlığını hiç öğrenemez.

İkinci ve üçüncü satırlar sıfır veriyor. Denetlenmeyen istisnada zincire hiç dokunulmadan
derleniyor; sekiz alt kümenin sekizi de geçerli, yani bildirim tümüyle isteğe bağlı. Sonuç
tipinde ise konulacak bir bildirim bile yok.

Buradan ölçülmüş bir karşılaştırma çıkıyor. Denetlenen istisna, hata yolunun varlığını
**zincirin her katmanına yazdırır**; bu bir belgelendirmedir ve derleyici tarafından
uygulanır. Diğer iki sözleşmede hata yolu ara katmanların hiçbirinde görünmez. Ne var ki
zorlanan şey yalnız **bildirmektir**, işlemek değil — ve bir sonraki ölçüm bunun ne anlama
geldiğini gösteriyor.

#### Aynı İş, Üç Sözleşme

- **GN29** — Her sözleşme için iki çağıran yazılır: derleyicinin kabul ettiği en kısa yazım
  ile hata yolunu gerçekten işleyen yazım. İkisi de aynı boş depoyla ve aynı dolu depoyla
  çalıştırılır.
- **GN30** — Çağıranın sonucundan yalnız dönen değer ya da çıkan istisnanın sınıf adı
  basılır.

```java
// Sozlesme.java — aynı iş üç sözleşmeyle, dikkatsiz ve dikkatli çağıranla
import java.util.function.Function;

class Olay extends Exception {
    Olay(String m) { super(m); }
}

class SessizOlay extends RuntimeException {
    SessizOlay(String m) { super(m); }
}

record Sonuc(String deger, String hata) { }

class Depo {
    private final String oge;

    Depo(String oge) { this.oge = oge; }

    String denetlenen() throws Olay {
        if (oge == null) throw new Olay("bos depo");
        return oge;
    }

    String denetlenmeyen() {
        if (oge == null) throw new SessizOlay("bos depo");
        return oge;
    }

    Sonuc sonuc() {
        return oge == null ? new Sonuc(null, "bos depo") : new Sonuc(oge, null);
    }
}

public class Sozlesme {
    static String dikkatsizDenetlenen(Depo d) {
        try {
            return d.denetlenen();
        } catch (Olay e) {
            return "";
        }
    }

    static String dikkatliDenetlenen(Depo d) {
        try {
            return d.denetlenen();
        } catch (Olay e) {
            return "yedek (" + e.getMessage() + ")";
        }
    }

    static String dikkatliDenetlenmeyen(Depo d) {
        try {
            return d.denetlenmeyen();
        } catch (SessizOlay e) {
            return "yedek (" + e.getMessage() + ")";
        }
    }

    static String dikkatliSonuc(Depo d) {
        Sonuc s = d.sonuc();
        return s.hata() == null ? s.deger() : "yedek (" + s.hata() + ")";
    }

    static String calistir(Function<Depo, String> f, Depo d) {
        try {
            return "deger \"" + f.apply(d) + "\"";
        } catch (RuntimeException e) {
            return "istisna " + e.getClass().getSimpleName();
        }
    }

    static void satir(String ad, Function<Depo, String> dikkatsiz, Function<Depo, String> dikkatli) {
        Depo bos = new Depo(null), dolu = new Depo("kuzey");
        System.out.printf("%-16s %-26s %-26s %s%n", ad,
                calistir(dikkatsiz, bos), calistir(dikkatli, bos), calistir(dikkatsiz, dolu));
    }

    public static void main(String[] args) {
        System.out.printf("%-16s %-26s %-26s %s%n",
                "sozlesme", "dikkatsiz / bos depo", "dikkatli / bos depo", "dikkatsiz / dolu depo");
        satir("denetlenen", Sozlesme::dikkatsizDenetlenen, Sozlesme::dikkatliDenetlenen);
        satir("denetlenmeyen", d -> d.denetlenmeyen(), Sozlesme::dikkatliDenetlenmeyen);
        satir("sonuc tipi", d -> String.valueOf(d.sonuc().deger()), Sozlesme::dikkatliSonuc);
    }
}
```

```
sozlesme         dikkatsiz / bos depo       dikkatli / bos depo        dikkatsiz / dolu depo
denetlenen       deger ""                   deger "yedek (bos depo)"   deger "kuzey"
denetlenmeyen    istisna SessizOlay         deger "yedek (bos depo)"   deger "kuzey"
sonuc tipi       deger "null"               deger "yedek (bos depo)"   deger "kuzey"
```

İlk sütun üç ayrı kusur biçimi veriyor ve üçü de aynı boş depodan çıkıyor.

Denetlenen sözleşmede dikkatsiz çağıran boş bir dizgi döndürüyor. Derleyici bir yakalama
yazmaya zorladı, ama yazılan yakalamanın içine ne konacağını sormadı; boş bir gövde
zorunluluğu tümüyle karşılıyor. Denetlenen istisnanın güvencesi **hata yolunun
görülmesidir**, işlenmesi değil.

Denetlenmeyen sözleşmede dikkatsiz çağıran çalışma zamanında düşüyor; kusur burada en
gürültülü biçimini alıyor. Sonuç tipinde ise ne derleme zamanında durduruluyor ne de çalışma
zamanında düşüyor; **`null` metnini bir değer olarak** döndürüyor. Üç biçim arasında en
sessiz olanı budur, çünkü hata yolu bir istisna değil, sıradan bir veri hâline gelmiştir.

İkinci sütun üç sözleşmenin ortak noktasını yazıyor: hata yolu gerçekten işlendiğinde üçü de
**aynı sonucu** veriyor. Üçüncü sütun ise ayrımın nerede yaşadığını gösteriyor — depo dolu
olduğunda üç sözleşme ayırt edilemez. Sözleşme seçimi doğru yolda hiçbir şeyi değiştirmez;
tamamı hata yoluna ilişkin bir karardır.

#### Kusur Yok Olmaz, Yer Değiştirir

Bu dersin sınırlayıcı ölçümü iki tablonun birlikte okunmasıdır ve üç sözleşmenin hiçbirini
üstün çıkarmaz.

Kusur tektir: boş bir depodan öğe istenmiştir. Üç sözleşme bu kusuru ortadan kaldırmıyor,
yalnız **görüneceği yeri** seçiyor. Denetlenen istisna onu çağıranın derlemesine taşıyor —
üç katman da hata yolunu yazmak zorunda, ama yazdıklarının doğru olması zorunlu değil.
Denetlenmeyen istisna onu çalışma zamanına taşıyor; kaynakta hiçbir katman kirlenmiyor, buna
karşılık hangi çağrının düşebileceği kaynağa bakarak görülemiyor. Sonuç tipi onu veri
katmanına taşıyor; hiçbir yerde düşmüyor, ama yanlış değer akmayı sürdürüyor.

Üç yerin üçünde de bedeli ödeyen ayrı taraflardır: birincisinde yazan, ikincisinde
çalıştıran, üçüncüsünde sonucu okuyan. Bu kursun ilk dersinden beri sürüp gelen ölçü burada
son biçimini alıyor — bir kararın nerede verildiği, kusurun nerede görüneceğini belirler ve
hiçbir seçim kusuru yok etmez.

#### Özet

- Denetlenen istisna çağrı zincirinin **her katmanında** bildirim zorunlu kılar: sekiz alt
  kümeden yalnız biri derlenir ve en küçük bildirim kümesinin büyüklüğü üçtür.
- Denetlenmeyen istisna ile sonuç tipi hiçbir noktayı zorlamaz; zincire dokunulmadan
  derlenir ve hata yolu ara katmanların kaynağında görünmez.
- Zorlanan şey bildirmektir, işlemek değil: dikkatsiz çağıran boş bir yakalama yazarak
  zorunluluğu karşılar ve boş bir değer döndürür.
- Aynı boş depo üç ayrı kusur biçimi üretir: boş değer, çalışma zamanı istisnası ve bir veri
  hâline gelmiş `null`. Hata yolu işlendiğinde üç sözleşme aynı sonucu verir; depo dolu
  olduğunda ayırt bile edilemezler.
- Sınırlayıcı ölçüm: üç sözleşmenin hiçbiri kusuru yok etmez. Kusur tektir ve sözleşme yalnız
  onun görüneceği yeri seçer — derleme, fırlatma ya da okuma.

#### Kurs Kapanışı

Kurs tek bir soruyla açıldı: bir ad iki tip taşıdığında, kararı hangisi verir? On üç ders bu
soruyu on üç ayrı dilsel karara sordu ve yanıtı her seferinde çalıştırılmış bir ölçümden
aldı. Toplandığında ortaya tek bir kural çıkıyor: **çağrılan davranış çalışma zamanı tipine,
seçilen ad bildirilen tipe bakar** — ve iki taraf arasındaki sınır sözdiziminde görünmez.

| Ders | Ölçülen karar | Kararı veren taraf | Sınırlayıcı ölçüm |
|---|---|---|---|
| Sınıf, Nesne ve Yapıcı | yapıcı zincirinden çağrılan yeniden tanımlı yöntem | çalışma zamanı tipi | özel ve `final` yöntemde tuzak doğmaz, üçü aynı komuta derlenir |
| Erişim Belirteçleri | görünürlük: 16 çiftin 10'u derlenir | bildirilen tip (derleme zamanı) | üye çalışma zamanında yerinde durur, ayrımı yalnız bir bayrak taşır |
| Kalıtım ve Yeniden Tanımlama | sekiz kararın dördü ve dördü | dördü çalışma zamanı, dördü bildirilen tip | imza denk düşmediğinde yeniden tanımlama doğmaz, karar taraf değiştirir |
| Soyut Sınıflar ve Arayüzler | aynı yeteneğin sekiz kuruluşu, üçünde derleme durur | derleyici, tarafı sormadan | durum taşıyan bir yetenek arayüzle kurulamaz |
| Statik Üyeler ve Başlatma Blokları | statik yöntem seçimi ve sınıf düzeyi durum | bildirilen tip | iki taraf çakıştığında ayrım gözlenemez |
| `final` Anahtar Sözcüğü | yeniden atama, yeniden tanımlama, genişletme kısıtları | bildirilen tip (derleme zamanı) | `final` referans nesneyi dondurmaz |
| İç ve Yerel Sınıflar | üretilen sınıf dosyaları ve gizli alanlar | derleyici, kaynakta yazılmadan | statik iç içe sınıf gizli alan taşımaz |
| Kayıtlar ve Numaralandırmalar | üretilen üyelerin hangi tarafta yanıt verdiği | çalışma zamanı tipi | kaydın koruması sığdır, dizi bileşeni dışarıdan değişir |
| Genel Tipler | tip parametresinin çalışma zamanında bıraktığı | bildirilen tip, tamamen | imza özniteliği kalır: silinen bilgi değil, denetimdir |
| Sınırlı Joker Karakterler | dört kap biçimi × iki işlem, sekizin dördü | bildirilen tip, tamamen | dördünden de `Object` okunur, dördüne de boş değer yazılır |
| İstisna Hiyerarşisi | denetlenen ayrımının zorladığı: dokuz yazımın biri reddedilir | yazımı bildirilen tip, dalı çalışma zamanı tipi | sanal makine bildirimi denetlemez |
| Kaynakla Deneme | kapatma sırası ve bastırılan istisna | derleyici, bildirilen tipe bakarak | bastırma yönü tektir, kapatma gövdedekini bastırmaz |
| İstisna Tasarımı | üç sözleşmenin zorladığı karar noktası: 3, 0, 0 | bildirilen tip (derleme zamanı) | üç sözleşme de kusuru yok etmez, yerini değiştirir |

Tablonun üçüncü sütunu kursun yöntemidir; dördüncü sütun ise her dersin kendi tezini
sınırladığı yerdir. İkisi birlikte okunduğunda kursun asıl iddiası görünüyor: **fark
sözdiziminde görünmez.** `d.ad()` ile `d.etiket` iki
karakterlik bir farkla yazılır ve ayrı taraflardan yanıt alır; `Depo<Ust>` ile `Depo<String>`
ayrı yazılır ve tek bir sınıf olur; `throws` bildirimi kaynakta durur ve çalışma zamanında
denetlenmez.

İkinci okuma genelliklerden geliyor ve son beş dersin ortak dersidir. Bir denetim derleme
zamanına taşındığında ortadan kalkmaz, yalnız **yer değiştirir**; ve o denetimi atlatan bir
yol açıldığında kusur, üretildiği yerde değil, çok sonra okunduğu yerde görünür. Ham tiple
yazılan bir değer okuyan satırda düşer, kaynakla deneme yazılmadığında istisna kapatma
satırında kaybolur, sözleşme yanlış seçildiğinde kusur veri hâline gelir.

Bu kursta sınıflar hep yalnız çalıştı: bir depo, bir üst sınıf, bir alt sınıf. Sıradaki kurs
olan **Standart Kütüphane ve Akışlar** aynı tipleri koleksiyonların ve akışların içine koyar.
Orada tip parametresi tek bir depoda değil, birbirine zincirlenen işlemlerde taşınır; ve
"kararı hangi taraf verdi" sorusu yeniden sorulabilir hâle gelir, çünkü bir akışın her
halkası kendi bildirilen tipini taşır.

