---
title: 'Statik Üyeler ve Başlatma Blokları'
source: 'https://academia.sh/tr/kurslar/java-nesne-modeli/statik-uyeler-ve-baslatma-bloklari'
course: 'Nesneye Dayalı Java'
language: tr
updated: '2026-08-17T18:09:41+00:00'
license: 'CC BY-SA 4.0'
---

# Statik Üyeler ve Başlatma Blokları

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

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

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

## Ölçüm Çekirdeği

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

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

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

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

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

## Aynı Yazım, İki Taraf

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Başlatmayı Hangi Erişim Tetikler

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Özet

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

## Sonraki Adım

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