← Kursa Dön
📄 Text · 12 min

Değişken Kapsamı ve Sabitler

Bir değişken nerede tanımlandıysa, orada yaşar ve orada ölür. Buna kapsam (scope) diyoruz. Kapsam kurallarını bilmezsen "değişken bulunamadı" hataları alırsın ya da daha kötüsü, yanlış değişkeni kullanırsın farkında olmadan.

Bu derste değişkenlerin nerede yaşadığını, final ile sabitleri, isimlendirme kurallarını ve Java 10+ ile gelen var keyword'ünü öğreneceğiz.


Kapsam (Scope) Nedir?

Kapsam, bir değişkenin erişilebilir olduğu kod bölgesidir. Genel kural basit: bir değişken tanımlandığı süslü parantezler `{}` içinde yaşar.

Bunu şöyle düşün: Her süslü parantez çifti bir oda. Bir odada tanımlanan eşya sadece o odada kullanılabilir. İç odadan dış odayı görebilirsin ama dış odadan iç odayı göremezsin.


Lokal Değişkenler (Local Variables)

Bir metot veya blok içinde tanımlanan değişkenler:

public void hesapla() {
    int x = 10; // x burada doğar
    
    if (x > 5) {
        int y = 20; // y burada doğar
        System.out.println(x + y); // 30 — x erişilebilir
    } // y burada ölür
    
    // System.out.println(y); // HATA! y bu scope'ta yok
    System.out.println(x);    // OK — x hâlâ yaşıyor
} // x burada ölür

Blok Kapsamı

Her {} yeni bir kapsam oluşturur:

public void ornekMetot() {
    int a = 1;
    
    {  // Anonim blok — yeni kapsam
        int b = 2;
        System.out.println(a + b); // 3 — dış değişkene erişir
    }
    
    // System.out.println(b); // HATA! b dış kapsamda yok
}

Döngü Kapsamı

// i sadece for döngüsü içinde yaşar
for (int i = 0; i < 5; i++) {
    System.out.println(i);
}
// System.out.println(i); // HATA! i burada yok

// Her iterasyonda yeni değişken
for (int j = 0; j < 3; j++) {
    int temp = j * 2; // Her iterasyonda yeni temp
    System.out.println(temp);
} // temp her iterasyon sonunda ölür
// while ile karşılaştır
int k = 0; // Döngü dışında tanımlanmalı
while (k < 5) {
    System.out.println(k);
    k++;
}
System.out.println(k); // 5 — k hâlâ erişilebilir

💡 Değişkeni mümkün olan en dar kapsamda tanımla. for döngüsünde sayaç gerekiyorsa döngü içinde tanımla — dışarıda gereksiz yere yaşamasın. Bu hem okunabilirliği artırır hem hata riskini azaltır.


Sınıf Alanları (Instance Variables / Fields)

Sınıf içinde ama metot dışında tanımlanan değişkenler. Nesne yaşadığı sürece yaşarlar.

public class Ogrenci {
    // Instance variables (alanlar)
    String isim;       // Tüm metotlardan erişilebilir
    int yas;
    double ortalama;
    
    // Varsayılan değer alırlar
    boolean aktif;     // false
    String adres;      // null
    
    public void bilgiYazdir() {
        // Alanlar metotların hepsinden erişilebilir
        System.out.println(isim + " - " + yas);
    }
    
    public void yasGuncelle(int yeniYas) {
        yas = yeniYas; // Alan'a erişim
    }
}

Lokal vs Alan — İsim Çakışması

Lokal değişken, aynı isimli alanı gölgeler (shadow):

public class Ornek {
    int x = 10; // Alan (field)
    
    public void metot() {
        int x = 20; // Lokal değişken — alanı gölgeler
        System.out.println(x);      // 20 (lokal)
        System.out.println(this.x); // 10 (alan)
    }
}

this keyword'ü ile alana açıkça erişebilirsin. Bu özellikle constructor'larda çok yaygın:

public class Kullanici {
    String isim;
    int yas;
    
    public Kullanici(String isim, int yas) {
        this.isim = isim; // this.isim = alan, isim = parametre
        this.yas = yas;
    }
}

Static Alanlar (Sınıf Değişkenleri)

static ile tanımlanan alanlar nesneye değil sınıfa aittir. Tüm nesneler arasında paylaşılır.

public class Sayac {
    static int toplamNesne = 0; // Sınıf değişkeni
    String isim;                 // Nesne değişkeni
    
    public Sayac(String isim) {
        this.isim = isim;
        toplamNesne++; // Her nesne oluştuğunda artar
    }
    
