Galileo Computing < openbook >
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.


Java ist auch eine Insel von Christian Ullenboom
Buch: Java ist auch eine Insel (Galileo Computing)
gp Kapitel 3 Klassen und Objekte
gp 3.1 Objektorientierte Programmierung
gp 3.1.1 Warum überhaupt OOP?
gp 3.1.2 Modularität und Wiederverwertbarkeit
gp 3.2 Klassen benutzen
gp 3.2.1 Die Klasse Point
gp 3.2.2 Etwas über die UML
gp 3.2.3 Anlegen eines Exemplars einer Klasse
gp 3.2.4 Zugriff auf Variablen und Methoden mit dem ».«
gp 3.2.5 Konstruktoren
gp 3.2.6 Die null-Referenz
gp 3.3 Die API-Dokumentation
gp 3.4 Mit Referenzen arbeiten
gp 3.4.1 Zuweisungen bei Referenzen
gp 3.4.2 Funktionen mit nichtprimitiven Parametern
gp 3.4.3 Gleichheit von Objekten und die Methode equals()
gp 3.5 Arrays
gp 3.5.1 Deklaration von Arrays
gp 3.5.2 Arrays mit Inhalt
gp 3.5.3 Die Länge eines Arrays über das Attribut length
gp 3.5.4 Zugriff auf die Elemente
gp 3.5.5 Array-Objekte erzeugen
gp 3.5.6 Fehler bei Arrays
gp 3.5.7 Arrays mit nichtprimitiven Elementen
gp 3.5.8 Arrays und Objekte
gp 3.5.9 Initialisierte Array-Objekte
gp 3.5.10 Mehrdimensionale Arrays
gp 3.5.11 Die Wahrheit über die Array-Initialisierung
gp 3.5.12 Arrays kopieren und füllen
gp 3.5.13 Mehrere Rückgabeparameter
gp 3.5.14 Parameter per Referenz übergeben
gp 3.5.15 Die Klasse Arrays
gp 3.5.16 Der Einstiegspunkt für das Laufzeitsystem main()
gp 3.5.17 Die Anzahl der Parameter
gp 3.5.18 Der Rückgabewert von main() und System.exit()
gp 3.5.19 Parser der Kommandozeilenargumente Apache CLI


Galileo Computing

3.2 Klassen benutzendowntop

Klassen sind das wichtigste Merkmal objektorientierter Programme. Eine Klasse beschreibt die Eigenschaften der Objekte und gibt somit den Bauplan an. Jedes Objekt ist ein Exemplar (auch »Instanz«1 oder »Ausprägung« genannt) einer Klasse.

Eine Klasse definiert

gp Attribute
gp E Operationen
gp E weitere Klassen (innere Klassen)

Attribute und Operationen heißen auch Eigenschaften4 eines Objekts. Welche Eigenschaften eine Klasse tatsächlich besitzen soll, wird in der Analyse- und Designphase festgesetzt. Dies wird in diesem Buch kein Thema sein; für uns liegen die Klassenbeschreibungen schon vor.

Die Operationen einer Klassendefinition werden in einer Programmiersprache durch Methoden (auch Funktionen genannt) umgesetzt. Die Attribute eines Objekts definieren die Zustände, und sie werden durch Variablen (auch »Felder«2 genannt) implementiert.

Um sich einer Klasse zu nähern, können wir einen lustigen Ich-Ansatz (Objektansatz) verwenden, der auch in der Analyse- und Designphase eingesetzt wird. Bei diesem Ich-Ansatz versetzen wir uns in das Objekt und sagen »Ich bin ...« für die Klasse, »Ich habe ...« für die Attribute und »Ich kann ...« für die Operationen. Meine Leser sollten dies einmal an den Klassen Mensch, Auto, Wurm und Kuchen testen.


Galileo Computing

3.2.1 Die Klasse Pointdowntop

Bevor wir uns mit eigenen Klassen beschäftigen, wollen wir zunächst einige Klassen aus der Standardbibliothek kennen lernen. Eine dieser Klassen ist Point. Sie beschreibt einen Punkt in einer zweidimensionalen Ebene durch die Koordinaten x und y und bietet einige Operationen an, mit denen sich Punkt-Objekte verändern lassen. Testen wir einen Punkt wieder mit dem Objektansatz:


Klassenname Ich bin ein Punkt.
Attribute Ich habe eine x- und y-Koordinate.
Operationen Ich kann mich verschieben und meine Position festlegen.

