Nie ma absolutnie żadnego problemu w tworzeniu wystąpień klasy w samej klasie. Pozorny problem z kurczakiem lub jajkiem jest rozwiązywany na różne sposoby, podczas gdy program jest kompilowany i kiedy jest uruchamiany.
kompilacji
Kiedy klasa, która tworzy instancję sobie jest kompilowany, kompilator uzna, że klasa ma circular dependency na siebie. Ta zależność jest łatwa do rozwiązania: kompilator wie, że klasa jest już kompilowana, więc nie będzie próbowała skompilować jej ponownie. Zamiast tego udaje, że klasa już istnieje, generuje odpowiedni kod.
Run-time
Największym problemem kurczaka lub jajko z klasą tworząc obiekt sam w sobie jest, gdy klasa ma jeszcze nawet nie istnieją; to znaczy, gdy klasa jest ładowana. Ten problem został rozwiązany przez zerwanie ładowania klas na dwa etapy: najpierw klasa jest zdefiniowana, a następnie jest zainicjowana.
Definiowanie oznacza rejestrację klasy w systemie uruchomieniowym (JVM lub CLR), aby wiedział, jaka jest struktura obiektów klasy i jaki kod powinien zostać uruchomiony po wywołaniu jego konstruktorów i metod.
Po zdefiniowaniu klasy następuje jej inicjalizacja. Odbywa się to poprzez inicjowanie elementów statycznych i uruchamianie statycznych bloków inicjalizujących i innych rzeczy zdefiniowanych w danym języku. Przypomnij sobie, że klasa jest już zdefiniowana w tym momencie, więc środowisko wykonawcze wie, jakie obiekty klasy wyglądają i jaki kod powinien zostać uruchomiony, aby je utworzyć. Oznacza to, że nie ma problemu z tworzeniem obiektów klasy podczas jej inicjowania.
Oto przykład, który ilustruje, jak i instancji klasy inicjalizacji współdziałać w Javie:
class Test {
static Test instance = new Test();
static int x = 1;
public Test() {
System.out.printf("x=%d\n", x);
}
public static void main(String[] args) {
Test t = new Test();
}
}
Załóżmy, krok po kroku, w jaki sposób JVM będzie uruchamiać ten program. Najpierw JVM ładuje klasę Test
.Oznacza to, że klasa jest pierwsza zdefiniowane tak, że JVM wie, że
- klasy o nazwie
Test
istnieje i że ma main
metody i konstruktora, a
- klasę
Test
ma dwa statyczne zmienne, jeden o nazwie x
i inny o nazwie instance
i
- jaki jest układ obiektu klasy
Test
. Innymi słowy: jak wygląda obiekt; jakie ma atrybuty. W tym przypadku Test
nie ma żadnych atrybutów instancji.
Teraz, gdy klasa jest zdefiniowana, jest to zainicjowana. Po pierwsze, domyślna wartość 0
lub null
jest przypisana do każdego atrybutu statycznego. To ustawia x
na 0
. Następnie JVM wykonuje inicjatory pól statycznych w kolejności kodu źródłowego. Istnieją dwa:
- Utwórz instancję klasy
Test
i przypisać ją do instance
. Aby utworzyć instancję, wykonaj dwa kroki:
- Pierwsza pamięć jest przydzielona dla obiektu. JVM może to zrobić, ponieważ zna już układ obiektu z fazy definiowania klasy.
- Konstruktor
Test()
jest wywoływany w celu zainicjowania obiektu. JVM może to zrobić, ponieważ ma już kod konstruktora z fazy definicji klasy. Konstruktor wypisze bieżącą wartość x
, która jest 0
.
- Ustaw zmienną statyczną
x
na 1
.
Dopiero teraz klasa zakończyła ładowanie. Zauważ, że maszyna JVM utworzyła instancję klasy, mimo że nie była jeszcze w pełni załadowana. Masz dowód na to, ponieważ konstruktor wydrukował początkową domyślną wartość 0
dla x
.
Po załadowaniu tej klasy przez JVM wywołuje ona metodę main
w celu uruchomienia programu. Metoda main
tworzy kolejny obiekt klasy Test
- drugi w wykonaniu programu. Ponownie konstruktor wypisze bieżącą wartość x
, która jest teraz 1
. Pełne wyjście programu jest:
x=0
x=1
Jak widać nie ma problemu z kurczaka albo jaj: oddzielenie klasy załadunku do definicji i fazy inicjalizacji unika problemu całkowicie.
Co zrobić, gdy instancja obiektu chce utworzyć inną instancję, jak w poniższym kodzie?
class Test {
Test buggy = new Test();
}
Po utworzeniu obiektu tej klasy ponownie nie występuje nieodłączny problem. JVM wie, jak obiekt powinien zostać umieszczony w pamięci, aby mógł przydzielić dla niego pamięć. Ustawia wszystkie atrybuty na ich wartości domyślne, więc jest ustawione na null
. Następnie JVM rozpoczyna inicjowanie obiektu.Aby to zrobić, musi utworzyć inny obiekt klasy Test
. Tak jak wcześniej, JVM wie już, jak to zrobić: alokuje pamięć, ustawia atrybut na null
i rozpoczyna inicjowanie nowego obiektu ... co oznacza, że musi utworzyć trzeci obiekt z tej samej klasy, a następnie czwarty, piąty itd., dopóki nie skończy się miejsce na stosie lub pamięć sterty.
Nie ma tutaj problemu koncepcyjnego: jest to zwykły przypadek nieskończonej rekursji w źle napisanym programie. Rekursję można kontrolować na przykład za pomocą licznika; konstruktor tej klasy używa rekursji do sieci obiektów:
class Chain {
Chain link = null;
public Chain(int length) {
if (length > 1) link = new Chain(length-1);
}
}
Tak długo, jak długo nie tworzysz 'MyClass 'w konstruktorze' MyClass' '(Yay dla nieskończonej rekurencji) nie ma absolutnie żadnego problemu. Wzornictwo wzoru kompozytowego jest nawet oparte na tym. Naprawdę nie rozumiem, o co chodzi. Ponadto 'public void class' nigdy się nie skompiluje. –
Wiem, że można to zrobić, ale moje pytanie brzmi: czy to nie jest tak, że używasz funkcji bez tworzenia funkcji na pierwszym miejscu. Jak możesz wyjaśnić to komuś, kto zna tylko programowanie funkcjonalne? –
Która część procesu ładowania klas i tworzenia instancji jest nadal niejasna? – Joni