Du verwendest einen veralteten Browser. Es ist möglich, dass diese oder andere Websites nicht korrekt angezeigt werden. Du solltest ein Upgrade durchführen oder ein alternativer Browser verwenden.
super ist ein Schlüsselwort, mit dem man sich zwar auf das aktuelle Objekt bezieht (wie this), dieses jedoch als Instanz der Oberklasse betrachtet. Somit erhält man in der abgeleiteten Klasse tatsächlich Zugriff auf Konstruktoren, Methoden und Instanzvariablen der Superklasse.
Die Frage ist: wozu?
Schauen wir uns einmal ein einfaches Beispiel an:
Java:
class Oberklasse /* extends java.lang.Object */ {
private final String wert;
public Oberklasse(String wert) {
this.wert = wert;
}
public String getWert() { return wert; }
}
In Java ist jede Klasse, die Du schreibst, von einer anderen Klasse (Superklasse) abgeleitet. Wenn Du nicht explizit von einer Klasse ableitest, wird implizit von der Klasse java.lang.Object abgeleitet, was oben durch den Kommentar verdeutlicht wird (man könnte tatsächlich extends java.lang.Object schreiben und würde genau das gleiche Ergebnis erhalten). Außerdem hat jede Klasse mindestens einen Konstruktor, wobei jeder Konstruktor in einer abgeleiteten Klasse einen Konstruktor der Superklasse aufruft. Gibst Du in einer Klasse gar keinen Konstruktor an, wird automatisch ein Standardkonstruktor erzeugt, der den parameterlosen Konstruktor der Superklasse aufruft. Dazu kommen wir gleich.
Nehmen wir einmal an, wir erstellen jetzt eine Unterklasse, die von Oberklasse abgeleitet wird:
Java:
class Unterklasse extends Oberklasse {
}
Das lässt sich nicht kompilieren, denn Konstruktoren werden nicht vererbt. Hier würde, wie eben erwähnt, ein Standardkonstruktor erzeugt, der den parameterlosen Konstruktor der Oberklasse aufrufen würde. Einen solchen parameterlosen Konstruktor gibt es in Oberklasse jedoch nicht -> Fehler.
Man könnte nun auf die Idee kommen, dass
Java:
class Unterklasse extends Oberklasse {
public Unterklasse() {}
}
funktioniert. Aber auch das ist falsch, denn jeder Konstruktor in Unterklasse ruft implizit oder explizit einen Konstruktor seiner Oberklasse auf. Wird in einem Konstruktor nichts anderes angegeben, wird der parameterlose Konstruktor der Superklasse aufgerufen.
Damit der Code funktioniert, muss Unterklasse einen Konstruktor erhalten, der den Konstruktor Oberklasse(String) aufruft. Und dafür lässt sich nun super verwenden:
Java:
class Unterklasse extends Oberklasse {
public Unterklasse() {
super("");
}
public Unterklasse(String wert) {
super(wert);
}
}
Mit super(...) wird also der betreffende Konstruktor der Oberklasse aufgerufen. Im Fall von Unterklasse() wird ein leerer String an den Konstruktor Oberklasse(String) übergeben. Dieser setzt die in Oberklasse vorhandene Instanzvariable wert dann auf den leeren String.
Das Codeschnipsel
Java:
Unterklasse a = new Unterklasse();
String wertVonA = a.getWert();
if (wertVonA.isEmpty()) {
System.out.println("a.getWert() liefert den leeren String");
} else {
System.out.printf("a.getWert() liefert den String \"%s\"%n", wertVonA);
}
würde somit a.getWert() liefert den leeren String ausgeben.
Und das gleiche Prinzip kann auch bei Methodenaufrufen verwendet werden, z. B. wenn Methoden überschrieben wurden. Nehmen wir mal an, wir möchten in Unterklasse bei getWert() dafür sorgen, dass dem Wert ein Präfix vorangestellt wird:
Java:
class Unterklasse extends Oberklasse {
private final String prefix;
public Unterklasse(String prefix, String wert) {
super(wert);
this.prefix = prefix;
}
@Override
public String getWert() {
return prefix + super.getWert();
}
}
Würde man in getWert() das super weglassen, würde sich die Methode selbst aufrufen (Endlosrekursion, führt in Java irgendwann zu einem Stackoverflow). Mit Hilfe von super wird nun die Methode getWert() aus Oberklasse aufgerufen.
Ist das ironisch gemeint? Du bist doch auch schon seit mehr als acht Jahren dabei.
Java und Objektorientierung leben davon. Es geht gar nicht ohne Vererbung. Die Basics sind dann auch nicht sooo schwer. Ich für meinen Teil habe da länger gebraucht, den Unterschied zwischen Klasse und Objekt zu verinnerlichen.
Sicherlich kann man auch viel Blödsinn mit Vererbung machen. Habe das mal ausgegraben.
Java:
class Dimension1 {
private int x;
public Dimension1(int x) {
this.x = x;
}
public Dimension1() {
this(0);
}
public int getX() {
return x;
}
@Override
public String toString() {
return String.format("%s, x=%d", super.toString(), getX());
}
}
class Dimension2 extends Dimension1 {
private int y;
public Dimension2(int x, int y) {
super(x);
this.y = y;
}
public Dimension2(int y) {
this(0, y);
}
public Dimension2() {
this(0);
}
public int getY() {
return y;
}
@Override
public String toString() {
return String.format("%s, y=%d", super.toString(), getY());
}
}
class Dimension3 extends Dimension2 {
private int z;
public Dimension3(int x, int y, int z) {
super(x, y);
this.z = z;
}
public Dimension3(int z) {
this(0, 0, z);
}
public Dimension3() {
this(0);
}
public int getZ() {
return z;
}
@Override
public String toString() {
return String.format("%s, z=%d", super.toString(), getZ());
}
}
public class Main {
public static void main(String...args) {
Dimension1 my1D = new Dimension1(42);
System.out.println(my1D);
System.out.println();
Dimension2 my2D = new Dimension2(5, -6);
System.out.println(my2D);
System.out.println();
Dimension3 my3D = new Dimension3(5);
System.out.println(my3D);
System.out.println();
my3D = new Dimension3(my2D.getX(), my2D.getY(), 9);
System.out.println(my3D);
}
}
"Gemein" an Vererbung ist eigentlich nur die Einhaltung des LSP (Liskov'sches Substitutionsprinzip), das sich automatisch ergibt, wenn man die "is-a"-Eigenschaft der Vererbungsbeziehung konsequent zu Ende denkt bzw. anwendet. Bekanntermaßen beduetet dies, dass Objekte abgeleiteter Klassen überall eingesetzt werden können müssen, wo der Supertyp erwartet wird. Dieses Prinzip lässt sich sehr leicht verletzen. Typisches Beispiel ist hier etwa Quadrat extends Rechteck, zumal das mathematisch erstmal sinnvoll erscheint, schließlich lernt man in der Schule, dass ein Quadrat der Spezialfall eines Rechtecks ist...
Die Frage ist doch, was jemand meint, wenn er Vererbung versucht zu vermeiden.
Ich würde hier nicht unterstellen, dass @krgewb da komplett verzichten will oder so. Sondern da vermute ich mal eher, dass er da im Hinterkopf etwas hat wie Regeln: "Composition over inheritance" und so.
Und wenn ich so überlege, was ich noch so an Vererbung sehe, dann ist da nicht mehr die große fachliche Analyse mit sorgfältig ausgearbeitetem Model, welches das Verhalten festlegt. Man hat doch eher einfach nur seine Entities (klar, da hat man auch Vererbung, weil man halt im Rahmen von Refactorings dann gewisses Verhalten in Oberklassen auslagert. Aber So eine Vererbungshierarchie mit "BaseEntity" <- "SomeSpecialBaseEntity" <- "MyEntity" ist nicht das, was ich mir so vorstelle.
Und wie wird dann strukturiert? Wenn ich mir die Typische Spring Boot Anwendung ansehe, dann habe ich da POJOs (Also nichts groß mit Verhalten oder so) und die werden dann verwaltet (Also sowas wie Repositories) und dann kommt da die Business Logik außen in Form von Services.
Ich gestehe: Ich sehe hier nur einen kleinen Marktanteil, aber das sind doch die typischen Anwendungen, die man im "Business Bereich" findet.
Nun kann man überlegen, was man unter "Ich finde Vererbung sehr verwirrend" verstehen könnte. Da kann @krgewb evtl. etwas ausführen. Aber egal wie viele Jahre jemand Software im Business Bereich schreibt: So eine Aussage würde mich nicht irritieren. Einfach mal das Szenario überlegen:
Da kommt einer auf eine Junior Stelle. Was an Vererbung nutzt er groß? Kommt da Business Logik wirklich in das Model? Die Chance ist groß, dass er da dann in erster Linie Anwendung schreiben wird, die Daten in irgend einer Form speichert. Um Zugriff und so zu vereinfachen kommt dann ein Framework wie Spring Boot, Quarkus oder so. Und Business Logik landet dann in irgendwelchen Services. Und das war dann das Backend. Was muss da ein Junior Entwickler lernen um dann aufzusteigen hinzu Senior oder Lead Developer? Da sehe ich dann nicht: "Tolle Vererbungshierarchien" oder so. Wäre auch schwer, wenn man agil arbeitet. Das setzt meiner Meinung nach voraus, dass man einen Analysten hat, der das wirklich gut aufgearbeitet hat. Da kommen dann doch eher das Meistern von Frameworks, Security und all sowas. Und dann Projekterfahrung bezüglich Kommunikation und Zusammenarbeit.
Das wäre zumindest meine Sichtweise auf diese Thematik.
Kleines Edit: Ich habe in dem Post "Verhalten" nicht eindeutig verwendet. In erster Linie meinte es das fachliche Verhalten von Entities und das sehe ich weniger. Was es aber auch gibt ist ein technisches Verhalten, was dann bei Entities oft zu finden ist. Das kann eine Festlegung von Feldern sein (eine Entität hat eine id) oder ein technisches Verhalten bezüglich z.B. equals (Sowas wie: Wenn die Klasse gleich sind und die ids gleich sind und nicht null, dann geht es um die gleiche Entität). Dies nur als Erläuterung, falls hier jemand über die Wortwahl stolpert und dass ich einmal sage, dass es kein Verhalten gäbe und ich dann dieses in Oberklassen verschieben möchte ...
Als ich 2000 mit Java begonnen habe, hatte ich bereits die ganzen 90er Jahre Clipper (später Alaska) gemacht, was rein modales programmieren war (die Windows-GUI von Alaska habe ich nie verwendet).
Für mich war verwirrend, wenn einer Objekt-Methode ein Objekt als Parameter übergeben wurde, auf welchem dann wiederum eine Methode aufgerufen wurde.
Design-Pattern habe ich erst ab 2003/2004 verwendet, die wurden nach meiner Meinung schlecht kommuniziert. Hat jemand das Gang-of-4-Buch über Design-Pattern gelesen? Dort wird im Kapitel über Fliegengewichte tatsächlich nicht erklärt, was ein Fliegengewicht ist, sondern sofort mit der eigentlich falschen Ausnahme eines veränderlichen Fliegengewichtes angefangen.
Prinzipiell kann man in Java auch so viele Methoden wie möglich static deklarieren und dann macht man kein OOP. Das es da unvermeidliche String-Objekte gibt, kann man ausblenden.
Ich habe Code gesehen, der in der main-Methode ein Objekt Sortierer mit anlegt und dann auf diesem eine Methode aufruft. Arrays.sort ist static, muss also nicht OOP sein.
Code kann man mit vielen static-Util-Methoden einfach und übersichtlich machen.
Wenn etwas in einer Applikation nur einmal existiert, wie das Flugsimulator-Anzeige-Beispiel, dass hier kürzlich diskutiert wurde, muss dies auch kein Objekt sein, ist eben nur einmal da. Sollte es mehrere Anzeigen geben, dann wäre es schon schön, wenn dies Objekte sind.
Ich fand auch verwirrend, dass man in einer Unterklasse beim Überschreiben die Oberklassen-Methode abfängt und die dann mit super aufrufen kann. Eigentlich hätte ich das Abfangen des Aufrufes in der Oberklasse benötigt. Dazu sollte man eben eine Oberklasse mit einer explizit aufzurufenden (eventuell abstrakten) Methode schreiben. Ist etwas hakeliger.
Der krampfhafte Versuch Vererbung als Code-Wiederverwendungs-Technik zu verwenden führt zu unübersichtlichem und fehleranfälligem Code. Es soll mal ein Open-Source-Projekt gegeben haben, dass bis zu 12 Vererbungsstufen hatte.
Methoden überschreiben und darin super aufzurufen, ist schlecht.
Wenn man wirklich Objekte benötigt, wie bei einem Baum, der spezielle Blatt-Objekte neben Knoten-Objekten hat, dann sollte man sich die Regel auferlegen, dass man nur abstrakte oder final Klassen hat. Objektorientierter Code sollte dann nur speziell eingesetzt werden, wenn es sinnvoll ist, zum Beispiel, dass jeder Knoten eigentlich auch ein Baum ist.
Überschreiben sollte dann wirklich nur eine Sonderlocke sein. Ich war 2003/2004 in einem Projekt, da gab es hunderte Sonderlocken, sollte man nicht machen.
Als ich 2000 mit Java begonnen habe, hatte ich bereits die ganzen 90er Jahre Clipper (später Alaska) gemacht, was rein modales programmieren war (die Windows-GUI von Alaska habe ich nie verwendet).
Für mich war verwirrend, wenn einer Objekt-Methode ein Objekt als Parameter übergeben wurde, auf welchem dann wiederum eine Methode aufgerufen wurde.
Design-Pattern habe ich erst ab 2003/2004 verwendet, die wurden nach meiner Meinung schlecht kommuniziert. Hat jemand das Gang-of-4-Buch über Design-Pattern gelesen? Dort wird im Kapitel über Fliegengewichte tatsächlich nicht erklärt, was ein Fliegengewicht ist, sondern sofort mit der eigentlich falschen Ausnahme eines veränderlichen Fliegengewichtes angefangen.
Prinzipiell kann man in Java auch so viele Methoden wie möglich static deklarieren und dann macht man kein OOP. Das es da unvermeidliche String-Objekte gibt, kann man ausblenden.
Ich habe Code gesehen, der in der main-Methode ein Objekt Sortierer mit anlegt und dann auf diesem eine Methode aufruft. Arrays.sort ist static, muss also nicht OOP sein.
Code kann man mit vielen static-Util-Methoden einfach und übersichtlich machen.
Wenn etwas in einer Applikation nur einmal existiert, wie das Flugsimulator-Anzeige-Beispiel, dass hier kürzlich diskutiert wurde, muss dies auch kein Objekt sein, ist eben nur einmal da. Sollte es mehrere Anzeigen geben, dann wäre es schon schön, wenn dies Objekte sind.
Ich fand auch verwirrend, dass man in einer Unterklasse beim Überschreiben die Oberklassen-Methode abfängt und die dann mit super aufrufen kann. Eigentlich hätte ich das Abfangen des Aufrufes in der Oberklasse benötigt. Dazu sollte man eben eine Oberklasse mit einer explizit aufzurufenden (eventuell abstrakten) Methode schreiben. Ist etwas hakeliger.
Der krampfhafte Versuch Vererbung als Code-Wiederverwendungs-Technik zu verwenden führt zu unübersichtlichem und fehleranfälligem Code. Es soll mal ein Open-Source-Projekt gegeben haben, dass bis zu 12 Vererbungsstufen hatte.
Methoden überschreiben und darin super aufzurufen, ist schlecht.
Wenn man wirklich Objekte benötigt, wie bei einem Baum, der spezielle Blatt-Objekte neben Knoten-Objekten hat, dann sollte man sich die Regel auferlegen, dass man nur abstrakte oder final Klassen hat. Objektorientierter Code sollte dann nur speziell eingesetzt werden, wenn es sinnvoll ist, zum Beispiel, dass jeder Knoten eigentlich auch ein Baum ist.
Überschreiben sollte dann wirklich nur eine Sonderlocke sein. Ich war 2003/2004 in einem Projekt, da gab es hunderte Sonderlocken, sollte man nicht machen.
Einiger dieser Aussagen sind sehr problematisch und du hättest das lieber für dich behalten. Das kann keinem Anfänger helfen, der versucht die Grundlagen zu verstehen. Ein Problem dabei ist auch, dass so lange Postings eigentlich eine noch längere Antwort benötigen usw.. Dazu fehlt mir aber die Motivation, so wie KonradN - der Sonntags um 03:09 noch längere Texte schreibt. In diesem Sinne - noch einen schönen Sonntag.
An den schwierigen Paradigmenwechsel von C nach C++ in den 90ern kann ich mich auch noch gut erinnern. Was bei mir aber wohl daran lag, dass ich das seinerzeit rein hobbymäßig gemacht habe und es praktisch keine gleichgesinnten gab. Dann kannte ich noch einen, der beruflich Programmierer war, aber selbst der wusste mit OOP nicht wirklich etwas anzufangen. Hinzu kam, dass zu der Zeit tatsächlich "Vererbung" der heiße Scheiß war. War halt eine andere Zeit.
Dort wird im Kapitel über Fliegengewichte tatsächlich nicht erklärt, was ein Fliegengewicht ist, sondern sofort mit der eigentlich falschen Ausnahme eines veränderlichen Fliegengewichtes angefangen.
Hm.... eigentlich wird dort wie bei allen Pattern mit der Motivation begonnen, hier ein graphischer Texteditor, und beschrieben, dass ein Flyweight ein Objekt ist, das merfach (shared) verwendet wird und der Hauptpunkt darin besteht, den extrinsischen Zustand aus einem Objekt herauszuziehen, während der intrinsische Zustand unabhängig vom Ausführungskontext zu sein hat. Über die Veränderlichkeit des Flyweight wird überhaupt keine explizite Aussage getroffen, das gegebene Beispiel legt aber Unveränderlichkeit nahe, schließlich wird für jeden Schriftzeichen ein Flyweight erzeugt.
Ich fand auch verwirrend, dass man in einer Unterklasse beim Überschreiben die Oberklassen-Methode abfängt und die dann mit super aufrufen kann. Eigentlich hätte ich das Abfangen des Aufrufes in der Oberklasse benötigt. Dazu sollte man eben eine Oberklasse mit einer explizit aufzurufenden (eventuell abstrakten) Methode schreiben. Ist etwas hakeliger.
Sind Empfehlungen (also kein zwingendes Muss) um (häufiger auftretende) SW-Strukturen sowie deren Änderungen oder Erweiterungen leichter durchführen zu können. Deren Verwendung spricht aber nicht zwingend für oder gegen die Professionalität einer implementierten SW-Lösung.
Den Compiler interessiert nur die formelle Korrektheit des Codes, nicht ob eine Einzelperson (oder ein Team) mit der Kombination der Schlüsselwörter Probleme aller Art hat.