    public static void main(String[] args) {
        Sayac a = new Sayac("A");
        Sayac b = new Sayac("B");
        Sayac c = new Sayac("C");
        
        System.out.println(Sayac.toplamNesne); // 3
    }
}

final Keyword — Sabitler

final bir değişkenin değerinin bir kez atanıp bir daha değiştirilememesini sağlar.

final int MAX_PUAN = 100;
// MAX_PUAN = 200; // DERLEME HATASI!

final double PI = 3.14159265358979;
final String VARSAYILAN_DIL = "tr";

final Kullanım Yerleri

1. Sabit değerler (constant):

public class Ayarlar {
    // static final → gerçek sabit
    public static final int MAX_KULLANICI = 1000;
    public static final double KDV_ORANI = 0.18;
    public static final String API_URL = "https://api.example.com";
}

// Kullanım
double kdv = fiyat * Ayarlar.KDV_ORANI;

static final ile tanımlanan sabitler büyük harfle ve alt çizgiyle yazılır. Bu bir konvansiyon.

2. Metot parametreleri:

public void hesapla(final int deger) {
    // deger = 42; // HATA! final parametresi değiştirilemez
    System.out.println(deger * 2);
}

3. Lokal değişkenler:

public void islem() {
    final int sonuc = karmasikHesaplama();
    // sonuc artık değişmez — okuyucu bunu bilir
    System.out.println(sonuc);
}

final ve Nesneler — Dikkat!

final referansı sabitler, nesnenin içeriğini değil:

final List<String> liste = new ArrayList<>();
liste.add("Merhaba");   // OK! İçerik değişebilir
liste.add("Dünya");     // OK!
// liste = new ArrayList<>(); // HATA! Referans değişemez
final int[] dizi = {1, 2, 3};
dizi[0] = 99;              // OK! Dizi içeriği değişebilir
// dizi = new int[]{4, 5};  // HATA! Referans değişemez

⚠️ final "bu değişken hep aynı nesneye işaret edecek" demektir, "bu nesne değişmeyecek" değil. Gerçek immutability için nesnenin kendisi immutable olmalı (String gibi).


İsimlendirme Kuralları (Naming Conventions)

Java'da yazılmamış ama herkesin uyduğu kurallar:

NeKuralÖrnek
DeğişkencamelCaseogrenciSayisi, toplamFiyat
SabitSCREAMING_SNAKE_CASEMAX_BOYUT, PI, KDV_ORANI
MetotcamelCasehesaplaOrtalama(), getIsim()
SınıfPascalCaseOgrenciListesi, HttpClient
Paketlowercasecom.sirket.proje
// İyi isimlendirme
int ogrenciSayisi = 42;
double toplamFiyat = 199.99;
boolean aktifMi = true;
String kullaniciAdi = "ahmet";
final int MAX_DENEME = 3;

// Kötü isimlendirme
int x = 42;         // Ne bu?
double tp = 199.99;  // Kısaltma anlaşılmıyor
boolean flag = true;  // Ne flag'ı?
int SAYI = 42;       // Sabit değilse büyük harf kullanma

İsimlendirme İpuçları

// Boolean değişkenler soru gibi olsun
boolean ogrenciMi = true;
boolean aktif = false;
boolean girisYapildi = true;
// Kötü: boolean durum = true; (ne durumu?)

// Koleksiyonlar çoğul olsun
List<String> isimler = new ArrayList<>(); // tek: isim
Map<String, Integer> puanlar = new HashMap<>();

// Geçici değişkenler kısa olabilir (dar kapsam)
for (int i = 0; i < 10; i++) { ... }  // OK — herkes i'yi bilir
for (String s : liste) { ... }         // OK — kısa döngü

var Keyword (Java 10+)

Java 10 ile gelen var, lokal değişkenlerde tip çıkarımı (type inference) sağlar. Derleyici tipi sağ taraftan anlar.

// Geleneksel
String isim = "Ahmet";
int yas = 25;
List<String> liste = new ArrayList<>();
Map<String, List<Integer>> karmasik = new HashMap<>();

// var ile
var isim = "Ahmet";           // String olduğu belli
var yas = 25;                   // int olduğu belli
var liste = new ArrayList<String>();
var karmasik = new HashMap<String, List<Integer>>();

var özellikle uzun generic tiplerde çok işe yarar:

// Eski
Map<String, List<Map<Integer, String>>> veri = new HashMap<String, List<Map<Integer, String>>>();

// var ile
var veri = new HashMap<String, List<Map<Integer, String>>>();
// Tip hâlâ tam olarak belirleniyor, sadece yazmıyorsun

var Kuralları

