Konstruktor in super()

#1
public class SingleSceneGraphNode extends SceneGraphCluster {
    public SingleSceneGraphNode(ClusterCore creator, SceneGraphClusterRoot parent) {
        super(createCore(creator), parent);
    }

    public static SingleSceneGraphClusterCore createCore(ClusterCore creator) {
        return new SingleSceneGraphClusterCore(creator);
    }
}

Warum geht das dort oben, aber wenn ich die statische Methode createCore() inline wirft der compiler einen Fehler?

#2

das sind halt die Java-Regeln zur Initialisierung,
vor AusfĂŒhrung von super() ist das Objekt der Klasse reichlich wenig existent, selbst der innere Teil zur Oberklasse noch nicht, da wĂ€re Zugriff auf Methoden oder Attribute fraglich

statische Methoden oder irgendwelche anderen Klassen sind dagegen kein Problem

die Fehlermeldung in Suchmaschine, wie so oft, fĂŒhrt zu einem Thema wie


viel mehr kann man kaum sagen

oder noch:
komplett logisch ist es gewiss nicht, erlaubt ist etwa:

public class Test {

	final int k;

	public Test() {
		go();
		k = 5;
		go();
	}

	void go() {
		System.out.println("k: " + k);
	}

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

mit Ausgaben 0 + 5,
man könnte es gewiss so einrichten, dass vor allen Konstruktoren die Objekte erzeugt und grundinitalisiert sind, eben etwa mit finalen Variablen noch nicht gesetzt, aber Objekte schon verwendbar, Methodenaufrufe erlaubt,
aber ein paar Sicherheiten hat Java stattdessen halt eingebaut

#3

Es wundert mich halt, es geht gar nicht darum aus der statischen Methode eine nicht-statische zu machen, sondern die Methode zu inlinen, also:

super(new SingleSceneGraphClusterCore(creator), parent);

geht nicht. Obwohl faktisch genau das gleiche gemacht wird.

#4

ah, das also mit ‘inlinen’ gemeint


also

public class SingleSceneGraphNode extends SceneGraphCluster {
	public SingleSceneGraphNode(ClusterCore creator, SceneGraphClusterRoot parent) {
		super(new SingleSceneGraphClusterCore(creator), parent);
	}

}

class SceneGraphCluster {

