Die Initialisierung von Feldern in ableitender Klasse erzwingen..?

sirbender

Top Contributor
Hallo,

ich wuerde gerne erzwingen, dass Klassen die von einen gewissen Typ implementieren bzw. sich von ihm ableiten

1. einen bestimmten Konstruktor besitzen
2. bestimmte Felder in diesem Konstruktor initialisieren

Java:
	static abstract class Result {
		final Dimension dimension; // final, muessen im Konstruktor initialisiert werden
		final int[] array;
		Result(Shape shape) {} // erzwungener Konstruktor
	}

Der Code oben ist leider keine valide Klassendefinition. Der Modifier final ist hier nicht erlaubt. Ausser ich weise einen Wert im Konstruktor zu. Diese Wertzuweisung soll aber EINMALIG in ableitenden Klassen von Result erfolgen.

Kann man sowas irgendwie doch schaffen?
 
Zuletzt bearbeitet von einem Moderator:

Hutzli

Aktives Mitglied
Hey

Ich glaube nicht, dass das so geht. Erstelle doch einen protected Konstruktor mit den Werten als Parametern, die du benötigst. Sieht dann so aus:

Java:
public abstract class BaseClass
{
	private final int mA;
	private final int mB;
	
	protected BaseClass( int pA, int pB )
	{
		this.mA = pA;
		this.mB = pB;
	}
}

Java:
public class DerivedClass extends BaseClass
{
	public DerivedClass()
	{
		super( 20, 30 );
	}
	
	public DerivedClass(int pA, int pB)
	{
		super(pA, pB);
		// TODO Auto-generated constructor stub
	}
}

Oder benütze Getter und Setter.
 

sirbender

Top Contributor
Ich will halt 1000% sicher gehen, dass die Felder dimension und array initialisiert werden wenn ein Objekt einer abgeleitete Klasse erstellt wird - also im Konstruktor.

Schade, dass das nicht geht. Sollte eigentlich. Wenn ich eine Klasse als abstract definiere und einige Felder als final ist ja klar, dass eine abgeleitete nicht-abstracte Klasse einen Konstruktor zwingend erstellen muss in welchem die Felder initialisiert werden.

Klar koennte man auch Getter erstellen fuer die Felder. Aber mir waere es lieber wenn der Nutzer explizit im Konstruktor EINMALIG den Feldern einen Wert zuweisen muss (dieser koennte uebrigens auch null sein, das will ich gar nicht verhindern).

Es ist schade, dass Java nicht solche fine-grained Zwangsvorgaben erlaubt. Vor allem "immutable" kann soooo elegant Probleme von vorneherein ausschliessen.
 

sirbender

Top Contributor
Uebrigens, dein Beispiel mit den finalen Feldern hab ich mir auch ueberlegt - nur muss man leider halt super(...) als ALLERERSTEN Befehl in einem Konstruktor aufrufen. Und die Werte die ich super(...) uebergeben will sollten eigentlich im Konstruktor und nicht vom Aufrufer berechnet werden.
 

kaoZ

Top Contributor
Ich will halt 1000% sicher gehen, dass die Felder dimension und array initialisiert werden wenn ein Objekt einer abgeleitete Klasse erstellt wird - also im Konstruktor.

Erste Frage : mit was initialisiert werden ? Null ? Null-Objekten ? Default-Werten ?

Warum initialisierst du diese dann nicht erst einmal von Hand in der Superklasse ? was du dann letzten endes über Parametern übergibst ist doch dann unabhängig davon , z.B könntest du dir eine DefaultShape klasse erstellen, und wenn diese nur in "leeres" Objekt enthält kannst du so zumindest sicherstellen das deine Felder initialisiert sind.

Wenn du ein Objekt einer Subklasse erstellst, wird eh immer der Superkonstruktor aufgerufen.

Ob das jetzt Sinnvoll ist oder nicht kommt halt ganz auf den Anwendungsfall an denke ich :

Java:
public class Test{
	
	public static void main(String[] args){
		
		SubClass s = new SubClass();
		
		System.out.println(s.length());
		System.out.println(s.getDim());
	}
	
}

class SuperClass{
	
	private Dimension dim;
	private int[] arr;
	
