---
title: '`final` Anahtar Sözcüğü'
source: 'https://academia.sh/tr/kurslar/java-nesne-modeli/final-anahtar-sozcugu'
course: 'Nesneye Dayalı Java'
language: tr
updated: '2026-08-17T18:09:41+00:00'
license: 'CC BY-SA 4.0'
---

# `final` Anahtar Sözcüğü

Tek bir anahtar sözcük üç ayrı kısıt koyar ve üçünü de derleme zamanında uygular: yeniden atama, yeniden tanımlama ve genişletme. Sabit bir final alanın değeri okuyan sınıfa yazılır ve kaynak değiştiğinde eski değer yerinde kalır; final bir referans ise nesneyi dondurmaz.

Önceki dersin son ölçümünde bir değer, ait olduğu sınıfı hiç başlatmadan okundu: `Ust.TAVAN`
bir `static final` alandı ve sınıf başlatma bloğu çalışmadan doğru sayı geldi. O sonucun
nedeni tek bir sözcükte, `final` sözcüğünde duruyordu ve orada yalnız bir yan etki olarak
göründü.

Bu ders o sözcüğü doğrudan ölçer. `final` üç ayrı yerde yazılabilir — bir değişkenin, bir
yöntemin ve bir sınıfın başında — ve üç ayrı kısıt koyar. Üç kısıtın ortak yanı, hepsinin
kararının **derleme zamanında** verilmesidir: hiçbiri çalışma zamanında sınanan bir koşul
değildir. Sorular üç tanedir: bu kısıtların kaçı derleyicide durur, sabit bir alanın değeri
nereye yazılır, ve `final` bir referans neyi korur?

## Ölçüm Çekirdeği

Ölçüm üç şey yapar: bir kaynağı derler ve derlenip derlenmediğini bildirir, ürettiği sınıf
dosyasındaki `final` bayraklarını okur, ve derlenmiş sınıfları ayrı bir yükleyicide
çalıştırır.

- **SN26** — Derleme denemelerinde kâhin kurgunun kendisidir: her denemede tek bir satır
  değişir, dolayısıyla derlemenin durması yalnız o satıra bağlanabilir. Sonuç tek bir çıkış
  koduyla okunur; hata metni okunmaz, çünkü metin yerel ayara bağlıdır.
- **SN27** — Her deneme kendi geçici dizininde derlenir; denemeler birbirinin ürettiği sınıf
  dosyasını görmez.
- **SN28** — Gömme ölçümünde `Sabit` iki kez derlenir, `Kullanan` **bir kez**; ikinci koşumda
  okunan sınıf dosyası birincisiyle aynı dosyadır.
- **SN29** — Koşum ayrı bir yükleyicide yapılır, böylece ilk koşumda yüklenen sınıf ikincisine
  taşınmaz.
- **SN30** — Ortama bağlı hiçbir veri okunmaz; sayılan şey derlenen deneme sayısı, sınıf
  dosyasındaki bayraklar ve okunan değerdir.

