Scope ve Lifetime
Giriş — Değişkenler Nerede Yaşar?
Bir değişken tanımladığında, o değişken her yerde erişilebilir değil. Sadece belirli bir bölgede geçerlidir. Bu bölgeye scope (kapsam) denir. Scope dışına çıktığında değişken yok olur — adına lifetime (ömür) diyoruz.
Scope'u anlamak önemli çünkü:
Aynı isimli değişkenlerin birbirini gölgelemesini (shadowing) anlamak için
Hataları (değişken bulunamadı, yanlış değişkene erişim) önlemek için
Temiz, bakımı kolay kod yazmak için
Analoji: Scope'u bir evin odaları gibi düşün. Oturma odasındaki kitap sadece oturma odasında erişilebilir. Yatak odasına geçtiğinde o kitabı göremezsin. Ama evin girişindeki (koridor) bir tablo tüm odalardan görülebilir.
Blok Scope (Block Scope)
Java'da bir blok, süslü parantezler { } arasındaki bölgedir. Blok içinde tanımlanan değişken, sadece o blok ve iç bloklar içinde geçerlidir.
{
int x = 10;
System.out.println(x); // OK — aynı blokta
}
// System.out.println(x); // HATA — x bu blokta tanımlı değil!if-else'te Scope
int puan = 75;
if (puan >= 50) {
String sonuc = "Geçti";
System.out.println(sonuc); // OK
} else {
String sonuc = "Kaldı"; // Farklı blok, farklı değişken
System.out.println(sonuc); // OK
}
// System.out.println(sonuc); // HATA — sonuc her iki blokta da kapsam dışı!sonuc değişkeni if bloğunun içinde tanımlandı, if bloğu bitince yok oldu. else bloğundaki sonuc tamamen farklı bir değişken.
Eğer sonuca if-else'den sonra erişmek istiyorsan, dışarıda tanımla:
int puan = 75;
String sonuc;
if (puan >= 50) {
sonuc = "Geçti";
} else {
sonuc = "Kaldı";
}
System.out.println(sonuc); // OK — sonuc dış bloktaö tanımlıfor Döngüsünde Scope
for (int i = 0; i < 5; i++) {
System.out.println(i); // OK
}
// System.out.println(i); // HATA — i for bloğunun dışında!for döngüsünde tanımlanan sayaç değişkeni (int i) sadece döngü içinde geçerlidir. Bu iyi bir şey — döngüden sonra gereksiz bir i değişkeni etrafta dolaşmaz.
// Döngüden sonra i'ye erişmek istiyorsan
int i;
for (i = 0; i < 5; i++) {
System.out.println(i);
}
System.out.println("Döngü sonrası i: " + i); // 5while ve do-while'da Scope
while (true) {
String girdi = scanner.nextLine();
if (girdi.equals("çık")) break;
System.out.println(girdi);
}
// System.out.println(girdi); // HATA — girdi while bloğunun içinde!Metot Scope (Method Scope)
Bir metot içinde tanımlanan değişkenler lokal değişken (local variable) olarak adlandırılır. Sadece o metot içinde geçerlidir.
public static void metotA() {
int x = 10;
System.out.println(x); // OK
}
public static void metotB() {
// System.out.println(x); // HATA — x metotA'da tanımlı, buradan erişilmez!
int x = 20; // Bu tamamen farklı bir x
System.out.println(x);
}Her metodun kendi scope'u var. metotA'daki x ve metotB'deki x birbirinden tamamen bağımsız.
Lokal Değişkenler Otomatik Başlatılmaz
public static void main(String[] args) {
int x;
// System.out.println(x); // DERLEME HATASI — x başlatılmamış!
x = 10;
System.out.println(x); // OK — şimdi başlatıldı
}Lokal değişkenlere varsayılan değer atanmaz. Kullanmadan önce değer vermelisin. Java derleyicisi bunu kontrol eder ve başlatılmamış değişken kullanmana izin vermez.
⚠️ Dikkat: Bu sadece lokal değişkenler için geçerli. Sınıf değişkenleri (field) otomatik olarak varsayılan değer alır (
int→ 0,boolean→ false,String→ null). Bu farkı OOP dersinde göreceğiz.
Metot Parametreleri de Lokal
Metot parametreleri de lokal değişkenlerdir — metot boyunca geçerlidirler:
public static void selamla(String isim) {
// isim burada geçerli
System.out.println("Merhaba, " + isim);
}
// isim burada geçerli değilParametre Scope
Parametreler, metot çağrıldığında oluşur ve metot bittiğinde yok olur. Parametrelerin scope'u tüm metot gövdesidir.
public static int topla(int a, int b) {
// a ve b metot boyunca geçerli
int sonuc = a + b;
return sonuc;
}
// a, b ve sonuc burada yok olmuşParametre ile Aynı İsimli Lokal Değişken
public static void metot(int x) {
// int x = 20; // DERLEME HATASI — x zaten parametre olarak tanımlı!
// Ama iç blokta tanımlayabilirsin... desek de Java izin vermez:
// Java'da bir scope'taki değişken, iç scope'ta tekrar tanımlanamaz.
}Java, aynı isimli değişkenin üst scope'ta (parametre dahil) ve alt scope'ta (iç blok) tekrar tanımlanmasına izin vermez. Bu, shadowing hatasını önler.
Scope Kuralları Özet
Sınıf düzeyi (class level)
└── Metot düzeyi (method level)
└── Blok düzeyi (block level)
└── İç blok düzeyi (inner block level)Bir değişken, tanımlandığı blokta ve iç bloklarda erişilebilir, ama dış bloklarda erişilemez.
public class ScopeOrnek {
static int sinifDegiskeni = 100; // Sınıf düzeyi — her yerde erişilir
public static void main(String[] args) {
int metotDegiskeni = 50; // Metot düzeyi
System.out.println(sinifDegiskeni); // OK
System.out.println(metotDegiskeni); // OK
if (true) {
int blokDegiskeni = 25; // Blok düzeyi
System.out.println(sinifDegiskeni); // OK — dış scope
System.out.println(metotDegiskeni); // OK — dış scope
System.out.println(blokDegiskeni); // OK — aynı scope
{
int icBlok = 10; // İç blok düzeyi
System.out.println(sinifDegiskeni); // OK
System.out.println(metotDegiskeni); // OK
System.out.println(blokDegiskeni); // OK
System.out.println(icBlok); // OK
}
// System.out.println(icBlok); // HATA — iç blok bitti
}
// System.out.println(blokDegiskeni); // HATA — if bloğu bitti
}
}💡 İpucu: Değişkenleri mümkün olduğunca dar scope'ta tanımla. İlk kullanıldığı yere en yakın yerde tanımla. Bu, kodun okunabilirliğini artırır ve hatayı azaltır.
Shadowing (Gölgeleme)
Bir iç scope'taki değişken, dış scope'taki aynı isimli değişkeni gölgeler (shadowing). İç scope'ta dış değişkene erişemezsin — iç değişken öncelik alır.
Sınıf Değişkeni vs Lokal Değişken
public class ShadowOrnek {
static int x = 100; // Sınıf değişkeni
public static void main(String[] args) {
System.out.println(x); // 100 — sınıf değişkeni
int x = 50; // Lokal değişken — sınıf değişkenini gölgeler!
System.out.println(x); // 50 — lokal değişken
System.out.println(ShadowOrnek.x); // 100 — sınıf adıyla erişim
}
}Lokal x tanımlandığı anda, sınıf düzeyindeki x gölgelenir. Sınıf değişkenine erişmek için sınıf adını kullanman gerekir.
Parametre ile Sınıf Değişkeni Shadowing
public class Kisi {
static String isim = "Varsayılan";
public static void selamla(String isim) { // Parametre gölgeler!
System.out.println("Merhaba, " + isim); // Parametre
System.out.println("Sınıf: " + Kisi.isim); // Sınıf değişkeni
}
public static void main(String[] args) {
selamla("Ali");
// Merhaba, Ali
// Sınıf: Varsayılan
}
}⚠️ Dikkat: Shadowing, kafa karıştırıcı hatalara yol açabilir. Farklı scope'larda aynı isimli değişken kullanmaktan kaçın. IDE'ler genellikle shadowing uyarısı verir — bu uyarıları dikkate al.
Shadowing Java'da Nerede İzin Verilir?
| Durum | İzin var mı? |
|---|---|
| Sınıf değişkeni ↔ lokal değişken | ✅ Evet (ama kaçın) |
| Sınıf değişkeni ↔ parametre | ✅ Evet (constructor'larda yaygın) |
| Parametre ↔ lokal değişken | ❌ Hayır (derleme hatası) |
| Dış blok ↔ iç blok | ❌ Hayır (Java buna izin vermez!) |
// Java'da bu ÇALIŞMAZ:
int x = 10;
if (true) {
int x = 20; // DERLEME HATASI! C/C++'ta çalışır, Java'da çalışmaz.
}Java, lokal scope içinde shadowing'e izin vermez. Bu, diğer dillerden farklıdır ve iyi bir karardır — hataları önler.
Lifetime (Ömür)
Scope, değişkenin erişilebilir olduğu bölgedir. Lifetime, değişkenin bellekte var olduğu süredir. Çoğu durumda ikisi örtüşür ama her zaman aynı değildir.
Lokal Değişkenlerin Lifetime'ı
public static void metot() {
int x = 10; // x burada oluşur (stack'te)
System.out.println(x);
} // x burada yok olur (stack'ten silinir)Lokal değişkenler stack'te yaşar. Metot çağrıldığında oluşur, metot bittiğinde yok olur. Her çağrıda yeni bir kopyası oluşur.
static Değişkenlerin Lifetime'ı
public class Sayac {
static int toplam = 0; // Program boyunca yaşar
public static void artir() {
toplam++;
}
public static void main(String[] args) {
artir();
artir();
artir();
System.out.println(toplam); // 3
}
}static değişkenler programın başlangıcından sonuna kadar yaşar. Tüm metot çağrıları arasında paylaşılır.
Döngüdeki Değişken Lifetime
for (int i = 0; i < 3; i++) {
String mesaj = "Tur " + i;
System.out.println(mesaj);
}Her tur'da mesaj değişkeni yeniden oluşur ve tur sonunda yok olur. i ise tüm döngü boyunca yaşar.
Pratik Örnek: Scope Tuzakları
Tuzak 1: Değişkeni Yanlış Scope'ta Tanımlama
// HATALI — sonuc sadece if içinde tanımlı
if (puan >= 50) {
String sonuc = "Geçti";
}
System.out.println(sonuc); // DERLEME HATASI!
// DOĞRU — dışarıda tanımla
String sonuc;
if (puan >= 50) {
sonuc = "Geçti";
} else {
sonuc = "Kaldı";
}
System.out.println(sonuc);Tuzak 2: Döngüdeki Değişken Her Tur Sıfırlanır
// HATALI — toplam her turda 0'a sıfırlanır!
for (int i = 1; i <= 5; i++) {
int toplam = 0;
toplam += i;
System.out.println("Toplam: " + toplam);
}
// Toplam: 1, Toplam: 2, Toplam: 3, Toplam: 4, Toplam: 5
// DOĞRU — toplam döngü dışında
int toplam = 0;
for (int i = 1; i <= 5; i++) {
toplam += i;
}
System.out.println("Toplam: " + toplam); // 15Tuzak 3: switch'te Scope
// HATALI — case'ler aynı scope'ta
switch (x) {
case 1:
int sonuc = 10;
break;
case 2:
int sonuc = 20; // DERLEME HATASI — zaten tanımlı!
break;
}
// DOĞRU — süslü parantez ile ayrı scope
switch (x) {
case 1: {
int sonuc = 10;
System.out.println(sonuc);
break;
}
case 2: {
int sonuc = 20;
System.out.println(sonuc);
break;
}
}
// EN DOĞRU — enhanced switch
switch (x) {
case 1 -> {
int sonuc = 10;
System.out.println(sonuc);
}
case 2 -> {
int sonuc = 20;
System.out.println(sonuc);
}
}Scope Best Practices
1. Değişkeni Mümkün Olduğunca Dar Scope'ta Tanımla
// KÖTÜ — reader gereksiz yere geniş scope'ta
BufferedReader reader = null;
String satir;
// ... 20 satır başka kod ...
reader = new BufferedReader(new FileReader("dosya.txt"));
satir = reader.readLine();
// İYİ — ilk kullanıldığı yerde tanımla
BufferedReader reader = new BufferedReader(new FileReader("dosya.txt"));
String satir = reader.readLine();2. Döngü Sayacını for İçinde Tanımla
// KÖTÜ — i gereksiz yere geniş scope
int i;
for (i = 0; i < dizi.length; i++) { ... }
// i hâlâ erişilebilir ama işe yaramaz
// İYİ — i döngüye ait
for (int i = 0; i < dizi.length; i++) { ... }3. Tek Kullanımlık Değişkeni Satır İçi Yap
// GEREKSIZ değişken
double alan = Math.PI * r * r;
System.out.println(alan);
// Eğer alan sadece burada kullanılıyorsa — satır içi
System.out.println(Math.PI * r * r);
// AMA: Açıklayıcı isim gerekiyorsa değişkeni tut
double daireAlani = Math.PI * r * r;
System.out.println("Daire alanı: " + daireAlani);4. Aynı İsmi Farklı Scope'larda Kullanma
// KÖTÜ — kafa karıştırıcı
static int sonuc = 0;
public static void hesapla(int sonuc) { // shadowing!
// Hangi sonuc? Parametre mi, sınıf değişkeni mi?
}
// İYİ — farklı isimler
static int toplamSonuc = 0;
public static void hesapla(int girdi) {
toplamSonuc = girdi * 2; // Net ve açık
}Scope ve Garbage Collection (Kısa Bakış)
Java'da bellek yönetimi otomatiktir — Garbage Collector (GC) kullanılmayan nesneleri temizler. Scope ile ilişkisi şudur: bir nesne hiçbir değişkenden referans almadığında GC tarafından toplanabilir hale gelir.
public static void metot() {
String mesaj = new String("Merhaba"); // Nesne oluştu
System.out.println(mesaj);
} // metot bitti, mesaj referansı yok oldu
// "Merhaba" nesnesi artık GC'ye uygun
public static void main(String[] args) {
metot();
// Bu noktada "Merhaba" nesnesi GC tarafından toplanabilir
}Scope dar tutmak, nesnelerin daha erken GC'ye uygun hale gelmesini sağlar. Bu da bellek kullanımını optimize eder.
// KÖTÜ — büyük dizi uzun süre bellekte kalır
public static void islem() {
int[] buyukDizi = new int[1_000_000];
// diziyi kullan...
// 100 satır başka kod — buyukDizi hâlâ bellekte!
}
// İYİ — büyük diziyi ayrı blokta işle
public static void islem() {
{
int[] buyukDizi = new int[1_000_000];
// diziyi kullan...
}
// buyukDizi scope dışı — GC toplayabilir
// 100 satır başka kod — bellek serbest
}Bu ileri seviye bir optimizasyon — çoğu durumda gerekmez. Ama büyük veri işlerken scope'u bilinçli kullanmak fark yaratabilir.
Lambda ve Inner Class'larda Scope
İleri seviye bir not — Java 8+ lambda ifadelerinde dış scope'tan değişken kullanabilirsin ama effectively final olmalı:
String mesaj = "Merhaba";
// mesaj = "Değişti"; // Bunu yaparsak lambda'da kullanamayız!
Runnable r = () -> {
System.out.println(mesaj); // OK — mesaj effectively final
};
r.run();"Effectively final" demek: değişkene bir kez değer atandı ve bir daha değiştirilmedi. Bu konuyu Lambda dersinde detaylı göreceğiz.
Gerçek Dünya Örneği: Scope Farkındalığı ile Temiz Kod
public static Map<String, Integer> kelimeSay(String metin) {
Map<String, Integer> sayac = new HashMap<>();
// kelimeler sadece burada gerekli — dar scope
String[] kelimeler = metin.toLowerCase().split("\\s+");
for (String kelime : kelimeler) {
// temiz sadece bu döngüde gerekli — dar scope
String temiz = kelime.replaceAll("[^a-zğüşıöç]", "");
if (!temiz.isEmpty()) {
sayac.merge(temiz, 1, Integer::sum);
}
}
return sayac; // sayac döndürülüyor — ömrü metot dışına taşıyor
}Her değişken tam olarak gerektiği yerde tanımlanmış. kelimeler dizisi for'dan önce çünkü for'da kullanılıyor. temiz string her turda yeniden oluşuyor çünkü sadece o turda lazım.
try-catch'te Scope
Exception handling'de de scope kuralları geçerli:
// HATALI — connection try bloğu dışında erişilemez
try {
Connection conn = getConnection();
// conn kullan
} catch (Exception e) {
// conn burada erişilemez!
}
// conn burada da erişilemez!
// DOĞRU — dışarıda tanımla
Connection conn = null;
try {
conn = getConnection();
} catch (Exception e) {
System.out.println("Hata: " + e.getMessage());
} finally {
if (conn != null) conn.close();
}Veya daha modern yaklaşımla try-with-resources:
try (Connection conn = getConnection()) {
// conn otomatik kapatılır
}Özet
Blok scope: Süslü parantez
{ }içinde tanımlanan değişken, o blok ve iç bloklar içinde geçerli.Metot scope: Lokal değişkenler ve parametreler sadece metot içinde geçerli, metot bitince yok olur.
Dar scope tercih et: Değişkeni ilk kullanıldığı yere en yakın yerde, mümkün olan en dar scope'ta tanımla.
Shadowing'den kaçın: Farklı scope'larda aynı isimli değişken kullanma — kafa karıştırır ve hatalara yol açar.
Java, lokal scope'ta shadowing'e izin vermez — dış blokta tanımlı değişken iç blokta tekrar tanımlanamaz.
Lifetime: Lokal değişkenler stack'te yaşar (metot süresince), static değişkenler program boyunca yaşar.
Garbage Collection ile ilişki: Scope dışına çıkan referanslar GC'ye uygun hale gelir. Dar scope, bellek kullanımını optimize eder.
Scope Kontrol Listesi
Kod yazarken şu soruları sor:
Bu değişken gerçekten bu kadar geniş bir scope'ta mı tanımlanmalı?
Değişkeni ilk kullanıldığı satıra yaklaştırabilir miyim?
Aynı isim başka bir scope'ta kullanılıyor mu? (shadowing riski)
Döngü içinde tanımlanması gereken değişken dışarıda mı?
Dışarıda tanımlanması gereken değişken döngü içinde mi? (her turda sıfırlanma hatası)
Bu sorulara "evet" cevabı veriyorsan, scope'u ayarla.
AI Asistan
Sorularını yanıtlamaya hazır