---
title: 'Java Temelleri'
source: 'https://academia.sh/tr/kurslar/java-temelleri'
language: tr
updated: '2026-08-17T04:41:18+00:00'
license: 'CC BY-SA 4.0'
lessons: 10
outcomes:
    - 'Java programının derlenme ve çalışma zincirini açıklar'
    - 'Temel tipler, denetim akışı ve diziler ile program yazar'
    - 'Değer ve referans anlamını doğru kullanır'
---

# Java Temelleri

Java'da yazılan kaynak, çalıştırılan şey değildir: kurs her sözdizim biçiminin sınıf dosyasında kaç adıma dönüştüğünü ve derleyicinin kaynakta bulunmayan hangi adımı eklediğini sayar; yürütme zincirinden değer ve referans aktarımına kadar dilin kendi düzeneğini bu ölçüyle kurar.

## Dil ve Çalışma Zamanı

Kaynaktan sınıf dosyasına ve sanal makineye giden zincirin ölçülmesi, bir programın giriş noktasıyla başlayıp sonlanana kadar geçtiği adımlar, ve paket ile içe aktarımın ad çözümlemesinde bıraktığı iz.

### Kaynak, Bayt Kodu ve Sanal Makine

Source: https://academia.sh/tr/kurslar/java-temelleri/kaynak-bayt-kodu-ve-sanal-makine

Java'da yazılan kaynak, çalıştırılan şey değildir: on iki yöntemin sekizi sınıf dosyasında kaynakta bulunmayan bir adım taşır ve toplam 13 eklenen adım sayılır; buna karşılık kaynakta duran bir birleştirme sınıf dosyasında hiç yoktur.

Python müfredatı beş kursla kapandı ve kapanış kursu bir projenin sayısını tek soruyla
tanımladı: aynı girdi kaç ayrı sonuç veriyor. O müfredat boyunca ölçülen şeylerin ortak bir
yanı vardı — yazılan kaynak, yorumlayıcının gördüğü şeye yakın duruyordu. Bir sözdizim
biçiminin altında hangi yöntemin çağrıldığı sorulduğunda yanıt **çalışma zamanında**
aranıyordu, çünkü kaynak metin ile çalışan şey arasındaki mesafe kısaydı.

Java'da o mesafe kısa değildir. Kaynak ile çalıştırılan şeyin arasına bir **derleyici
(compiler)** ve onun ürettiği bir **sınıf dosyası (class file)** girer; **Java sanal makinesi
(Java virtual machine)** kaynağı değil o dosyayı okur. Aradaki fark bir biçim farkı değildir:
sınıf dosyasında kaynakta yazılmamış şeyler bulunur. Bu dersin ve bütün kursun sorusu buradan
çıkar — yazılan satır, sınıf dosyasında **kaç adıma ve hangi adımlara** dönüşüyor?

#### Zincir ve Bu Dersin Payı

Bilgisayarlar Nasıl Çalışır kursu derleyici, yorumlayıcı ve sanal makine üçlüsünü, **bayt
kodu (bytecode)** kavramını ve **anında derlemeyi (just-in-time compilation)** kurdu; derleme
aşamalarını da ayrı bir derste anlattı. **Bunlar burada tekrarlanmaz.** Orada kurulan
**kavramdı**; burada ölçülen, bir derleyicinin bu kaynak için gerçekten ürettiği
**dosyadır**.

Zincir üç durak taşır. Kaynak metin insanın yazdığı şeydir. Derleyici onu bir sınıf dosyasına
çevirir; bu dosya bayt kodu taşır, yani sanal makinenin komut kümesiyle yazılmış bir komut
dizisi. Sanal makine dosyayı yükler ve komutları yürütür. İki geçişten yalnız birincisi bu
dersin konusudur: **kaynak ile sınıf dosyası arasındaki fark.**

Farkın ölçülebilir olması bir kolaylıktan gelir. Sınıf dosyasının biçimi tanımlıdır ve
standart kitaplık hem o dosyayı okuyan bir arayüz hem de program içinden çağrılabilen bir
derleyici taşır. Ölçüm için hiçbir dış araca gerek yoktur: bir metin veririz, derleriz, çıkan
dosyayı okuruz. Kaynağı kendimiz yazdığımız için ne yazdığımızı biliriz; dosyada bulduğumuz
fazlalık derleyicinin koyduğudur.

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

Kursun bütün ölçümleri tek bir yardımcı sınıftan geçer. `Olcek`, verilen kaynak metnini
derler, ürettiği sınıf dosyasını okur ve her yöntem için iki şey döndürür: **kaç komut
(instruction)** üretildiği ve **kaynakta yazılmayan hangi adımın** eklendiği. Eklenen adım
beş türden biridir — bir çağrı, bir dinamik çağrı, bir nesne ya da dizi üretimi, bir tip
denetimi, bir sayısal çevirme.

Ölçümün varsayımları:

- **CZ1** — Kâhin kurgunun kendisidir: kaynağı biz yazdığımız için ne yazdığımızı biliriz,
  dolayısıyla sınıf dosyasında bulunan fazlalık bir çıkarım değil bir dökümdür.
- **CZ2** — Derleyici ve sınıf dosyası okuyucusu standart kitaplığın parçasıdır ve program
  içinden çağrılır; hiçbir dış araç kullanılmaz.
- **CZ3** — Sayılan birim **komuttur**. Sayılar bir derleyici koşumundan gelir ve o koşumda
  belirlenimcidir; dersin tezi tek bir komut sayısına değil, sayılar arasındaki **yöne**
  dayanır.
- **CZ4** — Ölçüm hiçbir ortama bağlı veri okumaz: süre, bellek adresi, kimlik ve yol
  yazılmaz.
- **CZ5** — Ölçülen kaynak on iki yöntem taşır ve bu yöntemler kursun ders ders ödeyeceği
  sözdizim biçimlerini kapsar; dilin bütün sözdizimi değildir.

```java
// Olcek.java — ölçüm çekirdeği: derleyicinin sınıf dosyasına ne eklediğini okur
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 Yontem(String ad, int komut, List<String> eklenen) {}

    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<Yontem> oku(String kaynak, String sinif) throws Exception {
        List<Yontem> yontemler = new ArrayList<>();
        for (MethodModel m : derle(kaynak, sinif).methods()) {
            if (m.code().isEmpty()) continue;
            List<String> eklenen = new ArrayList<>();
            int komut = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) komut++;
                if (e instanceof InvokeInstruction iv)
                    eklenen.add(kisa(iv.owner().asInternalName()) + "." + iv.name().stringValue());
                else if (e instanceof InvokeDynamicInstruction id)
                    eklenen.add("dinamik:" + id.name().stringValue());
                else if (e instanceof NewObjectInstruction n)
                    eklenen.add("yeni " + kisa(n.className().asInternalName()));
                else if (e instanceof NewPrimitiveArrayInstruction a)
                    eklenen.add("yeni dizi " + a.typeKind().name().toLowerCase(Locale.ROOT));
                else if (e instanceof NewReferenceArrayInstruction a)
                    eklenen.add("yeni dizi " + kisa(a.componentType().asInternalName()));
                else if (e instanceof TypeCheckInstruction t && t.opcode() == Opcode.CHECKCAST)
                    eklenen.add("tip denetimi " + kisa(t.type().asInternalName()));
                else if (e instanceof ConvertInstruction c)
                    eklenen.add("cevirme " + c.opcode().name().toLowerCase(Locale.ROOT));
            }
            yontemler.add(new Yontem(m.methodName().stringValue(), komut, eklenen));
        }
        return yontemler;
    }

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

Ölçülen kaynak da ayrı bir dosyada durur, çünkü bu derste iki kez okunacaktır.

```java
// Kaynak.java — ölçülen kaynak metni: on iki yöntem, on iki sözdizim biçimi
class Kaynak {
    static final String ORNEK = """
        import java.util.List;
        class Ornek {
            static int topla(int a, int b) { return a + b; }
            static Object kutula(int n) { Integer k = n; return k; }
            static int kutuAc(Integer k) { int n = k; return n; }
            static String birlestir(String a, int n) { return a + n; }
            static String sabitBirlestir() { return "ba" + "sit"; }
            static String donguyleBirlestir(String[] d) {
                String s = "";
                for (String x : d) s += x;
                return s;
            }
            static int diziToplami(int[] d) { int t = 0; for (int x : d) t += x; return t; }
            static int listeToplami(List<Integer> l) { int t = 0; for (int x : l) t += x; return t; }
            static long yukselt(byte b, long u) { return b + u; }
            static byte daralt(byte b) { b += 300; return b; }
            static int degisken(int... sayilar) { return sayilar.length; }
            static int degiskenCagrisi() { return degisken(1, 2, 3); }
        }
        """;
}
```

#### Yöntem Yöntem: Kaç Komut, Hangi Adım

```java
// Ana.java — her yöntem için komut sayısı ve kaynakta yazılmayan adım
public class Ana {
    public static void main(String[] args) throws Exception {
        System.out.printf("%-18s %6s  %s%n", "yontem", "komut", "kaynakta yazilmayan adim");
        int yazilan = 0, eklenenli = 0, toplam = 0;
        for (Olcek.Yontem y : Olcek.oku(Kaynak.ORNEK, "Ornek")) {
            System.out.printf("%-18s %6d  %s%n", y.ad(), y.komut(),
                    y.eklenen().isEmpty() ? "-" : String.join(", ", y.eklenen()));
            if (y.ad().equals("<init>")) continue;
            yazilan++;
            if (!y.eklenen().isEmpty()) eklenenli++;
            toplam += y.eklenen().size();
        }
        System.out.println("yazilan yontem: " + yazilan
                + " | eklenen adim tasiyan: " + eklenenli
                + " | toplam eklenen adim: " + toplam);
    }
}
```

```
yontem              komut  kaynakta yazilmayan adim
<init>                  3  Object.<init>
topla                   4  -
kutula                  5  Integer.valueOf
kutuAc                  5  Integer.intValue
birlestir               4  dinamik:makeConcatWithConstants
sabitBirlestir          2  -
donguyleBirlestir      24  dinamik:makeConcatWithConstants
diziToplami            24  -
listeToplami           20  List.iterator, Iterator.hasNext, Iterator.next, tip denetimi Integer, Integer.intValue
yukselt                 5  cevirme i2l
daralt                  7  cevirme i2b
degisken                3  -
degiskenCagrisi        16  yeni dizi int, Ornek.degisken
yazilan yontem: 12 | eklenen adim tasiyan: 8 | toplam eklenen adim: 13
```

#### Sayıların Okunması

Yazılan on iki yöntemin **sekizi** sınıf dosyasında kaynakta bulunmayan bir adım taşıyor;
toplam **13 eklenen adım**. Kursun taşıyacağı ölçü budur: **eklenen adım kuraldır, istisna
değildir.** Tablonun her satırında solda yazdığımız yöntem, sağda derleyicinin oraya koyduğu
şey duruyor.

Satırlar tek tek okunduğunda eklemenin nereden geldiği görünür. `Integer k = n` kaynakta bir
atamadır, sınıf dosyasında bir `Integer.valueOf` çağrısıdır. `a + n` bir işleç gibi yazılır,
sınıf dosyasında bir **dinamik çağrıdır**. `b + u` satırında `byte` değerinin `long` ile
toplanabilmesi için bir çevirme komutu eklenmiştir. `degisken(1, 2, 3)` üç argümanlı bir
çağrı gibi durur; sınıf dosyasında önce bir dizi üretilir, sonra tek argümanlı bir çağrı
yapılır — bu yüzden üç komutluk yöntemin çağrısı **16 komut** tutar.

Eklenen adım taşımayan **dört** yöntem de aynı ölçüde bilgi verir: `topla`, `sabitBirlestir`,
`diziToplami` ve `degisken`. Ortak yanları şudur — hiçbiri çalışma zamanında bir başkasına
soru sormaz. İki tam sayıyı toplamak, bir dizinin öğesini okumak, bir dizinin uzunluğunu
sormak: bunların hepsi sanal makinenin doğrudan komutlarıyla karşılanır ve araya bir çağrı
girmez. Eklenen adımın koşulu buradan okunur: derleyici, yazılan biçimin karşılığını komut
kümesinde **bulamadığında** araya bir adım koyar.

Bu dördüncü yöntem ayrıca eklenen adımın nereye düştüğünü gösteriyor. `degisken` üç komuttur
ve hiçbir adım taşımaz; **çağrısı** ise 16 komut tutar ve bir dizi üretimi taşır. Değişken
sayıda argüman alan bir yöntemin bedeli tanımında değil, **çağrıldığı yerdedir**. Bir biçimin
maliyetini ararken yalnız tanımına bakmak yanıltır.

Tabloda kaynakta **hiç yazılmamış bir yöntem** de var: `<init>`, üç komutluk bir yapıcı.
Kaynakta `Ornek` sınıfı için bir yapıcı yazılmamıştır; sınıf dosyasında bir tane vardır ve
tek işi taban sınıfın yapıcısını çağırmaktır. Bir sonraki ders bu satırın ne zaman
kaybolduğunu ölçer.

En keskin okuma iki satırın karşılaştırmasından çıkar. `diziToplami` ile `listeToplami`
kaynakta **aynı** döngü yazımını taşır — ikisi de gelişmiş `for` döngüsüdür. Dizide bu döngü
**24 komut** ve **sıfır** eklenen adım üretiyor; listede **20 komut** ve **beş** eklenen adım.
Yani **daha az komut, daha çok eklenen adım**: komut sayısı ile eklenen adım sayısı aynı yöne
gitmez. Dizide sanal makinenin doğrudan komutları yeterken, listede bir yineleyici zinciri
kuruluyor — `iterator`, `hasNext`, `next`, bir tip denetimi ve bir `intValue`. İki ölçüm
ayrı şeyler söyler ve bir ölçüm, neyi saydığını söylemeden karşılaştırılamaz.

Aynı gözlem tersinden de kurulabilir. `donguyleBirlestir` ile `diziToplami` **eşit** komut
sayısı taşır — ikisi de 24 — ama biri bir dinamik çağrı taşır, öbürü hiçbir adım taşımaz.
Eşit komut sayısı, eşit iş anlamına gelmiyor. Komut saymak bir ölçüdür; eklenen adım saymak
başka bir ölçüdür ve ikisi birbirinin yerine geçmez.

#### Sınırlayıcı Ölçüm: Kaynakta Duran, Sınıf Dosyasında Olmayan

Buraya kadarki her satır bir **ekleme** gösterdi. Tezin sınırı da aynı tabloda duruyor:
`sabitBirlestir` kaynağında bir birleştirme yazar, sınıf dosyasında **2 komut** ve **sıfır**
eklenen adım taşır. Aynı `+` işleci `birlestir`de bir dinamik çağrıya dönüştüğüne göre burada
ne oldu? Sınıf dosyasının komut adlarına bakalım.

```java
// Iz.java — kaynakta duran birleştirme sınıf dosyasında duruyor mu
import java.lang.classfile.*;
import java.lang.classfile.constantpool.*;
import java.util.*;

public class Iz {
    static List<String> opkod(ClassModel cm, String yontem) {
        List<String> a = new ArrayList<>();
        for (MethodModel m : cm.methods())
            if (m.methodName().equalsString(yontem) && m.code().isPresent())
                for (CodeElement e : m.code().get())
                    if (e instanceof Instruction i) a.add(i.opcode().name().toLowerCase(Locale.ROOT));
        return a;
    }

    public static void main(String[] args) throws Exception {
        ClassModel cm = Olcek.derle(Kaynak.ORNEK, "Ornek");
        System.out.println("sabitBirlestir -> " + opkod(cm, "sabitBirlestir"));
        System.out.println("birlestir      -> " + opkod(cm, "birlestir"));
        Set<String> dizgi = new HashSet<>();
        for (PoolEntry pe : cm.constantPool())
            if (pe instanceof StringEntry se) dizgi.add(se.stringValue());
        for (String s : List.of("basit", "ba", "sit"))
            System.out.printf("sinif dosyasinda \"%s\" dizgi sabiti: %s%n", s, dizgi.contains(s));
    }
}
```

```
sabitBirlestir -> [ldc, areturn]
birlestir      -> [aload_0, iload_1, invokedynamic, areturn]
sinif dosyasinda "basit" dizgi sabiti: true
sinif dosyasinda "ba" dizgi sabiti: false
sinif dosyasinda "sit" dizgi sabiti: false
```

`sabitBirlestir` iki komuttan oluşuyor: bir sabit yükle, dön. Ortada birleştirme yoktur.
Dahası, birleştirmenin **işlenenleri** de yoktur: sınıf dosyası `"basit"` dizgisini taşır;
`"ba"` ile `"sit"` orada bulunmaz. İki sabit derleme sırasında tek bir sabite katlanmıştır —
Bilgisayarlar Nasıl Çalışır kursunun kavram olarak kurduğu **sabit katlama (constant
folding)** budur ve burada kavram değil ölçülen bir yokluk olarak görünüyor.

Böylece kursun ikinci iddiası doğuyor: **eklenen adım her zaman ekleme değildir; bazen
çıkarmadır.** Aynı `+` işleci iki yöntemde iki ayrı sonuç veriyor — birinde sıfır adım,
öbüründe bir dinamik çağrı. Ayrımı yaratan şey işlecin kendisi değil, **işlenenlerinin
derleme zamanında bilinip bilinmediğidir**. Yazılan biçime bakarak sınıf dosyasında ne
olacağını söyleyemeyiz.

#### Çevrilen Şey Hangi Makineye Yazıldı

Komut adları ikinci bir şey daha söylüyor. `aload_0` ile `iload_1` bir **yerel değişken
yuvasını (local variable slot)** numarasıyla anar, bir işlemci yazmacını değil. `ldc` bir
değeri **yığıta (stack)** koyar, `areturn` yığıttan alıp döner. Bu komut kümesi bir yığıt
makinesinindir ve hiçbir yerinde gerçek bir donanımın adı geçmez.

Aynı şey adlar için de geçerlidir. Tabloda `listeToplami` satırı `List.iterator` ve
`Iterator.hasNext` yazıyor: sınıf dosyası çağıracağı yöntemi bir adresle değil **adıyla**
taşır. Adın hangi koda karşılık geldiği derleme zamanında değil, dosya yüklenirken çözülür.

Taşınabilirliğin ölçüsü budur: derleyici, donanımı olan bir makineye değil, **belirtimi olan
bir makineye** yazdı. Sınıf dosyası gerçek işlemciye çevrilmemiştir; bu çeviriyi, çalışırken,
sanal makine kendi üstlenir — Bilgisayarlar Nasıl Çalışır kursunun anında derleme başlığıyla
kurduğu iş budur. Bu kursun ölçtüğü şey o ikinci geçiş değil, birincisidir.

#### Özet

- Java'da kaynak ile çalıştırılan şey arasında bir derleyici ve bir sınıf dosyası vardır;
  sanal makine kaynağı değil, sınıf dosyasındaki bayt kodu okur.
- Ölçüm çekirdeği `Olcek` bir kaynak metnini derler ve sınıf dosyasını okuyarak her yöntem
  için komut sayısını ve kaynakta yazılmayan adımları döker.
- Yazılan on iki yöntemin **sekizi** eklenen adım taşır, toplam **13 eklenen adım** sayılır;
  üstelik kaynakta hiç yazılmamış üç komutluk bir yapıcı da vardır.
- Aynı gelişmiş `for` döngüsü dizide **24 komut** ve **sıfır** eklenen adım, listede **20
  komut** ve **beş** eklenen adım üretir: komut sayısı ile eklenen adım aynı yöne gitmez.
- Kaynakta duran bir birleştirme sınıf dosyasında hiç yoktur; iki sabit tek sabite katlanır
  ve işlenenler dosyada bulunmaz. Eklenen adım bazen bir çıkarmadır.
- Sınıf dosyasının komutları yuva numarası ve yığıt işlemi anar, çağıracağı yöntemi adıyla
  taşır: çevrilen şey donanımı olan bir makineye değil, belirtimi olan bir makineye yazılmıştır.

#### Sonraki Adım

Bu derste sınıf dosyasının **ne olduğu** ölçüldü, ama içindeki komutların ne zaman
çalıştığına hiç bakılmadı. Tabloda kaynakta yazılmamış bir yapıcı vardı ve onun ne zaman
çağrıldığı söylenmedi; bir sınıfın kendi başlatma işinin nerede durduğu da sorulmadı. Sonraki
ders programın yaşam döngüsünü ölçer: sanal makine hangi imzayı **giriş noktası** sayıyor,
sınıf başlatma bloğu ile yapıcı hangi sırayla çalışıyor ve kaynakta iki ad yazılan bir
sınıfın sınıf dosyasındaki yüzeyi kaç üye taşıyor?

### Program Yaşam Döngüsü

Source: https://academia.sh/tr/kurslar/java-temelleri/program-yasam-dongusu

Sanal makine dört koşulu birden sağlayan tek bir imzayı giriş noktası sayar; sınıf başlatma bloğu programın başında değil sınıfın ilk kullanıldığı anda ve yalnız bir kez çalışır; kaynakta iki ad yazılan bir numaralandırma ise sınıf dosyasında üç alan ve beş yöntem taşır.

Önceki ders sınıf dosyasının **ne olduğunu** ölçtü: kaynakta yazılan on iki yöntemin sekizi
orada kaynakta bulunmayan bir adım taşıyordu ve kaynakta hiç yazılmamış üç komutluk bir
yapıcı da tabloda duruyordu. O yapıcının ne zaman çağrıldığı söylenmedi; sınıfın kendi
başlatma işinin nerede durduğu da sorulmadı.

Bu ders o boşluğu ölçer. Bir sınıf dosyası diskte durduğu sürece hiçbir şey yapmaz; sanal
makine onu yükler, hazırlar ve içindeki komutları bir sıraya göre çalıştırır. Sorular üç
tanedir: sanal makine hangi imzayı **giriş noktası (entry point)** sayıyor, bir sınıfın
başlatma işi ile bir örneğin başlatma işi hangi sırayla çalışıyor ve kaynakta yazılmamış
hangi üyeler bu sıraya katılıyor?

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

Önceki dersin `Olcek` çekirdeği komut sayıyordu. Bu derste sayılan şey komut değil **üyedir**:
sınıf dosyasındaki alanlar ve yöntemler, her yöntemin imzası, genel ve statik olup olmadığı,
dönüş tipi. Çekirdek aynı işi yapar — derler ve sınıf dosyasını okur — ama bu derste
kullanılmayan komut çözümlemesini taşımaz.

- **CZ6** — Giriş noktası ölçümü sınıf dosyasının kendisinden okunur: yöntemin adı,
  belirteçleri, dönüş tipi ve parametre listesi. Ölçüm çalıştırıcıyı çağırmaz; dilin
  belirttiği dört koşulu gerçek sınıf dosyalarına karşı sınar.
- **CZ7** — Sıra ölçümünde kâhin nesnenin kendi kaydıdır: her başlatma adımı çalıştığında
  adını ortak bir listeye yazar, dolayısıyla sıra bir çıkarım değil bir dökümdür.
- **CZ8** — Kayıt listesi program sonunda tek seferde basılır; basma işi ölçülen sıraya
  karışmaz.
- **CZ9** — Üye dökümü sınıf dosyasındaki **bütün** üyeleri sayar; kaynakta yazılanlar da
  listededir. Eklenen üye, listeyle kaynağın karşılaştırılmasından okunur.
- **CZ10** — Ölçüm ortama bağlı hiçbir veri okumaz: süre, bellek adresi, kimlik ve yol
  yazılmaz; sayılan tek şey üye sayısı ile çalışma sırasıdır.

```java
// Olcek.java — sınıf dosyasının üye dökümü: alanlar, yöntem imzaları ve belirteçleri
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.constant.MethodTypeDesc;
import java.lang.reflect.AccessFlag;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Olcek {
    record Uye(String ad, String imza, boolean genel, boolean statik, String donus) {}
    record Sinif(List<String> alan, List<Uye> yontem) {}

    static Sinif oku(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"));
        List<String> alan = new ArrayList<>();
        for (FieldModel f : cm.fields()) alan.add(f.fieldName().stringValue());
        List<Uye> yontem = new ArrayList<>();
        for (MethodModel m : cm.methods()) {
            MethodTypeDesc t = m.methodTypeSymbol();
            List<String> p = new ArrayList<>();
            for (var a : t.parameterList()) p.add(a.displayName());
            yontem.add(new Uye(m.methodName().stringValue(), "(" + String.join(", ", p) + ")",
                    m.flags().has(AccessFlag.PUBLIC), m.flags().has(AccessFlag.STATIC),
                    t.returnType().displayName()));
        }
        return new Sinif(alan, yontem);
    }

    static List<String> adlar(Sinif s) {
        List<String> a = new ArrayList<>();
        for (Uye u : s.yontem()) a.add(u.ad() + u.imza());
        return a;
    }
}
```

#### Giriş Noktası: Dört Koşulu Birden Sağlayan İmza

Bir programın nereden başladığı Java'da dosyanın ilk satırı değildir. Çalıştırıcıya bir
**sınıf** adı verilir; o sınıf yüklenir ve içinde belirli bir yöntem aranır. Aranan şey bir ad
değil, bir **imzadır**: yöntemin adı `main` olacak, genel olacak, statik olacak, hiçbir değer
döndürmeyecek ve tek parametresi bir dizgi dizisi olacaktır. Dört koşul birden sağlanmazsa o
yöntem giriş noktası değildir.

Aşağıdaki ölçüm beş ayrı kaynağı derler ve her birinin sınıf dosyasındaki `main` yöntemini bu
koşullara karşı sınar. Beşinin de kaynağı kusursuzdur ve beşi de sorunsuz derlenir — ayrım
derleme aşamasında değil, yükleme sonrasında ortaya çıkar.

```java
// Ana.java — sanal makinenin giriş noktası saydığı imza
import java.util.*;

