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.