8 czerwca 2008

SCJP - Bloki inicjalizacyjne

Dziś będzie o blokach inicjalizacyjnych (ang. initialization blocks). Kolejny kroczek w ciągnących się przygotowaniach do SCJP zapowiedzianych w artykule "Przygotowania do SCJP czas zacząć".

Bloki inicjalizacyjne występują w dwu rodzajach: jako bloki instancyjne i bloki statyczne. Blok inicjalizacyjny to fragment kodu, który wykonuje się – w przypadku bloków statycznych – gdy ładowana jest klasa, bądź – w przypadku bloków instancyjnych – gdy tworzona jest instancja. Zerknijmy na poniższy przykład.

public class Test {
public Test() {
System.out.println("constructor");
}

static {
System.out.println("static init block");
}

{
System.out.println("instance init block");
}

public static void main(String[] args) {
System.out.println("start main");

Test test = new Test();

System.out.println("end main");
}
}

Kod znajdujący się między konstruktorem a metodą ‘main(…)’ to właśnie bloki inicjalizacyjne. Ten oznaczony słówkiem kluczowym ‘static’ to blok statyczny. Ten drugi to blok instancji. Bloki statyczne – jak już powiedzieliśmy – wykonywane są w momencie ładowania klasy a więc zawsze jest to kod, który wykona się jako pierwszy. O blokach instancyjnych powiedzieliśmy sobie, że wykonują się w momencie tworzenia instancji, ale w momencie tworzenia instancji klasy wykonują się też konstruktory. Jaka jest zatem kolejność? Wpierw konstruktor czy blok inicjalizacyjny? Otóż ani to, ani to. Jak pisałem w artykule "SCJP - Konstruktory i inicjalizacja" pierwszą instrukcją konstruktora jest zawsze wywołanie konstruktora nadklasy albo innego konstruktora tej samej klasy. Właśnie zaraz po tym wywołaniu – przed wykonaniem jakiejkolwiek innej instrukcji konstruktora – wykonywany jest kod bloków inicjalizacyjnych instancji. Efekt uruchomienia powyższego programu będzie zatem taki:

static init block
start main
instance init block
constructor
end main

W poprzednim akapicie pisałem o blokach inicjalizacyjnych w liczbie mnogiej i bynajmniej nie był to mój błąd. W każdej klasie może występować dowolna ilość bloków inicjalizacyjnych. O kolejności ich wykonania decyduje kolejność w jakiej są one zapisane, patrząc od góry do dołu. Spójrzmy na kolejny przykład.

class SuperClass {
public SuperClass() {
System.out.println("superclass constructor");
}

{
System.out.println("superclass instance init block");
}
}

public class Test extends SuperClass {
public Test() {
System.out.println("constructor");
}

static {
System.out.println("static init block");
}

{
System.out.println("instance init block #1");
}

{
System.out.println("instance init block #2");
}

public static void main(String[] args) {
Test test = new Test();
}
}

Jak widzimy pojawiła się większa ilość bloków inicjalizacyjnych oraz nadklasa. Przekonajmy się, że kolejność wywoływania kodu bloków inicjalizacyjnych versus konstruktory jest taka właśnie jak to opisałem. Uruchomienie tego programu spowoduje wypisanie następującej sekwencji.

static init block
superclass instance init block
superclass constructor
instance init block #1
instance init block #2
constructor

A co się stanie, jeśli kod bloku inicjalizującego spowoduje wyjątek, np. odwołamy się do niewłaściwego indeksu tablicy? Naturalnie wyjątek zostanie zwrócony… w postaci opakowanej w java.lang.ExceptionInInitializerError.

5 komentarzy:

Jakub Neumann pisze...

Dla pełności dodałbym jeszcze tylko static block w klasie bazowej i zasadę, która się tutaj pojawia: inicjalizacja statyczna przed instancyjną, obie w kolejności od klas bazowych, ku pochodnym. Dotyczy to również inicjalizacji pól statycznych i instancyjnych.

Mariusz Lipiński pisze...

A to ciekawe co piszesz. Czy rzeczywiście jest tak, że statyczny blok inicjalizacyjny z nadklasy zawsze wykona się przed statycznym blokiem z podklasy? Jak wiadomo, bloki te wywoływane są w momencie ładowania klasy (jak rozumiem przez ClassLoader'a co niestety jest nieznanym mi tematem). Czy rzeczywiście zawsze jest tak, że jako pierwsza ładowana jest nadklasa?

P.S. Miło mi powitać na moim blogu i gratuluję założenia swojego.

Jakub Neumann pisze...

Moim zdaniem wynika to z faktu potencjalnych zależności: statyczne pola podklasy mogą zależeć od statycznych pól klasy pochodnej (podobnie jak instancyjne). Rozumowanie przenoszę na bloki. Statyczny kod klasy pochodnej może zależeć od "prawidłowej, pełnej" statycznej inicjalizacji w klasie bazowej. Przykłady ad-hoc zdają się to potwierdzać. Oczywiście mogę się mylić bo przy zgłębianiu języka w pierwszym podejściu próbuję przeanalizować "jak powinno moim zdaniem być". Jak się coś nie zgadza wtedy sięgam do specyfikacji :)

P.S. Na Twój blog trafiłem niedawno, strasznie przypadł mi do gustu. Bardzo go cenię za zgłębianie niebanalnych zagadnień, bardzo konkretny a zarazem zrozumiały język. Będę tu często zaglądał i wdawał się w dyskusję, jeśli pozwolisz.

Mariusz Lipiński pisze...

Bardzo się cieszę, czytając taką opinię jaką wyraziłeś powyżej. Dokładnie na tym mi zależy - aby pisać precyzyjnie i zwięźle a przy tym zrozumiale. Nie mogłeś mi sprawić większej radości niż formułując to właśnie w ten sposób :)

Swoją drogą, czy nie "poznaliśmy się" na JAVArsovii? Może nie zupełnie bezpośrednio, ale chyba braliśmy udział w tej samej dyskusji, tj. w panelu JUG'owym.

Mariusz Lipiński pisze...

Z tego wrażenia zapomniałem ustosunkować się do meritum. Zerknąłem właśnie do specyfikacji języka Java i mówiąc po polsku masz rację - nadklasy są inicjalizowane zawsze pierwsze. A cytując (p. 12.4) jest to tak:

"Initialization of a class consists of executing its static initializers and the initializers
for static fields (class variables) declared in the class. Initialization of an
interface consists of executing the initializers for fields (constants) declared there.
Before a class is initialized, its superclass must be initialized, but interfaces
implemented by the class are not initialized. Similarly, the superinterfaces of an
interface are not initialized before the interface is initialized."

Naturalnie wszelkie polemiki z tym co piszę i usupełnienia są bardzo mile widziane. Zwłaszcza te krytyczne ale konstruktywne albo te pochwalne, które z natury zawsze są konstruktywne.