Java-Freud und Java-Leid

Seit An­fang die­sen Jah­res bin ich be­rufs­tech­nisch von C++ auf Java um­ge­schwenkt. C++ ist ja be­kannt dafür, die di­ver­ses­ten Fall­stri­cke zu haben - und Java ist ja ir­gend­wann mal mit dem Vor­satz an­ge­tre­ten, das alles bes­ser zu ma­chen. Un­glück­li­cher­wei­se ist das al­len­falls par­ti­ell ge­lun­gen, denn ich finde al­lein schon im Ar­beits­all­tag immer wie­der hä­ß­li­che Fu­ßan­geln. So zum Bei­spiel fol­gen­des Pro­blem beim Auf­ruf über­la­de­ner Me­tho­den im Kon­struk­tor:

Ich skiz­zie­re mal grob fol­gen­des Sze­na­rio:
{syn­tax­high­ligh­ter brush:java}
ab­stract class A {
int i;
pu­blic A() {
foo();
}
pu­blic void foo() {
i = 42;
}
}
class B ex­tends A {
List daten;
pu­blic void foo() {
super.​foo();
daten = new Ar­ray­List();
daten.​add("Foo!"); // De­fault­wer­te ein­fü­gen
}
pu­blic int len() {
re­turn daten.​size();
}
}

(...)

B b = new B();
System.​out.​println(b.​len());
{/syn­tax­high­ligh­ter}

Mit C++ wäre die Bauch­lan­dung vor­pro­gram­miert (no pun in­ten­ded): Im Kon­struk­tor kennt C++ die Ver­er­bungs­hier­ar­chie noch nicht und würde des­halb nur A.​foo() auf­ru­fen - beim Auf­ruf von b.​len() wäre daten nicht in­itia­li­siert und es gäbe (aller Vor­aus­sicht nach, bei Ver­wen­dung von dy­na­mi­schen Struk­tu­ren) einen seg­fault. Was pas­siert nun aber bei Java?

Java kennt be­reits im Kon­struk­tor die Ver­er­bungs­hier­ar­chie und ruft fol­ge­rich­tig B.​foo(). Was aber lie­fer­te nun mein Code, der vom Prin­zip her dem ge­zeig­ten Code­schnip­sel ent­spricht? Eben­falls eine Null­Poin­ter­Ex­cep­ti­on!

Der Grund: Java ver­ar­bei­tet nicht zu­erst die In­itia­li­sie­rung aller Mem­ber­va­ria­blen, son­dern er­le­digt auch das in Auf­ruf­rei­hen­fol­ge der Kon­struk­to­ren. Und diese Lau­tet: Erst Kon­struk­tor der Va­ter­klas­se rufen, dann ei­ge­ne Va­ria­blen in­itia­li­sie­ren, dann Rest vom ei­ge­nen Kon­struktor­code aus­füh­ren (so­fern vor­han­den). Genau ge­nom­men ar­bei­tet B.​foo() also auf nicht in­i­ta­li­sier­ten Wer­ten (die es bei Java ei­gent­lich nicht geben soll­te)? Je­den­falls wird die In­itia­li­sie­rung aus B.​foo() zu­nich­te ge­macht, da die Auf­ruf­rei­hen­fol­ge fol­gen­der­ma­ßen aus­sieht:

  • Mem­bers von A in­itia­li­sie­ren
  • Kon­struk­tor von A aus­füren
  • Die­ser ruft foo(), das über­la­de­ne B.​foo() wird aus­ge­führt
  • Mem­bers von B in­itia­li­sie­ren, dabei wird daten auf null ge­setzt (aua!)
  • Kon­struktor­code von B aus­füh­ren (ist hier nicht vor­han­den)

Fast per­fekt, liebe Ja­va-De­si­gner... aber eben nur fast :-(

Edit: Er­staun­li­cher­wei­se re­pro­du­ziert der obige Code nicht mein Pro­blem... ob­wohl ich in mei­nem Pro­jekt­code die­ses Pro­blem und im De­bug­ger genau die oben be­schrie­be­ne Auf­ruf­rei­hen­fol­ge nach­voll­zie­hen konn­te. Das re­ha­bi­li­tiert die Ja­va-De­si­gner zwar, löste aber mein kon­kre­tes Pro­blem nicht ;-) Hm, ein VM-Bug?