Für die Darstellung einer Klasse lässt sich Programmcode verwenden, also eine Textform, oder aber eine grafische Notation. Eine dieser grafischen Beschreibungsformen ist die UML. Grafische Abbildungen sind für Menschen deutlich besser zu verstehen und erhöhen die Übersicht.

Im ersten Abschnitt des UML-Diagramms lassen sich die Attribute ablesen, im zweiten die Methoden. Das + vor den Eigenschaften zeigt an, dass sie öffentlich sind und jeder sie nutzen kann. Die Typenangabe ist gegenüber Java umgekehrt: Zuerst kommt der Name der Variable, dann der Typ beziehungsweise bei Methoden der Typ des Rückgabewerts.

Abbildung
Hier klicken, um das Bild zu Vergrößern


Galileo Computing

3.2.2 Etwas über die UMLdowntop

Die UML (Unified Modeling Language) ist mehr als nur eine Notation zur Darstellung von Klassen. Mit ihrer Hilfe lassen sich die Analyse und das Design im Softwareentwicklungsprozess beschreiben. Mittlerweile hat sich die UML jedoch zu einer allgemeinen Notation für andere Beschreibungen entwickelt, zum Beispiel für Datenbanken oder Workflow-Anwendungen.

Vor der UML waren andere Darstellungsvarianten wie OMT oder Booch verbreitet. Diese waren eng mit einer Methode verbunden, die einen Entwicklungsprozess und ein Vorgehensmodell beschrieb. Methoden versuchen eine Vorgehensweise beim Entwurf von Systemen zu beschreiben, etwa »erst Vererbung einsetzen und dann die Attribute finden« oder »erst die Attribute finden und dann mit Vererbung verfeinern«. Bekannte OO-Methoden sind etwa Shlaer/Mellor, Coad/Yourdon, Booch, OMT und OOSE/Objectory. Aus dem Wunsch heraus, OO-Methoden zusammenzufassen, entstand die UML. Anfangs stand die Abkürzung noch für Unified Method. Die Urversion 0.8 war die erste Veröffentlichung im Jahre 1995. Die Initiatoren waren Jim Rumbaugh und Grady Booch. Später trat Ivar Jacobson dazu, und die drei »Amigos« erweiterten die UML, die in der Version 1.0 bei der Object Management Group (OMG) als Standardisierungsvorschlag eingereicht wurde. Die Amigos nannten die UML nun »Unified Modeling Language«, was deutlich macht, dass die UML keine Methode ist, sondern lediglich eine Modellierungssprache. Folgende Tabelle gibt eine Kurzübersicht über die Veränderungen der UML:


1994 Booch und Rumbaugh vereinigen ihre Ansätze OOAD und OMT.
1995 Unified Method in der Version 0.8. Jacobson bringt OOSE mit ein, die UML wird vereinfacht, von einer einheitlichen Methode wird abgesehen.
1996 Die UML in der Version 0.9. Notation wird verfeinert, unter anderem von vielen Unternehmen wie Oracle, Microsoft, Digital und HP.
1997 Versionen 1.0 und 1.1 erscheinen. Die UML wird bei der OMG eingereicht. Im September wird die UML 1.1 zum Standard.
1998 Version 1.2 mit Detailverbesserungen und einigen Korrekturen
1999 Die UML in der Version 1.3
2000 UML 1.4
2003 UML 2.0. Augenmerk auf XML Metadata Interchange (XMI), Model Driven Architecture (MDA), Geschäftsprozessmodellierung (BPM) und Unterstützung von Echtzeitmodellierung (RT) durch neue Diagrammtypen

Eine aktuelle Version des Standards lässt sich unter http://www.rational.com/uml/ beziehen.

Diagramme in der UML

In der UML werden unterschiedliche Diagramme definiert, die die unterschiedlichen Sichten auf die Software beschreiben. Für die einzelnen Phasen im Softwareentwurf sind unterschiedliche Diagrammtypen wichtig. Wir wollen kurz drei Diagramme und ihr Einsatzgebiet besprechen.

Use-Cases

Die Use-Cases-Diagramme entstehen meist während der Anforderungsphase und beschreiben die Geschäftsprozesse, indem die Interaktion von Personen oder von bereits existierenden Programmen mit dem System dargestellt werden. Die handelnden Personen oder aktiven Systeme werden Aktoren genannt. Ein Use-Case beschreibt dann eine Interaktion mit dem System. Dazu werden die Aktoren als Männchen gemalt (wobei die Geschlechter nicht zu erkennen sind) und die einzelnen Anwendungsfälle (Use-Cases) als Ellipsen.