	public SuperClass(){
		dim = new Dimension(0,0);
		arr = new int[]{};
	}
	
	public int length(){
		return arr.length;
	}
	
	public Dimension getDim(){
		return dim;
	}
}

class SubClass extends SuperClass{
	
	
	public SubClass(){
		super();
	}
}

Ausgabe wäre dann in dem Fall zumindest keine NPE sondern

Code:
0
java.awt.Dimension[width=0,height=0]

wobei mir hier das werfen einer NPE wesentlich lieber wäre denke ich , du kannst aber natürlich so dafür sorgen das zumindest Standardwerte vorab initialisiert werden, was du dann in den Subklassen den beiden Feldern zuweist ist ja deine Sache und eine andere Geschichte.
 
Zuletzt bearbeitet:

sirbender

Top Contributor
Der int[] array muss immer individuell vom Implementierer geliefert werden. Auch sollte der int[] array im Konstruktor erstellt und nicht einfach dem Konstruktor uebergeben werden. Aehnlich verhaelt es sich mit dem Dimension Feld.

Ich glaube es geht einfach nicht.

Ich faende es jedenfalls toll wenn ich eine abstrakte Klasse mit finalen Feldern erstellen koennte wodurch ich sichergehen koennte, dass jemand der die Klasse ableiten will zumindest einen Konstruktor erstellt in dem er die finalen Felder initialisiert. Schade, dass das nicht geht.
 

Ruzmanz

Top Contributor
Mache mal ein konkretes Beispiel. Deine Aussagen und der Code sind nonsense. Wie sollte eine Kindklasse von Result aussehen (unabhängig davon, ob es funktioniert).

PS: Ich hoffe dein Ziel ist es nicht die Klasse Result immutable zu machen?!
 
Zuletzt bearbeitet:

sirbender

Top Contributor
Mache mal ein konkretes Beispiel. Deine Aussagen und der Code sind nonsense. Wie sollte eine Kindklasse von Result aussehen (unabhängig davon, ob es funktioniert).

PS: Ich hoffe dein Ziel ist es nicht die Klasse Result immutable zu machen?!


So wuerde das aussehen. Der Kompiler spielt leider nicht mit.


Java:
	abstract class Parent { // ein interface erlaubt erst gar keine nicht-statischen Felder
		final int[] content; // final erlaubt der Compiler nicht!
		final Dimension dim; // Obwohl es Sinn machen wuerde eine Abstrakte Klasse zu haben mit Felder die in jeder ableitenden Klasse im Konstruktor initialisiert werden muessen.
	}
	
	class ChildA extends Parent {

		public ChildA(Shape shape) {
			// langwierige Berechnung aus Shape die noetig ist um content und dim zu berechnen. Beide sind final!
			this.content = new int[] {1,2,3,4,5}; // muss! im Konstruktor initialisiert werden
			this.dim = new Dimension(800,600);    // muss! im Konstruktor initialisiert werden
		}
		
		public ChildA(Color color) {
			// JEDER neue Konstruktor MUSS content und dim berechnen und initialisieren
			this.content = new int[]{6,7,8};
			this.dim = new Dimension(100,100);
		}
	}
	
	class ChildB extends Parent {

		public ChildB(float[] input) {
			// selbes Spiel. Jeder der von Parent erbt muss content und dim berechnen und initialisieren. Beide sind final!
			this.content = new int[]{9,10,11};
			this.dim = new Dimension(42,42);
		}
		
	}
 
Zuletzt bearbeitet von einem Moderator:

coco07

Bekanntes Mitglied
Java:
import java.util.List;

public abstract class SuperClass {
	
	private final int value1;
	
	public SuperClass(List<Integer> list){
		this.value1 = initializeValue1(list);
	}
	
	public int getValue1(){
		return value1;
	}
	
	public abstract int initializeValue1(List<Integer> list);

}

class SubClass extends SuperClass {

	public SubClass(List<Integer> list) {
		super(list);
	}

	@Override
	public int initializeValue1(List<Integer> list) {
		// Berechne hier den Wert
		return list.size();
	}

}