```java
// Olcek.java — derleme sonucu, sınıf dosyasındaki final bayrağı ve okuyan tarafın izi
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.instruction.*;
import java.lang.reflect.AccessFlag;
import java.net.URL;
import java.net.URLClassLoader;
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 String finalBayraklari(Path d, String sinif) throws Exception {
        ClassModel cm = ClassFile.of().parse(d.resolve(sinif + ".class"));
        List<String> f = new ArrayList<>();
        if (cm.flags().has(AccessFlag.FINAL)) f.add("sinif " + sinif);
        for (FieldModel x : cm.fields())
            if (x.flags().has(AccessFlag.FINAL)) f.add("alan " + x.fieldName().stringValue());
        for (MethodModel x : cm.methods())
            if (x.flags().has(AccessFlag.FINAL)) f.add("yontem " + x.methodName().stringValue());
        return String.join(", ", f);
    }

    static List<String> izler(Path d, String sinif, String yontem) throws Exception {
        ClassModel cm = ClassFile.of().parse(d.resolve(sinif + ".class"));
        List<String> iz = 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 FieldInstruction f)
                    iz.add("alan okumasi " + kisa(f.owner().asInternalName())
                            + "." + f.name().stringValue());
                else if (e instanceof ConstantInstruction c)
                    iz.add("sabit yukleme " + c.constantValue());
                else if (e instanceof InvokeInstruction iv)
                    iz.add(switch (iv.opcode()) {
                        case INVOKEVIRTUAL -> "sanal cagri ";
                        case INVOKESTATIC -> "statik cagri ";
                        default -> "dogrudan cagri ";
                    } + kisa(iv.owner().asInternalName()) + "." + iv.name().stringValue());
            }
        }
        return iz;
    }

    static Object calistir(Path d, String sinif, String yontem) throws Exception {
        try (URLClassLoader cl = new URLClassLoader(new URL[] {d.toUri().toURL()}, null)) {
            var m = cl.loadClass(sinif).getDeclaredMethod(yontem);
            m.setAccessible(true);
            return m.invoke(null);
        }
    }

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

## Üç Kısıt, Üçü de Derleme Zamanında

İlk ölçüm dokuz kaynak derler. Denemeler çiftler hâlinde kurulmuştur: her `final` denemesinin
yanında, tek farkı o sözcüğün yokluğu olan bir eşi vardır.

```java
// Kisit.java — final üç ayrı kısıt koyar, üçü de derleme zamanında
import java.nio.file.Path;

public class Kisit {
    static final String[][] DENEME = {
        {"yerel degiskene yeniden atama",  "class T { void f() { int y = 1; y = 2; } }"},
        {"final yerele yeniden atama",     "class T { void f() { final int y = 1; y = 2; } }"},
        {"alana yeniden atama",            "class T { int a; T() { a = 1; } void f() { a = 2; } }"},
        {"final alana yeniden atama",      "class T { final int a; T() { a = 1; } void f() { a = 2; } }"},
        {"final alana yapicida atama",     "class T { final int a; T() { a = 1; } }"},
        {"yontemi yeniden tanimlama",      "class A { int f() { return 1; } } class T extends A { @Override int f() { return 2; } }"},
        {"final yontemi yeniden tanimlama","class A { final int f() { return 1; } } class T extends A { int f() { return 2; } }"},
        {"sinifi genisletme",              "class A { } class T extends A { }"},
        {"final sinifi genisletme",        "final class A { } class T extends A { }"},
    };

    public static void main(String[] args) throws Exception {
        System.out.printf("%-33s %s%n", "denenen yazim", "derleniyor mu");
        for (String[] d : DENEME)
            System.out.printf("%-33s %s%n", d[0], Olcek.derle(Olcek.yeniDizin(), "T", d[1]));

        String kaynak = """
            final class Bayrak {
                final int sabitAlan = 1;
                int digerAlan = 2;
                final int kilitli() { return 1; }
                int serbest() { final int y = 1; return y; }
            }
            """;
        Path d = Olcek.yeniDizin();
        Olcek.derle(d, "Bayrak", kaynak);
        System.out.println();
        System.out.println("sinif dosyasinda final bayragi tasiyanlar:");
        System.out.println("  " + Olcek.finalBayraklari(d, "Bayrak"));

        Path e = Olcek.yeniDizin();
        Olcek.derle(e, "A", """
            class A { final int kilitli() { return 1; } int serbest() { return 2; } }
            class Cagiran {
                static int a(A x) { return x.kilitli(); }
                static int b(A x) { return x.serbest(); }
            }
            """);
        System.out.println();
        System.out.println("final yontem cagrisi        : " + Olcek.izler(e, "Cagiran", "a"));
        System.out.println("final olmayan yontem cagrisi: " + Olcek.izler(e, "Cagiran", "b"));
    }
}
```

```
denenen yazim                     derleniyor mu
yerel degiskene yeniden atama     true
final yerele yeniden atama        false
alana yeniden atama               true
final alana yeniden atama         false
final alana yapicida atama        true
yontemi yeniden tanimlama         true
final yontemi yeniden tanimlama   false
sinifi genisletme                 true
final sinifi genisletme           false