// ✅ Lokal değişkenlerde kullanılabilir
var x = 42;
var s = "merhaba";
var liste = List.of(1, 2, 3);

// ✅ Enhanced for ve normal for döngüsünde
for (var eleman : liste) {
    System.out.println(eleman);
}

for (var i = 0; i < 10; i++) {
    System.out.println(i);
}

// ✅ try-with-resources'ta
try (var stream = new FileInputStream("dosya.txt")) {
    // ...
}
// ❌ Sınıf alanlarında KULLANILMAZ
// var alan = 42; // Derleme hatası!

// ❌ Metot parametrelerinde KULLANILMAZ
// public void metot(var x) { } // Hata!

// ❌ Dönüş tipinde KULLANILMAZ
// public var getIsim() { } // Hata!

// ❌ null ile KULLANILMAZ (tip çıkarılamaz)
// var x = null; // Hata!

// ❌ İlk değer vermeden KULLANILMAZ
// var x; x = 42; // Hata!

var Ne Zaman Kullanmalı?

// İYİ: Tip sağ taraftan açıkça belli
var isim = "Ahmet";                    // String belli
var sayilar = new ArrayList<Integer>(); // Tip belli
var dosya = new File("test.txt");       // File belli

// TARTIŞMALI: Tip metot adından anlaşılıyor
var sonuc = hesaplaOrtalama(notlar);   // double mı? int mi?
var veri = servis.getKullanici(id);    // Kullanici mı? Optional mı?

// KÖTÜ: Tip hiç belli değil
var x = metot();     // Ne dönüyor? Okunmuyor.
var sonuc = isle();  // ???

💡 Kural: var kullan ama okunabilirliği bozmadan. Sağ taraftan tip anlaşılmıyorsa açık tip yaz. var daha az yazmak için değil, daha okunabilir kod için kullanılmalı.


Değişken Tanımlama Best Practice'leri

1. Mümkün olan en dar kapsamda tanımla:

// Kötü — gereksiz geniş kapsam
int sonuc;
// ... 50 satır kod ...
sonuc = hesapla();
// ... 30 satır daha ...
System.out.println(sonuc);

// İyi — kullanıma yakın tanımla
// ... 50 satır kod ...
int sonuc = hesapla();
// hemen kullan
System.out.println(sonuc);

2. Değişmeyecekse final yap:

final int port = config.getPort();
final String url = config.getUrl();
// Kodun geri kalanında bunların değişmeyeceğini biliyorsun

3. Bir değişkeni birden fazla amaç için kullanma:

// Kötü
int temp = kullanici.getYas();
// ... bir şeyler yap ...
temp = urun.getFiyat(); // Aynı değişken, farklı amaç!

// İyi
int yas = kullanici.getYas();
// ... bir şeyler yap ...
double fiyat = urun.getFiyat(); // Farklı değişken

4. Anlamlı isim ver:

// Kötü
int d; // gün mü? mesafe mi? fark mı?

// İyi
int gunSayisi;
int mesafeKm;
int farkDakika;

5. Magic number kullanma — sabit tanımla:

// Kötü — 86400 ne demek?
if (gecenSure > 86400) {
    System.out.println("Süre aşıldı");
}

// İyi — anlaşılır
final int BIR_GUN_SANIYE = 86400;
if (gecenSure > BIR_GUN_SANIYE) {
    System.out.println("Süre aşıldı");
}

// Daha iyi — açıklayıcı hesaplama
final int BIR_GUN_SANIYE = 24 * 60 * 60; // 86400

Magic number, kodda açıklamasız kullanılan sayısal değerlerdir. Okuyucu ne anlama geldiğini bilemez. Sabit tanımlayarak kodun kendini açıklamasını sağla.


Record Sınıflar ve Kapsam (Java 16+)

Record sınıfları otomatik olarak final alanlar oluşturur:

// Record — tüm alanlar otomatik final ve private
record Koordinat(double x, double y) {
    // x ve y otomatik final — değiştirilemez
    // Constructor, getter, equals, hashCode, toString otomatik
}

Koordinat nokta = new Koordinat(3.5, 7.2);
System.out.println(nokta.x());     // 3.5
System.out.println(nokta.y());     // 7.2
System.out.println(nokta);         // Koordinat[x=3.5, y=7.2]
// nokta.x = 5.0; // HATA! final alan

Record'lar immutable veri taşıyıcıları oluşturmanın en kısa yolu. Tüm alanlar final olduğu için thread-safe'dir.


Pratik Örnek: Kapsam ve Sabitler Bir Arada

public class SicaklikIstasyonu {
    // Sabitler
    static final double MUTLAK_SIFIR_CELSIUS = -273.15;
    static final String BIRIM = "°C";
    