public class Ana {
    public static void main(String[] args) throws Exception {
        Map<String, String> aday = new LinkedHashMap<>();
        aday.put("Dogru",     "class Dogru { public static void main(String[] a) { } }");
        aday.put("Statiksiz", "class Statiksiz { public void main(String[] a) { } }");
        aday.put("Genelsiz",  "class Genelsiz { static void main(String[] a) { } }");
        aday.put("Dizisiz",   "class Dizisiz { public static void main(String a) { } }");
        aday.put("Donduren",  "class Donduren { public static int main(String[] a) { return 0; } }");
        System.out.printf("%-10s %-11s %-6s %-6s %-6s %s%n",
                "sinif", "imza", "genel", "statik", "donus", "giris noktasi");
        for (var g : aday.entrySet())
            for (Olcek.Uye u : Olcek.oku(g.getValue(), g.getKey()).yontem())
                if (u.ad().equals("main")) {
                    boolean giris = u.genel() && u.statik()
                            && u.donus().equals("void") && u.imza().equals("(String[])");
                    System.out.printf("%-10s %-11s %-6s %-6s %-6s %s%n", g.getKey(), u.imza(),
                            u.genel(), u.statik(), u.donus(), giris);
                }
    }
}
```

```
sinif      imza        genel  statik donus  giris noktasi
Dogru      (String[])  true   true   void   true
Statiksiz  (String[])  true   false  void   false
Genelsiz   (String[])  false  true   void   false
Dizisiz    (String)    true   true   void   false
Donduren   (String[])  true   true   int    false
```

Beş sınıfın **biri** giriş noktası taşıyor. Kalan dördü tek bir koşulda düşüyor ve her düşüş
ayrı bir şey söylüyor. `Statiksiz` statik değildir: statik olmayan bir yöntemi çağırmak için
önce bir örnek gerekir, oysa programın başında henüz hiçbir örnek yoktur — sanal makine
yaratmadan çağıramaz. `Genelsiz` sınıfın dışından erişilebilir değildir; çağıran taraf sınıfın
kendisi olmadığına göre erişim kapalıdır. `Dizisiz` bir dizgi alır, dizgi dizisi değil:
komut satırı argümanları bir dizidir ve sayısı önceden bilinmez. `Donduren` bir değer
döndürür; programın sonlanma bilgisi dönüş değeriyle değil, ayrı bir çağrıyla verilir.

Parametrenin dizgi dizisi olması bir biçim ayrıntısı değildir: programa dışarıdan verilen
argümanlar oraya, sırasıyla ve sayısı önceden bilinmeden gelir. Giriş noktası, dış dünyadan
gelen tek veriyi bu tek parametrede karşılar.

Buradaki asıl gözlem, kusurun **nerede yakalandığıdır**. Beş kaynağın hepsi derlenmiştir;
sınıf dosyaları oluşmuştur. Bir yöntemin giriş noktası olup olmadığı derleyicinin sorusu
değildir, sanal makinenin sorusudur. Bu, önceki dersin kurduğu ayrımın öbür yüzüdür: derleyici
kaynağa adım ekler, sanal makine ise sınıf dosyasında ne bulacağına dair kendi koşullarını
taşır.

#### Sınıf Bir Kez, Örnek Her Seferinde

Giriş noktası bulunduktan sonra sıra çalışmaya gelir. Bir sınıfın hazırlanmasında iki ayrı
düzey vardır: **sınıf düzeyi** iş bir kez yapılır, **örnek düzeyi** iş her yeni nesnede
yeniden yapılır. Her düzeyin de iki adımı vardır — alan başlatıcılar ve **başlatma blokları
(static initializer)**. Bunların üstüne bir de **yapıcı (constructor)** gelir.

Ölçüm için kursun somut ekseni kullanılır: bir depo kaydını tutan `Kayit`. Üç alanı vardır —
`ad`, `adet` ve gram cinsinden `agirlik`. Her başlatma adımı çalıştığında adını ortak bir
listeye yazar; liste sonda basılır.

```java
// Sira.java — sınıf başlatma bloğu, alan başlatıcı ve yapıcı hangi sırayla çalışır
import java.util.*;

public class Sira {
    static final List<String> IZ = new ArrayList<>();
    static void iz(String s) { IZ.add(s); }

    static class Kayit {
        static { iz("sinif: baslatma blogu"); }
        static final long BIRIM_AGIRLIK = agirlikBirimi();
        String ad;
        int adet = adetBasla();
        long agirlik;
        { iz("ornek: baslatma blogu"); }

        Kayit(String ad, long agirlik) {
            iz("yapici: " + ad);
            this.ad = ad;
            this.agirlik = agirlik;
        }

        static long agirlikBirimi() { iz("sinif: alan baslatici"); return 1L; }
        static int adetBasla() { iz("ornek: alan baslatici"); return 0; }
    }

    public static void main(String[] args) {
        iz("-- main basladi");
        new Kayit("civata", 12L);
        iz("-- ilk ornek bitti");
        new Kayit("somun", 7L);
        IZ.forEach(System.out::println);
    }
}
```

```
-- main basladi
sinif: baslatma blogu
sinif: alan baslatici
ornek: alan baslatici
ornek: baslatma blogu
yapici: civata
-- ilk ornek bitti
ornek: alan baslatici
ornek: baslatma blogu
yapici: somun
```

Dökümün ilk satırı beklenmedik olanıdır: `main` **sınıf başlatma bloğundan önce** başlıyor.
Bir sınıfın hazırlanması program açılışında değil, o sınıfa ilk kez gerçekten ihtiyaç
duyulduğunda yapılır — burada ilk `new` çağrısında. Bir sınıf hiç kullanılmazsa başlatma
bloğu hiç çalışmaz.

İkinci gözlem tekrarın yokluğudur. İkinci `new` çağrısında `sinif:` ile başlayan iki satır
**yeniden görünmüyor**; yalnız örnek düzeyi adımlar tekrarlanıyor. Sınıf düzeyi iş, kaç örnek
üretilirse üretilsin bir kez yapılır. Sayı olarak: iki örnek için sınıf düzeyi **2** adım,
örnek düzeyi **6** adım çalıştı.

Dökümdeki `sinif: alan baslatici` satırı ayrıca bir ayrımı görünür kılıyor. `BIRIM_AGIRLIK`
alanı `static final` bildirilmiştir, yani bir daha değişmeyecektir — buna karşın değeri sınıf
başlatma adımında **çalışarak** üretiliyor, çünkü bir yöntem çağrısından geliyor. Değeri
derleme zamanında hesaplanabilen bir sabit olsaydı önceki dersin sabit katlama ölçümündeki
yola girer ve burada hiçbir adım görünmezdi. Bir alanın değişmez olması, değerinin çalışmadan
elde edildiği anlamına gelmez.

Üçüncüsü sıranın kendisidir. Her iki düzeyde de **alan başlatıcı** ile **başlatma bloğu**
kaynaktaki yazılma sırasını izliyor; bu örnekte sınıf düzeyinde önce blok, örnek düzeyinde önce
alan başlatıcı çalışıyor. Yapıcının gövdesi bunların **sonuncusudur** — yani bir yapıcı, gövdesi
çalışmaya başladığında alanların çoktan başlatılmış olduğunu varsayabilir. Bir alanı yapıcıda
atamak ile bildiriminde atamak aynı işi yapmaz: ikincisi önce çalışır, birincisi onun üzerine
yazar.

#### Kaynakta Yazılmayan Üyeler

Önceki dersin tablosunda kaynakta hiç yazılmamış bir `<init>` vardı. Şimdi ona doğrudan
bakılabilir — ve yanına, çok daha fazlasını ekleyen bir biçim konabilir. Kaynakta iki ad
yazılan bir **numaralandırma (enum)**, sınıf dosyasında kaç üye taşır?

```java
// Uye.java — kaynakta yazılmayan üyeler ve yapıcı yazıldığında olan
public class Uye {
    public static void main(String[] args) throws Exception {
        Olcek.Sinif bos = Olcek.oku("class Depo { }", "Depo");
        System.out.println("class Depo { }            -> alan " + bos.alan()
                + ", yontem " + Olcek.adlar(bos));
        Olcek.Sinif kendi = Olcek.oku("class Depo { Depo(int adet) { } }", "Depo");
        System.out.println("class Depo { Depo(int) }  -> alan " + kendi.alan()
                + ", yontem " + Olcek.adlar(kendi));

        Olcek.Sinif y = Olcek.oku("enum Yon { KUZEY, GUNEY }", "Yon");
        System.out.println();
        System.out.println("kaynakta yazilan ad sayisi: 2");
        System.out.println("sinif dosyasindaki alanlar (" + y.alan().size() + "): " + y.alan());
        System.out.println("sinif dosyasindaki yontemler (" + y.yontem().size() + "): "
                + Olcek.adlar(y));
    }
}
```

```
class Depo { }            -> alan [], yontem [<init>()]
class Depo { Depo(int) }  -> alan [], yontem [<init>(int)]

kaynakta yazilan ad sayisi: 2
sinif dosyasindaki alanlar (3): [KUZEY, GUNEY, $VALUES]
sinif dosyasindaki yontemler (5): [values(), valueOf(String), <init>(String, int), $values(), <clinit>()]
```

Gövdesi tümüyle boş bir sınıf, sınıf dosyasında **bir yöntem** taşıyor: parametresiz bir
yapıcı. Kaynakta bir tek karakter yazılmadan bir üye eklenmiştir; bunun adı **varsayılan
yapıcıdır** ve tek işi taban sınıfın yapıcısını çağırmaktır. Önceki dersin tablosundaki üç
komutluk `<init>` satırı buydu.

Numaralandırma çok daha ileri gider. Kaynakta yazılan iki ada karşılık sınıf dosyasında **üç
alan** ve **beş yöntem** var. `KUZEY` ile `GUNEY` yazılmıştır; `$VALUES` yazılmamıştır.
Yöntemlerin hiçbiri yazılmamıştır: `values` ile `valueOf` numaralandırmanın belirtimde
tanımlanan yüzeyidir, `$values` onlara hizmet eder, `<init>` iki parametre alır — oysa
kaynakta hiçbir yapıcı çağrısı yazılmamıştır — ve `<clinit>` bir önceki bölümde ölçtüğümüz
sınıf başlatma bloğunun sınıf dosyasındaki adıdır. Numaralandırmanın sabitleri işte orada
kurulur.

Böylece kursun üçüncü iddiası doğuyor: **eklenen şey yalnız adım değil, üyedir.** Bir sınıfın
yüzeyi, kaynağında yazılan üyelerden ibaret değildir.

#### Sınırlayıcı Ölçüm: Yazınca Kaybolan Üye

Çıktının ikinci satırı tezin sınırını çiziyor. `Depo` sınıfına tek parametreli bir yapıcı
yazıldığında sınıf dosyasında yine **bir** yöntem var, ama bu sefer o yöntem `<init>(int)` —
yani yazdığımızın kendisi. Parametresiz yapıcı **kaybolmuştur**.

Bunun görünen sonucu vardır ve sessiz değildir. Aynı çağrıyı iki `Depo` biçiminin yanına
koyup derlemeyi deneyelim.

```java
// Etki.java — eklenen yapıcı kaybolunca çağıran tarafta ne oluyor
public class Etki {
    static String dene(String kaynak) {
        try { Olcek.oku(kaynak, "Depo"); return "derlendi"; }
        catch (Exception e) { return "derlenmedi"; }
    }

    public static void main(String[] args) {
        String kul = " class Kul { static Depo yap() { return new Depo(); } }";
        System.out.println("yapici yazilmamis + new Depo() -> " + dene("class Depo { }" + kul));
        System.out.println("yapici yazilmis   + new Depo() -> "
                + dene("class Depo { Depo(int adet) { } }" + kul));
    }
}
```

```
yapici yazilmamis + new Depo() -> derlendi
yapici yazilmis   + new Depo() -> derlenmedi
```

Çağıran taraf iki durumda da **aynı** satırı yazıyor ve satırın kendisi değişmediği hâlde
biri derleniyor öbürü derlenmiyor. `Depo` sınıfına eklenen bir yetenek kaldırılmadı; kaynağa
bir yapıcı **eklendiği için** eklenen yapıcı ortadan kalktı. Derleyici bir üyeyi yalnızca
**hiçbiri yazılmamışken** koyar; yazılan tek bir yapıcı, eklenen yapıcıyı yerinden eder.

Eklenen adımın koşulu buradan da okunur. Derleyici bir boşluğu doldurur; boşluk
doldurulduğunda ekleme yapılmaz. Bir sınıfın dosyasında ne bulacağımızı bilmek için kaynakta
ne yazdığımıza değil, **ne yazmadığımıza** bakmak gerekir.

#### Özet

- Bir sınıf dosyası ancak yüklendiğinde iş yapar; giriş noktası dört koşulu birden sağlayan
  tek bir imzadır — genel, statik, değer döndürmeyen, tek parametresi dizgi dizisi olan `main`.
- Beş aday sınıfın hepsi sorunsuz derlenir, yalnız **biri** giriş noktası taşır: bu koşullar
  derleyicinin değil, sanal makinenin sorusudur.
- Sınıf düzeyi başlatma programın açılışında değil, sınıfa ilk kez ihtiyaç duyulduğunda ve
  yalnız **bir kez** çalışır; iki örnek için sınıf düzeyi 2, örnek düzeyi 6 adım sayıldı.
- Her iki düzeyde sıra kaynaktaki yazılma sırasıdır: bu örnekte sınıf düzeyinde önce başlatma
  bloğu, örnek düzeyinde önce alan başlatıcı, en sonda yapıcının gövdesi.
- Gövdesi boş bir sınıf sınıf dosyasında bir varsayılan yapıcı taşır; kaynakta iki ad yazılan
  bir numaralandırma **üç alan** ve **beş yöntem** taşır — eklenen şey yalnız adım değil, üyedir.
- Tek parametreli bir yapıcı yazıldığında eklenen parametresiz yapıcı kaybolur: derleyici
  yalnız hiçbiri yazılmamış olan üyeyi ekler.

#### Sonraki Adım

Bu derste bir sınıfın ne zaman ve hangi sırayla çalıştığı ölçüldü, ama sanal makinenin o
sınıfı **nasıl bulduğu** hiç sorulmadı. Ölçümlerde geçen `Depo` ve `Kayit` adları tek
başlarına yazıldı; oysa bir sınıf dosyasındaki her tip adı, önceki derste gördüğümüz gibi,
tam biçimiyle taşınıyordu. Adın kısası ile tamı arasındaki fark nerede kapanıyor — derleme
zamanında mı, yükleme sırasında mı? Sonraki ders bunu ölçer: aynı program içe aktarımla ve
tam nitelikli adla yazıldığında üretilen sınıf dosyaları birbirinden ayırt edilebiliyor mu
ve paket adı bu dosyada ne değiştiriyor?

### Paketler ve İçe Aktarım

Source: https://academia.sh/tr/kurslar/java-temelleri/paketler-ve-ice-aktarim

İçe aktarım bir yükleme yönergesi değil bir kısaltma kuralıdır: aynı program içe aktarımla ve tam nitelikli adla yazıldığında üretilen sınıf dosyaları bayt bayt aynıdır, statik içe aktarım da hiçbir adım eklemez; buna karşılık paket adı dosyayı değiştirir ve iki ad aynı kısaltmaya sığmaz.

Önceki ders bir sınıfın ne zaman ve hangi sırayla çalıştığını ölçtü: giriş noktası dört
koşulu birden sağlayan tek bir imzaydı, sınıf düzeyi başlatma yalnız bir kez ve ilk
kullanımda çalışıyordu ve kaynakta yazılmayan üyeler sınıf dosyasında duruyordu. Ama sanal
makinenin o sınıfı **nasıl bulduğu** hiç sorulmadı.

İlk derste bir ayrıntı geçmişti: sınıf dosyası çağıracağı tipi adıyla taşıyor ve bu ad tam
biçimindeydi. Oysa kaynakta `List` yazmak yetiyor. Kısa ad ile tam ad arasındaki fark bir
yerde kapanmak zorunda — bu ders o yerin neresi olduğunu ölçer. Soru şudur: içe aktarımlı
yazım ile tam nitelikli yazım **ayrı sınıf dosyaları** mı üretiyor, yoksa aynısını mı?

#### Ad Alanı, Tam Nitelikli Ad, İçe Aktarım

Java'da bir sınıfın gerçek adı yazdığımız kısa ad değildir. Sınıflar **paketlere (package)**
konur ve bir sınıfın **tam nitelikli adı (fully qualified name)** paket adı ile kısa adın
birleşimidir. Paket, adların çakışmasını önleyen bir **ad alanıdır (namespace)**: iki ayrı
paketteki iki `Proxy` ayrı sınıflardır, çünkü tam adları ayrıdır.

**İçe aktarım (import)** bu tam adı her seferinde yazmaktan kurtaran bir bildirimdir. Adı
yanıltıcıdır: hiçbir şey "içeri aktarılmaz", hiçbir dosya yüklenmez, hiçbir kod
kopyalanmaz. Bildirim yalnız derleyiciye şunu söyler — bu dosyada `List` yazdığımda
`java.util.List` kastediyorum.

Bazı adlar bu bildirimi hiç gerektirmez. Dilin çekirdek paketindeki sınıflar — `String`,
`Object`, `Math` gibi — her kaynak dosyada kısa adlarıyla yazılabilir; o paket her dosyaya
kendiliğinden açıktır. Kaynakta hiçbir içe aktarım görünmemesi bu yüzden "hiçbir dış sınıf
kullanılmıyor" demek değildir. Kısaltmanın var olması ile bildirimin yazılmış olması ayrı
şeylerdir ve bu ders ikisinin de sınıf dosyasında ne bıraktığını sorar.

JavaScript ve Python müfredatları modül sistemini ve paket yapısını kendi kurslarında kurdu;
oradaki kavramlar burada tekrarlanmaz. Bu dersin sorduğu şey ayrıdır ve ölçülebilir: **ad
çözümlemesi sınıf dosyasında bir iz bırakıyor mu?** Aynı biçimde Programlama Temelleri
kursunda kurulan kapsam ve gölgeleme de tekrarlanmaz; burada ölçülen, bir adın hangi aşamada
tam biçimine kavuştuğudur.

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

Bu derste sayılan şey ne komut ne üyedir: karşılaştırılan şey sınıf dosyasının **ham
baytlarıdır**. Çekirdek bir kaynağı derler, çıkan dosyayı olduğu gibi okur ve iki yardımcı
bilgi verir — dosyanın taşıdığı tam nitelikli ad ile bir yöntem için kaydedilmiş satır
numarası.

- **CZ11** — Karşılaştırılan kaynaklar **aynı satır düzenini** taşır. Sınıf dosyası satır
  numarası kaydeder; düzen sabit tutulmazsa karşılaştırma ad yazımını değil satır düzenini
  ölçer. Bu yüzden ölçülen kaynaklar tek satıra yazılmıştır.
- **CZ12** — Karşılaştırma sınıf dosyasının bütün baytları üzerinde yapılır; hiçbir alan
  dışarıda bırakılmaz.
- **CZ13** — Derlenmeyen kaynak "derlenmedi" olarak yazılır; derleyicinin ürettiği hata metni
  basılmaz, çünkü ölçülen şey metnin kendisi değil derlemenin sonucudur.
- **CZ14** — Dosya adı kısa sınıf adından, dizin ise paket adından üretilir; ölçüm hiçbir
  gerçek yolu basmaz.
- **CZ15** — Ölçüm ortama bağlı hiçbir veri okumaz; sayılan tek şey bayt sayısı, satır
  numarası ve derlemenin sonucudur.

```java
// Olcek.java — sınıf dosyasının ham baytları, taşıdığı ad ve kaydettiği satır numarası
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.instruction.LineNumber;
import java.nio.file.*;
import java.util.spi.ToolProvider;

class Olcek {
    static byte[] derle(String kaynak, String sinif) throws Exception {
        Path d = Files.createTempDirectory("olcek");
        Path k = d.resolve(sinif.substring(sinif.lastIndexOf('.') + 1) + ".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 Files.readAllBytes(d.resolve(sinif.replace('.', '/') + ".class"));
    }

    static String ad(byte[] b) { return ClassFile.of().parse(b).thisClass().asInternalName(); }

    static int satir(byte[] b) {
        for (MethodModel m : ClassFile.of().parse(b).methods())
            if (m.methodName().equalsString("uzunluk"))
                for (CodeElement e : m.code().orElseThrow())
                    if (e instanceof LineNumber ln) return ln.line();
        return -1;
    }

    static String dene(String kaynak, String sinif) {
        try { derle(kaynak, sinif); return "derlendi"; }
        catch (Exception e) { return "derlenmedi"; }
    }
}
```

#### İçe Aktarım Sınıf Dosyasında İz Bırakmıyor

Aynı sınıfın dört yazımı derlenir. Birincisi içe aktarım kullanır, ikincisi tam nitelikli ad
yazar, üçüncüsü hiç kullanılmayan bir içe aktarım daha ekler, dördüncüsü birincinin aynısıdır
ama içe aktarımı alt satıra alır. İlk üçü tek satırdır ve bu bilinçlidir — dördüncüsü niçin
gerektiğini birazdan söyleyecek.

```java
// Ana.java — içe aktarım sınıf dosyasında iz bırakıyor mu
import java.util.Arrays;

public class Ana {
    static String kaynak(String onek, String tip) {
        return onek + "class Sayac { static int uzunluk(" + tip + " l) { return l.size(); } }\n";
    }