sinif dosyasinda final bayragi tasiyanlar:
  sinif Bayrak, alan sabitAlan, yontem kilitli

final yontem cagrisi        : [sanal cagri A.kilitli]
final olmayan yontem cagrisi: [sanal cagri A.serbest]
```

Dokuz denemenin **dördü** durdu ve dördü de bir `final` denemesiydi. Eşleri sorunsuz derlendi;
tek fark o sözcüktür. Beşinci satır kısıtın tam yerini gösterir: `final` bir alana atama
yasak değildir, **yeniden** atama yasaktır. Alan yapıcıda bir kez atanabilir ve orada
atanmazsa derleme yine durur; kısıt "hiç yazılmasın" değil, "tam bir kez yazılsın" biçimindedir.

Yedinci satır kursun sorusuna bağlanır. Bir yöntem `final` bildirildiğinde ona alt sınıftan
başka bir yanıt yazmak olanaksızdır. Önceki derste bir çağrının **iki** olası yanıtı vardı ve
hangisinin geleceğini çalışma zamanı tipi belirliyordu. `final` bu sayıyı **bire** indirir:
çalışma zamanı tipi ne olursa olsun tek bir gövde kalır, dolayısıyla soru anlamını yitirir.
`final` çalışma zamanı tipinin kararını bildirilen tipe taşımaz; kararı **ortadan kaldırır**.

Çıktının son iki satırı bu okumayı bir adım keskinleştiriyor. `final` bir yöntemin çağrısı da,
`final` olmayanınki de sınıf dosyasına **sanal çağrı** olarak yazılıyor; iki satır arasında
hiçbir fark yok. Yani derleyici çağrının düzeneğini değiştirmiyor, çağrının seçebileceği
gövde kümesini bire indiriyor. Önceki dersteki statik çağrı ile arasındaki fark budur: orada
yazım değişmeden **hedef** değişiyordu, burada hedef seçimi yerinde duruyor ama seçilecek
tek bir aday kalıyor.

Ortadaki blok kısıtların nerede durduğunu ayırır. Sınıf dosyasında `final` bayrağını üç şey
taşıyor: sınıfın kendisi, `sabitAlan` ve `kilitli` yöntemi. `serbest` yönteminin gövdesindeki
`final int y` bildirimi listede **yok**. Yerel değişkenin kısıtı sınıf dosyasına hiç
yazılmaz; o kısıt yalnız derleyicinin belleğinde yaşar ve derleme bittiğinde ortadan kalkar.
Öteki ikisi bayrak olarak kalır, çünkü onları **başka bir derleme** okuyacaktır: bir alt sınıf
yazan derleyici `kilitli` yönteminin bayrağını görmek zorundadır.

## Sabit Değer Nereye Yazılıyor

Önceki dersin `TAVAN` ölçümü bir sorun bırakmıştı: sınıf başlatılmadığı hâlde değer nereden
geldi? Aşağıdaki ölçüm iki alanı yan yana koyar. `TAVAN` değerini doğrudan bir sayıdan alır;
`TABAN` aynı sayıyı bir yöntem çağrısından alır. İkisi de `static final`dır.

```java
// Gomme.java — sabit final alanın değeri okuyan sınıfa yazılır
import java.nio.file.Path;

public class Gomme {
    static String sabit(int n) {
        return """
            class Sabit {
                static final int TAVAN = %d;
                static final int TABAN = hesapla();
                static int hesapla() { return %d; }
            }
            """.formatted(n, n);
    }

    static final String KULLANAN = """
        class Kullanan {
            static int tavan() { return Sabit.TAVAN; }
            static int taban() { return Sabit.TABAN; }
            static String deger() { return "TAVAN=" + tavan() + " TABAN=" + taban(); }
        }
        """;

