Peki statik ve dinamik bağlamanın gerçekte nasıl çalıştığını anlamak için ? veya derleyici ve JVM tarafından nasıl tanımlanırlar?
Mammal
Bir metoda speak()
ve Human
sınıfa sahip bir ebeveyn sınıfının Mammal
genişlediği, speak()
metodu geçersiz kıldığı ve sonra tekrar aşırı yüklediği aşağıdaki örneği ele alalım speak(String language)
.
public class OverridingInternalExample {
private static class Mammal {
public void speak() { System.out.println("ohlllalalalalalaoaoaoa"); }
}
private static class Human extends Mammal {
@Override
public void speak() { System.out.println("Hello"); }
public void speak(String language) {
if (language.equals("Hindi")) System.out.println("Namaste");
else System.out.println("Hello");
}
@Override
public String toString() { return "Human Class"; }
}
public static void main(String[] args) {
Mammal anyMammal = new Mammal();
anyMammal.speak();
Mammal humanMammal = new Human();
humanMammal.speak();
Human human = new Human();
human.speak();
human.speak("Hindi");
}
}
Yukarıdaki kodu derlediğimizde ve kullanarak bytecode'a bakmaya çalıştığımızda javap -verbose OverridingInternalExample
, derleyicinin çıkardığım ve programın kendisine dahil ettiğim program için her yöntem çağrısına ve bayt koduna tamsayı kodları atadığı sabit bir tablo oluşturduğunu görebiliriz ( her yöntem çağrısının altındaki yorumlara bakın)
Yukarıdaki kod bakarak biz baytkodlarına olduğunu görebilirsiniz humanMammal.speak()
, human.speak()
ve human.speak("Hindi")
tamamen farklı ( invokevirtual #4
, invokevirtual #7
, invokevirtual #9
) derleyici argüman listesi ve sınıf referansı dayalı aralarında ayrım yapabiliyor çünkü. Tüm bunlar derleme zamanında statik olarak çözüldüğünden, Yöntem Aşırı Yüklemesinin Statik Polimorfizm veya Statik Bağlama olarak bilinmesinin nedeni budur .
Ancak bayt kodu için anyMammal.speak()
ve humanMammal.speak()
aynıdır ( invokevirtual #4
) çünkü derleyiciye göre her iki yöntem de Mammal
başvuru üzerine çağrılır .
Öyleyse şimdi soru, her iki yöntem çağrısının da aynı bayt koduna sahip olması durumunda ortaya çıkıyor, o zaman JVM hangi yöntemi çağıracağını nasıl biliyor?
Cevap bayt kodunun kendisinde gizlidir ve invokevirtual
komut setidir. JVM invokevirtual
, C ++ sanal yöntemlerinin Java eşdeğerini çağırmak için talimatı kullanır . C ++ 'da, başka bir sınıftaki bir yöntemi geçersiz kılmak istiyorsak, onu sanal olarak bildirmemiz gerekir, ancak Java'da, tüm yöntemler varsayılan olarak sanaldır çünkü çocuk sınıfındaki her yöntemi geçersiz kılabiliriz (özel, son ve statik yöntemler hariç).
Java'da her referans değişkeni iki gizli işaret içerir
- Yine nesnenin yöntemlerini ve Class nesnesine bir işaretçi tutan bir tablo işaretçisi. örneğin [konuş (), konuş (String) Sınıf nesnesi]
- Bu nesnenin verileri için öbek üzerinde ayrılmış belleğe bir işaretçi, örneğin örnek değişkenlerinin değerleri.
Dolayısıyla, tüm nesne referansları, o nesnenin tüm yöntem referanslarını tutan bir tabloya dolaylı olarak bir referans içerir. Java bu kavramı C ++ 'dan ödünç almıştır ve bu tablo sanal tablo (vtable) olarak bilinir.
Bir vtable, sanal yöntem adlarını ve bunların referanslarını dizi indekslerinde tutan dizi benzeri bir yapıdır. JVM, sınıfı belleğe yüklediğinde sınıf başına yalnızca bir vtable oluşturur.
Dolayısıyla, JVM bir invokevirtual
yönerge kümesiyle her karşılaştığında , o sınıfın vtable'ını yöntem başvurusu için kontrol eder ve bizim durumumuzda başvurudan değil bir nesneden gelen yöntem olan belirli yöntemi çağırır.
Tüm bunlar yalnızca çalışma zamanında ve çalışma zamanında çözüldüğünden, JVM hangi yöntemi çağıracağını bildiği için, Yöntemi Geçersiz Kılma Dinamik Polimorfizm veya kısaca Polimorfizm veya Dinamik Bağlama olarak bilinir .
JVM Metodu Aşırı Yüklemeyi ve Dahili Olarak Geçersiz Kılmayı Nasıl İşler? Makalemde daha fazla ayrıntı okuyabilirsiniz .