    public static void main(String[] args) throws Exception {
        byte[] kisa = Olcek.derle(kaynak("import java.util.List; ", "List"), "Sayac");
        byte[] tam = Olcek.derle(kaynak("", "java.util.List"), "Sayac");
        byte[] fazla = Olcek.derle(
                kaynak("import java.util.List; import java.util.ArrayList; ", "List"), "Sayac");
        byte[] altSatir = Olcek.derle("import java.util.List;\n" + kaynak("", "List"), "Sayac");
        System.out.printf("%-24s %4d bayt  satir %d  ad %s%n",
                "ice aktarimli yazim", kisa.length, Olcek.satir(kisa), Olcek.ad(kisa));
        System.out.printf("%-24s %4d bayt  satir %d  ad %s%n",
                "tam nitelikli yazim", tam.length, Olcek.satir(tam), Olcek.ad(tam));
        System.out.printf("%-24s %4d bayt  satir %d%n",
                "kullanilmayan ek ile", fazla.length, Olcek.satir(fazla));
        System.out.printf("%-24s %4d bayt  satir %d%n",
                "ice aktarim alt satirda", altSatir.length, Olcek.satir(altSatir));
        System.out.println("ilk ikisi bayt bayt ayni  : " + Arrays.equals(kisa, tam));
        System.out.println("birinci ile ucuncu ayni   : " + Arrays.equals(kisa, fazla));
        System.out.println("birinci ile dorduncu ayni : " + Arrays.equals(kisa, altSatir));
    }
}
```

```
ice aktarimli yazim       304 bayt  satir 1  ad Sayac
tam nitelikli yazim       304 bayt  satir 1  ad Sayac
kullanilmayan ek ile      304 bayt  satir 1
ice aktarim alt satirda   304 bayt  satir 2
ilk ikisi bayt bayt ayni  : true
birinci ile ucuncu ayni   : true
birinci ile dorduncu ayni : false
```

İlk iki dosya **bayt bayt aynıdır**. Kaynaklardan biri `List`, öbürü `java.util.List` yazıyor
ve ikisi tek bir bayt farkla bile ayrılmıyor. Bunun anlamı kesindir: **içe aktarım sınıf
dosyasında hiçbir iz bırakmaz.** Sınıf dosyası zaten tam nitelikli adı taşır — kısa yazım,
derleyicinin o tam adı bulmasını sağlayan bir kuraldan ibarettir. Ad çözümlemesi derleme
zamanında biter; çalışma zamanına içe aktarım diye bir şey ulaşmaz. Aynı sonucun tersi de
doğrudur: tam nitelikli yazmak bir ek yük getirmez, çünkü kısaltılan şey yalnız kaynak
metindir.

Üçüncü satır aynı sonucu bir kez daha veriyor. Hiç kullanılmayan bir içe aktarım eklendiğinde
dosya yine aynıdır. Kullanılmayan bir bildirim bir ağırlık değildir, çünkü kullanılan
bildirim de bir ağırlık değildir: ikisinin de karşılığı sıfır adımdır.

Dördüncü satır ölçümün kendi denetimidir. İçe aktarım alt satıra alındığında dosya **aynı boyda**
kalıyor ama artık bayt bayt aynı değil ve ayrımın nerede olduğu görünüyor: kaydedilen satır
numarası **1** yerine **2**. Yani sınıf dosyası kaynağın her şeyine kayıtsız değildir — satır
düzenini kaydeder. "İz bırakmıyor" iddiası bu yüzden gevşek bir iddia olamaz; ancak düzen
sabit tutulduğunda ölçülen şey ad yazımı olur. Karşı örneği olmayan bir eşitlik iddiası neyi
ölçtüğünü söylemiş sayılmaz.

#### Paket Adı Dosyayı Değiştirir

İçe aktarım iz bırakmıyorsa, paket bildirimi de bırakmaz mı? İkisi de dosyanın başında duran
birer satırdır. Ölçüm ayrı yanıt verir.

```java
// Paket.java — paket adı sınıf dosyasını değiştiriyor mu
public class Paket {
    static final String GOVDE =
            "class Sayac { static int uzunluk(java.util.List l) { return l.size(); } }\n";

    public static void main(String[] args) throws Exception {
        byte[] paketsiz = Olcek.derle(GOVDE, "Sayac");
        byte[] paketli = Olcek.derle("package depo; " + GOVDE, "depo.Sayac");
        System.out.println("paketsiz: " + paketsiz.length + " bayt, ad " + Olcek.ad(paketsiz));
        System.out.println("paketli : " + paketli.length + " bayt, ad " + Olcek.ad(paketli));
    }
}
```

```
paketsiz: 304 bayt, ad Sayac
paketli : 309 bayt, ad depo/Sayac
```

Paket bildirimi eklendiğinde dosya **beş bayt** büyüyor ve taşıdığı ad `Sayac` yerine
`depo/Sayac` oluyor. Eklenen önek tam olarak beş karakterdir. Sınıf dosyası adı bir metin
olarak taşıdığına göre büyüme de o metnin büyümesidir.

Ayrım şuradan gelir: **içe aktarım kaynağı yazan tarafın kolaylığıdır, paket ise sınıfın
kimliğidir.** Birincisi bir dosyada geçerlidir ve dosyayla biter; ikincisi sınıfın adının
parçasıdır ve onunla birlikte her yere gider. Aynı gövdeden iki ayrı sınıf çıkmıştır, çünkü
tam nitelikli adları ayrıdır.

Ölçümün kendisi ikinci bir şeyi daha kanıtlıyor. Çekirdek, derlenmiş dosyayı ararken yolu
tam nitelikli addaki noktaları bölü işaretine çevirerek kuruyor; `depo.Sayac` ölçümü
başarıyla okunduğuna göre derleyici o dosyayı bir `depo` dizininin altına yazmıştır. Paket bir
isimlendirme sözleşmesi değildir: hem adın parçasıdır hem de sınıfın nerede aranacağını
belirler. Kaynakta tek bir sözcük olarak duran bildirim, dosya düzeninde bir dizin olarak
karşılık bulur.

#### Sınırlayıcı Ölçüm: Sıfır Adım, Sıfır Kural Değil

Kısaltmanın bir biçimi daha var. **Statik içe aktarım (static import)** bir sınıfın statik
üyesini de kısa adıyla yazdırır: `Math.max` yerine yalnız `max`. Bu da sıfır adım mı ekliyor
ve kısaltmanın hiç mi bedeli yok?

```java
// Statik.java — statik içe aktarım ve ad çakışması
import java.util.Arrays;

public class Statik {
    static String kaynak(String onek, String cagri) {
        return onek + "class Enb { static int enb(int a, int b) { return " + cagri + "(a, b); } }\n";
    }

    public static void main(String[] args) throws Exception {
        byte[] tam = Olcek.derle(kaynak("", "Math.max"), "Enb");
        byte[] statik = Olcek.derle(kaynak("import static java.lang.Math.max; ", "max"), "Enb");
        System.out.println("Math.max yazimi      : " + tam.length + " bayt");
        System.out.println("statik ice aktarimli : " + statik.length + " bayt");
        System.out.println("bayt bayt ayni       : " + Arrays.equals(tam, statik));
    }
}
```

```
Math.max yazimi      : 274 bayt
statik ice aktarimli : 274 bayt
bayt bayt ayni       : true
```

Statik içe aktarım da hiçbir adım eklemez; iki dosya bayt bayt aynıdır. Yöntemin sahibi olan
sınıfın adı çağrının içinde zaten yazılıdır, kısa yazım o adı yalnız kaynak metinden siler.

Buraya kadar her ölçüm sıfır verdi. Kısaltmanın hiç mi bedeli yok? Bedel çalışma zamanında
değil, derleme zamanında ödenir ve gerçektir. Aşağıdaki beş durum aynı iki sınıfı — iki ayrı
pakette bulunan, kısa adları aynı iki sınıfı — beş ayrı biçimde yazar.

```java
// Cak.java — ad çakışması hangi durumda derleme hatasıdır
public class Cak {
    public static void main(String[] args) {
        String[][] durum = {
            {"iki tam ad, kullanilmasa da",
             "import java.net.Proxy; import java.lang.reflect.Proxy; class Cakisma { }"},
            {"iki toplu ad, kullanilmadan",
             "import java.net.*; import java.lang.reflect.*; class Cakisma { }"},
            {"iki toplu ad, kullanilinca",
             "import java.net.*; import java.lang.reflect.*; class Cakisma { Proxy p; }"},
            {"tam ad + toplu ad, kullanilinca",
             "import java.net.Proxy; import java.lang.reflect.*; class Cakisma { Proxy p; }"},
            {"ice aktarimsiz, iki tam adla",
             "class Cakisma { java.net.Proxy a; java.lang.reflect.Proxy b; }"},
        };
        for (String[] d : durum)
            System.out.printf("%-32s %s%n", d[0], Olcek.dene(d[1], "Cakisma"));
    }
}
```

```
iki tam ad, kullanilmasa da      derlenmedi
iki toplu ad, kullanilmadan      derlendi
iki toplu ad, kullanilinca       derlenmedi
tam ad + toplu ad, kullanilinca  derlendi
ice aktarimsiz, iki tam adla     derlendi
```

Beş satır ad çözümlemesinin kuralını baştan sona veriyor. Birinci satır: iki sınıfı adlarıyla
tek tek içe aktarmak, o adlar **hiç kullanılmasa bile** derleme hatasıdır — kısaltma tablosu
kaynağın kendisinden önce kurulur ve bir kısa ad iki tam adı gösteremez. İkinci ile üçüncü
satır bunun karşıtını gösteriyor: paketin tamamını toplu biçimde içe aktarmak çakışmayı tek
başına doğurmuyor, hata ancak belirsiz ad **kullanıldığında** çıkıyor. Toplu içe aktarım bir
karar değil, bir arama listesidir.

Dördüncü satır sıralamayı söylüyor: adıyla yazılmış bir içe aktarım, toplu içe aktarımdan
**öncelikli** olduğu için belirsizlik doğmuyor. Beşinci satır ise sınırı çiziyor — iki sınıf
tam adlarıyla yazıldığında yan yana sorunsuz kullanılabiliyor. Yani ikisini birlikte kullanmak
yasak değildir, ikisini **aynı kısaltmaya sığdırmak** yasaktır.

Kursun ölçüsü buradan bir kez daha okunur. İlk derste eklenen adımın **olmadığı** bir durum
sabit katlamayla gösterilmişti; burada eklenen adımın olmadığı üç durum daha var ve üçünde de
bedeli ödeyen taraf derleyicidir. Bir biçimin çalışma zamanında hiçbir şeye mal olmaması, o
biçimin kuralsız olduğu anlamına gelmez.

#### Özet

- Bir sınıfın gerçek adı tam nitelikli adıdır; paket bir ad alanıdır ve sınıf dosyası bu adı
  metin olarak taşır.
- İçe aktarımlı yazım ile tam nitelikli yazımın ürettiği sınıf dosyaları **bayt bayt
  aynıdır**; kullanılmayan bir içe aktarım da dosyayı değiştirmez. Ad çözümlemesi derleme
  zamanında biter.
- Sınıf dosyası kaynağın satır düzenini kaydeder: içe aktarım alt satıra alındığında dosya
  aynı boyda kalır ama artık aynı değildir, çünkü kayıtlı satır numarası 1 yerine 2 olur.
- Paket bildirimi dosyayı **değiştirir**: taşınan ad `Sayac` yerine `depo/Sayac` olur ve dosya
  eklenen önek kadar, beş bayt büyür.
- Statik içe aktarım da sıfır adım ekler; buna karşılık aynı kısa ada sahip iki sınıfı adıyla
  içe aktarmak, o adlar hiç kullanılmasa bile derleme hatasıdır. Toplu içe aktarımda hata
  ancak belirsiz ad kullanıldığında çıkar ve adıyla yazılmış içe aktarım önceliklidir.
- Kısaltmanın çalışma zamanındaki bedeli sıfırdır, derleme zamanındaki kuralı gerçektir: bir
  dosyada bir kısa ad tek bir tam adı gösterebilir, ama iki sınıf tam adlarıyla yan yana
  kullanılabilir.

#### Sonraki Adım

Bu konu boyunca ölçülen şey hep **derleyicinin ne eklediği** oldu: bir adım, bir üye, bir ad.
Üçünde de karar derleme zamanında verildi ve çalışma zamanına yalnız sonucu ulaştı — hangi
sınıfın kastedildiği, sınıf dosyası yazılırken çoktan bellidir. Ad çözümlemesi derleme
zamanında bitiyorsa, **değerin kendisi** ne zaman belirleniyor? Sıradaki konu bunu ölçmeye
ilkel tiple sarmalayıcı arasındaki geçişten başlar: bir tam sayıyı bir sarmalayıcıya atamak
kaynakta tek bir eşittir işaretidir, sınıf dosyasında ise bir çağrı ve üretilen bir nesnedir.

## Temel Sözdizim

İlkel tiplerle sarmalayıcıların arasındaki otomatik kutulama, değişken türlerinin kapsam ve ömrü, işleçlerin sayısal yükseltmesi, denetim akışı biçimleri, dizilerin sabit boyutlu düzeni, dizgilerin değiştirilemezliği ve argüman aktarımının tek modeli.

### İlkel Tipler ve Sarmalayıcılar

Source: https://academia.sh/tr/kurslar/java-temelleri/ilkel-tipler-ve-sarmalayicilar

Bir harflik kaynak farkı sınıf dosyasında bir yöntem çağrısıdır: `Integer adet = 5` satırı `Integer.valueOf`, ters yönü `Integer.intValue` ekler; aynı 200 kutulama depoya göre 100 ile 200 arasında nesne üretir ve önbelleğin bittiği yerde kimlik ile değer ayrılır.

Önceki ders ad çözümlemesinin derleme zamanında bittiğini ölçtü: içe aktarım satırı sınıf
dosyasında hiçbir iz bırakmıyordu, çünkü derleyici adı çözüp yerine tam nitelikli karşılığını
koyuyordu. Geriye bir soru kaldı. Ad çözümlemesi derleme zamanında bitiyorsa, adın taşıdığı
**değerin kendisi** ne zaman ve nerede belirleniyor.

Java bu soruya tek yanıt vermez. `int adet = 5` yazıldığında değer yöntemin kendi yerel
alanında, sayının kendisi olarak durur. `Integer adet = 5` yazıldığında öbekte bir nesne
bulunur ve yerel alanda yalnız o nesnenin referansı durur. Kaynaktaki fark tek harftir;
sınıf dosyasındaki fark bir **yöntem çağrısıdır**. Programlama Temelleri kursu temel veri
tiplerini ve örtük dönüşümü kavram olarak kurmuştu — **bu tekrarlanmaz**. Orada bir değerin
tipinin ne belirlediği anlatıldı; burada ölçülen şey, derleyicinin bu geçiş için sınıf
dosyasına **kaç adım** koyduğudur.

#### İki Tip Ailesi

Java'nın tipleri ikiye ayrılır. **İlkel tipler** sekiz tanedir ve sayıdan başka bir şey
taşımazlar; **referans tipleri** ise nesnelerin adresini taşır. Her ilkel tipin bir
**sarmalayıcı** sınıfı vardır: aynı değeri bir nesnenin içinde tutan referans tipi.

| İlkel tip | Genişlik (bit) | Sarmalayıcı |
|---|---|---|
| `boolean` | belirtilmemiş | `Boolean` |
| `byte` | 8 | `Byte` |
| `short` | 16 | `Short` |
| `char` | 16 | `Character` |
| `int` | 32 | `Integer` |
| `long` | 64 | `Long` |
| `float` | 32 | `Float` |
| `double` | 64 | `Double` |

Ayrımın kaynaktaki görünürlüğü düşüktür, çünkü iki aile arasındaki geçiş yazılmaz. Bir
`int` değeri bir `Integer` bekleyen yere kendiliğinden girer (**otomatik kutulama**), bir
`Integer` de `int` bekleyen yere kendiliğinden girer (**kutudan çıkarma**). İki yönde de
kaynakta yalnız bir eşittir işareti görünür.

Ayrımın var olma nedeni de buradadır. Bir ilkel tip nesne değildir: yöntemi yoktur, `null`
olamaz ve **referans bekleyen hiçbir yere giremez**. Bir liste, bir eşleme ya da tip
değişkeni alan herhangi bir yapı yalnız referans saklar; bir `int` değerini listeye koymak
için önce onu saracak bir nesne gerekir. Sarmalayıcı bu boşluğu doldurur ve otomatik
kutulama, o dolduruşu kaynaktan **silen** bir kısaltmadır. Sonuç şudur: `List<Integer>`
yazan bir kaynak, `int` yazan bir kaynağın yaptığı işi yapmaz — arada bir nesne katmanı
vardır ve o katmanın bedeli sınıf dosyasında durur.

Kurs boyunca tek bir veri taşıyıcı kullanılır: bir depo kaydı olan `Kayit`, üç alanlı —
`String ad`, `int adet`, `long agirlik` (gram). Bu ders kutulamayı **`adet` alanı**
üzerinde ölçer.

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

Ölçüm, verilen bir kaynak metnini derleyiciyi program içinden çağırarak derler, ürettiği
sınıf dosyasını okur ve her yöntem için iki şey döndürür: kaç komut üretildiği ve o
yöntemde hangi çağrıların bulunduğu. Derleyici ve sınıf dosyası okuyucusu standart
kitaplığın parçasıdır.

- **SD1** — Kâhin kurgunun kendisidir: ölçülen kaynağı biz yazdığımız için ne yazdığımızı
  biliriz. Sağ sütun sınıf dosyasındaki **bütün** çağrıları listeler; kaynakta yazılmış
  olanı okuyucu düşer, kalan **derleyicinin eklediğidir**.
- **SD2** — Ölçülen kaynak metni sabittir ve her koşumda aynı derleyiciye verilir; sayılar
  tek bir derleyici koşumundan gelir ve o koşumda belirlenimcidir.
- **SD3** — Yapıcı (`<init>`) tabloya alınmaz; kaynakta yazılmayan üyeler ayrı bir konudur.

```java
// Olcek.java — verilen kaynağı derler, sınıf dosyasındaki komutları ve çağrıları okur
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 Yontem(String ad, int komut, List<String> eklenen) {}

    static List<Yontem> oku(String kaynak, String sinif) throws Exception {
        Path d = Files.createTempDirectory("olcek");
        Files.writeString(d.resolve(sinif + ".java"), kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow().run(bos, bos, "-d", d.toString(),
                d.resolve(sinif + ".java").toString()) != 0)
            throw new IllegalStateException("derlenmedi: " + sinif);
        List<Yontem> yontemler = new ArrayList<>();
        for (MethodModel m : ClassFile.of().parse(d.resolve(sinif + ".class")).methods()) {
            if (m.code().isEmpty()) continue;
            List<String> eklenen = new ArrayList<>();
            int komut = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) komut++;
                if (e instanceof InvokeInstruction iv)
                    eklenen.add(kisa(iv.owner().asInternalName()) + "." + iv.name().stringValue());
                else if (e instanceof TypeCheckInstruction t && t.opcode() == Opcode.CHECKCAST)
                    eklenen.add("tip denetimi " + kisa(t.type().asInternalName()));
            }
            yontemler.add(new Yontem(m.methodName().stringValue(), komut, eklenen));
        }
        return yontemler;
    }

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

#### Atamanın Eklediği Çağrı

Ölçülen kaynak beş yöntem taşır. `kutula` bir `int` değerini `Integer` adına bağlar,
`kutuAc` tersini yapar. `diziToplami` ile `listeToplami` **aynı döngüyü** yazar; tek fark
gezilen şeyin `int[]` mi yoksa `List<Integer>` mi olduğudur. `say` bir sayacı `adet`
değeriyle günceller.

```java
// Kutulama.java — ilkel tip ile sarmalayıcı arasındaki geçişin sınıf dosyasındaki izi
public class Kutulama {
    static final String KAYNAK = """
        import java.util.List;
        import java.util.Map;
        class Depo {
            static Object kutula(int adet) { Integer k = adet; return k; }
            static int kutuAc(Integer k) { int adet = k; return adet; }
            static int diziToplami(int[] adetler) { int t = 0; for (int x : adetler) t += x; return t; }
            static int listeToplami(List<Integer> adetler) { int t = 0; for (int x : adetler) t += x; return t; }
            static void say(Map<String, Integer> sayac, String ad, int adet) { sayac.put(ad, adet); }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-14s %6s  %s%n", "yontem", "komut", "sinif dosyasindaki cagri");
        int toplam = 0;
        for (Olcek.Yontem y : Olcek.oku(KAYNAK, "Depo")) {
            if (y.ad().equals("<init>")) continue;
            System.out.printf("%-14s %6d  %s%n", y.ad(), y.komut(),
                    y.eklenen().isEmpty() ? "-" : String.join(", ", y.eklenen()));
            toplam += y.eklenen().size();
        }
        System.out.println("toplam: " + toplam);
    }
}
```

```
yontem          komut  sinif dosyasindaki cagri
kutula              5  Integer.valueOf
kutuAc              5  Integer.intValue
diziToplami        24  -
listeToplami       20  List.iterator, Iterator.hasNext, Iterator.next, tip denetimi Integer, Integer.intValue
say                 7  Integer.valueOf, Map.put
toplam: 9
```

`kutula` kaynağında hiçbir çağrı yazmaz; sınıf dosyasında **`Integer.valueOf`** vardır.
`kutuAc` de hiçbir çağrı yazmaz; sınıf dosyasında **`Integer.intValue`** vardır. Otomatik
kutulama bir dil kolaylığı değil, kaynakta görünmeyen bir **yöntem çağrısıdır** ve tersi de
öyledir. `say` satırı SD1'in nasıl okunacağını gösterir: `Map.put` kaynakta yazılıdır ve
düşülür, `Integer.valueOf` yazılı değildir ve kalır.

Alt iki satır kursun ikinci iddiasını ödüyor. `diziToplami` **24** komut üretiyor ve
kaynakta yazılmayan **hiçbir** adım taşımıyor: gelişmiş `for` bir dizide sayaçlı bir
döngüye çevriliyor ve toplama doğrudan sayılar üzerinde yapılıyor. `listeToplami`
**20** komut üretiyor — daha az — ama kaynakta yazılmayan **beş** adım taşıyor: yineleyici
alınıyor, iki kez sorgulanıyor, dönen nesne `Integer`'a denetleniyor ve `intValue` ile
sayıya açılıyor. **Daha az komut, daha çok eklenen adım.** Komut sayısı ile eklenen adım
sayısı aynı yöne gitmez; hangisinin sayıldığı söylenmeden bir ölçüm karşılaştırılamaz.

Bu beş adımın dördü döngü gövdesinin içindedir, yani öğe başına ödenir. Sınıf dosyası bunu
göstermez; gösterdiği şey adımın **varlığıdır**. Kaç kez ödendiğini görmek için koşmak
gerekir.

#### Aynı Sayıda Kutulama, İki Ayrı Nesne Sayısı

`Integer.valueOf` her çağrıldığında yeni bir nesne üretmez. Belirtim, `int` değerleri için
**-128 ile 127 arasındaki** her değerin kutulamasında **aynı nesnenin** döndürülmesini
zorunlu kılar; bu aralığın dışı gerçekleştirime bırakılmıştır. Bunun ölçülebilir sonucu
şudur: aynı sayıda kutulama, değerlerin nereye düştüğüne göre ayrı sayıda nesne üretir.

- **SD4** — Üç depo da **100** kayıt taşır ve her kaydın `adet` değeri ayrıdır; tek fark
  değerlerin başladığı tabandır. Her depoda her `adet` **iki kez** kutulanır, yani üç ölçüm
  de **200** kutulama çağrısı yapar.
- **SD5** — Ayrı nesne sayısı, üretilen sarmalayıcılar kimliklerine göre bir eşleme içinde
  toplanarak bulunur; hiçbir nesnenin kimlik değeri basılmaz, yalnız **kaç ayrı nesne**
  olduğu sayılır.

```java
// Uretim.java — aynı sayıda kutulama, iki ayrı nesne sayısı
import java.util.IdentityHashMap;
import java.util.Map;

public class Uretim {
    record Kayit(String ad, int adet, long agirlik) {}

    static Kayit[] depo(int n, int taban) {
        Kayit[] d = new Kayit[n];
        for (int i = 0; i < n; i++) d[i] = new Kayit("K" + i, taban + i, 1000L + i);
        return d;
    }

    static int ayriNesne(Kayit[] depo) {
        Map<Integer, Boolean> kimlik = new IdentityHashMap<>();
        for (int tur = 0; tur < 2; tur++)
            for (Kayit k : depo) kimlik.put(k.adet(), Boolean.TRUE);   // her put bir valueOf
        return kimlik.size();
    }

    public static void main(String[] args) {
        System.out.printf("%-22s %8s %8s %10s%n", "depo", "kutulama", "deger", "ayri nesne");
        for (int taban : new int[] {1, 100, 1000}) {
            Kayit[] d = depo(100, taban);
            System.out.printf("adet %4d..%-4d %8d %8d %10d%n",
                    taban, taban + 99, 2 * d.length, d.length, ayriNesne(d));
        }
        Kayit[] buyuk = depo(100, 1000);
        long t = 0;
        for (Kayit k : buyuk) t += k.adet();
        System.out.println("ilkel toplama: " + t + " | uretilen sarmalayici: 0");
    }
}
```

```
depo                   kutulama    deger ayri nesne
adet    1..100       200      100        100
adet  100..199       200      100        172
adet 1000..1099      200      100        200
ilkel toplama: 104950 | uretilen sarmalayici: 0
```

Üç satırda da aynı kod çalışıyor, aynı sayıda kutulama yapılıyor ve aynı sayıda ayrı değer
kutulanıyor. Üretilen nesne sayısı **100**, **172** ve **200**. Kaynakta bu farkı üreten
hiçbir şey yoktur; fark tümüyle eklenen adımın, yani `Integer.valueOf`'un kararıdır.

Orta satır sınırı doğrudan gösteriyor. `adet` değerleri 100 ile 199 arasında dağıldığında
128'in altındaki 28 değer önbellekten dönüyor ve her biri için **tek** nesne oluşuyor;
kalan 72 değer iki turda **iki ayrı** nesne üretiyor. Toplam 28 artı 144, yani **172**.
Sayının kendisi bir eşiği işaretliyor.

