---
title: 'Sınıf, Nesne ve Yapıcı'
source: 'https://academia.sh/tr/kurslar/java-nesne-modeli/sinif-nesne-ve-yapici'
course: 'Nesneye Dayalı Java'
language: tr
updated: '2026-08-17T18:09:40+00:00'
license: 'CC BY-SA 4.0'
---

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

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

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

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

## Bir Ad, İki Tip

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

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

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

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

## Ölçüm Çekirdeği

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

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

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

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

    String etiket = "ust alan";

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

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

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

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

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

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

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

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

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

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

```
bildirilen tip Ust, calisma zamani tipi Alt

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

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

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

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

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

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

## Yarım Kurulmuş Nesnenin Bedeli

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Özet

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

## Sonraki Adım

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