---
title: 'İç ve Yerel Sınıflar'
source: 'https://academia.sh/tr/kurslar/java-nesne-modeli/ic-ve-yerel-siniflar'
course: 'Nesneye Dayalı Java'
language: tr
updated: '2026-08-17T18:09:41+00:00'
license: 'CC BY-SA 4.0'
---

# İç ve Yerel Sınıflar

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

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

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

## Ölçüm Çekirdeği

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Paylaşılan Alan, Kopyalanan Yerel

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Özet

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

## Sonraki Adım

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