Son satır ölçünün diğer ucudur. Aynı depo `int` üzerinden toplandığında hiç sarmalayıcı
üretilmez: `t += k.adet()` satırında kutulama yoktur, çünkü `long` da `int` de ilkel
tiptir. Bir döngüde sarmalayıcı kullanmanın bedeli, o döngünün her adımında bir nesne
üretilmesi olabilir — ve bu, kaynakta hiçbir yerde yazmaz.

#### Kimlik ile Değerin Ayrıldığı Yer

Nesne üretimindeki bu fark yalnız sayıda kalmaz; **görünen bir sonucu** vardır. İki
referans tipi `==` ile karşılaştırıldığında değerleri değil, **aynı nesne olup olmadıkları**
sınanır. Önbellek aralığında iki kutulama aynı nesneyi verdiği için `==` doğru sonuç verir
gibi görünür; aralığın dışında aynı kod yanlış sonuç verir.

```java
// Onbellek.java — sarmalayıcı kimliğinin ve değerinin ayrıldığı yer
public class Onbellek {
    record Kayit(String ad, int adet, long agirlik) {}

    static Kayit kimlikleAra(Kayit[] depo, Integer aranan) {
        for (Kayit k : depo) {
            Integer adet = k.adet();            // kutulama: Integer.valueOf
            if (adet == aranan) return k;       // iki sarmalayici: kimlik karsilastirmasi
        }
        return null;
    }

    static Kayit degerleAra(Kayit[] depo, Integer aranan) {
        for (Kayit k : depo) {
            Integer adet = k.adet();
            if (adet.equals(aranan)) return k;  // deger karsilastirmasi
        }
        return null;
    }

    public static void main(String[] args) {
        Kayit[] depo = { new Kayit("vida", 127, 40L), new Kayit("civata", 128, 90L) };
        System.out.printf("%-10s %-12s %-12s%n", "aranan", "kimlikle", "degerle");
        for (Integer aranan : new Integer[] {127, 128}) {
            Kayit a = kimlikleAra(depo, aranan), b = degerleAra(depo, aranan);
            System.out.printf("adet %-5d %-12s %-12s%n", aranan,
                    a == null ? "bulunamadi" : a.ad(), b == null ? "bulunamadi" : b.ad());
        }
        System.out.println();
        Integer a = 127, b = 127, c = 128, d = 128;
        System.out.println("Integer 127 == 127 : " + (a == b) + " | equals: " + a.equals(b));
        System.out.println("Integer 128 == 128 : " + (c == d) + " | equals: " + c.equals(d));
        Long e = 127L, f = 127L;
        Double g = 1.0, h = 1.0;
        System.out.println("Long    127 == 127 : " + (e == f) + " | equals: " + e.equals(f));
        System.out.println("Double  1.0 == 1.0 : " + (g == h) + " | equals: " + g.equals(h));
    }
}
```

```
aranan     kimlikle     degerle
adet 127   vida         vida
adet 128   bulunamadi   civata

Integer 127 == 127 : true | equals: true
Integer 128 == 128 : false | equals: true
Long    127 == 127 : true | equals: true
Double  1.0 == 1.0 : false | equals: true
```

`kimlikleAra` 127 adetli kaydı buluyor, 128 adetli kaydı **bulamıyor**. Kaynakta iki arama
arasında hiçbir fark yoktur; aranan değer bir arttığı için sonuç değişmiştir. Bu, kursun
dördüncü okumasının en açık örneğidir: **kararı programcı değil, eklenen adım vermiştir.**
Aynı kod küçük depolarda doğru, büyük depolarda yanlış çalışır ve derleyici uyarı üretmez.

Alt tablo sınırı çiziyor. `Integer` ve `Long` için 127 kimlik olarak eşit, 128 değil;
`Double` için 1.0 bile eşit değil, çünkü kesirli sarmalayıcılarda önbellek yoktur. `equals`
sütunu **dört satırda da doğru**: değer karşılaştırması kutulamanın hangi nesneyi
döndürdüğünden etkilenmez. Kural buradan çıkar — sarmalayıcılar `==` ile değil, `equals`
ile karşılaştırılır; `==` yalnız ilkel tiplerde değer karşılaştırmasıdır.

Aynı gözlem sınırlayıcı ölçümü de verir: bu dersin ölçtüğü eklenen adımın, kimlik üzerinde
hiçbir etkisinin **olmadığı** bir aralık vardır. -128 ile 127 arasında kutulama yeni nesne
üretmez, `==` beklendiği gibi davranır ve nesne sayısı değer sayısına eşittir. Eklenen
adımın görünürlüğü, değerin hangi aralığa düştüğüne bağlıdır.

#### Özet

- İlkel tip ile sarmalayıcı arasındaki geçiş kaynakta bir eşittir işaretidir, sınıf
  dosyasında bir çağrıdır: kutulama **`Integer.valueOf`**, kutudan çıkarma
  **`Integer.intValue`** ekler.
- Aynı döngü dizide **24** komut ve **0** eklenen adım, `List<Integer>` üzerinde **20**
  komut ve **5** eklenen adım üretir; daha az komut, daha çok eklenen adım.
- `Integer.valueOf` her çağrıda yeni nesne üretmez: aynı 200 kutulama, değerlerin
  aralığına göre **100**, **172** ya da **200** ayrı nesne bırakır.
- Belirtim -128 ile 127 arasında aynı nesnenin döndürülmesini zorunlu kılar; bu aralıkta
  `==` doğru görünür, dışında aynı kod yanlış sonuç verir ve derleyici uyarı üretmez.
- Sarmalayıcılar `equals` ile karşılaştırılır; `equals` dört ölçümün dördünde de doğru
  sonuç verdi, `==` yalnız ilkel tiplerde değer karşılaştırmasıdır.

#### Sonraki Adım

Bu ders bir değerin hangi biçimde tutulduğunu ölçtü: sayının kendisi mi, yoksa sayıyı
saran bir nesne mi. Geriye adın kendisi kaldı. Sıradaki ders aynı sayıda adı üç ayrı yere
yazar — bir yöntemin içine, bir örneğe ve sınıfın kendisine — ve sınıf dosyasında bu üç
adın nereye düştüğünü sayar: hangisi bir **yuvaya**, hangisi bir **alana** yazılıyor,
bir bloğun içinde açılan ad neden sınıf dosyasında hiç ad olarak görünmüyor ve iç blokta
aynı adı yeniden bildirmek neden derleme hatası veriyor.

### Değişkenler ve Kapsam

Source: https://academia.sh/tr/kurslar/java-temelleri/degiskenler-ve-kapsam

Aynı `int adet` bildirimi bulunduğu yere göre üç ayrı şeye derlenir: sınıf gövdesinde bir alana, yöntem gövdesinde bir yuvaya, statik olarak sınıfın tek kopyalı alanına; iki kardeş blokta aynı adı kullanan yöntem, aynı 20 komutu bir eksik yuvayla üretir.

Önceki ders bir değerin hangi biçimde tutulduğunu ölçtü: sayının kendisi mi, yoksa sayıyı
saran bir nesne mi. Fark kaynakta tek harfti, sınıf dosyasında bir çağrıydı. Bu ders aynı
soruyu adın kendisine sorar. `int adet = 5` satırı, yazıldığı yere göre üç ayrı şeye
derlenir; kaynakta üçünün de yazımı **aynıdır**.

Programlama Temelleri kursu değişkeni, bağlamayı, kapsamı ve yaşam süresini kavram olarak
kurmuştu — **bunlar tekrarlanmaz**. Orada bir adın nerede görünür olduğu anlatıldı. Burada
ölçülen şey, derleyicinin o ada sınıf dosyasında **hangi yeri** verdiğidir: sınıfın
gövdesinde duran bir alan mı, yoksa yöntemin çağrı başına ayrılan yerel alanında bir yuva
mı.

#### Üç Ad Ailesi

Java bir adı üç yerden birine yazar ve seçimi **bildirimin bulunduğu yer** belirler.

- **Yerel değişken** bir yöntemin gövdesinde bildirilir. Sınıf dosyasında ad olarak
  görünmez; yöntemin yerel alanında numaralı bir **yuva** tutar. Yöntem her çağrıldığında
  yeni bir kopyası oluşur ve çağrı bittiğinde kaybolur. Parametreler de yerel değişkendir.
- **Örnek değişkeni** sınıfın gövdesinde, `static` olmadan bildirilir. Sınıf dosyasında bir
  **alan** olarak durur ve her nesnede ayrı bir kopyası bulunur.
- **Sınıf değişkeni** sınıfın gövdesinde `static` ile bildirilir. O da bir alandır, ama
  kopyası **tektir**: nesne sayısından bağımsız olarak sınıfa aittir.

Ayrım kaynakta bir anahtar sözcüğe ve bir girintiye bağlıdır; sınıf dosyasında ise iki ayrı
yapıya. Alanlar sınıf dosyasının alan çizelgesinde adlarıyla durur. Yerel değişkenler
orada hiç yoktur — yöntemin kodu adları değil, **numaraları** kullanır. Bu ders üç ailenin
ayrımını, kurs boyunca sürdürülen depo kaydının `adet` alanı üzerinde ölçer.

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

Çekirdek önceki dersin çekirdeğidir, iki ölçüm eklenmiştir: her yöntemin kullandığı
**yuva sayısı** ve o yöntemdeki **alan erişimleri**. Sınıfın alan çizelgesi de okunur.
Derleme başarısızsa sınıf dosyası üretilmez ve çekirdek bunu ayrıca bildirir.

- **SD6** — Yuva sayısı, sınıf dosyasının o yöntem için ayırdığı yerel alan
  büyüklüğüdür — kaynakta yazılan ad sayısı değil. Örnek yöntemlerinde **sıfır numaralı
  yuva `this`'e** ayrılmıştır; statik yöntemlerde ilk parametreden başlar.
- **SD7** — Alan erişimi sütunu komut adını taşır: `getfield` ve `putfield` bir örneğin
  alanına, `getstatic` ve `putstatic` sınıfın alanına erişir.

```java
// Olcek.java — adın sınıf dosyasında nereye yazıldığını okur: alana mı, yuvaya mı
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.attribute.CodeAttribute;
import java.lang.classfile.instruction.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Olcek {
    record Yontem(String ad, int komut, int yuva, List<String> alanErisimi) {}
    record Sinif(List<String> alan, List<Yontem> yontem) {}

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

    static Sinif oku(String kaynak, String sinif) throws Exception {
        Path c = derle(kaynak, sinif);
        if (c == null) throw new IllegalStateException("derlenmedi: " + sinif);
        ClassModel cm = ClassFile.of().parse(c);
        List<String> alanlar = new ArrayList<>();
        for (FieldModel f : cm.fields()) alanlar.add(f.fieldName().stringValue());
        List<Yontem> yontemler = new ArrayList<>();
        for (MethodModel m : cm.methods()) {
            if (m.code().isEmpty()) continue;
            CodeAttribute kod = (CodeAttribute) m.code().get();
            List<String> erisim = new ArrayList<>();
            int komut = 0;
            for (CodeElement e : kod) {
                if (e instanceof Instruction) komut++;
                if (e instanceof FieldInstruction fi)
                    erisim.add(fi.opcode().name().toLowerCase(Locale.ROOT)
                            + " " + fi.name().stringValue());
            }
            yontemler.add(new Yontem(m.methodName().stringValue(), komut,
                    kod.maxLocals(), erisim));
        }
        return new Sinif(alanlar, yontemler);
    }
}
```

#### Sekiz Ad, Üç Ayrı Yer

Ölçülen sınıf `toplamAdet` ve `adet` adlarını sınıf gövdesinde, `gelen`, `yeni`, `n`, `t`,
`gecici` ve `ikinci` adlarını yöntem gövdelerinde bildirir. `blokKapsami` ile `bloksuz`
**aynı işi aynı sırayla** yapar; aralarındaki tek fark, iki geçici adın küme parantezi
içinde açılıp açılmadığıdır.

```java
// Yer.java — dört ad, üç ayrı yer
public class Yer {
    static final String KAYNAK = """
        class Depo {
            static int toplamAdet = 0;
            int adet;
            void ekle(int gelen) {
                int yeni = adet + gelen;
                adet = yeni;
                toplamAdet += gelen;
            }
            static int blokKapsami(int n) {
                int t = 0;
                { int gecici = n * 2; t += gecici; }
                { int ikinci = n * 3; t += ikinci; }
                return t;
            }
            static int bloksuz(int n) {
                int t = 0;
                int gecici = n * 2; t += gecici;
                int ikinci = n * 3; t += ikinci;
                return t;
            }
            static long genis(long agirlik, int ek) {
                long yeni = agirlik + ek;
                return yeni;
            }
            int gorunen(int adet) { return adet; }
            int alanaErisen(int adet) { return this.adet; }
        }
        """;

    public static void main(String[] args) throws Exception {
        Olcek.Sinif s = Olcek.oku(KAYNAK, "Depo");
        System.out.println("sinif dosyasindaki alanlar: " + s.alan());
        System.out.printf("%-14s %6s %5s  %s%n", "yontem", "komut", "yuva", "alan erisimi");
        for (Olcek.Yontem y : s.yontem())
            System.out.printf("%-14s %6d %5d  %s%n", y.ad(), y.komut(), y.yuva(),
                    y.alanErisimi().isEmpty() ? "-" : String.join(", ", y.alanErisimi()));
    }
}
```

```
sinif dosyasindaki alanlar: [toplamAdet, adet]
yontem          komut  yuva  alan erisimi
<init>              3     1  -
ekle               13     3  getfield adet, putfield adet, getstatic toplamAdet, putstatic toplamAdet
blokKapsami        20     3  -
bloksuz            20     4  -
genis               7     5  -
gorunen             2     2  -
alanaErisen         3     2  getfield adet
<clinit>            3     0  putstatic toplamAdet
```

Alan çizelgesinde **iki** ad var: `toplamAdet` ve `adet`. Kaynakta bildirilen sekiz addan
yalnız ikisi sınıf dosyasında ad olarak duruyor. Kalan altısı hiçbir yerde ad değil;
yalnız yuva numarası olarak yaşıyorlar. Bir yerel değişkenin adı, sınıf dosyasına
**girmez** — derleyicinin işi bittiğinde ad tükenmiştir. Derleyiciye ayrıca söylenirse
adlar hata ayıklama bilgisi olarak sınıf dosyasına eklenebilir, ama bu kodun bir parçası
değildir: yöntemin komutları o bilgi olmadan da aynı yuvaları kullanır. Alan adları ise
seçime bağlı değildir, çünkü onlara sınıfın dışından erişilir ve erişen kod adı bilmek
zorundadır.

`ekle` satırı üç aileyi bir arada gösteriyor. Aynı yöntemde `adet` iki kez örnek alanı
komutuyla (`getfield`, `putfield`), `toplamAdet` iki kez sınıf alanı komutuyla
(`getstatic`, `putstatic`) geçiyor; `gelen` ve `yeni` ise hiçbir alan komutuna girmiyor,
çünkü onlar yuvada. Üç yuva sayılıyor: sıfır `this`, bir `gelen`, iki `yeni`.

Ölçümün asıl satırı ortadaki çifttir. `blokKapsami` ile `bloksuz` **aynı 20 komutu**
üretiyor — çalışma zamanında yapılan iş birebir aynı. Yuva sayısı ayrı: **3** ve **4**.
Küme parantezi çalışma zamanında hiçbir şey yapmaz; yaptığı iş derleme zamanında biter.
Derleyici `gecici` adının ilk bloğun sonunda öldüğünü bildiği için `ikinci` adına **aynı
yuvayı** verir. Blok kapsamı, çalışma zamanında var olmayan bir kavramdır: sınıf
dosyasında ondan geriye kalan tek şey **bir eksik yuvadır**.

`genis` satırı yuvanın ad sayısı olmadığını başka bir yönden gösteriyor: üç ad bildiriliyor,
**beş** yuva sayılıyor. Bir yuva sabit genişliktedir ve 64 bitlik değerler ona sığmaz;
`long agirlik` iki yuva, `int ek` bir yuva, `long yeni` yine iki yuva tutuyor. Yerel alanın
büyüklüğü kaç ad yazıldığına değil, o adların **hangi tipte** olduğuna bağlıdır — önceki
dersin tablosundaki genişlik sütununun sınıf dosyasındaki karşılığı budur.

Son iki satır gölgelemeyi ayırıyor. `gorunen` ile `alanaErisen` aynı imzayı taşıyor ve
ikisinde de `adet` adı geçiyor; biri **hiç** alan erişimi yapmıyor, diğeri `getfield`
ediyor. Parametre olarak bildirilen `adet`, alan olan `adet`'i **gölgeliyor**; alana
ulaşmanın tek yolu `this.adet` yazmak. Hangisinin okunacağı derleme zamanında belirlenir
ve sınıf dosyasında iki ayrı komut olarak durur.

Tablonun iki köşesi de anlamlıdır. `<init>` ve `<clinit>` kaynakta yazılmamış yöntemlerdir;
ilki `adet` alanını hazırlayan yapıcı, ikincisi `toplamAdet = 0` atamasını taşıyan sınıf
başlatma bloğudur. İkisi de önceki konuda ölçülmüştü. `<clinit>`'in yuva sayısının **sıfır**
olması burada anlamlıdır: sınıf başlatma bloğunda `this` yoktur, çünkü ortada henüz hiçbir
nesne yoktur.

#### Kapsamın Görünen Sonucu

Yer ayrımının sayı olarak değil, **değer olarak** görünen sonucu vardır. Aynı sınıftan iki
nesne üretildiğinde örnek değişkeni ikiye ayrılır, sınıf değişkeni ayrılmaz.

- **SD8** — İki nesne üretilir ve üçüncü çağrı ikinci nesneye yapılır; sınıf değişkeni
  bütün çağrıların toplamını taşıdığı için beklenen değer 3 artı 4 artı 10'dur.
- **SD9** — Gölgeleme sınaması, alanı önceki çağrıların değiştirdiği bir noktada yapılır;
  böylece parametrenin mi yoksa alanın mı okunduğu değerden ayırt edilebilir.

```java
// Kapsam.java — alanın ve yuvanın görünen sonucu
public class Kapsam {
    static class Depo {
        static int toplamAdet = 0;                 // sinif degiskeni: tek kopya
        int adet;                                  // ornek degiskeni: ornek basina kopya

        void ekle(int gelen) {
            int yeni = adet + gelen;               // yerel degisken: cagri basina kopya
            adet = yeni;
            toplamAdet += gelen;
        }

        int gorunen(int adet) { return adet; }     // parametre alani golgeler
        int alanaErisen(int adet) { return this.adet; }
    }

    public static void main(String[] args) {
        Depo vida = new Depo(), civata = new Depo();
        vida.ekle(3);
        vida.ekle(4);
        civata.ekle(10);
        System.out.println("vida.adet: " + vida.adet + " | civata.adet: " + civata.adet
                + " | Depo.toplamAdet: " + Depo.toplamAdet);
        System.out.println("gorunen(9): " + vida.gorunen(9)
                + " | alanaErisen(9): " + vida.alanaErisen(9));
    }
}
```

```
vida.adet: 7 | civata.adet: 10 | Depo.toplamAdet: 17
gorunen(9): 9 | alanaErisen(9): 7
```

`adet` her nesnede ayrı: **7** ve **10**. `toplamAdet` tek: **17**, yani üç çağrının
toplamı. `yeni` hiçbir yerde görünmüyor, çünkü her çağrının sonunda yok oluyor. Üç ailenin
yaşam süresi de böylece ayrılıyor — yuva çağrı boyunca, örnek alanı nesne boyunca, sınıf
alanı sınıf yüklü kaldığı sürece yaşar.

İkinci satır gölgelemenin bedelini gösteriyor. Aynı nesneye aynı argümanla yapılan iki
çağrı **9** ve **7** döndürüyor. Kaynakta iki yöntemin gövdesi arasında `this.` dışında
hiçbir fark yoktur. Bir alan adıyla aynı adı taşıyan bir parametre, o alanı yöntemin
tamamında erişilemez kılar; okunan şey artık nesnenin durumu değil, çağırandan gelen
argümandır.

#### Sınırlayıcı Ölçüm: Hangi Yeniden Bildirim Geçiyor

Gölgeleme her yerde olmaz. İç içe bloklarda aynı adı yeniden bildirmek Java'da **derleme
hatasıdır**; gölgeleme yalnız ayrı ailelerden iki ad arasında, yani alan ile yerel arasında
olur. Aşağıdaki beş biçim aynı sınıf iskeletine yerleştirilir ve yalnız derlenip
derlenmediklerine bakılır.

- **SD10** — Beş durum aynı gövdeye, aynı sınıf adıyla yazılır; ölçüm derleyicinin ürettiği
  metne değil, **sınıf dosyasının oluşup oluşmadığına** bakar. Derleyici iletisi basılmaz.

```java
// Bildirim.java — hangi yeniden bildirim derleme aşamasını geçiyor
public class Bildirim {
    static final String[][] DURUM = {
        {"iki kardes blokta ayni ad",
         "int t = 0; { int g = n; t += g; } { int g = n; t += g; } return t;"},
        {"ic blokta distaki adi yeniden",
         "int t = 0; { int t = n; } return t;"},
        {"ic bloktaki ada disaridan erisim",
         "int t = 0; { int g = n; } return t + g;"},
        {"alan adiyla ayni yerel ad",
         "int adet = n; return adet + this.adet;"},
        {"parametre adiyla ayni yerel ad",
         "int n = 1; return n;"},
    };

    public static void main(String[] args) throws Exception {
        System.out.printf("%-34s %s%n", "bildirim bicimi", "derleme");
        for (String[] d : DURUM) {
            String kaynak = "class D { int adet = 5; int olc(int n) { " + d[1] + " } }";
            System.out.printf("%-34s %s%n", d[0],
                    Olcek.derle(kaynak, "D") != null ? "gecti" : "hata");
        }
    }
}
```

```
bildirim bicimi                    derleme
iki kardes blokta ayni ad          gecti
ic blokta distaki adi yeniden      hata
ic bloktaki ada disaridan erisim   hata
alan adiyla ayni yerel ad          gecti
parametre adiyla ayni yerel ad     hata
```

İki geçen satırın ortak yanı var: ikisinde de **iki ad hiçbir noktada aynı anda görünür
değil**. Kardeş bloklardaki `g` adları birbirini görmez, çünkü birincisi ikincisi
açılmadan kapanmıştır — bu, yuva paylaşımının kaynaktaki karşılığıdır. Alan ile yerel ise
ayrı ailelerdendir ve derleyici hangisinin kastedildiğini `this.` ile ayırabilir.

Üç hatanın ortak yanı da var: hepsinde tek bir yerel ad iki kez bildirilmiş ya da
kapsamı dışında kullanılmıştır. Java'da bir yerel değişkenin adı, kapsamı boyunca
**tekrar bildirilemez**; iç blok dıştakini gölgeleyemez. Beşinci satır bunun en dar
halidir — parametre de bir yerel değişkendir, dolayısıyla aynı adı gövdede yeniden
bildirmek hatadır. Sınır böylece net çizilir: gölgeleme yalnız alan ile yerel arasında
vardır, yerel ile yerel arasında yoktur ve derleyici bunu sınıf dosyası üretmeden önce
reddeder.

Bu yasağın bir bedeli olduğunu da yazmak gerekir: iç blokta kısa ve anlamlı bir ad ikinci
kez kullanılamadığı için uzun yöntemlerde adlar birikir. Karşılığında kazanılan şey,
bir adın bir yöntem gövdesinin herhangi bir noktasında **tek bir bildirimi** olmasıdır —
okuyan kişi bir adı gördüğünde hangi bildirime baktığını aramak zorunda kalmaz. Ölçüm bu
kararın çalışma zamanında hiçbir karşılığı olmadığını gösteriyor: geçen iki biçim de
sınıf dosyasında aynı komutları üretiyor.

#### Özet

- Bildirimin yeri adın sınıf dosyasındaki yerini belirler: sınıf gövdesindeki adlar
  **alan** çizelgesine girer, yöntem gövdesindekiler yalnız numaralı bir **yuva** tutar
  ve sınıf dosyasında ad olarak görünmez.
- `ekle` yöntemi üç aileyi bir arada gösterir: `adet` için `getfield`/`putfield`,
  `toplamAdet` için `getstatic`/`putstatic`, `gelen` ve `yeni` için hiçbir alan komutu.
- Blok kapsamı derleme zamanında biter: `blokKapsami` ile `bloksuz` **aynı 20 komutu**
  üretir, blokluk biçim yalnız **bir yuva** az kullanır.
- Örnek değişkeni nesne başına, sınıf değişkeni sınıf başına, yerel değişken çağrı başına
  yaşar; ölçümde **7**, **10** ve **17** değerleri bu üç ömrü ayırır.
