Wirtschaftsinformatik (Bachelor-Studiengang): Programmierung (2. Semester)
Sie sind hier: Startseite › Wirtschaftsinformatik › Programmierung Java (Kurs 2): Exkurs Objektorientierte Programmierung
HB / CM, Kurs vom 01.10.2002 - 31.03.2003
- Vererbung
- Wie sind abgeleitete Klassen in Java zu deklarieren?
- Das Überschreiben von Attributen und Methoden
- Vererbung und Zuweisungskompatibilität
- Methoden von Object
Exkurs Objektorientierte Programmierung:
Im Zentrum steht das Entwickeln von Datentypen. (genannt Klassen)
Dabei ist wichtig: Objekte der Klassen müssen stets in einem "sinnvollen" Zustand sein.
- Initialzustand mit Hilfe von Konstruktoren herstellen
public
-Methoden müssen von einen sinnvollen Zustand in einen sinnvollen überführen
Vererbung
Vererbung ist eine der charakteristischen Eigenschaften objektorientierter Programmierung neben Datenkapselung und Polymorphismus.
Sie besteht darin, dass es bei der Deklaration von Klassen möglich ist, Eigenschaften bereits vorhandener Klassen zu übernehmen.
Wenn eine Klasse B Eigenschaften der Klasse A übernimmt, spricht man von Vererbung, d.h. B erbt die Eigenschaften von A (d.h. alle Attribute und Methoden).
Es gibt verschiedene Terminologien, um die Vererbungsbeziehungen zwischen zwei Klassen A und B auszudrücken:
- A ist Oberklasse, B ist Unterklasse
- A ist Superklasse, B ist Subklasse
- A ist Basisklasse, B ist von A abgeleitete Klasse
Vererbung dient der Deklaration von Klassen unter Wiederverwendung bereits existierender Klassen.
Wenn Klasse B Eigenschaften der Klasse A übernimmt, sagt man, dass B von A erbt.
Wenn B von A erbt und C von B erbt, dann ist:
- A Basisklasse für B
- B von A abgeleitet
- B Basisklasse von C
- C von B abgeleitet
In Java kann jede Klasse nur von einer Basisklasse direkt abgeleitet werden ("erben"). Im Gegensatz zu C++, Eiffel (Mehrfacherbung).
Warum ist Vererbung so wichtig?
Durch die Übernahme bereits existierender Klassen wird die Wiederverwendung von Software unterstützt!
Damit hängt eng der Aspekt der Zuverlässigkeit von Software zusammen.
Voraussetzung für die häufige Wiederverwendung von Klassen ist deren möglichst große Allgemeingültigkeit.
Für das korrekte Anwenden von Vererbung ist das Verstehen der mit der intendierten Semantik:
Wenn ein Klasse B Eigenschaften der Klasse A erbt, bedeutet dies:
- Jedes Objekt von B hat alle Attribute und Methoden, die ein Objekt der Klasse A hat.
- Darüberhinaus hat jedes Objekt der Klasse B weitere Eigenschaften (Attribute, Methoden), die Objekte der Klasse A nicht haben, d.h. Eigenschaften, in denen es sich von Objekten der Klasse A unterscheidet.
Weil jedes Objekt der Klasse B alle Eigenschaften von Objekten der Klasse A hat, kann es an allen Stellen eingesetzt werden, an denen Objekte der Klasse A zulässig sind. Umgekehrt gilt dies nicht. Man sagt daher, dass Objekte der Klasse B spezieller sind als Objekte der Klasse A und nennt B auch eine Spezialisierung von A.
Dies wird graphisch so ausgedrückt:
- Basisklasse
erbt von,
ist abgeleitet von - abgeleitete Klasse
Bei Beteiligung mehrerer Klassen kommt es zu sogenannten Klassenhierarchien.
Im Zentrum der objektorientierten Programmierung steht der Entwurf von Klassen und Klassenhierarchien.
Wann soll eine Klasse B von A erben?
In der Literatur wird die Vererbungsbeziehung oft als eine
"Ist-ein-Beziehung" bezeichnet.
D.h.: Jedes Objekt der Klasse
B ist ein Objekt der Klasse A.
Diese oberflächliche Betrachtungsweise kann aber schnell zu
Problemen führen.
Beispiel: jeder Kreis ist eine Ellipse
andererseits:
- Ellipsen haben zwei Brennpunkte
- brennpunk1
- brennpunkt2
- Methoden für den Umgang dafür
Hinweis: Keine gute Modellierung!
» Das Vorhandensein einer
"Ist-ein-Beziehung" reicht im Allgemeinen nicht aus, um
Vererbung zu begründen.
Für das Vorsehen von Vererbung müssen darüber hinaus auch folgende Fragen positiv beantwortet werden:
- Soll jedes Objekt der Klasse B alle Eigenschaften von Objekten der Klasse A haben?
- Soll jedes Objekt der Klasse B auch in allen Situationen eingesetzt werden können, in denen Objekte der Klasse A zulässig sind?
Dass die "Ist-ein-Beziehung" nicht mit der Vererbungsbeziehung gleichzusetzen ist, sieht man auch an folgendem:
"Vorstellung": Objekt der Klasse B
Bildbeschreibung "Objekt der Klasse B": Attribute und Methoden der Klasse B bilden eine Menge. Das Subobjekt der Klasse A ist Teil dieser Menge.
Daher ist klar, dass jedes Objekt der Klasse B alle Eigenschaften hat wie Objekte der Klasse A.
Klar ist auch: Objekte der Klasse B sind nicht Objekte der Klasse A (braucht z.B. mehr Speicherplatz).
Wie sind abgeleitete Klassen in Java zu deklarieren?
public class A
{
...
}
public class B extends A
{
...
}
Es muss möglich sein, auf die Klasse A zuzugreifen. In folgenden Fällen ist das z.B. nicht möglich:
- ist nicht
public
und nicht im Package von B - die Klasse A wurde als
final
deklariert.
public final class A
{
...
}
Wie werden Objekte abgeleiteter Klassen erzeugt?
Durch einen Konstruktor (wie immer).
Da Konstruktoren nicht zu den Methoden von Klassen gehören,
werden sie auch nicht vererbt.
Hinweis: Abgeleitete Klassen haben in der Regel selbstgeschriebene Konstruktoren!
Ausnahme: A hat nur den default
-Konstruktor ==> B kann
auch nur den default
-Konstruktor haben.
Diese Situation ist bei selbstgeschriebenen Basisklassen untypisch.
Wie müssen die Konstruktoren abgeleiteter Klassen gestaltet werden?
Das Erzeugen von Objekten abgeleiteter Klassen erfolgt immer in 3 Schritten:
- es wird der Konstruktor der Basisklasse ausgeführt
- es werden die Attribute der abgeleiteten Klasse initialisiert
- entweder mit Hilfe von Initialisierern
- oder mit default-Werten:
boolean
(false
);char
('\n0000'
);byte, short
(0);int, long
(0);float
(+ 0.0f
);double
(+ 0.0
); Referenzdatentypen (null)
- die weiteren Anweisungen des Konstruktors der abgeleiteten Klasse werden ausgeführt
Jeder Konstruktor einer abgeleiteten Klasse aktiviert zuerst einen Konstruktor der Basisklasse.
Dafür gibt es zwei Möglichkeiten:
- die Basisklasse hat nur einen
default
-Konstruktor - die Basisklasse hat mindestens einen selbstgeschriebenen Konstruktor
im Fall A: alles automatisch
im Fall B: muss der Aufruf eines Konstruktors der Basisklasse die
erste Anweisung des Konstruktors der abgeleiteten Klasse sein
Wie hat der Aufruf auszusehen?
- mit
super
- oder mit
this
public class A
{
...
public A (String info, int nr)
{
...
}
...
}
public class B
{
...
public B (String text, int n, double x)
{
super(text, n); // aktiviert den Konstruktor der Basisklasse
...
}
public B (double x)
{
this("Nummer", 1, x); // aktiviert den Konstruktor darüber
...
}
}
wegen der Reihenfolge gilt:
Der Konstruktor der abgeleiteten Klasse kann bereits auf Attribute und Methoden der Basisklasse des zuvor erzeugten Teilobjektes des Basisklasse zugreifen.
- wenn sie
public
sind direkt, ansonsten nur überpublic
-Methoden
Der Konstruktor der abgeleiteten Klasse kann bereits die Initialwerte seines Objektes benutzen.
Hinweis: Dadurch kann sich der Zustand eines Objektes während des Konstruktionsprozesses mehrfach ändern!
Das Überschreiben von Attributen und Methoden
Was passiert, wenn ein Attribut oder eine Methode einer abgeleiteten Klasse versehentlich oder bewusst denselben Bezeichner hat, wie ein Attribut oder eine Methode der Basisklasse?
Überschreiben von Attributen
public class A
{
public int a = 1; // public! (ungewöhnlich)
...
}
public class B extends A
{
public int a = -1;
...
}
B x = new(B);
System.out.print(x.a); // ? welches a ?
X Objekt der Klasse B
Bildbeschreibung "X Objekt der Klasse B": Das Methode der Basisklasse überschreibt die Methode der abgeleiteten Klasse.
Hinweis: In dieser Situation kann direkt nur auf eines der beiden Attribute zugegriffen werden. Auf das in der abgeleiteten Klasse neu deklarierte.
Kann ein Objekt der abgeleiteten Klasse auf ein überschriebenes Attribut der Basisklasse zugreifen?
Ja, wenn es public
ist, mit Hilfe
von super
.
int b = super.a; // 1
int c = a; // -1
Kann von außen auf ein überschriebenes Attribut der Basisklasse zugegriffen werden?
Ja, wenn es public
ist, mit Hilfe geeigneter Methoden der abgeleiteten Klasse, aber
nicht direkt!
public int gibWertVonSuperA()
{
return super.a;
}
Es gibt noch eine weitere Möglichkeit des Zugriffs (Polymorphie).
Wann soll man das Überschreiben von Attributen einsetzen?
Gar nicht! Wenn alle Attribute private
sind, ist der
Zugriff von außen immer nur über Methoden möglich.
Dies führt im schlimmsten Fall zum Überschreiben von
Methoden, nicht von Attributen.
Warum muss das Überschreiben von Attributen dennoch geregelt werden?
Damit Basisklassen, von denen bereits eine Vielzahl von anderen
Klassen abgeleitet wurden, auch noch nachträglich um Attribute
erweitert werden können, die public
oder protected
sind!
Hinweis: Attribute sind so, dass es zu keinen Störungen kommen kann!
Überschreiben von Methoden
Wird sehr häufig eingesetzt!
Warum:
Objekte abgeleiteter Klassen sind spezieller als Objekte ihrer
Basisklassen.
Daher sollten manche ihrer Methoden ebenfalls spezieller sein, als
die gleichnamigen Methoden ihrer Basisklassen! (spezieller:
höherer Funktionsumfang)
public class Angestellter
{
private String name;
private double gehalt;
...
public double gibGehalt()
{
return gehalt;
}
...
}
public class Manager extends Angestellter
{
private double praemie;
...
public void setzePraemie(double p)
{
praemie = p;
}
...
public double gibGehalt()
{
return praemie + supter.gibGehalt();
}
Die überschriebene Methode der Basisklasse ist vorhanden, man kann aber nicht mehr direkt auf sie zugreifen!
Manager x = new Manager();
x.gibGehalt(); // Methode der Klasse Manager wird aktiviert
Sehr häufig werden überschreibende Methoden nach folgendem Schema gestaltet:
public class A
{
public void machWas()
{
...
}
}
public class B extends A
{
...
public void machWas()
{
super.machWas();
... // mach den Rest
}
}
Zugriff von außen auf überschriebene Methoden von Basisklassen ist mit Hilfe von Methoden der abgeleiteten Klasse möglich.
public double gibGrundGehalt()
{
return super.gibGehalt();
}
Was ist beim Überschreiben von Methoden zu beachten?
Ein Überschreiben liegt nur dann vor, wenn die Signatur und der Datentyp des Rückgabewertes der Methode in der abgeleiteten Klasse und in der Basisklasse übereinstimmen.
Wenn eine Methode der abgeleiteten Klasse im Namen mit einer Methode der Basisklasse übereinstimmt, aber eine andere Parameterliste hat (mehr, weniger, andere), handelt es sich um das Überladen von Methoden, nicht das Überschreiben. Dies ist zulässig und kommt häufig vor.
Wenn eine Methode der abgeleiteten Klasse dieselbe Signatur hat wie eine Methode der Basisklasse, aber einen anderen Datentyp für den Rückgabewert, ist das ein Fehler!
Methoden einer Klasse, die private
sind, können nicht
überschrieben werden.
Der Zugriffsschutz der überschreibenden Methoden kann ein anderer sein, als der der überschriebenen Methoden, aber nicht beliebig.
- Basisklasse:
"friendly
"
protected
public - abgeleitete Klasse:
"friendly
",protected, public
protected, public
public
Der Zugriff kann nie verschärft werden!
Die Zugriffsmodifizierer native
,
synchronized
und
strictfp
können beliebig
ausgetauscht werden.
Die Methode der abgeleiteten Klasse kann final
sein (die der
Basisklasse aber
nicht). Parameter der Methode der abgeleiteten Klasse können
final
sein, obwohl
dieselben Parameter der Methode der Basisklasse nicht final
waren.
Bei Exceptions gibt es einige Einschränkungen. (Selbststudium)
Statische Attribute und Methoden können nicht überschrieben werden.
Vererbung und Zuweisungskompatibilität
Da Java streng typisiert ist, prüft der Compiler bei jeder Wertzuweisung, ob der Datentyp des zugewiesenen Wertes kompatibel ist zum Datentyp des Empfängers.
Empfänger können unter anderem sein:
- Attribute von Objekten
- Variablen von Methoden/Konstruktoren
- Parameter von Methoden/Konstruktoren
- Komponenten von Arrays
- der Rückgabewert von Methoden
Liegt keine Zuweisungskompatibilität vor, kommt es zu einem Fehler.
Für die primitiven Datentypen ist Zuweisungskompatibilität exakt geregelt, für die Referenzdatentypen auch.
Für Referenzdatentypen gilt:
Null ist zuweisungskompatibel zu allen Referenzdatentypen (Array, Klassen, Interfaces).
Wenn der Referenzdatentyp A eine Klasse ist, ist der Referenzdatentyp B zuweisungskompatibel in folgenden Situationen:
- B ist identisch mit A
- B ist abgeleitet von A
Wenn der Referenzdatentyp A[ ]
ein Array-Datentyp ist, ist der
Array-Datentyp B[ ]
zuweisungskompatibel zu A[ ]
in folgender Situation:
- A und B sind identische primitive Datentypen
- A und B sind Referenzdatentypen und B ist zuweisungskompatibel zu A
Darüber hinaus gibt es Regeln für Interfaces.
Die Regeln für Klassen sind plausibel, da jedes Objekt einer abgeleiteten Klasse alle Eigenschaften der Basisklasse hat und an allen benutzt werden können sollte, an dem Objekt der Basisklasse zulässig sind.
Interessant wird die Situation, wenn es gleichzeitig überschriebene Attribute und/oder überschriebene Methoden gibt.
public class A
{
public int a = 1; // public untypisch
public schreibeTyp()
{
System.out.println("Klasse A");
}
}
public class B
{
public int a = -1; // public untypisch, überschreibt a!
public int b = 100;
public schreibeTyp() // überschreibt!
{
System.out.println("Klasse B");
}
...
A x = new A();
System.out.println("x.a = " + x.a); // 1
x.schreibeTyp();
...
B y = new B();
System.out.println("y.a = " + y.a); // -1
y.schreibeTyp();
...
A z = y; // ok! Regel 2!
System.out.println("z.a = " + z.a);
z.schreibeTyp();
Bildbeschreibung "überschriebene Attribute und/oder überschriebene Methoden": Graphische Darstellung von Vererbung und Zuweisungskompatibilität.
Welchen Wert hat z ?
Welche Methode wird bei z.schreibeTyp()
aufgerufen?
Für den Zugriff auf überschriebene Attribute gilt:
- der Datentyp der Referenzvariablen entscheidet darüber, auf welches Attribut zugegriffen wird:
y.a // - 1
z.a // 1
Der Datentyp des Objektes entscheidet darüber, auf welche Methode zugegriffen wird.
y.schreibeTyp() // Klasse B
z.schreibeTyp() // Klasse B
Polymorphismus
Aus Sicht der Referenzvariablen bedeutet dies:
- zeigt sie auf ein Objekt der Basisklasse, werden die Methoden der Basisklasse ausgeführt
- zeigt sie auf ein Objekt der abgeleiteten Klasse, werden die Methode der abgleiteten Klasse ausgeführt
Hinweis: Was genau eintritt, hängt also von der Situation ab. Das heißt, das Verhalten ist nicht genau vorhersehbar, sondern polymorph (vielgestaltig).
Aus Sicht des Objektes bedeutet dies,
- wird eine Referenzvariable der abgeleiteten Klasse auf das Objekt zugegriffen, erfolgt der Zugriff auf die überschreibenden Attribute.
- wird über eine Referenzvariable der Basisklasse auf das Objekt zugegriffen, erfolgt der Zugriff auf die überschriebenden Attribute.
Hinweis: polymorphes Verhalten
Polymorphismus ist die dritte charakteristische Eigenschaft objektorientierter Sprache (neben Datenkapselung und Vererbung).
Für den Aufruf von Methoden bedeutet dies, dass vom Compiler erst zur Laufzeit entschieden werden kann, welche Methode genau ausgeführt wird.
x.Methode() // welche Methode, dynamisch
Diese dynamische Bindung kostet Zeit, was ein weiterer Grund für die nachgesagte Langsamkeit objektorientiert Sprachen ist. Intern wird dynamische Verbindung mit Hilfe sogenannten Methodentabellen realisiert.
Für Arrays bedeuten die Regeln, dass die Komponenten eines Arrays von Klassen nicht alle denselben Datentyp haben müssen!
...
A[ ] feld = new A[ 3 ];
...
feld[ 0 ] = new A();
feld[ 1 ] = new B();
feld[ 2 ] = new A();
for (int i = 0; i < feld.length; i++)
System.out.println(feld[ i ].a); // 1 1 1
for (int i = 0; i < feld.length; i++)
feld[ i ].schreibeTyp(); // Klasse A Klasse B Klasse B
Hinweis: Upcasting
- ist immer zulässig
- kann eingesetzt werden, um direkt auf ein überschriebenes Attribut zuzugreifen
Downcasting
ist nur mit Hilfe des cast
-Operators möglich.
A x = new B();
System.out.println(x.a); // 1
System.out.println( (B) x).a); // -1
Mit dem binären instanceof
-Operator kann
festgestellt werden, ob ein Ausdruck zuweisungskompatibel ist zu
einem Datentyp.
x instanceof Referenzdatentyp
instanceof
: true / false- Referenzdatentyp: Klasse oder Array
for (int i = 0; i < feld.length; i++)
{
if (feld[ i ] instanceof B)
System.out.println( ( (B)feld[ i ]).a);
else
System.out.println(feld[ i ].a); // 1 -1 1
Was muss man noch über Vererbung wissen?
protected
kann sowohl für Attribute als auch für Methoden
eingesetzt werden.
Auf protected
-Attribute oder -Methoden
kann zugegriffen werden in folgenden Situationen:
- innerhalb der eigenen Klasse
- von allen abgeleiteten Klassen aus
- von allen Klassen desselben Package aus !!!
protected
bietet daher nur einen geringen Zugriffsschutz.
protected
für Attribute ist selten sinnvoll
("kaum eingesetzt").
Direkter Zugriff ist zwar nicht möglich, man braucht aber nur
eine Klasse abzuleiten, und hat von dieser dann direkten Zugriff.
Hinweis: So kann Datenkapselung umgangen werden. Schlecht!
Bei Methoden kann protected
zum Beispiel in folgenden Situationen sinnvoll sein:
- eine Hilfsmethode soll von außen nicht aktiviert werden
können (d.h. nicht
public
) - abgeleitete Klassen sollen aber die Hilfsmethode
überschreiben können, um sie spezielleren Situationen
besser anzupassen (also
protected
, nichtprivate
) - dabei kann aber der ursprünglich gewünschte Schutz
vor einem direkten Zugriff augehoben werden: denn die
überschreibende (Hilfs-)Methode kann statt
protected
selbstpublic
sein
Hinweis: Durch protected
wird der
Zugriffsschutz
erheblich gelockert. Daher nur sparsam damit umgehen!
Methoden, die als final
deklariert wurden, können
beim Ableiten nicht überschrieben werden. Dadurch kann zum
Beispiel sichergestellt werden, dass sicherheitsrelevante Methoden
(Passwort-Methoden) oder spezielle Algorithmen nicht verändert
werden können.
Der Aufruf von final
-Methoden kann vom Compiler optimiert werden,
da dynamische Bindung durch statische Bindung ersetzt werden kann.
Hinweis: positiver Effekt auf Laufzeitverhalten.
Andererseits wird durch final
das Überschreiben verhindert
und damit die Vererbung beschränkt.
Hinweis: final
nicht unüberlegt
einsetzen!
Noch drastischer ist die Einschränkung, eine gesamte Klasse
als final
zu
deklarieren. Von derartigen Klassen kann nicht geerbt werden.
Die Java Klassen-Bibliothek enthält eine Reihe von Klassen,
die als final
deklariert wurden.
- zum Beispiel die sogenannten "Wrapper-Klassen"
Alle Klassen in Java erben entweder direkt oder indirekt von der
vordefinierten Klasse Object
.
Object
ist die Wurzel der gesamten
Java-Klassen-Hierarchie.
Bildbeschreibung "Object": Alle Klassen in Java erben
entweder direkt oder indirekt von der vordefinierten Klasse Object
. Object
ist die
Wurzel der gesamten
Java-Klassen-Hierarchie.
In C++ gibt es keine gemeinsame Wurzel für alle Klassen.
Es ist nicht zulässig, bei der Deklaration von Klassen
Object
anzugeben! Dies geschieht gegebenenfalls automatisch.
Die Klasse Object
hat keine
Attribute, aber elf Methoden, die alle Klassen in Java erben.
Fünf dieser Methoden sind nur im Zusammenhang mit
"Parallelverarbeitung" mit Hilfe von
threads
relevant.
protected void
finalize() throws Throwable
:
Diese Methode wird vom Garbage-Collector unmittelbar vor dem
Zerstören von Objekten aktiviert. Eine typische Anwendung von
finalize
sieht
folgendermaßen aus:
- die Methode wird für eine eigene Klasse überschrieben, um zum Beispiel externe Ressourcen wieder frei zu geben (z.B. Netzwerkverbindungen, Drucker, Grafik, temporäre Dateien)
public boolean
equals(Object y)
:
Diese Methode liefert true
, wenn das aktuelle Objekt und y
"gleich" sind, ansonsten false
.
Wann sind zwei Objekte gleich?
Wenn equals
nicht
überschrieben wurde, bedeutet "gleich" lediglich,
dass es sich um Referenzen auf dasselbe Objekt handelt.
A x, y;
x = new A();
y = x;
boolean gleich = x.equals(y); // true
Für viele Anwendungen ist dieser Begriff von "Gleichheit" nicht angemessen.
public class Punkt
{
double x;
double y;
...
}
...
Punkt a = new Punkt(1.1, 3.57);
Punkt b = new Punkt(1.1, 3.57);
boolean gleich = a.equals(b); // false ==> nicht sinnvoll
Hinweis: In derartigen Situationen
equals
überschreiben! Achtung: ein sinnvolles
Überschreiben von equals
ist nicht trivial!
In der Java-Klassen-Bibliothek gibt es mehr als 150
Überschreibungen von equals
,
die nach Auffassung dieser Autoren nicht korrekt sind.
Methoden von Object
public int hashCode()
Liefert zu jedem Objekt eine ganze Zahl, so dass folgende Bedingungen erfüllt sind:
x.hashCode()
ist während der gesamten Lebensdauer von x immer dieselbe Zahl.- Wenn
x.equals(y)
den Werttrue
hat, ist stetsx.hashCode() == y.hashCode()
.
Der Hashcode von Objekten wird für die Datenorganisation mit sogennanten Hashtabellen gebraucht.
Über die Bedingung 2 sind equals
und
hashCode
miteinander verknüpft.
Hinweis: Wird equals
überschrieben,
muss hashCode
entsprechend angepasst werden!
public final Class get Class()
Liefert zum aktuellen Objekt ein Objekt der Klasse
Class
, das sogenannte
Laufzeitobjekt der Klasse, zu der das Objekt
gehört.
Class
verfügt über 36
Methoden, mit denen - während der Laufzeit - diverse
detaillierte Informationen über das Objekt und seine Klasse
erhalten werden können.
public String toString()
Liefert eine Darstellung des aktuellen Objektes als Zeichenkette.
Wenn toString
nicht überschrieben wird, besteht die
Zeichenkette aus einer Information über die Klasse und aus dem
Hashcode des Objektes als Hexadezimalzahl.
[I@7182c1
[I => int-Array
7182c1 => Hashcode (der aus der Adresse des Objektes erzeugt wird)
Hinweis: Daher ist es sinnvoll, toString
in allen selbstgeschriebenen Klassen zu überschreiben!
A x = new A();
System.out.print("Wert von x:" + x);
x.toString()
Wenn toString
sinnvoll überschrieben wird, werden Methoden
der Art "schreibeObjekt" überflüssig!
public class Punkt
{
double x;
double y;
public Punkt(doublex, double y)
{
this.x = x;
this.y = y;
}
void schreibePunkt()
{
System.out.print("(" + x + ", " + y + ")");
}
}
Das ganze ist mit toString
nicht notwendig:
public to String()
{
return("(" + x + ", " + y + ")");
}
...
Punkt a = new(1.1, 4.33);
a.schreibePunkt(); // (1.1, 4.33)
System.out.print(" " + a); // (1.1, 4.33)
protected Object clone() throws CloneNotSupportedException
Diese Methode dient dazu, eine exakte Kopie des aktuellen Objektes zu erzeugen.
Sie kann nur eingesetzt werden, wenn das Interface Cloneable
benutzt wird.
Meistens ist clone
zu
überschreiben. Das aber ist nicht ganz trivial. Stichwort
"deep cloning".
Da Object die Wurzel der gesamten Java-Klassenhierarchie ist, sind alle Referenzvariablen in Java zuweisungskompatibel zu Referenzen auf Object.
...
Object x;
...
A y = new A();
x = y; // Okay!
Dies gilt nicht für Variablen, die von einem primitiven Datentyp sind.
int z = 10;
...
x = z; // Fehler!
Daher gibt es zu jedem primitiven Datentyp eine sogenannte Wrapper-Klasse, die es gestattet, aus dem Wert eines primitiven Datentyps ein entsprechendes Objekt zu machen.
- primitiver Datentyp:
byte x = 10;
short x = -3;
int x = 12;
long x = 1000;
char x = 'a';
float, double, boolean - Wrapper-Klasse:
Byte x = new Byte(10);
Short x = new Short(-3);
Integer x = new Integer(13);
Long x = new Long(1000);
Character x = new Character('a');
Float, Double, Boolean
Die Objekte der Wrapper-Klassen sind zuweisungskompatibel zu
Object
-Referenzen.
Object x = new Integer(-7); // Okay!
Klasse | Attribute | Konstruktoren | eigene Methoden |
---|---|---|---|
Byte | 3 | 2 | 17 |
Short | 3 | 2 | 17 |
Integer | 3 | 2 | 24 |
Long | 3 | 2 | 24 |
Character | 55 | 1 | 34 |
Float | 6 | 3 | 22 |
Double | 6 | 2 | 22 |
Boolean | 3 | 2 | 8 |
Object-Arrays können Referenzen auf beliebige Klassen aufnehmen. Durch den Einsatz von Polymorphismus und Downcasting ergeben sich (völlig neue) interessante Möglichkeiten.
public class Tier
{
private String art;
public Tier (String dieArt)
{
art = dieArt;
}
public void sprich()
{
System.out.print("Ich kann nicht sprechen!")
}
Public String toString()
{
return art + ": ";
}
}
public class Hund extends Tier
{
private String name;
private String rasse;
public Hund(String dieArt, String derName, String dieRasse)
{
super(dieArt);
name = derName;
rasse = dieRasse;
}
public void sprich()
{
System.out.println(" Ich mache wau, wau!");
}
public String toString()
{
return super.toString() + " Ich bin ein " + rasse + " und heisse " + name + ".";
}
}
...
}
Generische Methoden
Dies ermöglicht die Gestaltung generischer Methoden.
int finde(Object[] feld, Object wert)
{
int fundStelle = -1;
boolean gefunden = false;
for(int i=0; !gefunden && i<feld.length; i++)
if (feld[i].equals(wert))
{
fundStelle = i;
gefunden = true;
}
return fundStelle;
}
Diese Methode funktioniert für alle Arrays mit Referenzen auf Klassen, für
die die Methode equals
sinnvoll definiert
ist! (=> "generische Programmierung")