	public SceneGraphCluster(SingleSceneGraphClusterCore x, SceneGraphClusterRoot r) {

	}
}

class ClusterCore {

}

class SingleSceneGraphClusterCore {
	public SingleSceneGraphClusterCore(ClusterCore c) {

	}
}

class SceneGraphClusterRoot {

}

compiliert bei mir, alles in einer Datei,
mit public-Testklassen genauso, ob weitere Vererbungsbeziehungen untereinander etwas Àndern können?..

evtl. etwas mit dem Code testen, vereinfachen, wann geht Fehler weg, falls wirklich vorhanden,
nicht einfach nur Spinnen von Eclipse/ andere IDE nur bei Nutzen der inline-Funktion?,

oder mehr vom Code posten, weitere Grundregel zu zitieren: vollstÀndiges Testprogramm vermeidet Neben-RÀtsel

1 Like
#5

Danke fĂŒr das Feedback. :slight_smile: Ich hab das ganze mal in Eclipse ausprobiert, da war dann alles in Ordnung. ZurĂŒck IntelliJ gestartet und dort dann plötzlich auch :smile:
Das kann natĂŒrlich wirklich sein, dass er aus irgendeinem Grund rumgesponnen hat.

Danke nochmal.

#6

Och doch, es ist logisch genug. Bevor der Code im Konstruktor ausgefĂŒhrt wird, werden alle Member initialisiert und dazu gehören auch Objektmethoden (ist auf VM-Ebene etwa mit “onclick=function() {}” in Javaacript vergleichbar). Bei der AusfĂŒhrung von super oder this sind die Member der Oberklasse sowie alle Methoden beider Klassen jedoch noch nicht initialisiert, deswegen funktioniert dort nur statischer Content. Wenn also der Content, der geinlined werden soll, wieder nur ein Objektmember ist, funktioniert es auch nicht.

Merke: Objektmember (inkl. Methoden) stehen erst nach super oder this zur VerfĂŒgung.

#7

Methoden haben keinen Zustand, jedenfalls keinen der einer Initialisierung braucht, AusfĂŒhrungsstand mit lokalen Variablen vielleicht


Methodenaufrufe sind nur verhindert wegen möglicher Interaktion mit Attributen/ Membern des Objektes, welche noch nicht initialisiert sind,

was aber eben auch relativ willkĂŒrlich ist,
technisch wÀre es leicht möglich, vor jedem User-Konstruktur das Objekt (auf allen Klassen-Schichten) mit einem Stand zu initialisieren wie in dem Beispiel der Ausgabe der final-Variablen

die EinschrĂ€nkung von Methoden vor super()/ this() schadet gewiss nicht, zu begrĂŒĂŸen, immerhin xy% der Probleme vermieden, aber sie ist recht beliebig gewĂ€hlt und insgesamt bleibt die Objektinitialisierung eine wacklige Sache

#8

Das ist nicht wirklich korrekt, denn Methoden haben eine Startadresse, die als Referenz benötigt wird und diese muss nun mal initialisiert werden. Die Adressen dieser (Objekt) Methoden sind dem Objekt also erst nach super oder this bekannt.

In Java mag das vllt. nicht auffallen, weil man da nur Method-IDs kennt, auf “nativer” Ebene (Maschinencode) lĂ€uft es aber in jeder Sprache nach diesem Schema ab und das ist weder willkĂŒrlich noch wackelig.

#9

hast du einen Link dazu? warum sollten Methoden mit einem bestimmten einzelnen Objekt verknĂŒpft sein, warum die Arbeit + Speicherplatz irgendwelche Adressen fĂŒr Methoden individuell fĂŒr dieses Objekt anzulegen, abzulegen, zu nutzen usw., bei jedem von potentiell Millionen Objekten einzeln?
das ergibt sich doch alles aus den Klassen,

interessant dabei vielleicht noch ‘Static Binding vs Dynamic binding’
https://javarevisited.blogspot.com/2012/03/what-is-static-and-dynamic-binding-in.html

aber alles was es fĂŒr Dynamic braucht ist das Objekt und darin die Information, welche konkrete Klasse es hat, entfernt vielleicht noch generische Parameter, dann der Rest wieder allein aus dem initialen Quellcode analysierbar, welche Methode aufzurufen usw.,

nirgendwo muss zum Objekt ein Plan von Methoden, Adressen, was auch immer abgelegt werden?!
das wÀre ja mal eine neue Einsicht, falls so vorhanden


passt ja auch nicht zu bekannten Speicheranalysen von Millionen abgelegten Objekten,
wieviel Byte ein Objekt im Speicher belegt und welcher Inhalt darin, das ist ziemlich bekannt,
nur die harten Fakten, die Inhalte der Instanzattribute, wenig allgemeines zum Objekt selber wie die Klasse
https://www.baeldung.com/java-size-of-object

alles andere passiert sowieso außenrum von der Runtime, Auswahl der Methoden usw., das Objekt hat dazu keine Informationen, keine Initialisierung,
jeder Aufruf, jeder Code am Ende in Maschinencode, gewiss, aber nichts in der Hinsicht dass ein Objekt irgendwelche Adressen initialisiert wissen muss



eine Spielerei zu Konstruktoren noch:

public class Test {

	public Test() {
		Test2 t2 = (Test2) this;
		t2.c = t2.a + t2.b;
	}

	public static void main(String[] args) {
		new Test2();
	}
}

class Test2 extends Test {

	final int a = 4;
	int b = 4;
	int c;
	final int d = c;