- Gölgeleme yalnız alan ile yerel arasında olur ve `this.` ile çözülür; iç blokta bir
  yerel adı yeniden bildirmek derleme hatasıdır.

#### Sonraki Adım

Bu ders adın nereye yazıldığını ölçtü. Adların taşıdığı değerler bir kez yerlerine
oturduğunda sıra onları birleştiren işleçlere gelir. Sıradaki ders `+` ve `+=` gibi tek
karakterlik yazımların sınıf dosyasında ne bıraktığını sayar: `int` ile `long`
toplandığında derleyicinin araya koyduğu çevirme, `+=` yazımının kaynakta hiç yazılmayan
bir daraltmayı kendiliğinden eklemesi ve bunun **görünen** sonucu, kısa devre
değerlendirmenin bir dal komutu olarak sınıf dosyasında belirmesi — ve iki sabitin
toplandığı yerde hiçbir adımın eklenmemesi.

### İşleçler ve İfadeler

Source: https://academia.sh/tr/kurslar/java-temelleri/islecler-ve-ifadeler

Tek karakterlik yazımlar sınıf dosyasına sessiz adımlar bırakır: `int` ile `long` toplandığında bir çevirme eklenir, `adet += 300` kaynakta yazılmayan bir daraltma koyar ve sonuç 45 çıkar, kısa devre bir dal komutu olarak belirir — sabitlerde ise hiçbir adım eklenmez.

Önceki ders adın sınıf dosyasında nereye yazıldığını ölçtü: alana mı, yuvaya mı. Adlar
yerlerine oturduğunda sıra onları birleştiren işleçlere gelir. Bir işleç kaynakta tek
karakterdir; sınıf dosyasında ise bir ya da birkaç komuttur ve bu komutların bir bölümü
kaynakta hiç yazılmaz. Bu ders o bölümü sayar.

Programlama Temelleri kursu işleçleri, önceliği, kısa devre değerlendirmeyi ve açık/örtük
dönüşümü kavram olarak kurmuştu — **bunlar tekrarlanmaz**. Orada bir dönüşümün ne zaman
genişletme, ne zaman daraltma olduğu ve daraltmanın neden sessiz bilgi kaybı üretebildiği
anlatıldı. Burada ölçülen şey, o daraltmayı **kimin eklediğidir**: kaynakta hiçbir yerde
dönüşüm yazmayan bir satır, sınıf dosyasında bir çevirme komutu taşıyabilir.

#### İşleç Bir Kısaltmadır

Java'nın aritmetik işleçleri yalnız dört genişlikte çalışır: `int`, `long`, `float` ve
`double`. `byte`, `short` ve `char` için ayrı toplama komutu yoktur. Bunun sonucu şudur:
küçük tiplerdeki değerler işleme girmeden önce `int`'e yükseltilir ve iki işlenen farklı
genişlikteyse dar olan geniş olana çevrilir. Buna **sayısal yükseltme** denir ve kaynakta
görünmez.

Yükseltmenin tersi de vardır. Bileşik atama — `+=`, `-=`, `*=` — sol tarafın tipini
korumak zorundadır. `byte` bir değişkene `+=` ile bir `int` eklendiğinde toplama `int`
genişliğinde yapılır, sonra sonuç geri `byte`'a **daraltılır**. Bu daraltma kaynakta
yazılmaz; yazılsaydı derleyici zaten istemezdi. Aynı işlem `adet = adet + 300` biçiminde
yazılsaydı derleme hatası verirdi, çünkü orada daraltmayı programcının yazması gerekir.

İki yön arasındaki asıl ayrım güvenliktir. Dar bir tipten geniş bir tipe geçiş **genişletmedir**
ve hiçbir bilgi kaybetmez; derleyici onu sormadan yapar. Geniş bir tipten dar bir tipe geçiş
**daraltmadır** ve sığmayan bitler atılır; derleyici onu normalde yapmaz, programcının yazmasını
ister. Bileşik atama bu kuralın **gediklerinden biridir**: sol tarafın tipi zaten bellidir, bu
yüzden derleyici daraltmayı isteyeceği yerde kendisi koyar. Ölçüm bu gediğin sınıf dosyasındaki
karşılığını arar.

Ders bu iki yönü kurs boyunca sürdürülen depo kaydının iki alanı üzerinde ölçer: `int adet`
ve `long agirlik`. Aradaki genişlik farkı, yükseltmenin ölçülebileceği en dar örnektir.

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

Çekirdek önceki derslerinkidir; bu derste iki ölçüm okur. Birincisi **çevirme komutlarıdır**
— sınıf dosyasında bir genişliği başka bir genişliğe çeviren komutlar ayrı bir aileyi
oluşturur ve adları çevrilen iki tipi taşır (`i2l`, `i2b`). İkincisi **dal komutlarının
sayısıdır**: akışı bir noktadan başka bir noktaya götüren her komut sayılır.

- **SD11** — Sağ sütun sınıf dosyasındaki çevirmeleri, dinamik çağrıları ve çağrıları
  listeler; kaynakta yazılmış olanı okuyucu düşer, kalanı derleyici eklemiştir.
- **SD12** — Ölçülen bütün yöntemler statiktir ve gövdeleri tek satırdır; komut sayısındaki
  fark yöntem çağrısı düzeninden değil, yalnız ifadenin kendisinden gelir.

```java
// Olcek.java — işlecin sınıf dosyasına bıraktığı çevirme, çağrı ve dal komutunu okur
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 Yontem(String ad, int komut, int dal, List<String> eklenen) {}

    static List<Yontem> oku(String kaynak, String sinif) throws Exception {
        Path d = Files.createTempDirectory("olcek");
        Files.writeString(d.resolve(sinif + ".java"), kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow().run(bos, bos, "-d", d.toString(),
                d.resolve(sinif + ".java").toString()) != 0)
            throw new IllegalStateException("derlenmedi: " + sinif);
        List<Yontem> yontemler = new ArrayList<>();
        for (MethodModel m : ClassFile.of().parse(d.resolve(sinif + ".class")).methods()) {
            if (m.code().isEmpty()) continue;
            List<String> eklenen = new ArrayList<>();
            int komut = 0, dal = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) komut++;
                if (e instanceof BranchInstruction) dal++;
                if (e instanceof ConvertInstruction c)
                    eklenen.add("cevirme " + c.opcode().name().toLowerCase(Locale.ROOT));
                else if (e instanceof InvokeDynamicInstruction id)
                    eklenen.add("dinamik:" + id.name().stringValue());
                else if (e instanceof InvokeInstruction iv)
                    eklenen.add(iv.name().stringValue());
            }
            yontemler.add(new Yontem(m.methodName().stringValue(), komut, dal, eklenen));
        }
        return yontemler;
    }
}
```

#### On Üç İfade, Sınıf Dosyasında Ne Bırakıyor

Ölçülen sınıf on üç yöntem taşır ve her biri tek bir ifadedir. `daralt` ile `acikDaralt`
aynı işi yapar; tek fark, daraltmanın kaynakta yazılıp yazılmadığıdır. `gecCevirme` ile
`erkenCevirme` aynı çarpımı yapar; tek fark, çevirmenin çarpımdan önce mi sonra mı
istendiğidir. `sabitAritmetik`, `sonSabitCarpim` ve `degiskenCarpim` **aynı çarpımı** üç
ayrı yazımla verir.

```java
// Islec.java — on üç ifade, sınıf dosyasında ne bırakıyor
public class Islec {
    static final String KAYNAK = """
        class Depo {
            static long yukselt(int adet, long agirlik) { return adet + agirlik; }
            static byte daralt(byte adet) { adet += 300; return adet; }
            static byte acikDaralt(byte adet) { adet = (byte) (adet + 300); return adet; }
            static int kucukToplam(byte adet, short raf) { return adet + raf; }
            static long gecCevirme(int adet, int birim) { return adet * birim; }
            static long erkenCevirme(int adet, int birim) { return (long) adet * birim; }
            static String birlestir(String ad, int adet) { return ad + adet; }
            static String sabitBirlestir() { return "vi" + "da"; }
            static int sabitAritmetik() { return 24 * 60 * 60; }
            static final int RAF = 24;
            static int rafSayisi = 24;
            static int sonSabitCarpim() { return RAF * 60 * 60; }
            static int degiskenCarpim() { return rafSayisi * 60 * 60; }
            static boolean kisaDevre(int adet, long agirlik) { return adet > 0 && agirlik > 0; }
            static boolean tamDeger(int adet, long agirlik) { return (adet > 0) & (agirlik > 0); }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-16s %6s %4s  %s%n", "yontem", "komut", "dal", "kaynakta yazilmayan adim");
        for (Olcek.Yontem y : Olcek.oku(KAYNAK, "Depo")) {
            if (y.ad().startsWith("<")) continue;
            System.out.printf("%-16s %6d %4d  %s%n", y.ad(), y.komut(), y.dal(),
                    y.eklenen().isEmpty() ? "-" : String.join(", ", y.eklenen()));
        }
    }
}
```

```
yontem            komut  dal  kaynakta yazilmayan adim
yukselt               5    0  cevirme i2l
daralt                7    0  cevirme i2b
acikDaralt            7    0  cevirme i2b
kucukToplam           4    0  -
gecCevirme            5    0  cevirme i2l
erkenCevirme          6    0  cevirme i2l, cevirme i2l
birlestir             4    0  dinamik:makeConcatWithConstants
sabitBirlestir        2    0  -
sabitAritmetik        2    0  -
sonSabitCarpim        2    0  -
degiskenCarpim        6    0  -
kisaDevre            10    3  -
tamDeger             14    4  -
```

İlk satır sayısal yükseltmenin sınıf dosyasındaki adıdır. `adet + agirlik` ifadesinde
`adet` bir `int`, `agirlik` bir `long`; toplama komutu ikisini birden alamaz, bu yüzden
derleyici araya bir **`i2l`** koyar. Kaynakta hiçbir dönüşüm yazmaz. Beş komutun biri,
programcının yazmadığı bir çeviridir.

İkinci satır bu dersin en sessiz adımıdır. `adet += 300` yazımı yedi komut üretir ve
içinde bir **`i2b`** vardır: toplama `int` genişliğinde yapılır, sonuç `byte`'a daraltılır.
Daraltmayı derleyici kendiliğinden ekler. Bu, önceki kursta anlatılan sessiz bilgi kaybının
sınıf dosyasındaki karşılığıdır — kavram orada kuruldu, burada eklendiği yer gösteriliyor.

Bir alt satır bunun ne kadar birebir olduğunu ölçüyor. `acikDaralt` daraltmayı **kaynakta
yazıyor** ve sınıf dosyasında `daralt` ile aynı çıkıyor: aynı **7** komut, aynı `i2b`. İki
yazım sınıf dosyasında ayırt edilemez. `+=` bir işleç değil, bu satırın **kısaltmasıdır**;
kısalttığı şeyin içinde bir dönüşüm vardır ve kısaltma onu gizler.

Dördüncü satır yükseltmenin her zaman komut üretmediğini gösteriyor. `byte` ile `short`
toplandığında ikisi de `int`'e yükselir, ama sınıf dosyasında **hiçbir çevirme yoktur**:
dört komut, sıfır eklenen adım. Bu tipler zaten `int` genişliğinde yüklenir, dolayısıyla
yükseltmenin ödediği bir komut yoktur. Yükseltme bir kural olarak her yerde vardır; komut
olarak yalnız genişlik gerçekten değiştiğinde belirir.

Sonraki iki satır çevirmenin **yerini** karşılaştırıyor. İkisi de aynı çarpımı
`long` olarak döndürüyor. `gecCevirme` tek bir `i2l` taşıyor ve o çevirme çarpımdan
**sonradır**: iki `int` çarpılıyor, sonuç `long`'a çevriliyor. `erkenCevirme` iki `i2l`
taşıyor — biri kaynakta yazılan dönüşüm, öteki ikinci işlenen için eklenen yükseltme — ve
çarpım artık `long` genişliğinde yapılıyor. Bir komut fazla, ama başka bir hesap.

Birleştirme satırları aynı `+` işlecini iki ayrı sonuca bağlıyor. Değişken içeren
birleştirme dört komutta bir **dinamik çağrı** bırakıyor; iki sabitin birleştirilmesi ise
iki komut ve **hiçbir** eklenen adım. Aynı işleç, biri bir çağrı, biri sıfır adım.

Son iki satır kısa devre değerlendirmenin sınıf dosyasındaki izidir. `&&` kullanan biçim
**10** komut ve **3** dal, `&` kullanan biçim **14** komut ve **4** dal üretiyor. `&`
biçimi iki karşılaştırmayı da doğruluk değerine çevirmek zorundadır; `&&` ilk
karşılaştırma yanlış çıktığında ikinciye hiç girmeyen bir dal koyar. Kısa devre bir çalışma
zamanı kolaylığı değil, sınıf dosyasında duran bir **atlama**dır.

#### Eklenen Çevirmenin Görünen Sonucu

Buraya kadar sayılan adımların hepsinin bir değeri değiştiren karşılığı vardır.

- **SD13** — Taşma ölçümünde kayıt sayısı 100000, birim ağırlık 50000 gramdır; çarpım
  `int` genişliğinde tutulamayacak kadar büyüktür ve iki yazım arasındaki tek fark
  çevirmenin yeridir.
- **SD14** — Kısa devre ölçümünde depo üç kayıt taşır ve ikisinin `adet` alanı sıfırdır;
  sağ işlenen bir yöntem çağrısıdır ve o yöntem her çağrılışında sayacı artırır, böylece
  atlanan dalın kaç kez atlandığı sayılabilir.

```java
// Sonuc.java — eklenen çevirmenin ve atlanan dalın görünen sonucu
public class Sonuc {
    record Kayit(String ad, byte adet, long agirlik) {}

    static int cagri = 0;

    static boolean agirMi(Kayit k) { cagri++; return k.agirlik() > 0; }

    public static void main(String[] args) {
        byte adet = 1;
        adet += 300;
        System.out.println("byte adet = 1; adet += 300      ->  " + adet);

        int kayit = 100000, birim = 50000;
        System.out.println("long gecCevirme(100000, 50000)  ->  " + (long) (kayit * birim));
        System.out.println("long erkenCevirme(100000,50000) ->  " + (long) kayit * birim);

        Kayit[] depo = { new Kayit("vida", (byte) 0, 40L),
                         new Kayit("civata", (byte) 3, 90L),
                         new Kayit("somun", (byte) 0, 12L) };
        cagri = 0;
        int a = 0;
        for (Kayit k : depo) if (k.adet() > 0 && agirMi(k)) a++;
        int kisaDevre = cagri;
        cagri = 0;
        int b = 0;
        for (Kayit k : depo) if ((k.adet() > 0) & agirMi(k)) b++;
        System.out.println("&&  sonuc: " + a + "  agirMi cagrisi: " + kisaDevre);
        System.out.println("&   sonuc: " + b + "  agirMi cagrisi: " + cagri);
    }
}
```

```
byte adet = 1; adet += 300      ->  45
long gecCevirme(100000, 50000)  ->  705032704
long erkenCevirme(100000,50000) ->  5000000000
&&  sonuc: 1  agirMi cagrisi: 1
&   sonuc: 1  agirMi cagrisi: 3
```

İlk satır `i2b` komutunun ödettiğidir. Bir kayda 300 adet eklendi ve sonuç **45** oldu.
Kaynakta hiçbir dönüşüm yazılı değildir, derleyici uyarı vermez ve program çalışmaya devam
eder. Kararı programcı değil, eklenen adım vermiştir.

İkinci ve üçüncü satırlar çevirmenin **yerinin** neye mal olduğunu gösteriyor. Çarpım
`int` genişliğinde yapıldığında sonuç **705032704**, `long` genişliğinde yapıldığında
**5000000000**. Sol taraftaki değişkenin `long` olması yetmez — sonucun genişliği,
çarpımdan sonra yapılan bir çeviriyle kurtarılamaz. Yükseltmenin işlem **başına**
uygulandığını gösteren en açık ölçüm budur.

Son iki satır kısa devrenin çalışma zamanındaki karşılığıdır. İki biçim de aynı sonucu
buluyor: **1** kayıt. Ödenen çağrı sayısı ayrı: `&&` biçimi sağ işleneni **1** kez,
`&` biçimi **3** kez çalıştırıyor. Sınıf dosyasındaki bir dal komutu farkı, burada iki
yöntem çağrısı farkına dönüşüyor. Sağ işlenen bir yan etki taşısaydı iki biçim aynı
programı vermezdi; `&` ile `&&` arasındaki seçim üslup değildir.

#### Sınırlayıcı Ölçüm: Sabitte Eklenen Adım Yok

Tablodaki üç satır aynı çarpımı üç ayrı yazımla veriyor ve üçünde de eklenen adım
**sıfır**. Komut sayıları ayrı: **2**, **2** ve **6**.

- **SD15** — Üç yöntem de aynı sayıyı döndürür; aralarındaki tek fark, çarpanların
  derleme zamanında bilinip bilinmediğidir.

`sabitAritmetik` iki komutta bitiyor: sınıf dosyasında çarpma komutu **yok**, doğrudan
sonuç yazılı. Derleme zincirinde tanıtılan sabit katlama, burada ölçülen bir şeye
dönüşüyor. `sonSabitCarpim` de iki komut — çünkü `RAF` bir `static final` alandır ve
başlangıç değeri derleme zamanında bellidir; derleyici adı değil, **değeri** yerine koyar
ve katlamayı yapar. `degiskenCarpim` altı komut: `rafSayisi` sonradan değişebileceği için
değeri alınıyor ve iki çarpma çalışma zamanında yapılıyor.

Bu üçlü dersin tezini sınırlıyor. Eklenen adım bir kural değil, bir **koşula bağlı
karşılıktır**: işlenenlerin genişlikleri ayrıldığında çevirme eklenir, sol taraf dar
olduğunda daraltma eklenir, işlenenlerden biri değişken olduğunda dinamik çağrı eklenir.
İşlenenlerin hepsi derleme zamanında biliniyorsa eklenecek bir şey kalmaz — tersine, o
zaman kaynakta yazılı olan işlem sınıf dosyasından **çıkar**. Eklenen adım bazen
çıkarmadır.

Sınırın pratik karşılığı da vardır: bir sabiti `static final` bildirmek yalnız değişmezlik
bildirimi değildir, derleyiciye o değeri yerine koyma izni verir. Aynı sabit `final`
olmadan bildirildiğinde çalışma zamanında iki çarpma ödenir. Aradaki fark bu ölçümde dört
komuttur ve kaynağa bakarak görünmez.

#### Özet

- Sayısal yükseltme sınıf dosyasında bir çevirme komutudur: `int` ile `long` toplandığında
  beş komutun biri kaynakta yazılmayan bir **`i2l`**'dir.
- Bileşik atama daraltmayı kendiliğinden ekler; `adet += 300` yedi komutta bir **`i2b`**
  bırakır ve sonucu **45** yapar, derleyici uyarı üretmez. Daraltmanın kaynakta yazıldığı
  biçim sınıf dosyasında bundan ayırt edilemez.
- Yükseltme her zaman komut üretmez: `byte` ile `short` toplandığında sınıf dosyasında
  hiçbir çevirme yoktur, çünkü ikisi de zaten `int` genişliğinde yüklenir.
- Yükseltme işlem başına uygulanır: aynı çarpım `int` genişliğinde **705032704**, `long`
  genişliğinde **5000000000** verir ve sonucu `long` bir değişkene atamak bunu düzeltmez.
- Kısa devre bir dal komutudur: `&&` biçimi **10** komut ve **3** dal, `&` biçimi **14**
  komut ve **4** dal üretir; çalışma zamanında sağ işlenen **1** ve **3** kez çalışır.
- İşlenenlerin hepsi derleme zamanında biliniyorsa eklenen adım kalmaz: aynı çarpım
  sabitlerle **2**, değişkenle **6** komuttur ve üçünde de eklenen adım sıfırdır.

#### Sonraki Adım

Bu derste akışı bir noktadan başka bir noktaya götüren dal komutu ilk kez ölçüldü ve bir
işlecin yan ürünü olarak ortaya çıktı. Sıradaki ders dalı asıl konusu yapar: `if` ve
`switch` ile yazılan aynı davranış sınıf dosyasında **iki ayrı komuta** derlenir ve iki
komutun arama maliyeti aynı değildir. Etiketli atlamanın iç içe döngülerde ne kazandırdığı
adım sayısıyla ölçülür, gelişmiş `for` döngüsünün dizide ve listede neden ayrı adım ürettiği
gösterilir — ve kazancın okunabilirlikte ne ödettiği aynı yerde yazılır.

### Denetim Akışı

Source: https://academia.sh/tr/kurslar/java-temelleri/denetim-akisi

Aynı davranış sınıf dosyasında iki ayrı arama komutuna derlenir: yoğun etiketli seçim doğrudan indeksleyen bir çizelge, seyrek etiketli seçim etiketler arasında arayan bir komut kurar; etiketli atlama düz `break`'e karşı hem adım hem doğru satır kazandırır, bayraklı biçime karşı ise sıfır adım.

Önceki derste dal komutu bir işlecin yan ürünü olarak ortaya çıkmıştı: kısa devre
değerlendirme, ikinci işlenene hiç girmeyen bir atlamaydı. Bu ders dalı asıl konusu yapar.
Kaynakta `if`, `switch`, `for`, `while`, `break` ve `continue` diye ayrı ayrı yazılan
biçimler, sınıf dosyasında bu çeşitliliği bulmaz. Orada yalnız birkaç tür komut vardır:
koşullu dal, koşulsuz atlama ve iki tane **seçim** komutu.

Programlama Temelleri kursu koşullu dallanmayı, döngüyü ve döngü denetimini kavram olarak
kurmuştu — **bunlar tekrarlanmaz**. Orada bir akış biçiminin ne yaptığı anlatıldı. Burada
ölçülen şey, aynı davranışın hangi komuta derlendiği ve iki farklı komutun **ayrı arama
maliyeti** taşıdığıdır.

#### Akış Biçimi ile Akış Komutu

Sınıf dosyasında bir seçimi karşılayan iki ayrı komut vardır ve derleyici hangisini
kullanacağına **etiketlerin dağılımına** bakarak karar verir.

- **Yoğun seçim komutu** bir çizelge tutar: en küçük etiketten en büyüğüne kadar her değer
  için bir giriş. Verilen değer çizelgeye doğrudan indekslenir; arama yoktur, tek bir
  konum hesabı vardır. Karşılığında çizelge, aradaki boşluklar için de yer tutar.
- **Seyrek seçim komutu** yalnız gerçek etiketleri sıralı olarak tutar ve verilen değeri
  bu sıralı liste içinde **arar**. Yer tutmaz, ama her seçimde bir arama ödenir.

Üçüncü bir yol da vardır: aynı davranışı `if` zinciriyle yazmak. O zaman ortada hiç seçim
komutu bulunmaz; etiket sayısı kadar koşullu dal art arda dizilir ve son etiket için
hepsi denenir. Bu ders üç yazımı da kurs boyunca sürdürülen deponun **raf numarası**
üzerinde ölçer, sonra döngü biçimlerine geçer.

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

Çekirdek önceki derslerinkidir; bu derste iki ölçüm okur. Birincisi **dal komutu
sayısıdır**. İkincisi, yöntemde bir seçim komutu varsa **hangisi** olduğudur: yoğun
seçimde çizelgenin kapsadığı aralık, seyrek seçimde tutulan etiket sayısı basılır.

- **SD16** — Dal sayısı sınıf dosyasındaki koşullu ve koşulsuz atlama komutlarının
  sayısıdır; kaç kez çalıştırıldıkları değil, kaç tane yazıldıkları sayılır.
- **SD17** — Seçim komutu tek bir yöntemde bir kez bulunur; sütun boş kaldığında o
  yöntemde hiç seçim komutu yok demektir.

