static
tragen.
Die Klassen, die wir jetzt besprechen wollen, sind neue Datentypen, die die ProgramiererIn selbst schafft, oder die ein von "Profis" zur Verfügung gestelltes package anbietet. Variable vom Typ einer Klasse heißen Objekte, sie erhalten durch ihre Deklaration einen Bezeichner. Zu einer solchen (nicht statischen) Klasse gehört immer ein Konstruktor, der ihre Objekte initialisiert. Eine Klasse hat gewisse Daten (auch Attribute oder Variable der Klasse genannt) und verfügt über Methoden. Wenn die Klasse nicht den Zusatz static besitzt, also keine statische Klasse ist, kann man eine Instanz (ein Objekt) dieser Klasse mit Hilfe des new-Operators und des Konstruktors initialisieren (allokieren). Danach kann auf die Daten und die Methoden des Objektes mit Hilfe der Punktnotation zugegriffen werden.
Wir kennen dies im Prinzip schon für die klassenverwandten
Datentypen Zeichenkette (String) und Feld: So wird ein
double
-Feld z.B. durch double[] Vektor1=new
double[6];
deklariert und initialisiert. Seine Länge (6) kann
durch Vektor1.length
ermittelt werden,
d.h. length
ist der Name eines Attributs aller Objekte
vom Typ eines Feldes. Entsprechend haben wir gesehen, dass
equals(), indexOf()
und length()
Methoden von
Variablen ("Objekten") des Typs String
bezeichnet.
Die Daten (Attribute, Variable) von Objekten einer Klasse, die komplexe Zahlen
darstellt, werden
also sinnvollerweise Real- (Re) und Imaginärteil
(Im) sein (wie verwenden gegen die
Konvention große Anfangsbuchstaben). In der darauf folgenden
Applikationsklasse Komplex1
wird eine
komplexe Zahl als Objekt der Klasse KomplexeZahl1
durch
Übergabe des Real- und des Imaginärteils an den Konstruktor
konstruiert. Spätere Erweiterungen dieser Klasse werden KomplexeZahl2,
KomplexeZahl3, ...
heißen.
Wir wollen der Klasse noch mit einer Methode versehen, die feststellt,
ob die komplexe Zahl reell, d.h., ob ihr Imaginärteil null
ist (da man wegen Rundungsfehler double
-Zahlen nicht auf
null hin prüfen sollte, sind wir etwas großzügiger in
Verteilung des Etiketts reell).
class KomplexeZahl1{ //Daten: double Re, Im;//gegen Konvention mit grossen Anfangsbuchstaben //Konstruktor: KomplexeZahl1(double x, double y){ Re=x; Im=y; }//Ende Konstruktor boolean reell(){ boolean w=false; if (Math.abs(Im)<1E-12) w=true; return w; }//Ende reell() }//Ende class KomplexeZahl1
Diese Klasse kann zwar kompiliert, aber nicht ausgeführt werden,
da es sich nicht um eine Applikationsklasse handelt (sie enthält
keine main()
-Methode).
Eine Applikationsklasse, die ein Objekt vom Typ
KomplexeZahl1
instanziert, könnte so aussehen:
Die wichtigste Zeile ist die, in der der Konstruktor in Verbindung mit dem new-Operator eingesetzt wird, um ein Objekt namensclass Komplex1{ static void main(String[] args){ KomplexeZahl1 z = new KomplexeZahl1(1.1, 2.3); boolean w=z.reell();//false }//Ende main()( }//Ende class Komplex1
z
zu konstruieren. Der
Bezeichner des Konstruktors muss stets identisch mit dem Namen der
zugehörigen Klasse sein. Die Methode reell()
kann man als eine Anfrage an das Objekt z
verstanden
werden, ob es als reelle Zahl aufgefasst werden kann.
Die imaginäre Einheit i hätte man mit
KomplexeZahl1 i =
new KomplexeZahl1(0, 1);
konstruieren können.
Jetzt geben wir eine Applikationsklasse an, die eine Methode vorsieht, die komplexe Zahlen miteinander multipliziert und eine solche Multiplikation auch für konkrete Zahlen ausführt:
Beachten Sie, dass sowohl die beiden Parameter als auch der Rückgabewert der Methodeclass Komplex2{ static KomplexeZahl1 mal(KomplexeZahl1 z1, KomplexeZahl1 z2){ double w1, w2; w1=z1.Re*z2.Re-z1.Im*z2.Im; w2=z1.Re*z2.Im+z1.Im*z2.Re; return new KomplexeZahl1(w1,w2); }//Ende mal() static void main(String[] args){ KomplexeZahl1 z1 = new KomplexeZahl1(1.1, 2.3); KomplexeZahl1 z2 = new KomplexeZahl1(1.1, -2.3); KomplexeZahl1 w=mal(z1,z2); if (w.reell()) System.out.println("z1*z2 ist reell"); }//Ende main()( }//Ende class Komplex2
mal()
vom Typ der
Klasse KomplexeZahl1
sind. Die Rückgabe kann ohne
Deklaration einer Variable diesen Typs erfolgen.
main()
-Block wird das Produkt w
nicht durch einen Konstruktor
initialisiert, sondern durch Ausführung der Methode
mal()
.
Achten Sie auf die Punktnotation,
z.B. in z1.Re, wenn auf die Daten
eines Objektes zugegriffen werden soll. Auf den Imaginärteil des Produktes
kann man mittels w.Im, auf die Methode
reell()
mittels w.reell()
zugreifen.
void
vermerkt wird. Wie bei Methoden kann man
auch Konstruktoren überladen, d.h.
es kann mehrere Konstruktoren geben. Diese müssen aber gleiche Namen
haben, sich jedoch in der Parameterliste unterscheiden, s.u.
Zuweilen möchte man den Parametern eines Konstruktors den gleichen Namen geben wie sie die Daten der Klasse haben. Dies geht unter Verwendung des Schlüsselworts this:
Die Verwendung von this bedarf einer Erklärung: Wenn die Bezeichner von Parametern von Konstruktoren mit denen von Daten der Klasse identisch sind, haben die Parameter als lokale Parameter Vorrang. Um auf die Daten zugreifen zu können, muss man this in der Punktnotation voranstellen, welches stellvertretend für den Bezeichner eines später zu konstruierenden Objektes steht.class KomplexeZahl2{ //Daten: double Re, Im; //Konstruktor: KomplexeZahl2(double Re, double Im){ this.Re=Re; this.Im=Im; }//Ende Konstruktor boolean reell(){ return (Math.abs(Im)<1E-12); }//Ende reell() }//Ende class KomplexeZahl2
Will man z.B. ausschließen, dass ein Objekt namens z
der Klasse KomplexeZahl1
nachträglich z.B. durch die
Anweisungen z.Re=-7.12
verändert wird, sollte
man dies dadurch verhindern, dass Daten den Zusatz private erhalten und sie damit für andere
Klassen nicht direkt zugänglich (unsichtbar) gemacht werden:
Allerdings ruft dann die Zeile
class KomplexeZahl
.......
//Daten:
private double Re, Im;
......
System.out.println("Re z="+z.Re+"
Im z="+z.Im);
in einer Applikationsklasse die
Compiler-Fehlermeldung Variable Re in
class KomplexeZahl not accessible from class
Komplex2
hervor. Will man wirklich den direkten
Zugriff auf die Daten
Re, Im
verbieten, aber doch zulassen, dass sie gelesen werden
können, muss man weitere
öffentliche Methoden in der Klasse KomplexeZahl
zur
Verfügung stellen, etwa so
Jetzt erreicht die folgende Applikationsklasse das gleiche Ziel wie
die Klasse
//KomplexeZahl3
......
private double Re, Im;
......
//Methoden:
....
public double getRe(){
return Re;
}
....
}//Ende class
Komplex2
:
Auf jeden Fall sollte die Klasse
class Komplex3{
static void main(String[] args){
KomplexeZahl3 z =
new KomplexeZahl3(1.1, 2.3);
System.out.println("Re z="+
z.getRe()+" Im z="+z.getIm());
}//Ende main()
}//Ende class Komplex3
KomplexeZahl
und ihr
Konstruktor öffentlich (public) sein, was schon dadurch erreicht
wird, dass der Zusatz weggelassen wird (wie bisher). Das bedeutet,
dass jede andere Klasse ein Objekt des Typs KomplexeZahl
initialisieren und seine öffentlichen Methoden benutzen kann.
Ein wesentlicher Vorteil bei einer Einschränkung von Zugriffsrechten bzw. der Sichtbarkeit ist, dass der Benutzer sich nur die öffentlichen Daten, Klassen und Methoden merken muss. Will man z.B. von den ungeheuer mächtigen Grafikklassen von Java Gebrauch machen, muss man in jedem Detail die Bezeichner der öffentlichen Klassen, ihrer öffentlichen Daten und Methoden, aber auch den Typ dieser Daten und die Paramterliste dieser Methoden kennen, im Zweifelsfall nachschlagen oder auswendig lernen. Die intern vom Programmierer verwendeten nicht öffentlichen Klassen, Daten und Methoden interessieren nicht!
Neben private, public ist noch der Zusatz protected wichtig. Er tritt aber nur in Verbindung mit Unterklassen auf und erlaubt die Sichtbarkeit innerhalb aller Unterklassen.
Winkel
mit dem Attribut double w
(dem
eigentlichen Winkel im Bogenmaß) ein, dessen Konstruktor dafür sorgt,
dass w
in [0, 2*PI) liegt:
Jetzt können wir einen zweiten Konstruktor in der Klasseclass Winkel{ private double phi; Winkel(double a){ while (a>= 2*Math.PI) a-=2*Math.PI; while (a<0) a+=2*Math.PI); phi=a; }//Ende Konstruktor Winkel() public double getphi(){ return phi; } }//Ende class Winkel
KomplexeZahl
vorsehen, dem Polarkoordinaten
übergeben werden und der den ersten Konstruktor überlädt:
Beachten Sie, dass beide Konstruktoren den gleichen Namen haben, dass sich ihre Parametertypen aber unterscheiden. Hätte ich nicht die Klasseclass KomplexeZahl4{ private double Re, Im; //Konstruktor 1 KomplexeZahl4(double x, double y){ Re=x; Im=y; } //Konstruktor 2 KomplexeZahl4(double r, Winkel W){ double phi=W.getphi(); Re=r*Math.cos(phi); Im=r*Math.sin(phi); } public double getRe(){ ... } }//Ende class KomplexeZahl4
Winkel
eingeführt, sondern einen Winkel als
einfache double
-Zahl angesehen, hätten die beiden
Konstruktoren jeweils zwei double
-Parameter und
hätten nicht unterschieden werden können. Das kann
natürlich nicht ausschlaggebender Grund sein, die Klasse
Winkel
einzuführen.
class Komplex4{ static KomplexeZahl4 mal(KomplexeZahl4 z1, KomplexeZahl4 z2){ double w1, w2; w1=z1.getRe()*z2.getRe()-z1.getIm()*z2.getIm(); w2=z1.getRe()*z2.getIm()+z1.getIm()*z2.getRe(); return new KomplexeZahl4(w1,w2); }//Ende mal() static void main(String[] args){ double ph=1.2*Math.PI; Winkel w=new Winkel(ph); double r=2.2; KomplexeZahl4 z = new KomplexeZahl4(r,w);//2.Konstruktor System.out.println("Re(z)="+z.getRe()+" Im(z)="+z.getIm()); }//Ende main() }//Ende class Komplex4