	Test2() {
		super();
		System.out.println("log: " + a + " - " + b + " - " + c + " - " + d);
	}

}

Ausgabe 4 - 4 - 4 - 4

bevor der Konstruktor der Oberklasse drankommt ist Zugriff wohl wirksam verhindert, außer vielleicht mit Reflection-Gemeinheiten,

bei AusfĂŒhrung bzw. zu Beginn des Oberklasse-Konstruktors ist die finale Variable a der Unterklasse auch schon initialisiert,
nicht aber finales d, von anderen abhÀngig,
nicht-finales b trotz gleicher fester Zahl wie a auch noch nicht initialisiert,

die Oberklasse kann c setzen (in einer Berechnung mit b noch als 0) und damit dem finalen d einen anderen Stand geben als wenn nicht eingegriffen


wĂŒrde c als “int c = 3;” definiert werden, wĂŒrden die Änderungen an c durch die Oberklasse mit diesem Wert 3 ĂŒberschrieben werden, sobald die Unterklasse ihre Hauptinitialisierungsrunde beginnt


eine Menge Stolperfallen, aber nur bei nicht zu empfehlenden Umgang, im Normalfall kaum Problem

#10

Wozu ein Link, was man in jeder einschlĂ€gigen Fachliteratur findet? Was ist wohl kĂŒrzer? Eine Method-ID oder eine Adresse? Welches Objekt har auf Maschinencode-Ebene noch einen Namen? Es gibt dort nur noch Startadressen von Klassen, Objekten und Methoden in Form von Referenzen. NatĂŒrlich liegt der Code einer ĂŒberschriebenen Methode an einer anderen Stelle als die ĂŒberschreibende Methode und das muss in der Sprungtabelle der jeweiligen Objektstruktur verzeichnet werden. In Fachkreisen heißen startadressen fĂŒr Methoden btw. Handle, falls du danach suchen und dich darin einlesen möchtest.

Und wie das passt. Wozu sollte ein ordinĂ€res Objekt ganze 16 Byte Speicher verbrauchen? Liegt da zufĂ€llig die Adresse auf die eben erwĂ€hnte Sprungtabelle? Wie labg diese Sprungtabelle ist, hĂ€ngt jedenfalls von der Anzahl der definierten Methoden ab und das wird von dem Programm in deinem Link gar nicht erfasst. Die Sprungtabellen können immer die selben sein, mĂŒssen sie aber nicht.

Genau, es passiert außenrum aber immerhin innerhalb der Runtime - die Initialisierung von Objektmethoden (Sprungtabellen) z.B. normalerweise unmittelbar nach super() oder this()

Die Kapriolen, die du bei deiner Klasse Test schlĂ€gst, sind dir hoffentlich bewusst - die Klasse kann nicht ohne Test2 (ClassCastException). Und wĂ€hrend der Konstruktor Test() aufgerufen wird, steht fĂŒr a, b, c und d bereits Speicher zur VerfĂŒgung, c wird nicht mal initialisiert - du kannst dich ja mal fragen, welches von Beiden (a oder b) zum Zeitpunkt der Verwendung im Konstruktor Test() noch 0 ist. Ich wĂŒrde ja sagen, dass es beide sind, die 4 aus dem Hut gezaubert (vom Stack geholt) wird und Initialisierung von Test2 (inkl. Sprungtabellen und Variablen) erst nach super() fertig gestellt wird. Feststellen kann man dies indem man einen BP vor “t2.c = t2.a + t2.b;” setzt.

Und jetzt komm mir nicht damit, ich hĂ€tte zuvor nur von Startadressen von Methoden gesprochen. Die Sache ist kompliziert genug und es genĂŒgt, sich zu merken, dass einem Objektmethoden erst nach super() und this() fĂŒr das Objekt zur VerfĂŒgung stehen, selbst wenn man sie bereits im Konstruktor von Test() aufrufen kann (Test2 ist sozusagen noch Ghost-Instance).

#11

jetzt mal Hand bei Fuss, denkst du dass zu jedem einzelnen 16 Byte-Objekt noch, wenn schon nicht direkt dazu, dann eben woanders, verlinkt, individuell jeweils extra neu (!) 100 Byte oder kb an komplexen Tabellen mit Adressen oder wer weiß was alles fĂŒr potentiell hunderte Methoden analysiert, angelegt, vorgehalten werden?
1 Mio. Integer brauchen statt ĂŒber den Daumen 20 MB auf einmal mehrere GB??

wenn doch nur allgemein eine derartige Struktur pro Klasse, nur eine fĂŒr Integer-Klasse, selbstverstĂ€ndlich, das ist vorhanden, aber das ist eben die Klasse, schon lange vorher fertig, im Hintergrund des großen Drumherums, hat mit der Objektinitialisierung nichts zu tun,

zu Objekt muss der Typ bekannt sein, ob nun Adresse, Handle oder sonst wie eindeutiges, Details doch egal, beim Anlegen des Objektes natĂŒrlich bekannt vom Code der dieses Objekt anlegt, nur die Info abgelegt, nichts zu berechnen, nichts zu initialisieren, Aufrufe von Methoden rein willkĂŒrlich verboten oder halt auch nicht gut möglich weil Objekt noch nicht im Speicher angelegt, fertig

#12

Ähm
 ganz sicher nicht.
Es existiert fĂŒr jede Methode eine Startadresse ob statisch oder dynamisch, ist dabei egal. Diese Adressen werden in Sprungtabellen zusammengefasst und erst hier wird in dynamischen und statischen Sprungtabellen unterschieden. Die ersten 16 Byte eines jeden Objekts zeigen auf eine Struktur, die wiederum Zeiger (bzw. Referenzen) auf eine statische und eine dynamische Sprungtabelle enthĂ€lt. Diese Strukturen gehören jedoch grĂ¶ĂŸtenteils bereits zum Speicherverbrauch der Klassen-Definition, nur bei dynamischen Instanz-Klassen ist das anders, denn fĂŒr diese mĂŒssen vor der AusfĂŒhrung andere Adressen fĂŒr Datenbereiche unterschiedlicher Objektreferenzen in Registern ĂŒbergeben oder auf den Stack gelegt werden. Ein inline instanziertes Interface bekommt z.B. auf jeden Fall immer eine neue Adresse auf seine Objektstruktur.
Man muss vermutlich Assembler können, um zu sehen, welche Möglichkeiten man da hat. Auf jeden Fall immer so Speicher sparend, wie möglich.

Die Details sind eben nicht egal, wenn es darum geht zu begrĂŒnden, warum ein Objekt definitiv erst nach super() bzw. this() fertig initialisiert ist und erst dann Objektmethoden aufgerufen werden können.

#13

also vom großen ‘sowie alle Methoden beider Klassen jedoch noch nicht initialisiert’ bleibt allein Verweis auf die Klasse an sich, wo alles schon lange bekannt ist
,
der eine kleine 4 Byte-Verweis, primĂ€r fĂŒr spĂ€teren beliebigen Zugriff, bei der Objekterzeugung sowieso gerade bekannt, schließlich muss ja auch der Konstruktor der Oberklasse aufgerufen werden usw.

die Attribut-Initialisierung ist real, Speicher muss aktiv reserviert werden, fĂŒr einzelne Attribute etwas da was vorher nicht war, teils schon mit Anfangswert gesetzt,
da gibt es ein klares vorher und nachher,
aber Methoden, da ist nichts zu initialisieren, die machen immer dasselbe

#14

Ja, keine Frage. Nur dynamische halt immer mit anderen (Instanz)Daten und diese mĂŒssen vor dem Aufruf einer solchen Methode nun mal individuell ĂŒbergeben werden. Das macht einen Call von Objektmethoden individuell und das muss irgendwo gespeichert werden und zwar möglichst bei der Objektinitialisierung. Wenn deine Klasse Test2 eine Instanzmethode hĂ€tte oder gar eine Methode von Test ĂŒberschreibt, hĂ€tte Test2 ganz sicher eine andere Adresse fĂŒr Sprungtabellen als Test1 und jede Instanz individuelle Adressen auf ihre Datenstrukturen. All dies ist ĂŒber die ersten 16 Byte einer jeden Objektinstanz erreichbar und steht der Instanz nun mal erst nach der Initialisierung zur VerfĂŒgung. Man muss also wissen, dass man es im Konstruktor Test() in deinem Beispiel mit einer Geisterinstanz zu tun hat und deswegen programmiert man so ja auch nicht. Und btw.: Deswegen ist es auch sinnvoll, Instanzvariablen erst im Konstruktor (nach super() oder innerhalb von this()) zu initialisieren, damit sie genau die definierten Werte haben, die sie haben sollen.