```java
// Olcek.java — akış biçiminin sınıf dosyasında hangi komuta derlendiğini okur
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 Yontem(String ad, int komut, int dal, String secim, List<String> eklenen) {}

    static List<Yontem> oku(String kaynak, String sinif) throws Exception {
        Path d = Files.createTempDirectory("olcek");
        Files.writeString(d.resolve(sinif + ".java"), kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow().run(bos, bos, "-d", d.toString(),
                d.resolve(sinif + ".java").toString()) != 0)
            throw new IllegalStateException("derlenmedi: " + sinif);
        List<Yontem> yontemler = new ArrayList<>();
        for (MethodModel m : ClassFile.of().parse(d.resolve(sinif + ".class")).methods()) {
            if (m.code().isEmpty()) continue;
            List<String> eklenen = new ArrayList<>();
            String secim = "-";
            int komut = 0, dal = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) komut++;
                if (e instanceof BranchInstruction) dal++;
                else if (e instanceof TableSwitchInstruction t)
                    secim = "yogun " + t.lowValue() + ".." + t.highValue();
                else if (e instanceof LookupSwitchInstruction l)
                    secim = "seyrek " + l.cases().size() + " etiket";
                else if (e instanceof InvokeInstruction iv)
                    eklenen.add(iv.name().stringValue());
                else if (e instanceof TypeCheckInstruction tc && tc.opcode() == Opcode.CHECKCAST)
                    eklenen.add("tip denetimi");
            }
            yontemler.add(new Yontem(m.methodName().stringValue(), komut, dal, secim, eklenen));
        }
        return yontemler;
    }
}
```

#### On Akış Biçimi

Ölçülen sınıfın ilk üç yöntemi **aynı yazımı** üç ayrı etiket dağılımıyla verir; dördüncüsü
üçüncüsünün eşlemesini `if` zinciriyle yazar. Sonraki dördü aynı toplamı dizide ve listede,
gelişmiş `for` ile ve elle açılmış biçimle hesaplar. Son ikisi iç içe iki döngüden çıkmanın
iki yolunu gösterir.

```java
// Akis.java — on akış biçimi, sınıf dosyasında hangi komut
public class Akis {
    static final String KAYNAK = """
        import java.util.Iterator;
        import java.util.List;
        class Depo {
            static int yogunSecim(int raf) {
                switch (raf) { case 1: return 10; case 2: return 20;
                               case 3: return 30; case 4: return 40; default: return 0; }
            }
            static int bosluklu(int raf) {
                switch (raf) { case 1: return 10; case 2: return 20;
                               case 3: return 30; case 10: return 40; default: return 0; }
            }
            static int seyrekSecim(int raf) {
                switch (raf) { case 1: return 10; case 100: return 20;
                               case 10000: return 30; case 1000000: return 40; default: return 0; }
            }
            static int ifZinciri(int raf) {
                if (raf == 1) return 10;
                if (raf == 100) return 20;
                if (raf == 10000) return 30;
                if (raf == 1000000) return 40;
                return 0;
            }
            static int diziToplami(int[] adetler) { int t = 0; for (int x : adetler) t += x; return t; }
            static int diziIndeksli(int[] adetler) {
                int t = 0;
                for (int i = 0; i < adetler.length; i++) t += adetler[i];
                return t;
            }
            static int listeToplami(List<Integer> adetler) { int t = 0; for (int x : adetler) t += x; return t; }
            static int elleYineleyici(List<Integer> adetler) {
                int t = 0;
                for (Iterator<Integer> y = adetler.iterator(); y.hasNext(); ) { int x = y.next(); t += x; }
                return t;
            }
            static int etiketli(int[][] depo, int aranan) {
                int satir = -1;
                dis:
                for (int i = 0; i < depo.length; i++)
                    for (int j = 0; j < depo[i].length; j++)
                        if (depo[i][j] == aranan) { satir = i; break dis; }
                return satir;
            }
            static int bayrakli(int[][] depo, int aranan) {
                int satir = -1;
                boolean bulundu = false;
                for (int i = 0; i < depo.length && bulundu == false; i++)
                    for (int j = 0; j < depo[i].length; j++)
                        if (depo[i][j] == aranan) { satir = i; bulundu = true; break; }
                return satir;
            }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-16s %6s %4s %-16s %s%n",
                "yontem", "komut", "dal", "secim komutu", "kaynakta yazilmayan adim");
        for (Olcek.Yontem y : Olcek.oku(KAYNAK, "Depo")) {
            if (y.ad().startsWith("<")) continue;
            System.out.printf("%-16s %6d %4d %-16s %s%n", y.ad(), y.komut(), y.dal(), y.secim(),
                    y.eklenen().isEmpty() ? "-" : String.join(", ", y.eklenen()));
        }
    }
}
```

```
yontem            komut  dal secim komutu     kaynakta yazilmayan adim
yogunSecim           12    0 yogun 1..4       -
bosluklu             12    0 yogun 1..10      -
seyrekSecim          12    0 seyrek 4 etiket  -
ifZinciri            22    4 -                -
diziToplami          24    2 -                -
diziIndeksli         18    2 -                -
listeToplami         20    2 -                iterator, hasNext, next, tip denetimi, intValue
elleYineleyici       20    2 -                iterator, hasNext, next, tip denetimi, intValue
etiketli             32    6 -                -
bayrakli             38    7 -                -
```

İlk üç satır dersin ana ölçümüdür. Üç yöntem de dört etiket ve bir varsayılan dal taşıyor,
aynı yazımla yazılmış ve sınıf dosyasında **aynı 12 komuta** dönüşüyor. Ama seçim komutu
aynı değil:
etiketler 1'den 4'e yayıldığında **yoğun** komut, milyonlara yayıldığında **seyrek** komut
üretiliyor. Kaynakta iki `switch` arasında yapısal hiçbir fark yoktur; fark yalnız
sayıların değerindedir ve derleyici o değerlere bakarak ayrı bir komut seçmiştir. Komut
sayısı bunu görmez — burada sayılacak şey komut sayısı değil, **hangi komut** olduğudur.

İkinci satır kararın nasıl verildiğini gösteriyor. `bosluklu` yalnız dört etiket taşıyor,
ama en küçüğü 1, en büyüğü 10; derleyici yine **yoğun** komutu seçiyor ve çizelge
**1'den 10'a** kadar uzanıyor. Yani sınıf dosyası kullanılmayan altı giriş için de yer
tutuyor. Derleyicinin verdiği karar bir takastır: dolduran boşluklar dosyada yer kaplar,
karşılığında her seçimde arama yerine tek bir indeksleme yapılır. Etiketler yeterince
dağıldığında takas tersine döner.

Dördüncü satır seçimin alternatifidir. Aynı eşleme `if` zinciriyle yazıldığında ortada
hiç seçim komutu yoktur: **22** komut ve **4** dal. Dört dalın anlamı, son etiketin
bulunabilmesi için önceki üçünün de denenmesi gerektiğidir. Seçim komutları bu denemeyi
tek adıma indirir; `if` zinciri etiket sayısıyla birlikte büyür. Aynı davranış, iki ayrı
maliyet.

#### Aynı Döngü, İki Ayrı Adım

Ortadaki dört satır gelişmiş `for` döngüsünün tek bir yazım olmadığını gösteriyor.

`diziToplami` **24** komut ve kaynakta yazılmayan **hiçbir** adım üretiyor: derleyici
gelişmiş `for` yazımını sayaçlı bir döngüye açıyor. `listeToplami` aynı gövdeyi **20**
komutta bitiriyor ama **beş** eklenen adım taşıyor — yineleyici alınıyor, iki kez
sorgulanıyor, dönen nesne denetleniyor ve sayıya açılıyor. Bu sayılar ilk derste
kutulamanın bedeli olarak okunmuştu; burada okunuşu başkadır. Aynı **yazım**, gezilen şeyin
tipine göre iki ayrı döngüye derleniyor. Sözdizim tek, komut iki.

Yanlarındaki iki satır bunu kanıtlıyor. `diziIndeksli` elle yazılmış sayaçlı döngüdür ve
**18** komut üretiyor — gelişmiş biçimden **altı** komut az, çünkü gelişmiş biçim dizi
referansını ve uzunluğu ayrı yuvalara kopyalar. `elleYineleyici` ise elle yazılmış
yineleyici döngüsüdür ve `listeToplami` ile **birebir aynı** çıkıyor: aynı 20 komut, aynı
iki dal, aynı beş adım. Liste üzerindeki gelişmiş `for`, tam olarak bu döngünün
kısaltmasıdır; dizi üzerindeki ise sayaçlı döngünün biraz daha bol yazılmış halidir.

Son iki satır bir sonraki bölümün konusudur: etiketli atlama **32** komut ve **6** dal,
bayraklı biçim **38** komut ve **7** dal.

#### Etiketli Atlamanın Ölçülen Kazancı

İç içe iki döngüden birden çıkmak için Java üç yol sunar: dış döngüye bir etiket koyup
`break dis` yazmak, bir bayrak değişkeni tutmak, ya da yalnız `break` yazıp iç döngüden
çıkmak. Üçü aynı programı **vermez**.

- **SD18** — Depo üç satır ve satır başına dört kayıt taşır; aranan ad **iki** satırda
  bulunur; ilki ikinci satırın üçüncü kaydıdır. Doğru yanıt **1**'dir.
- **SD19** — Adım sayacı yalnız iç döngünün gövdesinde artırılır, yani karşılaştırılan
  **kayıt sayısını** sayar; süre ölçülmez.

```java
// Atlama.java — üç çıkış biçimi, kaç adım ve hangi satır
public class Atlama {
    record Kayit(String ad, int adet, long agirlik) {}

    static int adim = 0;

    static Kayit[][] depo() {
        return new Kayit[][] {
            { new Kayit("vida", 4, 40L), new Kayit("pul", 9, 5L),
              new Kayit("somun", 2, 12L), new Kayit("mil", 1, 300L) },
            { new Kayit("conta", 7, 8L), new Kayit("yay", 3, 15L),
              new Kayit("civata", 5, 90L), new Kayit("bilye", 6, 20L) },
            { new Kayit("civata", 2, 90L), new Kayit("kama", 8, 30L),
              new Kayit("burc", 4, 60L), new Kayit("rulman", 1, 250L) },
        };
    }

    static int etiketli(Kayit[][] d, String aranan) {
        int satir = -1;
        dis:
        for (int i = 0; i < d.length; i++)
            for (int j = 0; j < d[i].length; j++) {
                adim++;
                if (d[i][j].ad().equals(aranan)) { satir = i; break dis; }
            }
        return satir;
    }

    static int bayrakli(Kayit[][] d, String aranan) {
        int satir = -1;
        boolean bulundu = false;
        for (int i = 0; i < d.length && bulundu == false; i++)
            for (int j = 0; j < d[i].length; j++) {
                adim++;
                if (d[i][j].ad().equals(aranan)) { satir = i; bulundu = true; break; }
            }
        return satir;
    }

    static int duzBreak(Kayit[][] d, String aranan) {
        int satir = -1;
        for (int i = 0; i < d.length; i++)
            for (int j = 0; j < d[i].length; j++) {
                adim++;
                if (d[i][j].ad().equals(aranan)) { satir = i; break; }
            }
        return satir;
    }

    public static void main(String[] args) {
        Kayit[][] d = depo();
        System.out.printf("%-12s %6s %8s%n", "bicim", "satir", "adim");
        adim = 0;
        System.out.printf("%-12s %6d %8d%n", "etiketli", etiketli(d, "civata"), adim);
        adim = 0;
        System.out.printf("%-12s %6d %8d%n", "bayrakli", bayrakli(d, "civata"), adim);
        adim = 0;
        System.out.printf("%-12s %6d %8d%n", "duz break", duzBreak(d, "civata"), adim);
    }
}
```

```
bicim         satir     adim
etiketli          1        7
bayrakli          1        7
duz break         2        8
```

Üçüncü satır ölçümün en önemli kısmıdır ve bir başarım farkı değildir. Düz `break` yalnız
**iç** döngüden çıkar; dış döngü kaldığı yerden sürer, ikinci satırdaki eşleşme
bulunduktan sonra üçüncü satır da taranır ve `satir` değişkeni **yeniden yazılır**. Sonuç
**2**, yani yanlış. Bir adım fazla ödenmiştir ve o adım yanlış yanıtı üretmiştir. İç içe
döngülerde `break`'in hangi döngüye ait olduğu bir üslup sorusu değildir.

İlk iki satır ise etiketli atlamanın gerçek kazancını sınırlıyor. Etiketli biçim ile
bayraklı biçim **aynı satırı** buluyor ve **aynı 7 adımı** ödüyor. Etiketli atlamanın
bayraklı biçime karşı çalışma zamanı kazancı **sıfırdır**.

#### Sınırlayıcı Ölçüm: Kazanç Nerede, Bedel Nerede

Etiketli atlamanın ölçülebilir kazancı iki yerdedir ve ikisi de küçüktür.

- **SD20** — Karşılaştırma sınıf dosyası ölçümündeki `etiketli` ve `bayrakli`
  yöntemleridir; ikisi de aynı imzayı taşır ve aynı sonucu döndürür.

Birincisi sınıf dosyasındadır: etiketli biçim **32** komut ve **6** dal, bayraklı biçim
**38** komut ve **7** dal üretiyor — altı komut ve bir dal az. Bayrak bir yuva tutar, her
dış tur başında okunur ve eşleşmede yazılır; bunların hepsi komuttur. İkincisi
kaynaktadır: bayraklı biçimde çıkış koşulu **iki yere** dağılır — dış döngünün başlığına
ve iç döngünün gövdesine. Etiketli biçimde çıkış tek satırdadır.

Bedel de yazılmalıdır. Etiket, akışı okuyan kişinin gözünü kaynakta **geriye** gönderir:
`break dis` satırını okuyan biri, `dis` etiketinin hangi döngüye konduğunu bulmak için
yukarı bakmak zorundadır. İki döngüde bu bakış kısadır; üç ya da dört düzeyde ve
aralarında `continue` de varsa, etiketler akışı izlenmesi güç bir hale getirir. Ölçüm
kararı vermez, yalnız neyin ne kadar olduğunu söyler: kazanç **altı komut ve bir
yuvadır**, çalışma zamanı adımı olarak **sıfırdır** ve bedel okuyanın yukarı bakmasıdır.

Bu sınır dersin tezini de tamamlıyor. Akış biçimleri arasındaki seçim, çoğu yerde komut
sayısını değiştirir ama ödenen adımı değiştirmez. Gerçekten değişen tek yer, biçimin
**yanlış davranışı** mümkün kıldığı yerdir — düz `break` satırındaki gibi.

#### Özet

- Aynı seçim, etiketlerin dağılımına göre iki ayrı komuta derlenir: yoğun dağılımda
  doğrudan indeksleyen bir çizelge, seyrek dağılımda etiketler arasında arayan bir komut.
  Üçünde de komut sayısı **12**'dir; değişen şey hangi komut olduğudur.
- Yoğun komutun çizelgesi en küçük etiketten en büyüğüne uzanır: dört etiketli bir seçim
  **1'den 10'a** kadar yer tutabilir. Karar bir takastır, dosya boyutuna karşılık arama.
- Aynı eşleme `if` zinciriyle yazıldığında hiç seçim komutu bulunmaz: **22** komut ve
  **4** dal ve son etiket için hepsi denenir.
- Gelişmiş `for` tek bir yazım değildir: dizide **24** komut ve **0** eklenen adım,
  listede **20** komut ve **5** eklenen adım üretir; liste biçimi elle yazılmış yineleyici
  döngüsüyle birebir aynıdır.
- Etiketli atlama düz `break`'e karşı hem bir adım hem de **doğru satırı** kazandırır;
  bayraklı biçime karşı kazancı **altı komut**, çalışma zamanı adımı olarak **sıfırdır**
  ve bedeli okuyanın etiketi yukarıda aramasıdır.

#### Sonraki Adım

Bu ders akış biçimlerinin hangi komuta derlendiğini ölçtü: aynı seçim iki ayrı arama
komutuna, aynı gelişmiş `for` döngüsü dizide ve listede ayrı adıma dönüştü. Ölçümlerin
hepsi bir şeyin **üzerinde** gezindi ve o şey hiç sorgulanmadı. Geriye üzerinde gezinilen
şeyin kendisi kaldı. Sıradaki ders diziyi ölçer: `new int[3]` satırı sınıf dosyasında kaç
komut bırakıyor, `d.length` yazımı bir alan okuması mı yoksa kendi başına bir komut mu ve
bir dizinin sınırını denetleyen adım kaynağın neresinde duruyor.

### Diziler

Source: https://academia.sh/tr/kurslar/java-temelleri/diziler

Java'da dizi bir nesnedir ve ölçüm bunu üç yerde gösterir: üretim tek bir komut bırakır, `d.length` alan okuması değil kendi başına bir komuttur, sınır denetimi ile dizi kovaryansının tip denetimi ise sınıf dosyasında hiç görünmeden çalışma zamanına bırakılır.

Önceki ders akış biçimlerinin hangi komuta derlendiğini ölçtü: aynı seçim iki ayrı arama
komutuna, aynı gelişmiş `for` döngüsü dizide ve listede ayrı adıma dönüşüyordu. Geriye
üzerinde gezinilen şeyin kendisi kaldı.

Dizi, Veri Yapıları kursunda bir veri yapısı olarak kuruldu: sabit boyut, bitişik yerleşim,
konumdan adrese giden aritmetik. Bu ders onu tekrarlamaz. Buradaki soru şudur: `new int[3]`
satırı sınıf dosyasında kaç komut bırakır, `d.length` yazımı bir alan okuması mıdır ve
dizinin sınırını denetleyen adım kaynağın neresinde durur? Üçünün de yanıtı, dizinin Java'da
bir **nesne** olmasından çıkar.

#### Dizi Bir Nesnedir

- **SD21** — Ö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 fazlalık derleyicinin eklediğidir.
- **SD22** — `Olcek` çekirdeği ortak tanımdan gelir. Bu ders ona yalnız bir ölçüm ekler:
  bir yöntemin komut adlarını sırayla döndüren `komutlar`. Var olan ölçümlerin davranışı
  değişmez.
- **SD23** — Komut adları sınıf dosyasındaki adlardır ve `Locale.ROOT` ile küçük harfe
  çevrilerek basılır; yerel ayar sonucu etkilemez.
- **SD24** — Derleyici sıfırdan farklı bir sonuç döndürürse çekirdek istisna fırlatır. Ders
  bu istisnayı yakalayıp yalnız "derlenmedi" satırını basar; derleyicinin ileti metni
  basılmaz.

```java
// Olcek.java — ortak ölçüm çekirdeği: derleyicinin sınıf dosyasına ne koyduğunu okur
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 Yontem(String ad, int komut, List<String> eklenen) {}
    record Sinif(List<Yontem> yontem, List<String> alan) {}

    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 Sinif oku(String kaynak, String sinif) throws Exception {
        ClassModel cm = derle(kaynak, sinif);
        List<Yontem> yontemler = new ArrayList<>();
        for (MethodModel m : cm.methods()) {
            if (m.code().isEmpty()) continue;
            List<String> eklenen = new ArrayList<>();
            int komut = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) komut++;
                if (e instanceof InvokeInstruction iv)
                    eklenen.add(kisa(iv.owner().asInternalName()) + "." + iv.name().stringValue());
                else if (e instanceof NewPrimitiveArrayInstruction a)
                    eklenen.add("yeni dizi " + a.typeKind().name().toLowerCase(Locale.ROOT));
                else if (e instanceof NewReferenceArrayInstruction a)
                    eklenen.add("yeni dizi " + kisa(a.componentType().asInternalName()));
            }
            yontemler.add(new Yontem(m.methodName().stringValue(), komut, eklenen));
        }
        List<String> alanlar = new ArrayList<>();
        for (FieldModel f : cm.fields()) alanlar.add(f.fieldName().stringValue());
        return new Sinif(yontemler, alanlar);
    }

    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
// Dizi.java — dizi üretimi sınıf dosyasında ne bırakıyor
import java.util.Arrays;

class Kayit {
    String ad;
    int adet;
    long agirlik;
}

public class Dizi {
    static final String K = """
        class DiziOrnek {
            static class Kayit { String ad; int adet; long agirlik; }
            static int[] bos() { return new int[3]; }
            static Kayit[] kayitlar() { return new Kayit[3]; }
            static int[] sabitli() { return new int[] {12, 18, 7}; }
            static int uzunluk(int[] d) { return d.length; }
            static int adet(Kayit k) { return k.adet; }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-10s %6s  %s%n", "yontem", "komut", "kaynakta yazilmayan adim");
        for (Olcek.Yontem y : Olcek.oku(K, "DiziOrnek").yontem())
            if (!y.ad().equals("<init>"))
                System.out.printf("%-10s %6d  %s%n", y.ad(), y.komut(),
                        y.eklenen().isEmpty() ? "-" : String.join(", ", y.eklenen()));

        System.out.println();
        System.out.println("d.length okuma: " + Olcek.komutlar(K, "DiziOrnek", "uzunluk"));
        System.out.println("k.adet okuma:   " + Olcek.komutlar(K, "DiziOrnek", "adet"));

        int[] sayilar = new int[3];
        Kayit[] kayitlar = new Kayit[3];
        System.out.println();
        System.out.println("int[] sinifi:      " + sayilar.getClass().getName());
        System.out.println("Kayit[] sinifi:    " + kayitlar.getClass().getName());
        System.out.println("dizinin ust sinifi: " + sayilar.getClass().getSuperclass().getName());
        System.out.println("baslangic degerleri: " + Arrays.toString(sayilar)
                + " " + Arrays.toString(kayitlar));

        try {
            Olcek.oku(K.replace("return d.length;", "d.length = 5; return 0;"), "DiziOrnek");
        } catch (IllegalStateException e) {
            System.out.println("d.length = 5   -> " + e.getMessage());
        }
    }
}
```

```
yontem      komut  kaynakta yazilmayan adim
bos             3  yeni dizi int
kayitlar        3  yeni dizi DiziOrnek$Kayit
sabitli        15  yeni dizi int
uzunluk         3  -
adet            3  -

d.length okuma: [aload_0, arraylength, ireturn]
k.adet okuma:   [aload_0, getfield, ireturn]

int[] sinifi:      [I
Kayit[] sinifi:    [LKayit;
dizinin ust sinifi: java.lang.Object
baslangic degerleri: [0, 0, 0] [null, null, null]
d.length = 5   -> derlenmedi: DiziOrnek
```

İlk iki satır dizi üretiminin **üç komut** olduğunu söylüyor: boyutu yükle, diziyi üret,
döndür. Ölçek bu üretimi `yeni dizi` diye kaydediyor, çünkü sınıf dosyasında dizi üretimi
nesne üretimiyle aynı sınıfa girer. `Kayit[3]` satırının kaydında sınıfın **tam adı**
duruyor, öğe tipi diziye yazılmıştır.

Üçüncü satır bir fark koyuyor. Köşeli ayraç içinde üç değer yazan `sabitli` yöntemi
**15** komut üretiyor; boyu aynı olan boş dizi **3** komut. Aradaki on iki komut, üç öğenin
tek tek yazılmasıdır: her öğe için diziyi çoğalt, konumu yükle, değeri yükle, yaz. Kaynakta
tek bir sabit gibi duran `{12, 18, 7}` yazımının sınıf dosyasında sabit karşılığı yoktur.
İlk derste ölçülen sabit katlama sayılarda işler; dizide işlemez, çünkü katlanacak şey bir
değer değil, çalışma zamanında üretilen bir nesnedir.

Son üç satır bunu adıyla koyuyor. Dizinin bir sınıfı var — `int[]` için `[I`, `Kayit[]`
için `[LKayit;` — ve o sınıfın üst sınıfı `java.lang.Object`. Dizi bir nesnedir: öbekte
üretilir, bir referansla tutulur, `Object` bekleyen her yere geçer. Üretim öğeleri üretmez;
sayı dizisi sıfırlarla, kayıt dizisi `null` ile başlar. Kayıt dizisi kurmak üç kayıt
kurmaz, üç boş yer kurar.

Uzunluk satırları ayrımın ikincisini veriyor. `d.length` yazımı `arraylength` komutuna,
`k.adet` yazımı `getfield` komutuna derleniyor. Sözdizim ikisinde de aynı — nokta ve bir ad
— ama karşılıkları ayrı komutlardır. `length` bir alan değildir; sınıf dosyasında alan
listesinde bulunmaz, kendi komutu vardır. Bunun görünen sonucu son satırda: `d.length = 5`
yazan kaynak **derlenmiyor**. Bir diziyi büyütmek bir yasak yüzünden değil, karşılığı olan
bir komut bulunmadığı için olanaksızdır.

#### Sınırı Denetleyen Adım Kaynakta Yok

- **SD25** — Sınır ölçümünde yalnız istisnanın **sınıf adı** basılır. İstisna iletisi
  konumu ve uzunluğu içerir; o metin basılmaz, çünkü ölçülen şey denetimin varlığıdır.