    // Alan
    private final String istasyonAdi;
    private double sonOlcum;
    
    public SicaklikIstasyonu(String istasyonAdi) {
        this.istasyonAdi = istasyonAdi; // final alan, constructor'da atanır
    }
    
    public void olcumYap(double sicaklik) {
        // Lokal değişken — sadece bu metotta yaşar
        final boolean gecerli = sicaklik > MUTLAK_SIFIR_CELSIUS;
        
        if (gecerli) {
            this.sonOlcum = sicaklik;
            
            // Blok kapsamı
            var mesaj = String.format("%s: %.1f%s", 
                istasyonAdi, sicaklik, BIRIM);
            System.out.println(mesaj);
        } else {
            System.out.println("Geçersiz ölçüm: " + sicaklik);
        }
        
        // mesaj burada erişilemez — if bloğu kapsamında kaldı
    }
    
    public static void main(String[] args) {
        var istasyon = new SicaklikIstasyonu("Ankara");
        istasyon.olcumYap(23.5);  // Ankara: 23.5°C
        istasyon.olcumYap(-300);  // Geçersiz ölçüm: -300.0
    }
}

Metot Parametresi Kapsamı

Metot parametreleri de lokal değişken gibidir — sadece o metot içinde geçerlidir:

public void selamla(String isim, int tekrar) {
    // isim ve tekrar sadece burada yaşar
    for (int i = 0; i < tekrar; i++) {
        System.out.println("Merhaba " + isim);
    }
}
// isim ve tekrar burada erişilemez

Parametre ismi ile alan ismi aynı olabilir — bu durumda parametre alanı gölgeler:

public class Kullanici {
    private String isim;
    
    // Parametre "isim" alanı gölgeler
    public void setIsim(String isim) {
        // isim → parametre
        // this.isim → alan
        this.isim = isim;
    }
}

Switch İçinde Kapsam

switch bloğundaki kapsam kuralları dikkat ister:

// Eski stil switch — case'ler aynı kapsamı paylaşır!
switch (gun) {
    case 1:
        String mesaj = "Pazartesi";  // mesaj burada tanımlanır
        System.out.println(mesaj);
        break;
    case 2:
        // String mesaj = "Salı"; // HATA! mesaj zaten tanımlı
        mesaj = "Salı";           // Mevcut mesaj'ı kullan
        System.out.println(mesaj);
        break;
}

// Süslü parantez ile kapsam oluştur
switch (gun) {
    case 1: {
        String mesaj = "Pazartesi"; // Bu mesaj sadece bu blokta
        System.out.println(mesaj);
        break;
    }
    case 2: {
        String mesaj = "Salı"; // OK! Farklı kapsam
        System.out.println(mesaj);
        break;
    }
}

// Java 14+ switch expression — her case kendi kapsamında
String mesaj = switch (gun) {
    case 1 -> "Pazartesi";
    case 2 -> "Salı";
    default -> "Bilinmeyen";
};

Try-Catch İçinde Kapsam

// Yanlış — değişken try bloğunda kaldı
try {
    int sonuc = riskliBirIslem();
} catch (Exception e) {
    System.out.println("Hata: " + e.getMessage());
}
// System.out.println(sonuc); // HATA! sonuc burada yok

// Doğru — dışarıda tanımla
int sonuc = 0;
try {
    sonuc = riskliBirIslem();
} catch (Exception e) {
    System.out.println("Hata: " + e.getMessage());
}
System.out.println(sonuc); // OK
// try-with-resources kapsamı
try (var reader = new BufferedReader(new FileReader("dosya.txt"))) {
    String satir = reader.readLine();
    System.out.println(satir);
} // reader burada otomatik kapatılır ve kapsam dışına çıkar
// reader burada erişilemez

Effectively Final (Java 8+)

Lambda ifadelerinde ve anonim sınıflarda dış kapsamdaki değişkenleri kullanabilirsin — ama sadece effectively final olanlları (değeri atandıktan sonra değişmeyen):

String sehir = "İstanbul"; // effectively final — değeri hiç değişmiyor

List<String> isimler = List.of("Ali", "Veli", "Ayşe");
isimler.forEach(isim -> {
    System.out.println(isim + " - " + sehir); // OK! sehir effectively final
});

// Ama bu olmaz:
int sayac = 0;
isimler.forEach(isim -> {
    // sayac++; // HATA! sayac effectively final değil
    System.out.println(isim);
});

"Effectively final" demek: final yazmamışsın ama yazsan da derleme hatası vermeyecek — yani değeri hiç değişmiyor.

