Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5 (Tiger-Release)
Buch: Java ist auch eine Insel
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 mit new
    gp 3.2.4 Zugriff auf Variablen und Methoden mit dem ».«
    gp 3.2.5 Konstruktoren
  gp 3.3 Import und Pakete
  gp 3.4 Die API-Dokumentation
  gp 3.5 Mit Referenzen arbeiten
    gp 3.5.1 Die null-Referenz
    gp 3.5.2 Zuweisungen bei Referenzen
    gp 3.5.3 Funktionen mit nichtprimitiven Parametern
    gp 3.5.4 Gleichheit von Objekten und die Methode equals()
  gp 3.6 Arrays
    gp 3.6.1 Deklaration von Arrays
    gp 3.6.2 Arrays mit Inhalt
    gp 3.6.3 Die Länge eines Arrays über das Attribut length
    gp 3.6.4 Zugriff auf die Elemente
    gp 3.6.5 Array-Objekte erzeugen
    gp 3.6.6 Fehler bei Arrays
    gp 3.6.7 Arrays mit nicht-primitiven Elementen
    gp 3.6.8 Initialisierte Array-Objekte
    gp 3.6.9 Die erweiterte for-Schleife
    gp 3.6.10 Mehrdimensionale Arrays
    gp 3.6.11 Die Wahrheit über die Array-Initialisierung
    gp 3.6.12 Mehrere Rückgabewerte
    gp 3.6.13 Argument per Referenz übergeben
    gp 3.6.14 Arrays klonen
    gp 3.6.15 Feldinhalte kopieren
    gp 3.6.16 Die Klasse Arrays zum Vergleichen, Füllen, Suchen
    gp 3.6.17 Methode mit variabler Argumentanzahl (vararg)
  gp 3.7 Der Einstiegspunkt für das Laufzeitsystem main()
    gp 3.7.1 Kommandozeilen-Parameter ausgeben
    gp 3.7.2 Der Rückgabewert von main() und System.exit()
    gp 3.7.3 Parser der Kommandozeilenargumente Apache CLI
  gp 3.8 Eigene Pakete schnüren
    gp 3.8.1 Die package-Anweisung
    gp 3.8.2 Importieren von Klassen mit import
    gp 3.8.3 Paketnamen
    gp 3.8.4 Hierarchische Strukturen und das Default-Package
    gp 3.8.5 Klassen mit gleiche Namen in unterschiedlichen Paketen
    gp 3.8.6 Statische Imports
    gp 3.8.7 Eine Verzeichnisstruktur für eigene Projekte


Galileo Computing

3.2 Klassen benutzedowntop

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

Eine Klasse definiert

gp  Attribute
gp  Operationen
gp  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« 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 Point  downtop

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

Abbildung 3.1   Die Klasse java.awt.Point in der üblichen UML-Darstellung und in der ‚Eclipse-Darstellung’


Galileo Computing

3.2.2 Etwas über die UML  downtop

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
2004 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 Klasse mit new  downtop

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 Objektvariablen. 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ückgibt, 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.

MyPointUsesPoint

Abbildung 3.2   Die eigene Klasse MyPoint nutzt java.a.wt.Point, was als Abhängigkeit im UML-Diagramm angezeigt werden kann. Die Parameter und Rückgabetypen sind in UML optional und hier nicht dargestellt.

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

Nach dem Punkt geht’s weiter

Die Funktion toString() liefert als Ergebnis ein String-Objekt, das den Zustand des Punkts preisgibt.


Point p = new Point();
String s = p.toString();
System.out.println( s );            // java.awt.Point[x=0,y=0]
System.out.println( s.length() );   // 23

Das String-Objekt besitzt selbst wieder Methoden, eine davon ist length(), die die Länge der Zeichenkette liefert. Das Erfragen des String-Objekts und dessen Länge können wir verbinden zu einer Anweisung – p sei wieder unser Point-Objekt.


System.out.println( p.toString().length() );

Der Punkt-Operator für den Zugriff auf Objekt- oder Klassenvariablen beziehungsweise -methoden sollte nicht durch Freiraum von dem Klassennamen oder dem Ausdruck für die Objektreferenz oder dem Methoden- beziehungsweise Variablennamen abgetrennt werden. So ist es vernünftiger, anstatt


p.                toString()      .                 length()

die Anweisung mit p.toString().length() zu programmieren. Obwohl der Java-Compiler auch Programmcode mit eingebetteten Kommentaren und Zeilenvorschüben akzeptiert, ist von Konstruktionen wie


p./* um wen geht es */toString()/* ich bin’s */.
/*meine Länge*/length()

dringend abzuraten.


Galileo Computing

3.2.5 Konstruktoren  toptop

Werden Objekte mit dem new-Operator angelegt, so wird ein Konstruktor aufgerufen, eine Art Methode mit besonderer Signatur. 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.

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.






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.





Copyright © 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