```java
// Erisim.java — sınır denetimi hangi komutun içinde
public class Erisim {
    static final String K = """
        class ErisimOrnek {
            static int oku(int[] d, int i) { return d[i]; }
            static void yaz(int[] d, int i, int v) { d[i] = v; }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.println("d[i] okuma: " + Olcek.komutlar(K, "ErisimOrnek", "oku"));
        System.out.println("d[i] yazma: " + Olcek.komutlar(K, "ErisimOrnek", "yaz"));

        int[] d = {12, 18, 7};
        System.out.println();
        for (int i : new int[] {0, 2, 3, -1}) {
            try {
                System.out.printf("d[%d] -> %d%n", i, d[i]);
            } catch (RuntimeException e) {
                System.out.printf("d[%d] -> %s%n", i, e.getClass().getSimpleName());
            }
        }
        int boy = -1;
        try {
            System.out.println("new int[boy] -> " + new int[boy].length);
        } catch (RuntimeException e) {
            System.out.println("new int[boy] -> " + e.getClass().getSimpleName() + " (boy = " + boy + ")");
        }
    }
}
```

```
d[i] okuma: [aload_0, iload_1, iaload, ireturn]
d[i] yazma: [aload_0, iload_1, iload_2, iastore, return]

d[0] -> 12
d[2] -> 7
d[3] -> ArrayIndexOutOfBoundsException
d[-1] -> ArrayIndexOutOfBoundsException
new int[boy] -> NegativeArraySizeException (boy = -1)
```

Okuma dört, yazma beş komut. İkisinde de bir karşılaştırma, bir dallanma ya da bir çağrı
yok: diziyi yükle, konumu yükle, oku ya da yaz. Sınır denetimi kaynakta yazılmamıştır ve
sınıf dosyasında da **ayrı bir komut olarak durmaz**. Denetim `iaload` ve `iastore`
komutlarının tanımının içindedir; sanal makine her erişimde onu yapar ve konum aralık
dışındaysa istisna fırlatır.

Bu, eklenen adımın üçüncü biçimidir. Birincisi sınıf dosyasına konan bir komuttur —
çevirme, çağrı, üretim. İkincisi kaynakta hiç yazılmamış bir üyedir. Üçüncüsü ise komutun
kendi tanımına gömülüdür ve **komut sayısını hiç değiştirmez**; ölçek onu sayamaz, ancak
çalışma zamanındaki sonucundan okur. Ölçümdeki dört erişimin ikisi değer veriyor, ikisi
istisna; eksi konum ayrı bir kural değil, aynı kuralın sonucudur.

Son satır denetimin ikinci yerini gösteriyor: boyutu eksi olan üretim isteği de çalışma
zamanında düşer ve ayrı bir istisna verir, çünkü boyut derleme zamanında bilinmek zorunda
değildir.

#### Çok Boyutlu Dizi Bir Dizi Dizisidir

- **SD26** — Satır uzunlukları ölçümde tek tek yazılır; hiçbiri rastgele üretilmez, hiçbiri
  ortamdan okunmaz.

```java
// CokBoyut.java — çok boyutlu dizi bir dizi dizisidir
public class CokBoyut {
    static final String K = """
        class BoyutOrnek {
            static int[][] duzgun() { return new int[2][3]; }
            static int[][] tirtikli() { return new int[2][]; }
            static int satirUzunlugu(int[][] d, int i) { return d[i].length; }
        }
        """;

    public static void main(String[] args) throws Exception {
        for (String y : new String[] {"duzgun", "tirtikli", "satirUzunlugu"})
            System.out.printf("%-14s %s%n", y, Olcek.komutlar(K, "BoyutOrnek", y));

        int[][] t = new int[3][];
        t[0] = new int[] {12};
        t[1] = new int[] {18, 7, 4};
        t[2] = new int[0];
        System.out.println();
        System.out.println("dis dizinin sinifi: " + t.getClass().getName());
        System.out.println("bir satirin sinifi: " + t[0].getClass().getName());
        System.out.printf("dis uzunluk %d, satir uzunluklari %d %d %d%n",
                t.length, t[0].length, t[1].length, t[2].length);

        int[][] d = new int[2][3];
        System.out.println("duzgun dizide satirlar ayni nesne mi: " + (d[0] == d[1]));
        d[0][0] = 12;
        System.out.println("d[0][0] = 12 sonrasi d[1][0]: " + d[1][0]);
    }
}
```

```
duzgun         [iconst_2, iconst_3, multianewarray, areturn]
tirtikli       [iconst_2, anewarray, areturn]
satirUzunlugu  [aload_0, iload_1, aaload, arraylength, ireturn]

dis dizinin sinifi: [[I
bir satirin sinifi: [I
dis uzunluk 3, satir uzunluklari 1 3 0
duzgun dizide satirlar ayni nesne mi: false
d[0][0] = 12 sonrasi d[1][0]: 0
```

Üçüncü satır dersin cümlesini yazıyor: `d[i].length` okuması **iki** adımdır. Önce `aaload`
ile satır yüklenir — çünkü satır bir referanstır, bir nesnedir — sonra o nesnenin uzunluğu
`arraylength` ile okunur. İki boyutlu dizi tek bir bitişik blok değildir; dış dizinin
öğeleri iç dizilere giden referanslardır. Sınıf adı da bunu yazıyor: dış dizinin sınıfı
`[[I`, bir satırınki `[I`.

İlk iki satır iki ayrı üretimi ayırıyor. Bütün boyutları verilen `new int[2][3]` tek bir
`multianewarray` komutudur ve dış diziyle birlikte satırları da üretir. Yalnız dış boyutu
verilen `new int[2][]` ise `anewarray` komutudur; dış dizi üretilir, satırları `null`
kalır. İkincisi satırların **ayrı uzunlukta** kurulmasına izin verir: ölçümdeki üç satırın
uzunlukları 1, 3 ve 0. Son iki satır ise düzgün dizide bile satırların ayrı nesneler
olduğunu doğruluyor — `d[0]` ile `d[1]` aynı nesne değil ve birinci satıra yazmak ikinciyi
değiştirmiyor. Java'da ayrı bir çok boyutlu dizi tipi yoktur.

#### Yazma Başına Bir Tip Denetimi

- **SD27** — Aynı yazma üç ayrı kaynakta denenir. İkisi derlenir ve komutları basılır,
  biri derlenmez ve "derlenmedi" satırıyla geçer; hangisinin hangi zamanda düştüğü
  ölçümün kendisidir.

```java
// Kovaryans.java — dizi kovaryansı: derlenen yazma, düşen yazma
public class Kovaryans {
    static final String DIZGI = "class KovOrnek { static void yaz(String[] d) { d[0] = %s; } }";
    static final String NESNE = "class KovOrnek { static void yaz(Object[] d) { d[0] = %s; } }";

    static void dene(String ad, String kaynak) {
        try {
            System.out.printf("%-28s -> %s%n", ad, Olcek.komutlar(kaynak, "KovOrnek", "yaz"));
        } catch (Exception e) {
            System.out.printf("%-28s -> %s%n", ad, e.getMessage());
        }
    }

    public static void main(String[] args) throws Exception {
        dene("String[] d; d[0] = \"kuzey\"", DIZGI.formatted("\"kuzey\""));
        dene("String[] d; d[0] = 12", DIZGI.formatted("12"));
        dene("Object[] d; d[0] = 12", NESNE.formatted("12"));

        String[] adlar = new String[2];
        Object[] gorunum = adlar;
        System.out.println();
        System.out.println("Object[] gorunum = adlar;  ayni nesne mi: " + (gorunum == adlar));
        System.out.println("gorunum sinifi: " + gorunum.getClass().getName());
        gorunum[0] = "kuzey";
        System.out.println("gorunum[0] = \"kuzey\" -> adlar[0] = " + adlar[0]);
        try {
            gorunum[1] = 12;
            System.out.println("gorunum[1] = 12 -> " + adlar[1]);
        } catch (RuntimeException e) {
            System.out.println("gorunum[1] = 12 -> " + e.getClass().getSimpleName()
                    + ", adlar[1] = " + adlar[1]);
        }
        int[] sayilar = new int[2];
        sayilar[1] = 12;
        System.out.println("int[] tarafinda ayni yazma -> " + sayilar[1]);
    }
}
```

```
String[] d; d[0] = "kuzey"   -> [aload_0, iconst_0, ldc, aastore, return]
String[] d; d[0] = 12        -> derlenmedi: KovOrnek
Object[] d; d[0] = 12        -> [aload_0, iconst_0, bipush, invokestatic, aastore, return]

Object[] gorunum = adlar;  ayni nesne mi: true
gorunum sinifi: [Ljava.lang.String;
gorunum[0] = "kuzey" -> adlar[0] = kuzey
gorunum[1] = 12 -> ArrayStoreException, adlar[1] = null
int[] tarafinda ayni yazma -> 12
```

Java'da diziler **kovaryanttır** (array covariance): öğe tipi bir üst tipin altındaysa,
dizi de o üst tipin dizisi yerine geçer. `String[]` bir `Object[]` yerine geçer ve
ölçümün dördüncü satırı bunu doğruluyor — iki ad aynı nesneyi tutuyor, sınıfı hâlâ
`[Ljava.lang.String;`.

İlk üç satır kovaryansın bedelini yazıyor. `String[]` olarak adlandırılan diziye sayı
yazan kaynak **derlenmiyor**: derleyici bildirilen tipe bakar ve hatayı derleme zamanında
verir. Aynı dizi `Object[]` olarak adlandırıldığında **aynı yazma derleniyor** — üstelik
sayıyı sarmalayıcıya çeviren bir çağrı da eklenerek. Yazma sözdizim düzeyinde geçerlidir,
çünkü `Object[]` içine her nesne yazılabilir.

Kararı çalışma zamanı veriyor: dizgi yazması geçiyor ve çağırana görünüyor, sayı yazması
`ArrayStoreException` ile düşüyor ve `adlar[1]` **hâlâ `null`** — yazma yapılmadı ve yarım da
kalmadı. Sanal makine `aastore` komutunu yürütürken değerin dizinin **gerçek** öğe tipine uyup
uymadığını denetler; uymuyorsa yazmaz.

Buradaki sınırlayıcı ölçüm iki yönlüdür. Birincisi: bu denetim komut sayısına hiç
yansımaz. `aastore` tek komuttur ve `iastore` da tek komuttur; sınıf dosyasına bakarak
denetimin varlığını sayamayız. İkincisi: son satırdaki `int[]` yazmasında böyle bir denetim
**yoktur**. Öğe tipi ilkelse dizi kovaryansı diye bir soru doğmaz, çünkü ilkel tipli bir
dizi başka bir dizi tipinin yerine geçmez. Eklenen adım burada kaybolur — ve kaybolduğu
yer, tam olarak alt tip ilişkisinin bittiği yerdir.

#### Özet

- Dizi bir nesnedir: kendi sınıfı vardır, üst sınıfı `java.lang.Object`'tir ve üretimi
  sınıf dosyasında **3** komuttur; köşeli ayraçlı `{12, 18, 7}` yazımı ise **15** komuttur,
  çünkü öğeler tek tek yazılır. Üretim öğeleri üretmez: sayı dizisi sıfırlarla, referans
  dizisi `null` değerlerle başlar.
- `d.length` bir alan okuması değildir; `arraylength` komutuna derlenir, `k.adet` ise
  `getfield` komutuna. Uzunluğa yazan kaynak derlenmez.
- Sınır denetimi ne kaynakta ne de ayrı bir komutta durur; erişim komutunun tanımının
  içindedir, komut sayısını değiştirmez ve aralık dışı erişimde istisna fırlatır.
- `int[][]` ayrı bir tip değil, bir `int[]` dizisidir: `d[i].length` iki komuttur, satırlar
  ayrı nesnelerdir ve ayrı uzunlukta olabilir.
- Dizi kovaryansı yüzünden `Object[]` olarak adlandırılan bir dizgi dizisine sayı yazmak
  derlenir ama çalışma zamanında `ArrayStoreException` ile düşer; aynı denetim ilkel tipli
  dizide hiç yoktur.

#### Sonraki Adım

Bu derste `String[]` dizisine yazmanın çalışma zamanında denetlendiği ölçüldü; yazılan
değer her seferinde hazır bir dizgi nesnesiydi. Sıradaki ders o nesneye bakar: bir dizgi
sabiti sınıf dosyasında nerede durur, aynı sabiti iki kez yazmak kaç nesne üretir ve
döngü içinde birleştirilen bir metin yol boyunca kaç nesne bırakır? Dizginin
değiştirilemez olması da, dizinin sabit boyutlu olması gibi, kaynakta görünmeyen adımları
belirler.

### Dizgiler

Source: https://academia.sh/tr/kurslar/java-temelleri/dizgiler

Aynı `+` işleci dizgide iki ayrı sonuç verir: iki sabit arasında sınıf dosyasında hiçbir iz bırakmaz, değişkenler arasında bir dinamik çağrıya dönüşür; döngüde ise 24 komutluk yazım 20 dizgi nesnesi bırakırken 27 komutluk biriktirici tek bir nesne üretir.

Önceki ders `String[]` dizisine yazmanın çalışma zamanında denetlendiğini ölçtü; yazılan
değer her seferinde hazır bir dizgi nesnesiydi. Bu ders o nesneye bakar.

Dizginin değiştirilemez olduğu Programlama Temelleri kursunda kuruldu ve burada
tekrarlanmıyor. Buradaki soru şudur: değiştirilemezlik ne kadar tutuyor? Kaynakta yazılan
`+` işleci sınıf dosyasında hangi adıma dönüşür, aynı sabiti iki kez yazmak kaç nesne
üretir ve bir döngü içinde büyütülen metin yol boyunca kaç nesne bırakır?

#### Birleştirme İşleci Sınıf Dosyasında Kalmaz

- **SD28** — Ölçülen kaynak metni dersin içinde yazılır; sınıf dosyasında bulunan her
  fazlalık derleyicinin eklediğidir.
- **SD29** — `Olcek` çekirdeği ortak tanımdan gelir. Bu ders onun yalnız kullandığı üç
  kaydını yazar: çağrı, dinamik çağrı ve nesne üretimi. Ölçümlerin davranışı değişmez.
- **SD30** — Ölçülen altı yöntemin dördü birleştirmenin dört ayrı biçimidir: iki sabit
  arasında, bir değişkenle, üç işleçle ve bir döngü boyunca. Kalan ikisi karşılaştırma
  içindir.