// Bu ikisi eşdeğer:
String a = "test";         // effectively final
final String b = "test";   // explicitly final

// İkisi de lambda'da kullanılabilir
Runnable r = () -> System.out.println(a + b);

Gerçek Proje Örneği: Konfigürasyon Sabitleri

public class AppConfig {
    // Uygulama sabitleri — değişmez, paylaşılır
    public static final String APP_NAME = "MyApp";
    public static final String VERSION = "2.1.0";
    public static final int MAX_CONNECTIONS = 100;
    public static final int TIMEOUT_MS = 30_000;
    public static final String DB_URL = "jdbc:mysql://localhost:3306/mydb";
    
    // Environment'tan okunan "sabit" — runtime'da belirlenir
    public static final String API_KEY;
    
    // Static initializer block
    static {
        String key = System.getenv("API_KEY");
        API_KEY = (key != null) ? key : "default-key";
    }
    
    // Private constructor — instance oluşturulmasın
    private AppConfig() {
        throw new AssertionError("Instance oluşturulamaz");
    }
}

// Kullanım
System.out.println(AppConfig.APP_NAME + " v" + AppConfig.VERSION);

Bu pattern gerçek projelerde çok yaygın. Sabitler static final olarak bir sınıfta toplanır, kodun her yerinden erişilir.


Sık Yapılan Hatalar

1. Kapsam dışında erişim:

if (true) {
    int x = 10;
}
// System.out.println(x); // HATA! x if bloğunda kaldı

// Çözüm: dışarıda tanımla
int x = 0;
if (true) {
    x = 10;
}
System.out.println(x); // 10

2. Lokal değişkeni initialize etmeden kullanmak:

int x;
// System.out.println(x); // HATA! Atanmamış

// Tüm yollar initialize etmeli
int y;
if (kosul) {
    y = 10;
} else {
    y = 20;
}
System.out.println(y); // OK — her durumda atanmış

3. final nesne içeriğinin değişebileceğini unutmak:

final List<String> list = new ArrayList<>();
list.add("test"); // Bu çalışır! final referans, içerik değil

// Gerçek immutability istiyorsan:
final List<String> immutable = List.of("a", "b", "c");
// immutable.add("d"); // UnsupportedOperationException!

4. var'ı her yerde kullanmak:

var s = servis.isle(); // Ne dönüyor? Object? String? List?
// Tip belli değilse var kullanma

// İyi: tip sağ taraftan belli
var users = new ArrayList<String>();
var config = new HashMap<String, Object>();

5. Aynı isimli iç içe değişken (shadowing farkında olmamak):

int x = 10;
if (true) {
    // int x = 20; // HATA! Java'da aynı isimli lokal değişken
    // (C/C++'tan farklı olarak Java buna izin vermez)
    x = 20; // Bu OK — mevcut x'i günceller
}

Özet

  • Değişken tanımlandığı `{}` bloğu içinde yaşar — bu kapsam (scope) kuralıdır

  • Lokal değişkenler varsayılan değer almaz, kullanmadan önce mutlaka initialize et

  • `final` değişkeni sabitler — bir kez atandıktan sonra değiştirilemez. static final ile gerçek sabitler oluştur

  • final referansı sabitler, nesne içeriğini değilfinal List içine eleman eklenebilir

  • İsimlendirme: değişkenler camelCase, sabitler SCREAMING_SNAKE_CASE, sınıflar PascalCase

  • `var` (Java 10+) lokal değişkenlerde tip çıkarımı sağlar — okunabilirliği bozmadan kullan, her yerde değil

  • Effectively final değişkenler lambda ifadelerinde kullanılabilir — değeri atandıktan sonra değişmeyen lokal değişkenler

  • Magic number kullanma — açıklamasız sayısal değerler yerine anlamlı sabitler tanımla


Mülakat Soruları

S: Lokal değişken ile instance variable farkı? C: Lokal değişkenler metot/blok içinde tanımlanır, varsayılan değer almaz, stack'te yaşar. Instance variable'lar sınıfta tanımlanır, varsayılan değer alır (0, null, false), heap'te nesneyle birlikte yaşar.

S: final ile immutable farkı? C: final referansı sabitler — değişken başka nesneye atanamaz. Ama nesnenin içeriği değişebilir (final List'e eleman eklenebilir). Immutable ise nesnenin kendisinin değişmez olması demek (String gibi).

S: var keyword'ü tipi kaldırır mı? C: Hayır. Java hâlâ statik tipli. var sadece derleyicinin tipi sağ taraftan çıkarmasını sağlar. Derleme sonrası tam tip bilgisi vardır. Runtime'da fark yok.