---
title: 'Sınırlı Joker Karakterler'
source: 'https://academia.sh/tr/kurslar/java-nesne-modeli/sinirli-joker-karakterler'
course: 'Nesneye Dayalı Java'
language: tr
updated: '2026-08-17T18:09:41+00:00'
license: 'CC BY-SA 4.0'
---

# Sınırlı Joker Karakterler

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Özet

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

## Sonraki Adım

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