```java
// Olcek.java — ortak ölçüm çekirdeği: derleyicinin sınıf dosyasına ne koyduğunu okur
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 Yontem(String ad, int komut, List<String> eklenen) {}
    record Sinif(List<Yontem> yontem, List<String> alan) {}

    static Sinif oku(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"));
        List<Yontem> yontemler = new ArrayList<>();
        for (MethodModel m : cm.methods()) {
            if (m.code().isEmpty()) continue;
            List<String> eklenen = new ArrayList<>();
            int komut = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) komut++;
                if (e instanceof InvokeInstruction iv)
                    eklenen.add(kisa(iv.owner().asInternalName()) + "." + iv.name().stringValue());
                else if (e instanceof InvokeDynamicInstruction id)
                    eklenen.add("dinamik:" + id.name().stringValue());
                else if (e instanceof NewObjectInstruction n)
                    eklenen.add("yeni " + kisa(n.className().asInternalName()));
            }
            yontemler.add(new Yontem(m.methodName().stringValue(), komut, eklenen));
        }
        List<String> alanlar = new ArrayList<>();
        for (FieldModel f : cm.fields()) alanlar.add(f.fieldName().stringValue());
        return new Sinif(yontemler, alanlar);
    }

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

```java
// Birlestirme.java — dizgi birleştirme sınıf dosyasında neye dönüşüyor
public class Birlestirme {
    static final String K = """
        class DizgiOrnek {
            static String birlestir(String ad, int adet) { return ad + adet; }
            static String sabitBirlestir() { return "ba" + "sit"; }
            static int uzunluk(String s) { return s.length(); }
            static String satir(String ad, int adet, long agirlik) {
                return ad + " x" + adet + " (" + agirlik + " g)";
            }
            static String donguyleBirlestir(String[] d) {
                String s = "";
                for (String x : d) s += x;
                return s;
            }
            static String biriktiriciyle(String[] d) {
                StringBuilder b = new StringBuilder();
                for (String x : d) b.append(x);
                return b.toString();
            }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-18s %6s  %s%n", "yontem", "komut", "kaynakta yazilmayan adim");
        for (Olcek.Yontem y : Olcek.oku(K, "DizgiOrnek").yontem())
            if (!y.ad().equals("<init>"))
                System.out.printf("%-18s %6d  %s%n", y.ad(), y.komut(),
                        y.eklenen().isEmpty() ? "-" : String.join(", ", y.eklenen()));

        String ad = "kuzey";
        int adet = 12;
        long agirlik = 1800;
        System.out.println("\nsatir yaziminin urettigi metin: "
                + ad + " x" + adet + " (" + agirlik + " g)");
    }
}
```

```
yontem              komut  kaynakta yazilmayan adim
birlestir               4  dinamik:makeConcatWithConstants
sabitBirlestir          2  -
uzunluk                 3  String.length
satir                   5  dinamik:makeConcatWithConstants
donguyleBirlestir      24  dinamik:makeConcatWithConstants
biriktiriciyle         27  yeni StringBuilder, StringBuilder.<init>, StringBuilder.append, StringBuilder.toString

satir yaziminin urettigi metin: kuzey x12 (1800 g)
```

Birinci satır dersin ana ölçümüdür. Kaynakta `ad + adet` yazılıdır ve orada bir işleç
durur; sınıf dosyasında ise bir **dinamik çağrı** vardır. Dizgi birleştirme, sınıf
dosyasında bir işleç olarak bulunmaz — çünkü karşılığı olan bir birleştirme komutu yoktur.
Derleyici işleci bir çağrıya çevirir; çağrının nasıl gerçekleştirileceği çalışma zamanına
bırakılır ve sınıf dosyasında yalnız işlemin adıyla kaynakta yazılan sabit parçalar durur.
Kaynağa bakan biri bir sayının metne çevrildiğini de göremez: `adet` bir tam sayıdır ve o
çevrim de aynı çağrının içindedir.

İkinci satır aynı işleci sıfıra indiriyor. `"ba" + "sit"` yazan yöntem **2** komuttur:
sabiti yükle, döndür. Ortada birleştirme yoktur. İki dizgi sabiti derleme sırasında tek bir
sabite katlanmıştır — ilk derste ölçülen sabit katlamanın dizgideki karşılığı budur. Aynı
`+` işleci, bir satırda bir çağrı, öteki satırda hiçbir şey.

Üçüncü satır önceki dersle arasındaki farkı koyuyor. Dizide `d.length` bir komuttu; dizgide
`s.length()` bir **çağrıdır** ve ölçek onu eklenen adım olarak kaydediyor. Sözdizim de bunu
söyler: dizide ayraç yoktur, dizgide vardır. Uzunluk dizide nesnenin başlığından okunur,
dizgide ise bir yöntem çağrısıyla istenir; ikisi aynı sözcükle yazıldığı için karıştırılır,
sınıf dosyasında hiç benzemezler.

Dördüncü satır işlecin en çok kullanılan biçimini ölçüyor. Kayıt satırını kuran `satir`
yönteminde **üç** `+` işleci var, ama sınıf dosyasında **tek bir** dinamik çağrı duruyor ve
yöntemin tamamı **5** komut. Derleyici art arda gelen birleştirmeleri ayrı ayrı çevirmez;
bütün parçaları tek bir çağrıya toplar. Kaynağa bakarak "her `+` bir ara metin üretir"
demek bu yüzden yanlış olurdu — bir ifade içindeki birleştirmelerin sayısı, üretilen ara
nesnelerin sayısı değildir. Alt satır bu yazımın ürettiği metni gösteriyor: kaydın adı,
adedi ve gram cinsinden ağırlığı tek bir çağrıda birleşiyor.

Alt iki satır ikinci çifti kuruyor. Döngü içinde `s += x` yazan yöntem **24** komut,
biriktiriciyle yazılan yöntem **27** komut. Komut sayısına bakan biri birinciyi seçerdi.
Eklenen adım listesi ise ters yönü gösteriyor: birincide döngünün her turunda çalışan tek
bir dinamik çağrı var, ikincide bir nesne üretimi ve üç çağrı. Sayılar burada bir seçim
gerekçesi vermiyor; komut sayısı programın kaç kez ne yaptığını söylemez.

#### Döngüde Kalan Nesneler

- **SD31** — Ölçüm süre ölçmez; **üretilen dizgi nesnesi** sayar. Her turda sonucun bir
  önceki nesneyle aynı olup olmadığı `==` ile sorulur ve ayrı çıkan sayılır. Hiçbir kimlik
  sayısı basılmaz.
- **SD32** — Parça sayısı üç ayrı değerle denenir; ölçülen şey tek bir sayı değil, sayının
  parça sayısıyla nasıl değiştiğidir.

```java
// Uretim.java — döngüde kaç dizgi nesnesi kalıyor
public class Uretim {
    static String[] parcalar(int n) {
        String[] d = new String[n];
        for (int i = 0; i < n; i++) d[i] = (i % 2 == 0) ? "kuzey" : "-";
        return d;
    }

    static int islecle(String[] d) {
        String s = "";
        int uretilen = 0;
        for (String x : d) {
            String onceki = s;
            s = s + x;
            if (s != onceki) uretilen++;
        }
        return uretilen;
    }

    static boolean biriktiriciAyni(String[] d) {
        StringBuilder b = new StringBuilder();
        boolean ayni = true;
        for (String x : d) ayni = ayni && (b.append(x) == b);
        return ayni;
    }

    public static void main(String[] args) {
        System.out.printf("%6s %22s %28s%n", "parca", "islecle uretilen dizgi",
                "append ayni nesneyi dondurdu");
        for (int n : new int[] {5, 10, 20})
            System.out.printf("%6d %22d %28s%n", n, islecle(parcalar(n)),
                    biriktiriciAyni(parcalar(n)));

        StringBuilder b = new StringBuilder();
        for (String x : parcalar(5)) b.append(x);
        System.out.println("\ntoString iki cagri ayni nesne mi: " + (b.toString() == b.toString()));
        String isleclerle = "";
        for (String x : parcalar(5)) isleclerle += x;
        System.out.println("iki yol ayni metni mi veriyor: " + isleclerle.equals(b.toString()));
        System.out.println("sonuc: " + b);
    }
}
```

```
 parca islecle uretilen dizgi append ayni nesneyi dondurdu
     5                      5                         true
    10                     10                         true
    20                     20                         true

toString iki cagri ayni nesne mi: false
iki yol ayni metni mi veriyor: true
sonuc: kuzey-kuzey-kuzey
```

Sayı parça sayısıyla birebir gidiyor: 5 parça 5 nesne, 20 parça 20 nesne. Bunların yalnız
sonuncusu kullanılır; öteki on dokuzu kurulup atılır. Bu bir gerçekleştirim ayrıntısı
değil, değiştirilemezliğin doğrudan sonucudur. `s += x` yazımı `s` nesnesine bir ek
yapamaz, çünkü dizgi üzerinde yazma diye bir işlem yoktur; yapabileceği tek şey yeni bir
dizgi kurup adı ona bağlamaktır. Önceki dersteki dizinin sabit boyutu gibi, bu da bir yasak
değil, karşılığı olan bir işlemin yokluğudur.

Buradaki `+=` yazımı da bir kısaltmadır ve kısaltmanın açılımı tipe göre değişir. Sayıda
`+=` sessiz bir daraltma çevirmesi ekler ve değeri yerinde değiştirir; dizgide bir dinamik
çağrı ekler, yeni bir nesne kurar ve adı ona **yeniden bağlar**. İki yazım aynı görünür,
iki eklenen adım ayrıdır ve ikisi de kaynakta yazılmamıştır.

Üçüncü sütun biriktiricinin neden ayrıldığını söylüyor: `append` çağrısı **çağrıldığı nesnenin
kendisini** döndürüyor, yirmi turun yirmisinde de. Biriktirici değiştirilebilir bir tampon tutar;
ekleme onun içine yazılır ve yeni nesne kurulmaz. Dizgi yalnız `toString` çağrıldığında üretilir
ve o çağrı da bir kez yapılır. Alt satır bu çağrının gerçekten üretim olduğunu doğruluyor: art
arda iki `toString` çağrısı **ayrı** nesneler veriyor.

Böylece iki yolun tablosu tamamlanıyor. İşleçli yol 24 komut ve **n** nesne, biriktiricili
yol 27 komut ve **1** nesne; ikisi de aynı metni veriyor. Kursun ikinci iddiası burada tam
biçimini alıyor: komut sayısı ile yapılan iş aynı yöne gitmez. Sınıf dosyasındaki komut
sayısı bir yöntemin **ne kadar yazıldığını** ölçer, kaç kez çalıştığını değil. Döngü
içindeki tek bir komut, döngü dışındaki üç komuttan daha çok iş yapar.

Buradan doğrudan bir kural çıkar ve o kural yazıma bakılarak uygulanabilir: birleştirme
sayısı **derleme zamanında belliyse** işleç yeterlidir, çünkü tek çağrıya derlenir; sayı
çalışma zamanında belirleniyorsa biriktirici kullanılır.

#### Sabit Üretilmez, Havuzdan Alınır

- **SD33** — Kimlik ölçümünde yalnız `==` ve `equals` sonuçları basılır; hiçbir kimlik
  değeri ya da adres yazılmaz.
- **SD34** — Karşılaştırılan beş dizginin beşi de aynı harfleri taşır; ölçülen tek şey
  bunların **aynı nesne** olup olmadığıdır.

```java
// Havuz.java — dizgi sabiti üretilmez, havuzdan alınır
public class Havuz {
    static final String K = """
        class HavuzOrnek {
            static String bir() { return "kuzey"; }
            static String iki() { return "kuzey"; }
            static String uretilmis() { return new String("kuzey"); }
        }
        """;

    static void yaz(String ad, String a, String b) {
        System.out.printf("%-32s %7s %8s%n", ad, a == b, a.equals(b));
    }

    public static void main(String[] args) throws Exception {
        System.out.printf("%-14s %6s  %s%n", "yontem", "komut", "kaynakta yazilmayan adim");
        for (Olcek.Yontem y : Olcek.oku(K, "HavuzOrnek").yontem())
            if (!y.ad().equals("<init>"))
                System.out.printf("%-14s %6d  %s%n", y.ad(), y.komut(),
                        y.eklenen().isEmpty() ? "-" : String.join(", ", y.eklenen()));

        String sabit = "kuzey";
        String ikinci = "kuzey";
        String sabitToplam = "kuz" + "ey";
        String hesaplanan = "kuz" + sabit.substring(3);
        String uretilmis = new String("kuzey");

        System.out.printf("%n%-32s %7s %8s%n", "\"kuzey\" ile karsilastirilan", "==", "equals");
        yaz("ikinci kez yazilan sabit", ikinci, sabit);
        yaz("\"kuz\" + \"ey\"", sabitToplam, sabit);
        yaz("calisma zamaninda birlestirilen", hesaplanan, sabit);
        yaz("new String(\"kuzey\")", uretilmis, sabit);
        yaz("hesaplanan.intern()", hesaplanan.intern(), sabit);
    }
}
```

```
yontem          komut  kaynakta yazilmayan adim
bir                 2  -
iki                 2  -
uretilmis           5  yeni String, String.<init>

"kuzey" ile karsilastirilan           ==   equals
ikinci kez yazilan sabit            true     true
"kuz" + "ey"                        true     true
calisma zamaninda birlestirilen    false     true
new String("kuzey")                false     true
hesaplanan.intern()                 true     true
```

Üst tablo dersin sınırlayıcı ölçümüdür. Bir dizgi sabiti döndüren yöntem **2** komuttur ve
eklenen adım sütunu boştur: hiçbir üretim, hiçbir çağrı, hiçbir çevirme yok. Sabit sınıf
dosyasında hazır durur ve çalışma zamanında **üretilmez**, yalnız yüklenir. Aynı sabiti
ikinci kez yazmak da ikinci bir nesne kurmaz. Buna karşılık `new String("kuzey")` yazımı
**5** komuttur ve iki adım ekler: bir nesne üretimi ve bir yapıcı çağrısı. Kaynakta yazılan
tek fark bir anahtar sözcüktür; sınıf dosyasındaki fark üç komut ve bir nesnedir.

Alt tablo bunun kimlik üzerindeki sonucunu veriyor. İki kez yazılan sabit **aynı**
nesnedir; iki sabitin birleştirilmesi de aynı nesnedir, çünkü o birleştirme derleme
zamanında yapılmış ve sonuç yine bir sabit olmuştur. Aynı harfler çalışma zamanında
birleştirildiğinde ise sonuç **ayrı** bir nesnedir ve `new String` her çağrıda ayrı bir
nesne verir. Beş satırın beşinde de `equals` doğru: içerik hep aynı, kimlik değil.

Bunları toplayan yer **dizgi havuzudur**: sabitler tek bir havuzda tutulur ve aynı harf
dizisi bir kez saklanır. Son satır havuza sonradan girmenin yolunu gösteriyor — `intern`
çağrısı, çalışma zamanında kurulmuş bir dizginin havuzdaki karşılığını verir ve sonuç
sabitle aynı nesne çıkar. Bunun tek pratik sonucu şudur: dizgiler `==` ile
karşılaştırılmaz. İşleç bazen doğru yanıt verir ve tam da bu yüzden güvenilmezdir; doğru
yanıtı veren şey içerik değil, sabitin nereden geldiğidir.

`intern` çağrısı bu tabloyu düzeltmenin yolu gibi görünebilir, ama düzelttiği şey ölçümün
kendisi değildir: havuza her giriş bir arama ve bir saklama ister ve bu ders o adımları
saymadı. Karşılaştırma için `equals` kullanmak hem daha az varsayım hem daha az adım
gerektirir; `intern` yalnız aynı metnin çok kez tutulduğu ve tek bir kopyada toplanmasının
istendiği durumda ölçülecek bir seçenektir.

#### Özet

- Dizgi birleştirme sınıf dosyasında bir işleç değildir: değişkenler arasındaki `+` bir
  dinamik çağrıya dönüşür, sayıdan metne çevrim de o çağrının içindedir ve bir ifadedeki
  üç işleç yine **tek** çağrı verir.
- Dizide `d.length` bir komuttur; dizgide `s.length()` bir çağrıdır. Aynı sözcük iki tipte
  ayrı adımlara derlenir.
- İki dizgi sabitinin birleştirilmesi **hiçbir** adım eklemez: yöntem 2 komuttur, sabit
  derleme zamanında katlanmıştır.
- Döngüde `+=` ile büyütülen metin parça başına bir dizgi bırakır — 20 parça 20 nesne —
  çünkü dizgi üzerinde yazma diye bir işlem yoktur; biriktiricide ise `append` çağrıldığı
  nesnenin kendisini döndürür ve dizgi yalnız `toString` ile bir kez üretilir.
- İşleçli yol **24**, biriktiricili yol **27** komuttur: daha az komut daha az iş demek
  değildir, çünkü komut sayısı kaç kez çalıştığını söylemez.
- Aynı harfleri taşıyan beş dizginin üçü sabitle aynı nesne, ikisi ayrıdır; `equals`
  beşinde de doğrudur, bu yüzden dizgiler `==` ile karşılaştırılmaz.

#### Sonraki Adım

Bu derste bir dizginin `==` ile aynı, `equals` ile eşit çıkabildiği ölçüldü; iki ad bazen
tek bir nesneyi tuttu. Aynı soru bir yöntem çağrısında da sorulabilir: bir kayıt nesnesi
bir yönteme verildiğinde çağrılan yöntem hangi şeye erişir ve o nesnenin alanını
değiştirmekle parametreyi yeniden bağlamak neden ayrı sonuçlar verir? Sıradaki ders
Java'nın tek aktarım modelini sınıf dosyasından okur ve kursu kapatır.

### Değer ve Referans Aktarımı

Source: https://academia.sh/tr/kurslar/java-temelleri/deger-ve-referans-aktarimi

Java'nın tek bir aktarım modeli vardır ve sınıf dosyası bunu yazar: beş ayrı tipteki beş argüman aynı biçimde yüklenir, parametrenin alanına yazmak çağırana görünür, parametreyi yeniden bağlamak görünmez ve değiştirilemez bir tipte iki durum ayırt edilemez.

Önceki ders bir dizginin `==` ile aynı, `equals` ile eşit çıkabildiğini ölçtü; iki ad bazen
tek bir nesneyi tutuyordu. Aynı soru bir çağrının sınırında da sorulabilir.

Değerle ve referansla aktarım Programlama Temelleri kursunda kavram olarak kuruldu ve
burada tekrarlanmıyor. Buradaki soru şudur: Java bu kavramların hangisini gerçekleştirir
ve bunu sınıf dosyasından nasıl okuruz? Kaynağa bakan biri bir kaydı yönteme verdiğinde
nesnenin mi yoksa ona giden referansın mu geçtiğini göremez; sınıf dosyası görür.

#### Argümanlar Tek Bir Biçimde Yüklenir

- **SD35** — Ölçülen kaynak dersin içinde yazılır; sınıf dosyasında bulunan her fazlalık
  derleyicinin eklediğidir.
- **SD36** — `Olcek` çekirdeğinin bu derste kullanılan iki ölçümü yazılır: yöntem başına
  komut sayısı ile eklenen adım ve bir yöntemin komut adları dizisi.
- **SD37** — Çağrı ölçümündeki beş parametrenin tipleri bilerek ayrıdır: iki ilkel tip
  (biri iki yuva kaplar), bir sınıf, bir dizi ve değiştirilemez bir sınıf.

```java
// Olcek.java — ortak ölçüm çekirdeği: derleyicinin sınıf dosyasına ne koyduğunu okur
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 Yontem(String ad, int komut, List<String> eklenen) {}

    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<Yontem> oku(String kaynak, String sinif) throws Exception {
        List<Yontem> yontemler = new ArrayList<>();
        for (MethodModel m : derle(kaynak, sinif).methods()) {
            if (m.code().isEmpty()) continue;
            List<String> eklenen = new ArrayList<>();
            int komut = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) komut++;
                if (e instanceof InvokeInstruction iv)
                    eklenen.add(kisa(iv.owner().asInternalName()) + "." + iv.name().stringValue());
                else if (e instanceof InvokeDynamicInstruction id)
                    eklenen.add("dinamik:" + id.name().stringValue());
                else if (e instanceof NewObjectInstruction n)
                    eklenen.add("yeni " + kisa(n.className().asInternalName()));
            }
            yontemler.add(new Yontem(m.methodName().stringValue(), komut, eklenen));
        }
        return yontemler;
    }

    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
// Aktarim.java — argüman yükleme ve parametreye yazma sınıf dosyasında
public class Aktarim {
    static final String K = """
        class AktarimOrnek {
            static class Kayit { String ad; int adet; long agirlik; }
            static void sayiAl(int adet) {}
            static void agirlikAl(long agirlik) {}
            static void kayitAl(Kayit k) {}
            static void diziAl(int[] d) {}
            static void metinAl(String s) {}
            static void cagir(int adet, long agirlik, Kayit k, int[] d, String s) {
                sayiAl(adet); agirlikAl(agirlik); kayitAl(k); diziAl(d); metinAl(s);
            }
            static void alaniDegistir(Kayit k) { k.adet = 99; }
            static void yenidenBagla(Kayit k) { k = new Kayit(); k.adet = 99; }
            static void sayiyiArtir(int adet) { adet = adet + 1; }
            static void metniUzat(String s) { s = s + " x"; }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.println("cagir: " + Olcek.komutlar(K, "AktarimOrnek", "cagir"));
        System.out.println();
        System.out.printf("%-14s %6s  %s%n", "yontem", "komut", "kaynakta yazilmayan adim");
        for (Olcek.Yontem y : Olcek.oku(K, "AktarimOrnek"))
            if (y.ad().endsWith("Degistir") || y.ad().endsWith("Bagla")
                    || y.ad().endsWith("Artir") || y.ad().endsWith("Uzat"))
                System.out.printf("%-14s %6d  %s%n", y.ad(), y.komut(),
                        y.eklenen().isEmpty() ? "-" : String.join(", ", y.eklenen()));
        for (String y : new String[] {"alaniDegistir", "yenidenBagla"})
            System.out.printf("%-14s %s%n", y, Olcek.komutlar(K, "AktarimOrnek", y));
    }
}
```

```
cagir: [iload_0, invokestatic, lload_1, invokestatic, aload_3, invokestatic, aload, invokestatic, aload, invokestatic, return]

yontem          komut  kaynakta yazilmayan adim
alaniDegistir       4  -
yenidenBagla        8  yeni AktarimOrnek$Kayit, AktarimOrnek$Kayit.<init>
sayiyiArtir         5  -
metniUzat           4  dinamik:makeConcatWithConstants
alaniDegistir  [aload_0, bipush, putfield, return]
yenidenBagla   [new, dup, invokespecial, astore_0, aload_0, bipush, putfield, return]
```

İlk satır kursun bu derse ait tezidir. Beş ayrı tipte beş argüman geçiyor ve sınıf
dosyasında **beş yükleme komutu** var: `iload`, `lload` ve üç `aload`. Hepsi aynı işi
yapıyor — bir yerel değişken yuvasındaki değeri yığıta koyuyor — ve aralarındaki tek fark
değerin genişliği. Tam sayı bir yuva, uzun tam sayı iki yuva kaplar; bu yüzden üçüncü
argüman `aload_3` ile yükleniyor, ikinci argüman iki numarayı birden almıştır.

Burada olmayan şey, olandan daha çok konuşuyor. Nesneyi kopyalayan bir komut yok. Bir
referansı adresiyle geçiren ayrı bir komut yok. Sınıf dosyasında "değerle geçir" ve
"referansla geçir" diye iki ayrı yol bulunmuyor; **tek** yol var ve o yol yuvadaki değeri
kopyalayıp yığıta koymaktır. Kayıt, dizi ve dizgi için yuvada duran değer bir **referanstır**;
kopyalanan şey nesne değil, o referanstır. Java'nın aktarım modeli bu tek cümledir ve
ölçümün geri kalanı bu cümlenin sonuçlarıdır.

Alt tablo iki sonucu ayırıyor. `alaniDegistir` **4** komuttur ve hiçbir adım eklenmez:
referansı yükle, değeri yükle, alana yaz. Yazma `putfield` ile **nesnenin içine** gider.
`yenidenBagla` ise **8** komuttur ve iki adım ekler: bir nesne üretimi ve bir yapıcı
çağrısı. Komut dizisi devamını yazıyor — `astore_0`, yani yeni referans **yuvanın üstüne**
yazılır. Yuva çağrılan yöntemin kendi yuvasıdır; çağıranın yuvasıyla arasında hiçbir bağ
yoktur.

Son iki satır aynı ayrımın ilkel tipteki ve dizgideki karşılığıdır: `sayiyiArtir` yuvaya
yazar, `metniUzat` bir dinamik çağrıyla yeni dizgi kurup yine yuvaya yazar.

#### Görünen Değişiklik ile Görünmeyen

- **SD38** — Görünürlük ölçümünde hiçbir kimlik sayısı basılmaz; kimlik yalnız `==` ile
  sorulur.
- **SD39** — Her ölçümden önce alan bilinen bir değere kurulur; böylece değişen ile
  değişmeyen ayrılabilir.
- **SD40** — Değiştirilemez tip ölçümünde ilkel tiple dizgi **aynı** biçimde denenir. İkisi
  için de yalnız yeniden bağlama yazılabilir, çünkü dizgide yazılabilir bir üye yoktur.

```java
// Gorunur.java — hangi değişiklik çağırana görünüyor
class Kayit {
    String ad;
    int adet;
    long agirlik;
}

public class Gorunur {
    static boolean ayniNesneMi(Kayit gelen, Kayit disaridaki) { return gelen == disaridaki; }
    static void alaniDegistir(Kayit k) { k.adet = 99; }
    static void yenidenBagla(Kayit k) { k = new Kayit(); k.adet = 99; }
    static void takas(Kayit a, Kayit b) { Kayit g = a; a = b; b = g; }
    static void diziyeYaz(int[] d) { d[0] = 99; }
    static void sayiyiArtir(int adet) { adet = adet + 1; }
    static void metniUzat(String s) { s = s + " x"; }

    public static void main(String[] args) {
        Kayit k = new Kayit();
        k.ad = "kuzey";
        k.adet = 12;
        System.out.println("parametre disaridakiyle ayni nesne mi: " + ayniNesneMi(k, k));

        alaniDegistir(k);
        System.out.println("alaniDegistir sonrasi adet : " + k.adet);
        k.adet = 12;
        yenidenBagla(k);
        System.out.println("yenidenBagla sonrasi adet  : " + k.adet);

        Kayit a = new Kayit();
        a.ad = "kuzey";
        Kayit b = new Kayit();
        b.ad = "guney";
        takas(a, b);
        System.out.println("takas sonrasi a.ad, b.ad   : " + a.ad + " " + b.ad);

        int[] d = {12, 18};
        diziyeYaz(d);
        System.out.println("diziyeYaz sonrasi d[0]     : " + d[0]);

        int adet = 12;
        sayiyiArtir(adet);
        String metin = "kuzey";
        metniUzat(metin);
        System.out.println("sayi ve metin sonrasi      : " + adet + " " + metin);
    }
}
```

```
parametre disaridakiyle ayni nesne mi: true
alaniDegistir sonrasi adet : 99
yenidenBagla sonrasi adet  : 12
takas sonrasi a.ad, b.ad   : kuzey guney
diziyeYaz sonrasi d[0]     : 99
sayi ve metin sonrasi      : 12 kuzey
```

İlk satır önceki bölümün cümlesini çalışma zamanında doğruluyor: parametredeki referans ile
dışarıdaki referans **aynı nesneyi** gösteriyor. Nesne kopyalanmadı. Bu yüzden ikinci satır
şaşırtıcı değil — alana yazmak çağırana görünüyor, `adet` **99** oldu.

Üçüncü satır ayrımı koyuyor. `yenidenBagla` da alana **99** yazıyor, ama önce parametreyi
yeni bir nesneye bağladığı için yazdığı yer başka bir nesnedir; çağıranın kaydı **12**
kalıyor. Farkı yapan şey yazmanın kendisi değil, **hangi nesneye** yazıldığıdır. Dördüncü
satır bunun en bilinen biçimidir: iki parametreyi takas eden yöntem çağırana hiçbir şey
yapmaz, çünkü takas ettiği şey iki yuvadır.

Beşinci satır kuralın diziye uygulanışıdır: dizi de bir nesnedir, parametredeki referans
aynı diziyi gösterir ve `d[0] = 99` yazması çağırana görünür.

Buradan uygulanabilir bir kural çıkar. Bir yöntemin çağırana görünen etkisi ancak
**erişilebilir bir nesnenin içine yazdığında** doğar; parametreye yapılan atama hiçbir
zaman görünmez. Çağıranın verdiği nesnenin değişmesini istemiyorsak, ya nesnenin
değiştirilebilir bir üyesi olmamalı ya da yöntem kendi kopyasını kurmalıdır.

#### Değiştirilemez Tipte Ayrım Gözlenmez

Son satır dersin sınırlayıcı ölçümüdür ve iki değeri yan yana koyar: sayı **12**, metin
**kuzey**. İkisi de değişmedi.

Sayının değişmemesi beklenendir; `adet` bir ilkel tiptir ve yuvada duran değer sayının
kendisidir. Asıl sonuç metindedir. `metniUzat` bir referans aldı, o referansın gösterdiği
nesne çağıranınkiyle aynıydı ve çağıran yine hiçbir değişiklik görmedi. Yanıtı önceki
ders ölçtü: dizgide yazılabilir bir üye yoktur. `s = s + " x"` satırı nesnenin içine
yazmaz, yazamaz; yeni bir dizgi kurup parametrenin yuvasına koyar.

Bunun ölçüm açısından anlamı şudur: **değiştirilemez bir tiple bu dersin sorusu
sorulamaz.** Dizgi parametresi bir ilkel tip parametresinden ayırt edilemez, dolayısıyla
dizgiye bakarak Java'nın hangi modeli kullandığına karar verilemez. Ayrımı gözlenebilir
kılan şey aktarım biçimi değil, **nesnenin değiştirilebilir bir üyesi olmasıdır**.

#### Özet

- Sınıf dosyasında argüman geçirmenin tek bir biçimi vardır: yuvadaki değeri yığıta koymak.
  Beş ayrı tipteki beş argüman beş yükleme komutuyla geçer; tek fark, değerin kapladığı
  yuva sayısıdır.
- Referans tiplerinde yuvada duran değer bir referanstır; kopyalanan şey nesne değil o
  referanstır ve parametre ile dışarıdaki ad aynı nesneyi gösterir.
- Parametrenin alanına yazmak `putfield` ile nesnenin içine gider ve çağırana **görünür**;
  parametreyi yeniden bağlamak `astore` ile yöntemin kendi yuvasına yazar ve **görünmez**.
- İki parametreyi takas eden yöntem çağırana hiçbir şey yapmaz; dizinin bir öğesine yazan
  yöntemin etkisi ise görünür, çünkü dizi de bir nesnedir.
- Değiştirilemez bir tipte ayrım gözlenmez: dizgi parametresi ilkel tip parametresinden
  ayırt edilemez, çünkü nesnenin içine yazılacak bir üye yoktur.

#### Kurs Kapanışı

Kurs tek bir soruyla açıldı: yazılan satır, sınıf dosyasında kaç adıma ve hangi adımlara
dönüşüyor? On ders bu soruyu on ayrı biçime sordu ve yanıtı her seferinde çalıştırılmış bir
ölçümden aldı. Toplandığında tek bir kural çıkıyor: **Java'da sözdizim bir kısaltmadır.**
Kaynak, sınıf dosyasına konan adımların tamamını göstermez; gösterdiği şey, o adımların
hangi biçimde isteneceğidir.

| Ders | Ölçülen biçim | Eklenen adım | Sınırlayıcı ölçüm |
|---|---|---|---|
| Kaynak, Bayt Kodu ve Sanal Makine | kaynak metni ile sınıf dosyasının karşılaştırılması | 12 yöntemin 8'inde, toplam 13 adım | kaynakta duran birleştirme dosyada hiç yok |
| Program Yaşam Döngüsü | giriş noktası ve başlatma sırası | üye eklenir: varsayılan yapıcı, 3 alan ve 5 yöntem | yapıcı yazılınca eklenen yapıcı kaybolur |
| Paketler ve İçe Aktarım | içe aktarım ile tam nitelikli ad | sıfır adım: iki dosya bayt bayt aynı | paket adı dosyayı beş bayt büyütür |
| İlkel Tipler ve Sarmalayıcılar | ilkel tip ile sarmalayıcı arasındaki geçiş | `Integer.valueOf` ve `Integer.intValue` | önbellek aralığında `==` doğru, dışında yanlış |
| Değişkenler ve Kapsam | bildirimin bulunduğu yer | alan komutları ya da yalnız bir yuva | blok kapsamı aynı 20 komutu, bir eksik yuvayla üretir |
| İşleçler ve İfadeler | sayısal yükseltme ve bileşik atama | `i2l` ve `i2b` çevirmeleri, kısa devrede dal | iki sabitte hiçbir adım eklenmez |
| Denetim Akışı | seçim ve döngü biçimleri | seçimde iki ayrı arama komutu; gelişmiş `for` listede 5 adım | aynı döngü dizide 24 komut ve sıfır adım üretir |
| Diziler | dizi üretimi, uzunluk ve erişim | dizi üretimi; `arraylength`; öğe başına yazma | kovaryansın tip denetimi komut sayısında görünmez |
| Dizgiler | birleştirme, havuz ve biriktirici | dinamik çağrı; biriktiricide üretim ve üç çağrı | iki sabitin birleştirilmesi 2 komut, sıfır adım |
| Değer ve Referans Aktarımı | argüman yükleme ve parametreye yazma | yeniden bağlamada üretim ve yuvaya yazma | değiştirilemez tipte ilkel tiple ayrım gözlenmez |

Tablonun son sütunu kursun yöntemidir. Her ders eklenen adımın **olmadığı** ya da
**kaybolduğu** bir durumu da ölçtü, çünkü her yerde bulunan bir şey hiçbir şeyi açıklamaz.
Sabit katlama, yazılmış bir yapıcı, önbellek aralığı, ilkel tipli dizi — her biri kuralın
sınırını çiziyor. Kursun ikinci okuması da buradan gelir: komut sayısı ile eklenen adım
aynı yöne gitmez ve daha az komut daha az iş demek değildir.

Bu kursta sınıf yalnız bir kap olarak kullanıldı. Oysa ölçümler boyunca sınıfın kendisi de
sorular üretti: kaynakta yazılmayan bir yapıcı, kaynakta yazılmayan üç alan ve beş yöntem,
bir kaydın alanına yazıldığında çağırana görünen değişiklik. Sıradaki kurs olan **Nesneye
Dayalı Java** sınıfı kap olmaktan çıkarıp konu yapar. Aynı ölçü orada da işini görecek —
bir çağrının hangi yönteme gittiğine kimin ve ne zaman karar verdiği yine sınıf
dosyasından okunabilir.