    public static void main(String[] args) throws Exception {
        Path d = Olcek.yeniDizin();
        Olcek.derle(d, "Sabit", sabit(100));
        Olcek.derle(d, "Kullanan", KULLANAN);
        for (String y : new String[] {"tavan", "taban"})
            System.out.println("Kullanan." + y + "() icindeki iz: " + Olcek.izler(d, "Kullanan", y));
        System.out.println();
        System.out.println("ilk kosum    : " + Olcek.calistir(d, "Kullanan", "deger"));
        Olcek.derle(d, "Sabit", sabit(200));
        System.out.println("Sabit 200 ile yeniden derlendi, Kullanan derlenmedi");
        System.out.println("ikinci kosum : " + Olcek.calistir(d, "Kullanan", "deger"));
    }
}
```

```
Kullanan.tavan() icindeki iz: [sabit yukleme 100]
Kullanan.taban() icindeki iz: [alan okumasi Sabit.TABAN]

ilk kosum    : TAVAN=100 TABAN=100
Sabit 200 ile yeniden derlendi, Kullanan derlenmedi
ikinci kosum : TAVAN=100 TABAN=200
```

İlk iki satır soruyu yanıtlıyor. `tavan` yönteminin sınıf dosyasında `Sabit` sınıfına hiçbir
referans yok; yalnız **100 sayısının kendisi** var. `taban` yönteminde ise bir alan okuması
duruyor ve o okumanın sahibi `Sabit`tir. Kaynakta iki satır birbirinin aynıdır — ikisi de bir
noktadan sonra bir alan adı yazar — ama biri değeri okuyan sınıfa **kopyalamış**, öteki bir
bağ **bırakmıştır**.

Kopyalamanın bedeli ikinci koşumda görünüyor. `Sabit` 200 değeriyle yeniden derlendi;
`Kullanan` derlenmedi ve diskte eski hâliyle duruyor. Çalıştırıldığında `TABAN` yeni değeri,
**200**'ü veriyor: bağ duruyordu, bağ üzerinden okundu. `TAVAN` ise hâlâ **100** diyor.
Kaynağa bakan biri `Sabit` içinde 200 görecek, çalışan programda 100 okuyacaktır.

Bu, kursun sorduğu sorunun en sert biçimidir. Karar burada bildirilen tiple çalışma zamanı
tipi arasında değil, **derleme zamanıyla çalışma zamanı** arasında bölünmüştür — ama sonucu
aynıdır: iki taraftan hangisinin yanıt verdiğini bilmeyen bir programcı, kaynakta gördüğü
değerin çalıştığını sanır. Kusur `Sabit`i değiştiren kişide de değildir; `Kullanan`ın yeniden
derlenmemesindedir ve hiçbir yerde bir hata iletisi üretmez.

## Sınırlayıcı Ölçüm: `final` Referans Nesneyi Dondurmaz

Üç kısıt da ölçüldü ve hepsi derleme zamanında duruyor. Şimdi bu tezin sınırı: `final` bir ada
bağlı nesnenin **içi** ne oluyor?

```java
// Dondur.java — final referans nesneyi dondurmaz
import java.util.Arrays;

public class Dondur {
    static class Kayit {
        String ad;
        int adet;
        Kayit(String ad, int adet) { this.ad = ad; this.adet = adet; }
        @Override public String toString() { return ad + ":" + adet; }
    }

    static final String GOVDE = """
        class Deneme {
            static class Kayit { int adet; }
            void f() { final Kayit k = new Kayit(); %s }
        }
        """;

    public static void main(String[] args) throws Exception {
        final Kayit k = new Kayit("civata", 12);
        final int[] agirlik = {10, 20, 30};
        Kayit ilk = k;
        System.out.println("baslangic        : " + k + " " + Arrays.toString(agirlik));
        k.ad = "somun";
        k.adet = 99;
        agirlik[0] = 99;
        System.out.println("degisiklikten son: " + k + " " + Arrays.toString(agirlik));
        System.out.println("referans ayni mi : " + (k == ilk));
        System.out.println();
        for (String[] y : new String[][] {{"k.adet = 1;", "alani degistirme"},
                                          {"k = new Kayit();", "referansi yeniden baglama"}})
            System.out.printf("%-26s -> derleniyor mu: %s%n", y[1],
                    Olcek.derle(Olcek.yeniDizin(), "Deneme", GOVDE.formatted(y[0])));
    }
}
```

```
baslangic        : civata:12 [10, 20, 30]
degisiklikten son: somun:99 [99, 20, 30]
referans ayni mi : true