Klassendiagramme

Für die statische Sicht auf einen Programmentwurf sind Klassendiagramme einer der wichtigsten Diagrammtypen. Sie sind besonders interessant, da Hilfsprogramme aus diesen Diagrammen automatisch Teile des Quellcodes erzeugen können. Die Diagramme stellen zum einen die Elemente der Klasse dar, zum anderen die Beziehungen der Klassen untereinander. Die Diagramme werden in diesem Buch häufiger eingesetzt. Klassen werden als Rechteck dargestellt und die Beziehungen zwischen den Klassen durch Linien angedeutet.

Interaktionsdiagramme

Der Begriff umfasst zwei Unterdiagramme zur Darstellung der zeitlichen Abläufe eines Systems, die Sequenzdiagramme und die Kollaborationsdiagramme. Damit wird im Gegensatz zum Klassendiagramm das dynamische Verhalten von Objekten dargestellt.


Galileo Computing

3.2.3 Anlegen eines Exemplars einer Klassedowntop

Von der Klasse Point werden zur Laufzeit Exemplare erzeugt, die Point-Objekte. Eine Klasse beschreibt also, wie ein Objekt aussehen soll. In einer Mengen- beziehungsweise Element-Beziehung ausgedrückt entsprechen Objekte den Elementen und Klassen den Mengen, in denen die Objekte als Elemente enthalten sind.

Wir verbinden nun einen Variablennamen mit der Klasse und definieren beziehungsweise deklarieren somit eine Variable, die eine Referenz auf ein Point-Objekt (ein Element der Klasse Point) erzeugt.


Beispiel Definiere die Variable p vom Typ Point
Point p;

Vergleichen wir dies mit der bereits bekannten Deklaration einer Variablen für einen ganzzahligen Wert.

int i;

Links steht in beiden Fällen der Typ und rechts der Name der Variable.

Im oberen Beispiel deklarieren wir eine Variable p und teilen dem Compiler mit, dass diese Variable Referenzen auf Objekte vom Typ Point speichern soll. Falls es sich bei p um eine Objekt- oder Klassenvariable handelt, wird p anfangs mit der Null-Referenz (null) initialisiert, die auf kein Objekt verweist; als lokale Variable hätte p keinen vordefinierten Wert, sie ist undefiniert. Referenztypen können nicht in primitive Typen konvertiert werden und umgekehrt.

Der new-Operator

Durch die Deklaration einer Variablen mit dem Namen einer Klasse als Typ wird noch kein Exemplar erzeugt. Dazu müssen wir mit dem new-Operator explizit ein Objekt erzeugen. Hinter dem new-Operator folgt immer der Name der Klasse, von der ein Exemplar erzeugt werden soll, und ein Paar Klammern. Wir werden später sehen, dass hier ein spezieller Methodenaufruf (Konstruktoraufruf) stattfindet, bei dem wir auch Werte übergeben können.


Beispiel Anlegen eines Objekts und Speichern der Referenz in der Variablen p
p = new Point();

Das tatsächliche Punkt-Objekt wird erst dynamisch, also zur Laufzeit, mit new erzeugt. Damit stellt das System Speicher für ein Point-Objekt bereit und speichert eine Referenz auf diesen reservierten Speicherblock in der Variablen p ab.

Die Deklaration der Variablen p und die separate Erzeugung eines Exemplars der Klasse Point lassen sich, wie bei der Deklaration primitiver Datentypen, auch kombinieren.


Beispiel Deklaration mit Initialisierung
double pi = 3.1415926535;
Point p = new Point();

Abbildung
Hier klicken, um das Bild zu Vergrößern

(Strg)+(1) ermöglicht, entweder eine neue lokale Variable oder Objektvariable für den Ausdruck anzulegen.

Abbildung
Hier klicken, um das Bild zu Vergrößern


Galileo Computing

3.2.4 Zugriff auf Variablen und Methoden mit dem ».«downtop

Die in einer Klasse definierten Variablen werden »Objektvariablen« (auch »Exemplar-, Instanz- oder Ausprägungsvariablen«) genannt. Wird ein Objekt geschaffen, dann erhält es seinen eigenen Satz von Objektvariablen3. Sie bilden einen Zustand.

