Ders 02 / 12
Bağımlılık Yönetimi
Aynı kaynak, önce bir derleme sınıf yoluna karşı derlenip sonra başka bir koşum sınıf yoluna karşı koşturulur: altı tipin 1'i yalnız derlemede, 3'ü yalnız koşumda, 2'si her ikisinde gerekli çıkar. Dört kusurun 1'i derleme hatası, 1'i koşum hatası, 2'si hiçbir şey söylemeden sessizce yanlış sonuç üretir — biri aynı arayüzün iki gerçekleştirimi arasındaki sıraya bağlı seçim, öbürü sabiti değiştirip çağıranı yeniden derlememenin bedeli.
İçindekiler
Önceki ders bir bağımlılığın hangi sınıf yolunda gerekli olduğunu iki uç örnekle gösterdi: biri yalnız derlemede, biri yalnız koşumda. Ama gerçek bir bildirim çoğu zaman ikisinin ortasında duran bağımlılıklar da taşır — hem derlemede hem koşumda gerekli olan, ya da derlemede hiç görünmeyip yalnız başka bir bağımlılığın üzerinden koşuma sızan. Bu ders altı bağımlılık tipini bir arada koyup hepsine aynı soruyu soruyor: bu tip hangi sınıf yolunda bulunmak zorunda? Yanıt kaynak metinden çıkarılamaz — çıkarılabilseydi bağımlılık kapsamı diye bir kavrama hiç gerek kalmazdı.
Bağımlılık Kapsamı: Üç Değer, Tek Soru
Bir bağımlılığın bağımlılık kapsamı (dependency scope) üç değerden birini alır: yalnız derleme sınıf yolunda gerekli, yalnız koşum sınıf yolunda gerekli, ya da her ikisinde birden. Kapsam bir tahmin değil, bildirimde yazılan bir karardır — ama kararın doğru olup olmadığı yalnız iki yolu gerçekten ayırarak sınanır. Aşağıdaki altı tip aynı soruya altı farklı yanıt veriyor, ve hiçbiri kaynak metnin yüzeysel okunuşundan çıkarılamıyor:
-
Sabit— bir derleme zamanı sabiti taşıyan bir sınıf. -
Arac—Sabiti değil amaDerini doğrudan çağıran bir yardımcı sınıf. -
Derin— yalnızAracın çağırdığı,Kullananda hiç doğrudan adlandırılmayan bir sınıf. -
Cephe— tek yöntemli bir arayüz. -
KayitA,KayitB—Cephenin iki ayrı gerçekleştirimi; kaynak metinde hiç adlandırılmadan bir servis bildirimiyle bulunuyorlar. -
DR7. Altı tipin sınıf yolu gereksinimi, aynı kaynağı önce bir derleme sınıf yoluna karşı derleyip sonra başka bir koşum sınıf yoluna karşı koşturarak ölçülüyor; tek yolla yapılan bir deneme bu ayrımı göstermez.
-
DR8.
Sabit’in derleme zamanı sabiti oluşu (static final int), Nesneye Dayalı Java kursunda kurulan değer gömme düzeneğiyle aynı kaynaktan geliyor; burada tekrar kurulmuyor, yalnız sınıf yolu sonucuna araç olarak kullanılıyor. -
DR9.
Cephebir arayüz;KayitAveKayitBona bir servis bildirimiyle (META-INF/servicesaltında) bağlanıyor.Cagiranhiçbir gerçekleştirim adını kaynak metninde yazmıyor, yalnızCepheyi arıyor.
// SinifYolu.java — bir tipin hangi sinif yolunda gerekli oldugunu ve bir ayrimin nerede yakalandigini sayan yardimci
import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;
public class SinifYolu {
static int derleme = 0, kosum = 0, ikisi = 0;
static int derlemeHatasi = 0, kosumHatasi = 0, sessiz = 0;
static Path yaz(Path dizin, String ad, String kaynak) throws IOException {
Files.createDirectories(dizin);
Path p = dizin.resolve(ad + ".java");
Files.writeString(p, kaynak);
return p;
}
static boolean derle(Path cikti, List<Path> sinifYolu, Path kaynak) throws IOException {
Files.createDirectories(cikti);
List<String> arg = new ArrayList<>(List.of("-d", cikti.toString()));
if (!sinifYolu.isEmpty()) {
StringJoiner sj = new StringJoiner(File.pathSeparator);
for (Path p : sinifYolu) sj.add(p.toString());
arg.add("-classpath");
arg.add(sj.toString());
}
arg.add(kaynak.toString());
ToolProvider derleyici = ToolProvider.findFirst("javac").orElseThrow();
StringWriter sessizCikti = new StringWriter();
int sonuc = derleyici.run(new PrintWriter(sessizCikti), new PrintWriter(sessizCikti), arg.toArray(new String[0]));
return sonuc == 0;
}
static String kos(List<Path> sinifYolu, String anaSinif) throws Exception {
URL[] url = new URL[sinifYolu.size()];
for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
try (URLClassLoader yukleyici = new URLClassLoader(url, SinifYolu.class.getClassLoader().getParent())) {
Class<?> sinif = Class.forName(anaSinif, true, yukleyici);
sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
return "tamam";
} catch (java.lang.reflect.InvocationTargetException e) {
return e.getCause().getClass().getSimpleName();
} catch (Throwable t) {
return t.getClass().getSimpleName();
}
}
static void bildirSaglayici(Path dizin, String saglayici) throws IOException {
Path d = dizin.resolve("META-INF").resolve("services");
Files.createDirectories(d);
Files.writeString(d.resolve("Cephe"), saglayici + "\n");
}
static String secilenSaglayici(List<Path> sinifYolu) throws Exception {
URL[] url = new URL[sinifYolu.size()];
for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
try (URLClassLoader yukleyici = new URLClassLoader(url, SinifYolu.class.getClassLoader().getParent())) {
Class<?> sinif = Class.forName("Cagiran", true, yukleyici);
sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
return (String) sinif.getField("secilen").get(null);
}
}
static int okunanAlan(List<Path> sinifYolu, String anaSinif, String alan) throws Exception {
URL[] url = new URL[sinifYolu.size()];
for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
try (URLClassLoader yukleyici = new URLClassLoader(url, SinifYolu.class.getClassLoader().getParent())) {
Class<?> sinif = Class.forName(anaSinif, true, yukleyici);
sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
return sinif.getField(alan).getInt(null);
}
}
static void tip(String ad, boolean derlemeGerekli, boolean kosumGerekli) {
String yer;
if (derlemeGerekli && kosumGerekli) { ikisi++; yer = "ikisi"; }
else if (derlemeGerekli) { derleme++; yer = "derleme"; }
else { kosum++; yer = "kosum"; }
System.out.printf("%-32s %s%n", ad, yer);
}
static void kusur(String ad, String sinif) {
if (sinif.equals("derleme")) derlemeHatasi++;
else if (sinif.equals("kosum")) kosumHatasi++;
else sessiz++;
System.out.printf("%-32s %s%n", ad, sinif);
}
static void ozet() {
System.out.printf("tip : %d yalniz derleme, %d yalniz kosum, %d ikisi%n", derleme, kosum, ikisi);
System.out.printf("kusur : %d derleme hatasi, %d kosum hatasi, %d sessiz%n", derlemeHatasi, kosumHatasi, sessiz);
}
}
SinifYolu üç ayrı ölçümü tek çatı altında topluyor: bir tipin hangi sınıf yolunda gerekli
olduğu (tip), bir kusurun hangi aşamada düştüğü (kusur), ve ikisinin toplamı (ozet).
derle ve kos yöntemleri önceki dersteki aynı işlemleri yapıyor; bildirSaglayici bir
servis bildirimini diske yazıyor, secilenSaglayici ise Cagiranı koşturup hangi
gerçekleştirimin seçildiğini okuyor.
Altı Tip, Bir Ölçüm
- DR10. Geçişli bağımlılık (
Derin), doğrudan bağımlılığın (Arac) derleme sınıf yolunda bulunmasına rağmen kendisi derleme sınıf yoluna hiç girmeden ölçülüyor; kaynağın derlenmesi bunun kanıtı değil. - DR11. Çakışma çözümü, aynı arayüzün iki gerçekleştirmesi (
KayitA,KayitB) koşum sınıf yoluna aynı anda konularak ve sıraları değiştirilerek ölçülüyor; hangi sırayla verildiği tek değişken.
// BagimlilikOlcumu.java — alti tipin hangi sinif yolunda gerekli oldugunu ve dort kusurun nerede dustugunu olcer
import java.nio.file.*;
import java.util.*;
public class BagimlilikOlcumu {
public static void main(String[] args) throws Exception {
Path kok = Files.createTempDirectory("bagimlilik");
Path kaynak = kok.resolve("kaynak");
Path sabitD = kok.resolve("sabit"), aracD = kok.resolve("arac"), derinD = kok.resolve("derin");
Path cepheD = kok.resolve("cephe"), aD = kok.resolve("kayitA"), bD = kok.resolve("kayitB");
Path kullananD = kok.resolve("kullanan"), cagiranD = kok.resolve("cagiran");
Path sSabit = SinifYolu.yaz(kaynak, "Sabit", "public class Sabit { public static final int DEGER = 7; }");
Path sDerin = SinifYolu.yaz(kaynak, "Derin", "public class Derin { public static int uc() { return 3; } }");
Path sArac = SinifYolu.yaz(kaynak, "Arac", "public class Arac { public static int iki() { return Derin.uc() - 1; } }");
Path sKullanan = SinifYolu.yaz(kaynak, "Kullanan",
"public class Kullanan { public static void main(String[] a) { int t = Sabit.DEGER + Arac.iki(); if (t < 0) throw new IllegalStateException(); } }");
Path sCephe = SinifYolu.yaz(kaynak, "Cephe", "public interface Cephe { String ad(); }");
Path sA = SinifYolu.yaz(kaynak, "KayitA", "public class KayitA implements Cephe { public String ad() { return \"A\"; } }");
Path sB = SinifYolu.yaz(kaynak, "KayitB", "public class KayitB implements Cephe { public String ad() { return \"B\"; } }");
Path sCagiran = SinifYolu.yaz(kaynak, "Cagiran",
"import java.util.ServiceLoader;\n"
+ "public class Cagiran {\n"
+ " public static String secilen = \"yok\";\n"
+ " public static void main(String[] a) {\n"
+ " for (Cephe c : ServiceLoader.load(Cephe.class, Cagiran.class.getClassLoader())) { secilen = c.ad(); return; }\n"
+ " }\n"
+ "}");
SinifYolu.derle(sabitD, List.of(), sSabit);
SinifYolu.derle(derinD, List.of(), sDerin);
SinifYolu.derle(aracD, List.of(derinD), sArac);
SinifYolu.derle(cepheD, List.of(), sCephe);
SinifYolu.derle(aD, List.of(cepheD), sA);
SinifYolu.derle(bD, List.of(cepheD), sB);
SinifYolu.bildirSaglayici(aD, "KayitA");
SinifYolu.bildirSaglayici(bD, "KayitB");
SinifYolu.derle(kullananD, List.of(sabitD, aracD), sKullanan);
SinifYolu.derle(cagiranD, List.of(cepheD), sCagiran);
System.out.println("-- alti tip hangi sinif yolunda gerekli --");
boolean sabitDerleme = !SinifYolu.derle(kok.resolve("d1"), List.of(aracD), sKullanan);
boolean sabitKosum = !SinifYolu.kos(List.of(kullananD, aracD, derinD), "Kullanan").equals("tamam");
SinifYolu.tip("Sabit (derleme zamani sabiti)", sabitDerleme, sabitKosum);
boolean aracDerleme = !SinifYolu.derle(kok.resolve("d2"), List.of(sabitD), sKullanan);
boolean aracKosum = !SinifYolu.kos(List.of(kullananD, sabitD, derinD), "Kullanan").equals("tamam");
SinifYolu.tip("Arac (dogrudan bagimlilik)", aracDerleme, aracKosum);
boolean derinDerleme = !SinifYolu.derle(kok.resolve("d3"), List.of(sabitD, aracD), sKullanan);
boolean derinKosum = !SinifYolu.kos(List.of(kullananD, sabitD, aracD), "Kullanan").equals("tamam");
SinifYolu.tip("Derin (gecisli bagimlilik)", derinDerleme, derinKosum);
boolean cepheDerleme = !SinifYolu.derle(kok.resolve("d4"), List.of(), sCagiran);
boolean cepheKosum = !SinifYolu.kos(List.of(cagiranD, aD), "Cagiran").equals("tamam");
SinifYolu.tip("Cephe (arayuz)", cepheDerleme, cepheKosum);
boolean aDerleme = !SinifYolu.derle(kok.resolve("d5"), List.of(cepheD), sCagiran);
SinifYolu.tip("KayitA (gerceklestirim)", aDerleme, true);
SinifYolu.tip("KayitB (ikinci gerceklestirim)", aDerleme, true);
System.out.println();
System.out.println("-- gecisli bagimlilik: Derin, Arac uzerinden derleme sinif yoluna hic girmiyor --");
System.out.println("Kullanan, [Sabit, Arac] derleme sinif yoluna karsi derlendi mi: " + SinifYolu.derle(kok.resolve("gecis"), List.of(sabitD, aracD), sKullanan));
System.out.println("bu derleme sinif yolunda Derin hic yok, ama Kullanan derleniyor");
System.out.println();
System.out.println("-- cakisma cozumu: kosum yolunda iki gerceklestirim, secimi sira belirliyor --");
String ilkAB = SinifYolu.secilenSaglayici(List.of(cagiranD, cepheD, aD, bD));
String ilkBA = SinifYolu.secilenSaglayici(List.of(cagiranD, cepheD, bD, aD));
System.out.println("A once verildiginde secilen: " + ilkAB);
System.out.println("B once verildiginde secilen: " + ilkBA);
System.out.println();
System.out.println("-- yollar ayrildiginda kusur nerede goruluyor --");
SinifYolu.kusur("derleme yolunda tip yok", SinifYolu.derle(kok.resolve("d6"), List.of(sabitD), sKullanan) ? "sessiz" : "derleme");
SinifYolu.kusur("kosum yolunda tip yok", SinifYolu.kos(List.of(kullananD, sabitD), "Kullanan").equals("tamam") ? "sessiz" : "kosum");
SinifYolu.kusur("kosum yolunda iki gerceklestirim", ilkAB.equals(ilkBA) ? "kosum" : "sessiz");
Path sabitYeni = kok.resolve("sabitYeni");
SinifYolu.derle(sabitYeni, List.of(), SinifYolu.yaz(kok.resolve("kaynak2"), "Sabit",
"public class Sabit { public static final int DEGER = 8; }"));
Path olcen = kok.resolve("olcen");
Path sOlcen = SinifYolu.yaz(kaynak, "Olcen",
"public class Olcen { public static int gorulen = -1;\n"
+ " public static void main(String[] a) { gorulen = Sabit.DEGER; } }");
SinifYolu.derle(olcen, List.of(sabitD), sOlcen);
int eskiDeger = SinifYolu.okunanAlan(List.of(olcen, sabitYeni), "Olcen", "gorulen");
SinifYolu.kusur("sabit degisti, cagiran derlenmedi", eskiDeger == 8 ? "kosum" : "sessiz");
System.out.println("yeni sinif yolundaki sabit : 8");
System.out.println("cagiranin gordugu deger : " + eskiDeger);
System.out.println();
SinifYolu.ozet();
}
}
-- alti tip hangi sinif yolunda gerekli -- Sabit (derleme zamani sabiti) derleme Arac (dogrudan bagimlilik) ikisi Derin (gecisli bagimlilik) kosum Cephe (arayuz) ikisi KayitA (gerceklestirim) kosum KayitB (ikinci gerceklestirim) kosum -- gecisli bagimlilik: Derin, Arac uzerinden derleme sinif yoluna hic girmiyor -- Kullanan, [Sabit, Arac] derleme sinif yoluna karsi derlendi mi: true bu derleme sinif yolunda Derin hic yok, ama Kullanan derleniyor -- cakisma cozumu: kosum yolunda iki gerceklestirim, secimi sira belirliyor -- A once verildiginde secilen: A B once verildiginde secilen: B -- yollar ayrildiginda kusur nerede goruluyor -- derleme yolunda tip yok derleme kosum yolunda tip yok kosum kosum yolunda iki gerceklestirim sessiz sabit degisti, cagiran derlenmedi sessiz yeni sinif yolundaki sabit : 8 cagiranin gordugu deger : 7 tip : 1 yalniz derleme, 3 yalniz kosum, 2 ikisi kusur : 1 derleme hatasi, 1 kosum hatasi, 2 sessiz
Altı satırın ilk beşi tek tek derlenip koşturularak elde edildi: her satırda aynı
Kullanan kaynağı, o tipin bulunmadığı bir sınıf yoluna karşı önce derlenmeye, sonra
koşturulmaya çalışılıyor. Derleme başarısızsa o tip derlemede gerekli; koşum başarısızsa
koşumda gerekli. Sabit yalnız derlemede gerekli çıktı — değeri çağrı yerine gömüldüğü
için koşum sınıf yolunda hiç aranmıyor. Arac ve Cephe her iki yolda da gerekli: ikisi
de kaynakta doğrudan adlandırılıyor, ikisinin de sınıf dosyası hem derleme hem koşum
aşamasında aranıyor. Derin, KayitA ve KayitB yalnız koşumda gerekli — üçü de kaynak
metinde hiçbir yerde doğrudan yazılmıyor.
Sonuç: altı tipin biri yalnız derleme sınıf yolunda, üçü yalnız koşum sınıf yolunda, ikisi her iki yolda birden gerekli. Bu sayı bu konunun ortak ölçümüdür ve konu boyunca değişmeden kalıyor.
Geçişli Bağımlılık ve Çakışma Çözümü
Derin’in durumu en çok yanıltan olanı. Arac, Derini doğrudan çağırıyor — ama
Kullanan, Aracı çağırırken Derini hiç adlandırmıyor. Derleme sınıf yolu yalnız
Sabit ve Aracı içerdiğinde Kullanan sorunsuz derleniyor; Derin bu yolda hiç yok.
Bu, geçişli bağımlılığın (transitive dependency) derleme sınıf yolunda görünmeden
koşumda gerekebileceğinin doğrudan kanıtı — derlemenin geçmesi, koşum sınıf yolunun
eksiksiz olduğunu göstermiyor.
Aynı kaynak metin farklı bir soruyu da açığa çıkarıyor: koşum sınıf yolunda Cephe’nin
iki gerçekleştirimi (KayitA, KayitB) aynı anda bulunduğunda hangisi seçiliyor?
Cagiran servis bildirimini tarıyor ve ilk bulduğunu alıyor; ilk bulunan, sınıf yoluna
verilme sırasına bağlı. A önce verildiğinde seçilen A, B önce verildiğinde
seçilen B — çakışma çözümü (conflict resolution) burada bir kural değil, bir
sıralama sonucu. Kod hiçbir yerde hata vermiyor; iki farklı kurulum aynı programdan iki
farklı yanıt üretiyor.
Sınırlayıcı Ölçüm: Sessiz Kusurun İki Yüzü
- DR12. “Sessiz” kusur sınıfı, ne derleyicinin ne çalışma zamanının hiçbir şey söylemediği ama sonucun beklenenden farklı çıktığı durumları topluyor; her sessiz satırda beklenen değer de yazılıyor.
- DR13.
SabitdeğiştirilipKullananyeniden derlenmeden koşum sınıf yolundaki sürüm değiştirildiğinde, iki taraf arasındaki fark yalnız okunan alanın değeriyle ölçülüyor; hata fırlatılmıyor.
Aynı ölçüm dört ayrı kusuru üç sınıfa ayırıyor: derleme sınıf yolunda tip yokluğu
derleyici tarafından reddediliyor (derleme), koşum sınıf yolunda tip yokluğu çalışma
zamanında bir hata
fırlatıyor (kosum), geri kalan ikisi hiçbir şey söylemiyor (sessiz). Koşum yolunda iki
gerçekleştirim bulunması bunlardan biri — program hiçbir hata vermeden iki farklı yanıt
üretiyor, ve hangi yanıtın “doğru” olduğu kodun kendisinden anlaşılmıyor.
İkinci sessiz kusur daha ince: Sabit’in değeri 8’e çıkarılıyor, ama Kullanan bu
yeni sürüme karşı yeniden derlenmiyor. Koşum sınıf yolundaki Sabit 8 taşısa da,
Kullanan’ın sınıf dosyasına önceden gömülen değer 7 olarak kalıyor — çünkü gömme
derleme anında oldu, koşum anında değil. Sınıf yolu güncellenmiş görünüyor, ama derlenmiş
sınıf hâlâ eski değeri taşıyor; bu satır sessiz sınıfına giriyor çünkü ne derleme ne koşum
bir şikâyet üretiyor, sonuç yalnız yanlış.
Toplamda: dört kusurun biri derleme hatası, biri koşum hatası, ikisi sessiz.
“Derlendi” sonucu bu yüzden bir kanıt değil — geçişli bağımlılık derleme sınıf yolunda hiç
bulunmadan kaynak sorunsuz derleniyor, ama koşumda gerekiyor; derlemenin geçmesi koşum
sınıf yolunun eksiksiz olduğunu göstermiyor. Bir tipin koşum sınıf yolunda gerekmemesi de
ona bağlı olunmadığı anlamına gelmiyor — Sabit’in yokluğu onun kopyalanmış olduğu
anlamına geliyor, ve o kopya iki taraf ayrıştığında sessizce yanlış sonuç üretiyor.
Özet
- Bağımlılık kapsamı üç değer alır (derleme, koşum, ikisi); altı tip üzerinde ölçülünce dağılım 1 yalnız derleme, 3 yalnız koşum, 2 ikisi çıkar ve kaynak metinden çıkarılamaz.
- Geçişli bağımlılık (
Derin) derleme sınıf yolunda hiç bulunmadan kaynak sorunsuz derlenir, ama koşumda gerekir; “derlendi” bir kanıt değildir. - Çakışma çözümü, aynı arayüzün iki gerçekleştirimi koşum sınıf yolunda birlikte bulunduğunda sıraya bağlı hâle gelir; hata yoktur, sonuç kurulumdan kuruluma değişir.
- Dört kusurdan biri derleme hatası, biri koşum hatası, ikisi sessizdir; sessiz kusurlarda derleyici de çalışma zamanı da hiçbir şey söylemez, sonuç yalnız yanlış çıkar.
- Bir tipin koşum sınıf yolunda gerekmemesi ona bağlı olunmadığı anlamına gelmez: değeri gömülmüş olabilir, ve o gömülü değer kaynak değiştiğinde sessizce eskir.
Sonraki Adım
Bu derste altı tekil tip ölçüldü — her biri tek bir sınıfın ya da arayüzün sınırları içinde kaldı. Ama gerçek bir bildirim tek dosyalık sınıflardan değil, birden çok kaynak dizininden kurulu modüllerden oluşur, ve bir modülün sınırı derlemede nasıl bir karşılığa denk düşüyor sorusu henüz sorulmadı. Sıradaki ders bunu ölçüyor: bir modülün “iç” saydığı bir tip, sınır aşıldığında derlemede mi yakalanıyor, koşumda mı, yoksa hiç yakalanmıyor mu — ve bu yakalamanın kaynağı gerçekten bir dizin ayrımı mı.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.