alani degistirme           -> derleniyor mu: true
referansi yeniden baglama  -> derleniyor mu: false
```

`k` bir `final` yerel değişkendir ve iki alanının ikisi de değişti; `agirlik` de `final`dır ve
ilk öğesi değişti. Buna karşılık `k == ilk` hâlâ **doğru**: referans hiç değişmedi. Korunan
şey nesne değil, **ad ile nesne arasındaki bağdır**.

Son iki satır ayrımı tek ölçüye indiriyor. Aynı `final` bildirimi üzerinde alanı değiştiren
satır derleniyor, referansı yeniden bağlayan satır derlenmiyor. `final`, Java Temelleri
kursunda ölçülen aktarım modelinin üstüne bir kısıt koyar ve o kısıt yalnız **bağlamaya**
değer: değerin kendisi bir referans olduğunda, referansın gösterdiği nesne bu kısıtın dışında
kalır.

Buradan iki sonuç çıkar. Birincisi, `final` bir alan **değiştirilemezlik** değildir; bir tipin
değiştirilemez olması için alanlarının `final` olması yetmez, o alanların gösterdiği nesnelerin
de değişmemesi gerekir. İkincisi, ölçümün gözlenemediği durum vardır: `agirlik` yerine bir
sayı ya da bir dizgi konsaydı ayrım hiç görünmezdi, çünkü o değerlerin değiştirilebilecek bir
içi yoktur. Kısıtın yetip yetmediği, `final` sözcüğüne değil **tipe** bağlıdır.

## Özet

- `final` üç ayrı kısıt koyar ve dokuz denemenin dördü tam bu sözcük yüzünden durur: yeniden
  atama, yeniden tanımlama ve genişletme.
- Kısıt "hiç atanmasın" değil, "tam bir kez atansın" biçimindedir; `final` bir alan yapıcıda
  atanabilir.
- `final` bir yöntem çalışma zamanı tipinin verebileceği yanıt sayısını ikiden **bire**
  indirir; kararı öteki tarafa taşımaz, ortadan kaldırır.
- Sınıf dosyasında `final` bayrağını sınıf, alan ve yöntem taşır; yerel değişkenin kısıtı hiç
  yazılmaz, çünkü onu başka bir derlemenin okumasına gerek yoktur.
- Sabit bir `static final` alanın değeri okuyan sınıfa yazılır: kaynak 200 olduğu hâlde
  yeniden derlenmeyen okuyucu **100** okumayı sürdürür, hesaplanan alan ise **200** verir.
- `final` bir referans nesneyi dondurmaz; alanları ve dizinin öğeleri değişir, yalnız ad ile
  nesne arasındaki bağ korunur.

## Sonraki Adım

Bu dersin ölçümünde bir kısıt sınıf dosyasına hiç yazılmadı: `final` bir yerel değişkenin
kısıtı yalnız derleyicide durdu ve derleme bittiğinde ortadan kalktı. Öyleyse o kısıt neye
yarıyor? Java'da derleyicinin bu sözü **yazılmadığı hâlde kendiliğinden istediği** bir yer
vardır: bir yöntemin gövdesi içinde tanımlanan bir sınıf, dışarıdaki yerel değişkeni
kullandığında o değişkenin bir daha atanmamasını şart koşar. Şartın nedeni, o değişkenin
oraya kopyalanmasıdır — ve kopyayı taşımak için ortaya kaynakta hiç yazılmamış yeni sınıf
dosyaları çıkar. Sonraki ders bu dosyaları sayar: iç içe yazılan bir sınıf kaç ayrı dosya
üretiyor, hangileri dış örneğe gizli bir alan taşıyor, ve o gizli alanla kopyalanan değer
arasındaki fark ne zaman görünür oluyor?