ps: Wenn ich dein Problem falsch verstanden habe, versuche mal den Code klar und deutlich lesbar zu posten.
Es ist alles durcheinander. Ich befürchte auch fast, dass du die Regeln der OOP nicht ganz verstanden hast.

grüße coco07!
 
Zuletzt bearbeitet:

Ruzmanz

Top Contributor
Hilft nicht weiter, da Hutzlis Antwort für dein Codebeispiel zutrifft. Die Berechnung ist relevant. Es kommt nicht darauf an wie etwas berechnet wird, sondern von welchen Objekten die Berechnung abhängig ist.

Java:
public class ClassA extends Parent {

	public static void main(String[] args) {
		System.out.println(new ClassA(new Rectangle(new Dimension(100, 191))).getWidth());
	}

	public ClassA(Shape shape) {
		super(shape);
	}

	@Override
	protected Dimension initDim() {
		Shape shape = (Shape) object;
		return new Dimension((int) shape.getBounds2D().getWidth(), (int) shape.getBounds2D().getHeight());
	}

	@Override
	protected int[] initContent() {
		return new int[] { 1, 2, 3, 4, 5 };
	}

	public double getWidth() {
		return dim.getWidth();
	}
}

abstract class Parent {
	protected Object object;
	protected final int[] content;
	protected final Dimension dim;

	public Parent(Object object) {
		this.object = object;
		content = initContent();
		dim = initDim();
	}

	protected abstract Dimension initDim();

	protected abstract int[] initContent();
}
 

nvidia

Bekanntes Mitglied
An alle die meinen es wäre eine gute Idee in Unterklassen überschriebene Methoden im Konstruktor der Oberklasse aufzurufen, das ist extrem grenzwertig. Ich hoffe jedem ist klar das bei Instanzen bestehend aus mehreren Typen der Teil aus dem obersten Typen zuerst initialisiert wird. Und dadurch Methoden aus dem, zu diesem Zeitpunkt, unfertigen Teil des Subtypen herangezogen werden. Wenn diese Methoden noch auf Instanzvariablen des Subtypen zurückgreifen dann ist das ein ziemlich fieser Bug.

Für weitere Erläuterungen einfach Google bemühen.
 

Ruzmanz

Top Contributor
Ganz ruhig :popcorn: Ich möchte erstmal wissen, was er machen möchte und dann schauen wir weiter ... immutable ist da auch noch gar nichts.
 

sirbender

Top Contributor
Hmmm. Ich glaube wir sagen einfach es geht nicht und wir lassen es dabei. Das letzte Codebeispiel ist zu verrueckt fuer mein Verstaendnis.

Mir geht es lediglich darum finale Felder (die auch immutable Objekte wie Strings halten koennen) in einer abstrakten Klasse zu definieren und dann jemand zwingen der die Klasse ableitet zwingen zumindest EINEN Konstruktor zu erstellen, wo die finalen Felder initialisiert werden.

Als naechsten Schritt wuerde ich gerne auch noch sicherstellen, dass die finalen Variablen nicht mit 'null' initialisiert werden. Vielleicht geht das per Annotation @NotNull. Aber soweit hab ich gar nicht gedacht, weil eben der erste Schritt mit den finalen Feldern so nicht geht.

Um ein bischen weiter zu erklaeren: ich erstelle die abstrakte Klasse von der abgeleitet wird. Andere Entwickler sollen diese Klasse ableiten. Nun will ich aber, dass die sich Gedanken machen weil int[] array zum Teil schwierig zu initialisieren ist. Deswegen will ich dem Implementierer soviel Gedankenarbeit abnehmen wie moeglich indem ich ihn zwinge gewisse Vorgaben einzuhalten. Konstruktor erstellen, int[] array dort berechnen, verbieten, dass int[] einfach 'null' gesetzt wird, usw.

Eigentlich koennte ich ein paar abstrakte Methoden wie int[] getData() vorgeben, die ueberschrieben werden muessen. Bei String getJSONData() wird es noch deutlicher weil String auch noch immutable ist.

Ein Feld final String jsonData ist also nicht zu ersetzen durch String getJSONData(). Weil getJSONData hat KEINE Auflagen was denn zurueckgegeben wird. Die Methode kann JEDESMAL was anderes generieren. Aehnlich ist es mit int[] getData().

