Ders 05 / 10
Değişkenler ve Kapsam
Aynı `int adet` bildirimi bulunduğu yere göre üç ayrı şeye derlenir: sınıf gövdesinde bir alana, yöntem gövdesinde bir yuvaya, statik olarak sınıfın tek kopyalı alanına; iki kardeş blokta aynı adı kullanan yöntem, aynı 20 komutu bir eksik yuvayla üretir.
İçindekiler
Önceki ders bir değerin hangi biçimde tutulduğunu ölçtü: sayının kendisi mi, yoksa sayıyı
saran bir nesne mi. Fark kaynakta tek harfti, sınıf dosyasında bir çağrıydı. Bu ders aynı
soruyu adın kendisine sorar. int adet = 5 satırı, yazıldığı yere göre üç ayrı şeye
derlenir; kaynakta üçünün de yazımı aynıdır.
Programlama Temelleri kursu değişkeni, bağlamayı, kapsamı ve yaşam süresini kavram olarak kurmuştu — bunlar tekrarlanmaz. Orada bir adın nerede görünür olduğu anlatıldı. Burada ölçülen şey, derleyicinin o ada sınıf dosyasında hangi yeri verdiğidir: sınıfın gövdesinde duran bir alan mı, yoksa yöntemin çağrı başına ayrılan yerel alanında bir yuva mı.
Üç Ad Ailesi
Java bir adı üç yerden birine yazar ve seçimi bildirimin bulunduğu yer belirler.
- Yerel değişken bir yöntemin gövdesinde bildirilir. Sınıf dosyasında ad olarak görünmez; yöntemin yerel alanında numaralı bir yuva tutar. Yöntem her çağrıldığında yeni bir kopyası oluşur ve çağrı bittiğinde kaybolur. Parametreler de yerel değişkendir.
- Örnek değişkeni sınıfın gövdesinde,
staticolmadan bildirilir. Sınıf dosyasında bir alan olarak durur ve her nesnede ayrı bir kopyası bulunur. - Sınıf değişkeni sınıfın gövdesinde
staticile bildirilir. O da bir alandır, ama kopyası tektir: nesne sayısından bağımsız olarak sınıfa aittir.
Ayrım kaynakta bir anahtar sözcüğe ve bir girintiye bağlıdır; sınıf dosyasında ise iki ayrı
yapıya. Alanlar sınıf dosyasının alan çizelgesinde adlarıyla durur. Yerel değişkenler
orada hiç yoktur — yöntemin kodu adları değil, numaraları kullanır. Bu ders üç ailenin
ayrımını, kurs boyunca sürdürülen depo kaydının adet alanı üzerinde ölçer.
Ölçüm Çekirdeği
Çekirdek önceki dersin çekirdeğidir, iki ölçüm eklenmiştir: her yöntemin kullandığı yuva sayısı ve o yöntemdeki alan erişimleri. Sınıfın alan çizelgesi de okunur. Derleme başarısızsa sınıf dosyası üretilmez ve çekirdek bunu ayrıca bildirir.
- SD6 — Yuva sayısı, sınıf dosyasının o yöntem için ayırdığı yerel alan
büyüklüğüdür — kaynakta yazılan ad sayısı değil. Örnek yöntemlerinde sıfır numaralı
yuva
this’e ayrılmıştır; statik yöntemlerde ilk parametreden başlar. - SD7 — Alan erişimi sütunu komut adını taşır:
getfieldveputfieldbir örneğin alanına,getstaticveputstaticsınıfın alanına erişir.
// Olcek.java — adın sınıf dosyasında nereye yazıldığını okur: alana mı, yuvaya mı
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.attribute.CodeAttribute;
import java.lang.classfile.instruction.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;
class Olcek {
record Yontem(String ad, int komut, int yuva, List<String> alanErisimi) {}
record Sinif(List<String> alan, List<Yontem> yontem) {}
static Path derle(String kaynak, String sinif) throws Exception {
Path d = Files.createTempDirectory("olcek");
Files.writeString(d.resolve(sinif + ".java"), kaynak);
PrintWriter bos = new PrintWriter(Writer.nullWriter());
int sonuc = ToolProvider.findFirst("javac").orElseThrow()
.run(bos, bos, "-d", d.toString(), d.resolve(sinif + ".java").toString());
return sonuc == 0 ? d.resolve(sinif + ".class") : null;
}
static Sinif oku(String kaynak, String sinif) throws Exception {
Path c = derle(kaynak, sinif);
if (c == null) throw new IllegalStateException("derlenmedi: " + sinif);
ClassModel cm = ClassFile.of().parse(c);
List<String> alanlar = new ArrayList<>();
for (FieldModel f : cm.fields()) alanlar.add(f.fieldName().stringValue());
List<Yontem> yontemler = new ArrayList<>();
for (MethodModel m : cm.methods()) {
if (m.code().isEmpty()) continue;
CodeAttribute kod = (CodeAttribute) m.code().get();
List<String> erisim = new ArrayList<>();
int komut = 0;
for (CodeElement e : kod) {
if (e instanceof Instruction) komut++;
if (e instanceof FieldInstruction fi)
erisim.add(fi.opcode().name().toLowerCase(Locale.ROOT)
+ " " + fi.name().stringValue());
}
yontemler.add(new Yontem(m.methodName().stringValue(), komut,
kod.maxLocals(), erisim));
}
return new Sinif(alanlar, yontemler);
}
}
Dört Ad, Üç Ayrı Yer
Ölçülen sınıf toplamAdet ve adet adlarını sınıf gövdesinde, gelen, yeni, n, t,
gecici ve ikinci adlarını yöntem gövdelerinde bildirir. blokKapsami ile bloksuz
aynı işi aynı sırayla yapar; aralarındaki tek fark, iki geçici adın küme parantezi
içinde açılıp açılmadığıdır.
// Yer.java — dört ad, üç ayrı yer
public class Yer {
static final String KAYNAK = """
class Depo {
static int toplamAdet = 0;
int adet;
void ekle(int gelen) {
int yeni = adet + gelen;
adet = yeni;
toplamAdet += gelen;
}
static int blokKapsami(int n) {
int t = 0;
{ int gecici = n * 2; t += gecici; }
{ int ikinci = n * 3; t += ikinci; }
return t;
}
static int bloksuz(int n) {
int t = 0;
int gecici = n * 2; t += gecici;
int ikinci = n * 3; t += ikinci;
return t;
}
static long genis(long agirlik, int ek) {
long yeni = agirlik + ek;
return yeni;
}
int gorunen(int adet) { return adet; }
int alanaErisen(int adet) { return this.adet; }
}
""";
public static void main(String[] args) throws Exception {
Olcek.Sinif s = Olcek.oku(KAYNAK, "Depo");
System.out.println("sinif dosyasindaki alanlar: " + s.alan());
System.out.printf("%-14s %6s %5s %s%n", "yontem", "komut", "yuva", "alan erisimi");
for (Olcek.Yontem y : s.yontem())
System.out.printf("%-14s %6d %5d %s%n", y.ad(), y.komut(), y.yuva(),
y.alanErisimi().isEmpty() ? "-" : String.join(", ", y.alanErisimi()));
}
}
sinif dosyasindaki alanlar: [toplamAdet, adet] yontem komut yuva alan erisimi <init> 3 1 - ekle 13 3 getfield adet, putfield adet, getstatic toplamAdet, putstatic toplamAdet blokKapsami 20 3 - bloksuz 20 4 - genis 7 5 - gorunen 2 2 - alanaErisen 3 2 getfield adet <clinit> 3 0 putstatic toplamAdet
Alan çizelgesinde iki ad var: toplamAdet ve adet. Kaynakta bildirilen sekiz addan
yalnız ikisi sınıf dosyasında ad olarak duruyor. Kalan altısı hiçbir yerde ad değil;
yalnız yuva numarası olarak yaşıyorlar. Bir yerel değişkenin adı, sınıf dosyasına
girmez — derleyicinin işi bittiğinde ad tükenmiştir. Derleyiciye ayrıca söylenirse
adlar hata ayıklama bilgisi olarak sınıf dosyasına eklenebilir, ama bu kodun bir parçası
değildir: yöntemin komutları o bilgi olmadan da aynı yuvaları kullanır. Alan adları ise
seçime bağlı değildir, çünkü onlara sınıfın dışından erişilir ve erişen kod adı bilmek
zorundadır.
ekle satırı üç aileyi bir arada gösteriyor. Aynı yöntemde adet iki kez örnek alanı
komutuyla (getfield, putfield), toplamAdet iki kez sınıf alanı komutuyla
(getstatic, putstatic) geçiyor; gelen ve yeni ise hiçbir alan komutuna girmiyor,
çünkü onlar yuvada. Üç yuva sayılıyor: sıfır this, bir gelen, iki yeni.
Ölçümün asıl satırı ortadaki çifttir. blokKapsami ile bloksuz aynı 20 komutu
üretiyor — çalışma zamanında yapılan iş birebir aynı. Yuva sayısı ayrı: 3 ve 4.
Küme parantezi çalışma zamanında hiçbir şey yapmaz; yaptığı iş derleme zamanında biter.
Derleyici gecici adının ilk bloğun sonunda öldüğünü bildiği için ikinci adına aynı
yuvayı verir. Blok kapsamı, çalışma zamanında var olmayan bir kavramdır: sınıf
dosyasında ondan geriye kalan tek şey bir eksik yuvadır.
genis satırı yuvanın ad sayısı olmadığını başka bir yönden gösteriyor: üç ad bildiriliyor,
beş yuva sayılıyor. Bir yuva sabit genişliktedir ve 64 bitlik değerler ona sığmaz;
long agirlik iki yuva, int ek bir yuva, long yeni yine iki yuva tutuyor. Yerel alanın
büyüklüğü kaç ad yazıldığına değil, o adların hangi tipte olduğuna bağlıdır — önceki
dersin tablosundaki genişlik sütununun sınıf dosyasındaki karşılığı budur.
Son iki satır gölgelemeyi ayırıyor. gorunen ile alanaErisen aynı imzayı taşıyor ve
ikisinde de adet adı geçiyor; biri hiç alan erişimi yapmıyor, diğeri getfield
ediyor. Parametre olarak bildirilen adet, alan olan adet’i gölgeliyor; alana
ulaşmanın tek yolu this.adet yazmak. Hangisinin okunacağı derleme zamanında belirlenir
ve sınıf dosyasında iki ayrı komut olarak durur.
Tablonun iki köşesi de anlamlıdır. <init> ve <clinit> kaynakta yazılmamış yöntemlerdir;
ilki adet alanını hazırlayan yapıcı, ikincisi toplamAdet = 0 atamasını taşıyan sınıf
başlatma bloğudur. İkisi de önceki konuda ölçülmüştü. <clinit>’in yuva sayısının sıfır
olması burada anlamlıdır: sınıf başlatma bloğunda this yoktur, çünkü ortada henüz hiçbir
nesne yoktur.
Kapsamın Görünen Sonucu
Yer ayrımının sayı olarak değil, değer olarak görünen sonucu vardır. Aynı sınıftan iki nesne üretildiğinde örnek değişkeni ikiye ayrılır, sınıf değişkeni ayrılmaz.
- SD8 — İki nesne üretilir ve üçüncü çağrı ikinci nesneye yapılır; sınıf değişkeni bütün çağrıların toplamını taşıdığı için beklenen değer 3 artı 4 artı 10’dur.
- SD9 — Gölgeleme sınaması, alanı önceki çağrıların değiştirdiği bir noktada yapılır; böylece parametrenin mi yoksa alanın mı okunduğu değerden ayırt edilebilir.
// Kapsam.java — alanın ve yuvanın görünen sonucu
public class Kapsam {
static class Depo {
static int toplamAdet = 0; // sinif degiskeni: tek kopya
int adet; // ornek degiskeni: ornek basina kopya
void ekle(int gelen) {
int yeni = adet + gelen; // yerel degisken: cagri basina kopya
adet = yeni;
toplamAdet += gelen;
}
int gorunen(int adet) { return adet; } // parametre alani golgeler
int alanaErisen(int adet) { return this.adet; }
}
public static void main(String[] args) {
Depo vida = new Depo(), civata = new Depo();
vida.ekle(3);
vida.ekle(4);
civata.ekle(10);
System.out.println("vida.adet: " + vida.adet + " | civata.adet: " + civata.adet
+ " | Depo.toplamAdet: " + Depo.toplamAdet);
System.out.println("gorunen(9): " + vida.gorunen(9)
+ " | alanaErisen(9): " + vida.alanaErisen(9));
}
}
vida.adet: 7 | civata.adet: 10 | Depo.toplamAdet: 17 gorunen(9): 9 | alanaErisen(9): 7
adet her nesnede ayrı: 7 ve 10. toplamAdet tek: 17, yani üç çağrının
toplamı. yeni hiçbir yerde görünmüyor, çünkü her çağrının sonunda yok oluyor. Üç ailenin
yaşam süresi de böylece ayrılıyor — yuva çağrı boyunca, örnek alanı nesne boyunca, sınıf
alanı sınıf yüklü kaldığı sürece yaşar.
İkinci satır gölgelemenin bedelini gösteriyor. Aynı nesneye aynı argümanla yapılan iki
çağrı 9 ve 7 döndürüyor. Kaynakta iki yöntemin gövdesi arasında this. dışında
hiçbir fark yoktur. Bir alan adıyla aynı adı taşıyan bir parametre, o alanı yöntemin
tamamında erişilemez kılar; okunan şey artık nesnenin durumu değil, çağırandan gelen
argümandır.
Sınırlayıcı Ölçüm: Hangi Yeniden Bildirim Geçiyor
Gölgeleme her yerde olmaz. İç içe bloklarda aynı adı yeniden bildirmek Java’da derleme hatasıdır; gölgeleme yalnız ayrı ailelerden iki ad arasında, yani alan ile yerel arasında olur. Aşağıdaki beş biçim aynı sınıf iskeletine yerleştirilir ve yalnız derlenip derlenmediklerine bakılır.
- SD10 — Beş durum aynı gövdeye, aynı sınıf adıyla yazılır; ölçüm derleyicinin ürettiği metne değil, sınıf dosyasının oluşup oluşmadığına bakar. Derleyici iletisi basılmaz.
// Bildirim.java — hangi yeniden bildirim derleme aşamasını geçiyor
public class Bildirim {
static final String[][] DURUM = {
{"iki kardes blokta ayni ad",
"int t = 0; { int g = n; t += g; } { int g = n; t += g; } return t;"},
{"ic blokta distaki adi yeniden",
"int t = 0; { int t = n; } return t;"},
{"ic bloktaki ada disaridan erisim",
"int t = 0; { int g = n; } return t + g;"},
{"alan adiyla ayni yerel ad",
"int adet = n; return adet + this.adet;"},
{"parametre adiyla ayni yerel ad",
"int n = 1; return n;"},
};
public static void main(String[] args) throws Exception {
System.out.printf("%-34s %s%n", "bildirim bicimi", "derleme");
for (String[] d : DURUM) {
String kaynak = "class D { int adet = 5; int olc(int n) { " + d[1] + " } }";
System.out.printf("%-34s %s%n", d[0],
Olcek.derle(kaynak, "D") != null ? "gecti" : "hata");
}
}
}
bildirim bicimi derleme iki kardes blokta ayni ad gecti ic blokta distaki adi yeniden hata ic bloktaki ada disaridan erisim hata alan adiyla ayni yerel ad gecti parametre adiyla ayni yerel ad hata
İki geçen satırın ortak yanı var: ikisinde de iki ad hiçbir noktada aynı anda görünür
değil. Kardeş bloklardaki g adları birbirini görmez, çünkü birincisi ikincisi
açılmadan kapanmıştır — bu, yuva paylaşımının kaynaktaki karşılığıdır. Alan ile yerel ise
ayrı ailelerdendir ve derleyici hangisinin kastedildiğini this. ile ayırabilir.
Üç hatanın ortak yanı da var: hepsinde tek bir yerel ad iki kez bildirilmiş ya da kapsamı dışında kullanılmıştır. Java’da bir yerel değişkenin adı, kapsamı boyunca tekrar bildirilemez; iç blok dıştakini gölgeleyemez. Beşinci satır bunun en dar halidir — parametre de bir yerel değişkendir, dolayısıyla aynı adı gövdede yeniden bildirmek hatadır. Sınır böylece net çizilir: gölgeleme yalnız alan ile yerel arasında vardır, yerel ile yerel arasında yoktur ve derleyici bunu sınıf dosyası üretmeden önce reddeder.
Bu yasağın bir bedeli olduğunu da yazmak gerekir: iç blokta kısa ve anlamlı bir ad ikinci kez kullanılamadığı için uzun yöntemlerde adlar birikir. Karşılığında kazanılan şey, bir adın bir yöntem gövdesinin herhangi bir noktasında tek bir bildirimi olmasıdır — okuyan kişi bir adı gördüğünde hangi bildirime baktığını aramak zorunda kalmaz. Ölçüm bu kararın çalışma zamanında hiçbir karşılığı olmadığını gösteriyor: geçen iki biçim de sınıf dosyasında aynı komutları üretiyor.
Özet
- Bildirimin yeri adın sınıf dosyasındaki yerini belirler: sınıf gövdesindeki adlar alan çizelgesine girer, yöntem gövdesindekiler yalnız numaralı bir yuva tutar ve sınıf dosyasında ad olarak görünmez.
ekleyöntemi üç aileyi bir arada gösterir:adetiçingetfield/putfield,toplamAdetiçingetstatic/putstatic,gelenveyeniiçin hiçbir alan komutu.- Blok kapsamı derleme zamanında biter:
blokKapsamiilebloksuzaynı 20 komutu üretir, blokluk biçim yalnız bir yuva az kullanır. - Örnek değişkeni nesne başına, sınıf değişkeni sınıf başına, yerel değişken çağrı başına yaşar; ölçümde 7, 10 ve 17 değerleri bu üç ömrü ayırır.
- Gölgeleme yalnız alan ile yerel arasında olur ve
this.ile çözülür; iç blokta bir yerel adı yeniden bildirmek derleme hatasıdır.
Sonraki Adım
Bu ders adın nereye yazıldığını ölçtü. Adların taşıdığı değerler bir kez yerlerine
oturduğunda sıra onları birleştiren işleçlere gelir. Sıradaki ders + ve += gibi tek
karakterlik yazımların sınıf dosyasında ne bıraktığını sayar: int ile long
toplandığında derleyicinin araya koyduğu çevirme, += yazımının kaynakta hiç yazılmayan
bir daraltmayı kendiliğinden eklemesi ve bunun görünen sonucu, kısa devre
değerlendirmenin bir dal komutu olarak sınıf dosyasında belirmesi — ve iki sabitin
toplandığı yerde hiçbir adımın eklenmemesi.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.