Moje wyobrażenie o zawartości obiektów i klas na poziomie implementacji opiszę w tym poście. Niekoniecznie jest to w 100% zgodne z rzeczywistością.
Załóżmy, że jest taki kod:
class A {
int i;
int a;
}
abstract class B extends A {
int m() {
return 1;
}
public abstract char n();
int i;
int b;
}
interface D {
char n();
float o();
}
class C extends B implements D {
char n() {
return 'c';
}
float o() {
return 3.14f;
}
int i;
int c;
}
Po stworzeniu obiektu za pomocą new C()
dostajemy obiekt o następujących 4-bajtowych elementach:
- wskaźnik do klasy/ identyfikator klasy/ cokolwiek prowadzące do klasy - załóżmy, że to wskaźnik do klasy,
- zmienna pomocnicza (tak mi wychodzi z obliczeń - narzut na obiekt to 8 bajtów nawet w 32-bitowej Javie, więc to musi gdzieś tutaj siedzieć),
- zmienna
i
pochodząca z klasy A
,
- zmienna
a
pochodząca z klasy A
,
- zmienna
i
pochodząca z klasy B
,
- zmienna
b
pochodząca z klasy B
,
- zmienna
i
pochodząca z klasy C
,
- zmienna
c
pochodząca z klasy C
,
Klasa C
zawiera vtable w czterech wersjach:
- wersja dla klasy
A
, która jest pusta, bo w A
nie ma metod,
- wersja dla klasy
B
zawierające dwa elementy:
-- metoda m
prowadząca do implementacji z klasy B
,
-- metoda n
prowadząca do implementacji z klasy C
,
- wersja dla klasy
C
zawierająca trzy elementy:
-- metoda m
prowadząca do implementacji z klasy B
,
-- metoda n
prowadząca do implementacji z klasy C
,
-- metoda o
prowadząca do implementacji z klasy C
,
- wersja dla interfejsu
D
zawierająca dwa elementy:
-- metoda n
prowadząca do implementacji z klasy C
,
-- metoda o
prowadząca do implementacji z klasy C
,
Wskaźniki do metod zawsze prowadzą do klasy najniżej w hierarchii, w której jest zdefiniowana czy nadpisana dana metoda. Tak działają metody wirtualne, które są podstawą dziedziczenia w Javie. Stąd od typu zmiennej referencyjnej zależy to do którego vtable się dobieramy, ale każdy vtable ma te same wskaźniki dla tych samych metod.
Hierarchia powyżej w rzeczywistości jest błędna, bo pomija istnienie klasy Object i to, że każdy obiekt z niej dziedziczy. Z tego powodu vtable dla klasy A
powinno zawierać wszystkie metody z klasy Object i analogicznie dla reszty vtable.
Dobieranie się do pól obiektu wykonuje się bezpośrednio, z pominięciem vtable. Jeżeli zdefiniujemy zmienną o tej samej nazwie na kilku poziomach dziedziczenia to nasz obiekt będzie zawierał tę zmienną w kilku kopiach i to do której kopii się odwołamy będzie zależeć od typu zmiennej referencyjnej, którą wykorzystujemy do operowania na obiekcie. Przykład: http://ideone.com/p9sdzK
Update:
Jeszcze jedna sprawa. W Javie alokowanie pamięci dla obiektu oraz wywołanie konstruktora to dwie osobne instrukcje na poziomie bajtkodu. Alokowanie pamięci dla obiektu jest połączone z czyszczeniem tejże pamięci czyli ustawianiem wartości pól na wartości domyślne (czyli int
to 0
, boolean
to false
, obiekty to null
itd). Dopiero po pełnym zaalokowaniu i wyczyszczeniu wywoływany jest konstruktor.
Ponadto konstruktory są efektywnie wywoływane od najwyższego (czyli z klasy Object
) do najniższego (czyli tego który podajemy po słówku new
). Działa to tak, że pierwszą instrukcją w kodzie Javowym konstruktora (lub jedną z pierwszych na poziomie bajtkodu) jest wywołanie super()
czyli konstruktora z klasy nadrzędnej. Wywołanie to jest robione explicite (poprzez wpisanie tego super(cośtam)
do konstruktora) lub implicite (czyli jak kompilator nie znajdzie super()
w konstruktorze to sobie sam dopisze). Ale w sumie kolejność inicjalizacji to osobny temat, więc możesz to tutaj olać.