Ein finales Feld int[] array oder String jsonData waere da wirklich sowas wie eine feste Vorgabe! Leider geht es nicht, dass ich die in einer abstrakten Klasse definiere und den der die Klasse ableitet initialisieren lasse.
 

Ruzmanz

Top Contributor
Hmmm. Ich glaube wir sagen einfach es geht nicht

Wenn du keine Lust hast es umzusetzen, dann ist das mir egal. Ich behaupte weiterhin, dass es geht und du deine Anforderungen noch immer nicht auf den Punkt gebracht hast.

Java:
public class SubClass extends AClass {

	public static void main(String[] args) {
		System.out.println(new SubClass(6).getValue1());
	}

	public SubClass(int value) {
		super(new ICalc() {

			@Override
			public int calcValue1(int value1) {
				return value1 + 10;
			}
		});
	}
}

abstract class AClass {

	private final int value1;

	public AClass(ICalc calc) {
		this.value1 = Objects.requireNonNull(calc.getValue1());
	}

	public final int getValue1() {
		return value1;
	}
}

interface ICalc {
	int calcValue1(int value1);
}
 
Zuletzt bearbeitet:

sirbender

Top Contributor
Wenn du keine Lust hast es umzusetzen, dann ist das mir egal. Ich behaupte weiterhin, dass es geht und du deine Anforderungen noch immer nicht auf den Punkt gebracht hast.

Java:
public class SubClass extends AClass {

	public static void main(String[] args) {
		System.out.println(new SubClass(6).getValue1());
	}

	public SubClass(int value) {
		super(new ICalc() {

			@Override
			public int calcValue1(int value1) {
				return value1 + 10;
			}
		});
	}
}

abstract class AClass {

	private final int value1;

	public AClass(ICalc calc) {
		this.value1 = Objects.requireNonNull(calc.getValue1());
	}

	public final int getValue1() {
		return value1;
	}
}

interface ICalc {
	int calcValue1(int value1);
}

Konntest Du dein Source-Beispiel kompilieren? Es sagt mir: The method getValue1() is undefined for the type ICalc. Ich denke ich weiss aber wie man es fixen muss.

Ich glaub aber ich verstehe jetzt wie das Beispiel gemeint ist. Es sieht zwar ein bischen krass aus, aber ich denke das ist mehr oder weniger genau was ich wollte, da ja Java leider nicht die "einfache Loesung" mit finalen Feldern zulaesst.

Das mit dem Interface stoesst mir ein bissl sauer auf. Und fuer jede finale Variable muss man eine eigene Interface-Methode definieren, die dann das finale Feld in AClass initialisiert. Nichtsdestotrotz ist es schoen, dass es geht. Das Wichtige ist wie gesagt, das AClass die finalen Felder definiert und man sie mit deinem Trick auch initialisieren kann.

Vielleicht kann man es sogar eleganter machen mit Lambda-Ausdruecken anstatt einer anonymen Interface-Implementierung?

Vielen Dank!
 

Ruzmanz

Top Contributor
Habe da noch ein bisschen rumgepfuscht, als ich das geschrieben habe. So kannst du es kompilieren:
https://gist.github.com/RuZman/eee0b531981896bcbb5b

Du erwartes ein konkretes Verhalten, sodass ich ein Interface angebracht finde. Die Objekte / Parameter sollen spezielle Anforderungen (Interface) erfüllen, sodass ein selbst zu definierendes Verhalten (Klasse) ausgeführt und gespeichert wird.

Die annonyme Klasse war eigentlich nur Faulheit. Eigentlich müsstest du für jede Berechnung eine eigene Klasse schreiben, damit das halbwegs ordentlich wird. Im Endeffekt wird dein Projekt übersichtlicher, wenn du die Berechnung direkt in die Klasse packst (siehe https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html):

https://gist.github.com/RuZman/a92b18e0502191906ebd

Meiner Meinung nach solltest du alle Parameter in das Interface packen. Es dient lediglich dazu deine AClass zu initialisieren und hat sonst keine weitere Bedeutung.
 
Zuletzt bearbeitet:

Ähnliche Java Themen

Neue Themen


Oben