Ist das Objekt angelegt, wird auf die Methoden oder Variablen mit einem ».« zugegriffen. Der Punkt (auch »Selektor« genannt) ist ein Operator und steht zwischen einem Ausdruck, der eine Referenz zurückgibt4, und der Objekteigenschaft. (Der Punkt als Operator hat natürlich nichts mit der gleichnamigen Klasse zu tun.)


Beispiel Folgende Zeilen erzeugen ein Point-Objekt, speichern eine Referenz auf dieses Objekt in der Variablen p und weisen den Objektvariablen x und y Werte zu.
Point p = new Point();
p.x = 12;
p.y = 45;

Abbildung
Hier klicken, um das Bild zu Vergrößern

(Strg)+(_____) zeigt an, welche Eigenschaften eine Referenz definiert. Eine Auswahl mit Return wählt die Eigenschaft aus und setzt insbesondere bei Funktionen den Cursor zwischen das Klammerpaar.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Der Typ links vom Punkt muss immer eine Referenz sein. Im Prinzip funktioniert auch Folgendes:

new Point().x = 1;

Dies ist allerdings unsinnig, da zwar das Objekt erzeugt und ein Attribut gesetzt wird, anschließend aber der Garbage-Collector das Objekt wieder wegräumt. Für einen Methodenaufruf kann dies schon sinnvoller sein.

Ein Methodenaufruf gestaltet sich genau so einfach wie eine Objekterzeugung. Hinter dem Ausdruck mit der Referenz und dem Punkt folgt der Methodenname. Das nachfolgende Beispiel ist lauffähig und bindet zugleich noch die Point-Klasse aus dem Paket java.awt ein. Ein Paket ist eine Gruppe zusammengehöriger Klassen.

Listing 3.1 MyPoint.java

import java.awt.Point;
class MyPoint
{
  public static void main( String args[] )
  {
    Point p = new Point();
    p.x = p.y = 12;
    p.setLocation( -3, 2 );
    System.out.println( p.toString() );   // java.awt.point[x=-3,y=2]
//    alternativ
//    System.out.println( p );
  }
}

Die letzte Anweisung ist gültig, da println() bei einem Objekt automatisch die toString()-Methode aufruft.

Abbildung
Hier klicken, um das Bild zu Vergrößern

(Strg)+(_____) auf einem Eigenschaftennamen (oder bei einer Funktion im Klammerpaar) bringt die API-Dokumentation hervor. Dazu muss allerdings das Java SDK eingebunden sein - das JRE reicht nicht, da bei ihm keine Dokumentation zu finden ist.

Abbildung
Hier klicken, um das Bild zu Vergrößern


Galileo Computing

3.2.5 Konstruktorendowntop

Werden Objekte mit dem new-Operator angelegt, so wird ein Konstruktor aufgerufen, eine Art Methode mit besonderer Signatur5. Bei der Schaffung eines Objekts sollen in der Regel die Objektvariablen initialisiert werden. Diese Initialisierung wird dazu in den Konstruktor gesetzt, um sicherzustellen, dass das neue Objekt einen sinnvollen Anfangszustand aufweist.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Ein Konstruktor ohne Argumente ist der Standard-Konstruktor (auch »Default-Konstruktor«, selten »No-Arg-Konstruktor« genannt).


Beispiel Folgende Zeilen erzeugen schlussendlich zwei Point-Objekte mit denselben Koordinaten. Die Variablen p und q referenzieren jedoch zwei völlig getrennte Objekte; lediglich die Belegung der x- und y-Koordinaten ist bei den beiden Objekten »zufällig« gleich.
Point p = new Point();
p.setLocation( 10, 10 );
Point q = new Point( 10, 10 );

Der erste Konstruktor ist der Standard-Konstruktor, der Zweite ein parametrisierter Konstruktor.


Was bei new passiert

Ein Konstruktoraufruf wird bei der Erschaffung eines Objekts durch den new-Operator ausgelöst. So erzeugt

Point p = new Point();

ein Exemplar der Klasse Point. Die Laufzeitumgebung von Java reserviert so viel Speicher, dass ein Point-Objekt dort Platz hat. Anschließend ruft die Laufzeitumgebung den Konstruktor auf und gibt eine Referenz auf das Objekt zurück, die im obigen Beispiel der Variablen p zugewiesen wird. Kann das System nicht genügend Speicher bereitstellen, so wird der GC aufgerufen. Kann dieser keinen freien Platz finden, generiert die Laufzeitumgebung einen OutOfMemoryError.


Galileo Computing

3.2.6 Die null-Referenztoptop

In Java gibt es das spezielle Literal null, das anzeigt, dass eine Referenzvariable auf kein Objekt verweist. Der Wert ist nur für Referenzen vorgesehen und kann nicht in einen primitiven Typ wie die Ganzzahl 0 umgewandelt werden.6 Die null-Referenz ist typenlos, das heißt, sie kann jedem Objekt zugewiesen werden und jeder Funktion übergeben werden, die ein Objekt erwartet. Daher ist Folgendes gültig:

Point  p = null;
String s = null;
System.out.println( null );

Da es nur ein null gibt, ist zum Beispiel(Point) null == (String) null.

Mit null lässt sich eine ganze Menge machen. Der Haupteinsatz sieht vor, damit uninitialisierte Referenzvariablen zu kennzeichnen. In Listen oder Bäumen kennzeichnet null aber auch keinen gültigen Nachfolger; null ist dann ein gültiger Indikator und kein Fehlerfall.

Die NullPointerException

Da sich hinter null kein Objekt verbirgt, ist es auch nicht möglich, eine Methode aufzurufen. Der Compiler kennt zwar den Typ jedes Objekts, weiß aber erst zur Laufzeit, was referenziert wird. Wird versucht, über die null-Referenz auf eine Eigenschaft eines Objekts zuzugreifen, dann wird eine NullPointerException7 ausgelöst.


Beispiel Wir beobachten eine NullPointerException. Die Zeilen sind durch Kommentare deutlich gemacht.

Listing 3.2 NullPointer.java

/*  1 */import java.awt.Point;
/*  2 */
/*  3 */public class NullPointer
/*  4 */{
/*  5 */  public static void main( String args[] )
/*  6 */  {
/*  7 */    Point  p = null;
/*  8 */    String s = null;
/*  9 */
/* 10 */    p.setLocation(1, 2);
/* 11 */    s.length();
/* 12 */}

Das Programm bricht bei p.setLocation() mit folgender Ausgabe ab:

java.lang.NullPointerException
    at NullPointer.main(NullPointer.java:10)
 Exception in thread "main"

Die Laufzeitumgebung teilt uns in der Fehlermeldung mit, dass sich der Fehler, die NullPointerException, in Zeile 10 befindet.


null-Referenzen testen

Wir wollen an dieser Stelle noch einmal auf die logischen Kurzschlussoperatoren und normalen logischen Operatoren zu sprechen kommen. Letztere werten Operanden nur so lange von links nach rechts aus, bis der Wert der Operation feststeht. Es scheint auf den ersten Blick nicht viel auszumachen, ob alle Teilausdrücke ausgewertet werden oder nicht. Es ist aber in einigen Ausdrücken wichtig, wie das folgende Beispiel zeigt:

if ( p != null && p.x >= 10 )

Die Bedingung testet, ob p überhaupt auf ein Objekt verweist, und ob die x-Koordinate mindestens 10 ist. Diese Schreibweise tritt häufig auf, und der Und-Operator zur Verknüpfung muss ein Kurzschlussoperator sein, denn in diesem Fall kommt es ausdrücklich darauf an, dass die Koordinate nur dann bestimmt wird, wenn die Variable p überhaupt auf ein Point-Objekt verweist. Andernfalls bekämen wir eine NullPointerException, wenn jeder Teilausdruck ausgewertet würde und p gleich null ist.






1 Ich vermeide das Wort »Instanz« und verwende dafür durchgängig im Tutorial das Wort »Exemplar«. An die Stelle von »instanziieren« tritt das einfache Wort »erzeugen«. Instanz ist eine irreführende Übersetzung des englischen Ausdrucks »instance«.

2 Den Begriff »Feld« benutze ich im Folgenden nicht. Er bleibt für Arrays reserviert.

3 Es gibt auch den Fall, dass sich mehrere Objekte eine Variable teilen, so genannte statische Variablen. Diesen werden wir später betrachten.

4 Sprachlich wird diese Formulierung gerne abgekürzt zu »Rechts steht eine Referenz«.

5 Ein Konstruktor hat keinen Rückgabetyp und heißt auch immer so wie die Klasse.

6 Hier unterscheiden sich C(++) und Java.

7 Der Name zeigt das Überbleibsel von Zeigern. Zwar haben wir es in Java nicht mir Zeigern zu tun, sondern mit Referenzen, doch heißt es NullPointerException und nicht NullReferenceException. Das erinnert daran, dass eine Referenz ein Objekt identifiziert und eine Referenz auf ein Objekt ein Pointer ist.





Copyright (c) Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de