Galileo Computing < openbook >
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel (3. Aufl.) von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Buch: Java ist auch eine Insel
gp Kapitel 6 Eigene Klassen schreiben
  gp 6.1 Eigene Klassen definieren
    gp 6.1.1 Methodenaufrufe und Nebeneffekte
    gp 6.1.2 Argumentübergabe mit Referenzen
    gp 6.1.3 Die this-Referenz
    gp 6.1.4 Überdeckte Objektvariablen nutzen
  gp 6.2 Assoziationen zwischen Objekten
  gp 6.3 Privatsphäre und Sichtbarkeit
    gp 6.3.1 Wieso nicht freie Methoden und Variablen für alle?
    gp 6.3.2 Privat ist nicht ganz privat. Es kommt darauf an, wer's sieht
    gp 6.3.3 Zugriffsmethoden für Attribute definieren
    gp 6.3.4 Zusammenfassung zur Sichtbarkeit
    gp 6.3.5 Sichtbarkeit in der UML
  gp 6.4 Statische Methoden und Variablen
    gp 6.4.1 Warum statische Eigenschaften sinnvoll sind
    gp 6.4.2 Statische Eigenschaften mit static
    gp 6.4.3 Statische Eigenschaften als Objekteigenschaften nutzen
    gp 6.4.4 Statische Eigenschaften und Objekteigenschaften
    gp 6.4.5 Statische Variablen zum Datenaustausch
    gp 6.4.6 Warum die Groß- und Kleinschreibung wichtig ist
    gp 6.4.7 Konstanten mit dem Schlüsselwort final bei Variablen
    gp 6.4.8 Typsicherere Konstanten
    gp 6.4.9 Statische Blöcke
  gp 6.5 Objekte anlegen und zerstören
    gp 6.5.1 Konstruktoren schreiben
    gp 6.5.2 Einen anderen Konstruktor der gleichen Klasse aufrufen
    gp 6.5.3 Initialisierung der Objekt- und Klassenvariablen
    gp 6.5.4 Finale Werte im Konstruktor setzen
    gp 6.5.5 Exemplarinitialisierer (Instanzinitialisierer)
    gp 6.5.6 Zerstörung eines Objekts durch den Müllaufsammler
    gp 6.5.7 Implizit erzeugte String-Objekte
    gp 6.5.8 Zusammenfassung: Konstruktoren und Methoden
  gp 6.6 Veraltete (deprecated) Methoden/Konstruktoren
  gp 6.7 Vererbung
    gp 6.7.1 Vererbung in Java
    gp 6.7.2 Einfach- und Mehrfachvererbung
    gp 6.7.3 Kleidungsstücke modelliert
    gp 6.7.4 Sichtbarkeit
    gp 6.7.5 Das Substitutionsprinzip
    gp 6.7.6 Automatische und explizite Typanpassung
    gp 6.7.7 Finale Klassen
    gp 6.7.8 Unterklassen prüfen mit dem Operator instanceof
  gp 6.8 Methoden überschreiben
    gp 6.8.1 super: Aufrufen einer Methode aus der Oberklasse
    gp 6.8.2 Nicht überschreibbare Funktionen
    gp 6.8.3 Fehlende kovariante Rückgabewerte
  gp 6.9 Die oberste aller Klassen: Object
    gp 6.9.1 Klassenobjekte
    gp 6.9.2 Hashcodes
    gp 6.9.3 Objektidentifikation mit toString()
    gp 6.9.4 Objektgleichheit mit equals() und Identität
    gp 6.9.5 Klonen eines Objekts mit clone()
    gp 6.9.6 Aufräumen mit finalize()
    gp 6.9.7 Synchronisation
  gp 6.10 Die Oberklasse gibt Funktionalität vor
    gp 6.10.1 Dynamisches Binden als Beispiel für Polymorphie
    gp 6.10.2 Keine Polymorphie bei privaten, statischen und finalen Methoden
    gp 6.10.3 Konstruktoren in der Vererbung
  gp 6.11 Abstrakte Klassen
    gp 6.11.1 Abstrakte Klassen
    gp 6.11.2 Abstrakte Methoden
    gp 6.11.3 Über abstract final
  gp 6.12 Schnittstellen
    gp 6.12.1 Die Mehrfachvererbung bei Schnittstellen
    gp 6.12.2 Erweitern von Interfaces - Subinterfaces
    gp 6.12.3 Vererbte Konstanten bei Schnittstellen
    gp 6.12.4 Vordefinierte Methoden einer Schnittstelle
    gp 6.12.5 CharSequence als Beispiel einer Schnittstelle
  gp 6.13 Innere Klassen
    gp 6.13.1 Geschachtelte Top-Level-Klassen und Schnittstellen
    gp 6.13.2 Mitglieds- oder Elementklassen
    gp 6.13.3 Lokale Klassen
    gp 6.13.4 Anonyme innere Klassen
    gp 6.13.5 Eine Sich-Selbst-Implementierung
    gp 6.13.6 this und Vererbung
    gp 6.13.7 Implementierung einer verketteten Liste
    gp 6.13.8 Funktionszeiger
  gp 6.14 Gegenseitige Abhängigkeiten von Klassen
  gp 6.15 Pakete


Galileo Computing

6.3 Privatsphäre und Sichtbarkeit downtop

Innerhalb einer Klasse sind alle Funktionen und Attribute für die Methoden sichtbar. Damit die Daten einer Klasse vor externem Zugriff geschützt sind und Methoden nicht von außen aufgerufen werden können, verbietet das Schlüsselwort private allen von außen zugreifenden Klassen den Zugriff.

In der UML werden private Eigenschaften mit einem führenden Minus gekennzeichnet.

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

Abbildung 6.3 Ein privates Attribut pass
Beispiel   Eine Klasse Password mit dem privaten Attribut pass

Listing 6.5 PassDemo.java, Teil 1

class Password
{
  private String pass = "";
 
  void setPassword( String oldpass, String newpass )
  {
    if ( oldpass != null && oldpass.equals(pass) )
    {
      pass = newpass;
      System.out.println( "Passwort gesetzt." );
    }
    else
      System.out.println( "Passwort konnte nicht gesetzt werden." );
  }
}

Wir sehen, dass öffentliche Objektmethoden ganz selbstverständlich auf das private-Element zugreifen können.

Eine Klasse PassDemo will nun auf das Passwort von außen zugreifen.

Listing 6.6 PassDemo.java, Teil 2

public class PassDemo
{
  public static void main( String args[] )
  {
    Password pwd = new Password();
    pwd.setPassword( "", "TeutoburgerWald" );
    pwd.setPassword( "TeutoburgerWald", "Doppelkeks" );
    pwd.setPassword( "Dopplerkeks", "panic" );
    //    System.out.println( pwd.pass );   // Compilerfehler
  }
}

Die Klasse Password enthält den privaten String pass, und dieser kann nicht referenziert werden. Der Compiler erkennt zur Übersetzungs- beziehungsweise Laufzeit Verstöße und meldet diese. So schreibt zum Beispiel der Compiler Jikes von IBM zur Übersetzungszeit:

19.     System.out.println( pwd.pass );   // Compilerfehler
                                <
*** Error: The field "pass" in type "Password" is private\ and not accessible here.-->

Allerdings wäre es manchmal besser, wenn der Compiler uns nicht verraten würde, dass das Element privat ist, sondern einfach nur melden würde, dass es dieses Element nicht gibt.

elipse

Bei einem durch die falsche Sichtbarkeit verursachten Fehler bietet Eclipse mit (Strg)+(1) eine Änderung der Sichtbarkeit an.

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


Galileo Computing

6.3.1 Wieso nicht freie Methoden und Variablen für alle? downtop

Private Funktionen und Variablen dienen in erster Linie dazu, den Klassen Modularisierungsmöglichkeiten zu geben, die von außen nicht sichtbar sein müssen. Zwecks Strukturierung werden Teilaufgaben in Funktionen gegliedert, die aber von außen nie alleine aufgerufen werden dürfen. Da die Implementierung versteckt wird und der Programmierer vielleicht nur eine Zugriffsfunktion sieht, wird auch der Terminus »Data Hiding« verwendet. Nehmen wir zum Beispiel ein Radio. Von außen bietet es die Funktionen an(), aus(), lauter() und leiser() an, aber wie es ein Radio zum Musikspielen bringt, ist eine ganz andere Frage, die wir als gewöhnliche Benutzer eines Radios lieber nicht beantwortet wissen wollen.

Dem unerlaubten Zugriff steht der freie Zugriff auf Funktionen und Variablen entgegen. Eingeleitet wird dieser durch das Schlüsselwort public. Damit ist von außen jederzeit ein Zugriff möglich. Wird weder public, protected noch private verwendet, so ist ein Zugriff von außen nur innerhalb des Pakets möglich. Damit können Gruppen von Klassen gebildet werden, die gegenseitig Teile ihres Innenlebens kennen. Von außerhalb des Pakets ist der Zugriff auf diese Teile dann untersagt, analog zu private.

Der Einsatz von private zeigt sich besonders in Unterklassen, denn werden in einer Oberklasse Eigenschaften mit private gekennzeichnet, dann ist der Zugriff auf diese Funktionen in der Unterklasse nicht erlaubt. Interessieren diese Informationen auch manche Unterklassen, lassen sich die Methoden und Attribute protected deklarieren. Damit räumt eine Oberklasse den Unterklassen spezielle Privilegien ein. Mit protected sind die Mitglieder einer Klasse für die Unterklassen sichtbar und ebenso im gesamten Paket.


Galileo Computing

6.3.2 Privat ist nicht ganz privat. Es kommt darauf an, wer's sieht downtop

Wir wollen sehen, dass es in einem Spezialfall für eine Referenz ref doch möglich ist, auf ein privates Attribut oder eine private Methode des referenzierten Objekts zuzugreifen. Dazu muss dieser Zugriff in der Methode einer Klasse stattfinden, in der die private-Eigenschaft oder -Methode selbst definiert ist. Das sehen wir am besten an einem Beispiel.

Listing 6.7 WhoMayUsePrivate.java

public class WhoMayUsePrivate
{
  public static void main( String args[] )
  {
     Compare m = new Compare();
     boolean b = m.compare( new Compare() );
     System.out.println( b );
  }
}
class Compare
{
  private int priv = (int)Math.random();
  public boolean compare( Compare comp )
  {
    // Zugriff auf ein privates Element über comp.priv
    return priv == comp.priv;
  }
}

Interessant ist die Zeile unter dem Kommentar. Die Methode compare() der Klasse Compare vergleicht das eigene Attribut priv des aufrufenden Objekts mit dem Attribut des als Parameter übergebenen Objekts comp. An dieser Stelle sehen wir, dass der Zugriff auf comp.priv zulässig ist, obwohl priv privat ist. Dieser Zugriff ist aber erlaubt, da die compare()-Methode in der Compare-Klasse definiert ist und der Parameter ebenfalls vom Typ Compare ist. Mit Unterklassen funktioniert das schon nicht mehr. Private Attribute und Methoden sind also gegen Angriffe von außerhalb der definierenden Klasse geschützt.

Beispiele aus String und Integer

Auch in den Klassen der Standardbibliothek erkennen wir Beispiele dieser Sichtbarkeitsregel, häufig an der Methode equals(). Oft kapselt eine Klasse ein Attribut privat und bietet dann Zugriffsmethoden an. Wir wollen uns die equals()-Methode von String und die compareTo()-Methode von Integer daher etwas genauer in der Sun-Implementierung anschauen.

class String
{
  private char value[];
  private int count;
  private int offset;
  public boolean equals(Object anObject) {
    if (this == anObject)
      return true;
    if (anObject instanceof String) {
      String anotherString = (String)anObject;
      int n = count;
      if (n == anotherString.count) {
        // Hier den zeichenweisen Vergleich
      }
    }
    return false;
  }
}

Die String-Klasse speichert eine Referenz auf ein char-Feld, ebenso die Länge und eine Verschiebung (Offset) in privaten Variablen. Ein String-Objekt kann seinen Inhalt mit dem eines anderen Strings vergleichen. Dazu verwenden wir die überschriebene Methode equals(), die mit anObject als Parameter einen String annimmt, mit dem der aktuelle String seinen Vergleich durchführt. Der Vergleich von Zeichenketten ist dabei denkbar einfach: Falls das vergleichende Objekt mit dem zu vergleichenden identisch ist, müssen sie auch inhaltlich gleich sein und der Rückgabewert ist true. Er kann auf keinen Fall true sein, wenn der Typ von anObject nicht auch String ist. Wenn die Typen passen, dann wird Zeichen für Zeichen des Zeichenfelds verglichen. Bei anotherString.count, der typkonformen Variante von anObject, sehen wir, dass zuerst die Längen verglichen werden. Wenn diese schon nicht gleich sind, können auch die Zeichenketten nicht gleich sein. In anotherString.count steckt schon der Zugriff auf das private Element des anderen Strings.

Das zweite Beispiel bezieht sich auf die Klasse Integer mit dem internen Attribut value und der Zugriffsmethode intValue(), die den Wert direkt durchleitet, sowie auf die Vergleichsmethoden compareTo() und equals(). Der Unterschied zwischen den beiden Vergleichsmethoden ist, dass equals() per Definition aus Object die Rückgabewerte true oder false geben muss, während compareTo() entweder -1, 0 oder 1 liefert.

class Integer
{
  private int value;
  public int intValue() {
    return value;
  }
  public int compareTo(Integer anotherInteger) {
    int thisVal = this.value;
    int anotherVal = anotherInteger.value;
    return (thisVal<anotherVal ? -1 :
             (thisVal==anotherVal ? 0 : 1) );
  }
  public boolean equals(Object obj) {
    if (obj instanceof Integer)
      return value == ((Integer)obj).intValue();
    return false;
  }
}

Die Umsetzungen sind jetzt nicht besonders spannend. Was allerdings erstaunt, ist, dass bei compareTo() die Variable anotherInteger.value den privaten Inhalt preisgibt, die Programmierer allerdings bei equals() wundersam intValue() verwendeten. Da sind die Programmierer von Kaffe, Transvirtual Technologies, schon konsequenter. Sie schreiben einfach:

public boolean equals( Object obj )
{
  return ( obj instanceof Integer) &&
           (((Integer)obj).value == this.value );
}

Galileo Computing

6.3.3 Zugriffsmethoden für Attribute definieren downtop

Bisher sind wir davon ausgegangen, dass Attribute eine tolle Sache sind und dass es für den Nutzer eines Objekts nur Vorteile hat, wenn dieser über die Attribute auf den Zustand des Objekts zugreifen kann. Leider ist das nicht immer ohne Probleme möglich, wie die nachfolgenden Fälle zeigen:

1. Bei manchen Variablen gibt es Wertebereiche, die einzuhalten sind. Das Alter einer Person kann nicht kleiner Null sein, und Menschen, die älter als zweihundert Jahre sind, werden nur in der Bibel genannt. Wenn wir das Alter privat machen, kann eine Zugriffsfunktion wie setzeAlter(int) mit Hilfe einer Bereichsprüfung nur bestimmte Werte in die Variable eintragen und den Rest ablehnen. Die öffentliche Methode holeAlter() gibt dann Zugriff auf die Variable.
2. Mit einigen Variablen sind Abhängigkeiten verbunden. Wenn zum Beispiel ein Konto-Objekt einen Wert für den Kontostand speichert, kann das Konto gleichzeitig eine Wahrheitsvariable Soll und Haben deklarieren. Wenn der Kontostand negativ wird, soll die Wahrheitsvariable ebenfalls negiert werden. Diese Abhängigkeit lässt sich mit zwei öffentlichen Variablen nicht wirklich erzwingen. Eine Methode setzeKontostand(double) kann jedoch bei privaten Werten diese Konsistenz einhalten. 3. Wie das Beispiel mit dem Radio zeigt, gibt es bei Klassen ein Geheimnisprinzip. Obwohl es vorrangig für Methoden gilt, sollte es auch für Variablen gelten. Möchten Entwickler etwa ihr internes Attribut von int auf BigInteger ändern, damit sich mit dieser Klasse beliebig große Ganzzahlen darstellen lassen, hätten wir ein beträchtliches Problem, denn an jeder Stelle des Vorkommens müsste ein Objekt eingesetzt werden. Wollten wir zwei Variablen einführen, ein int, damit die alte, derzeit benutzte Software ohne Änderung auskommt, und ein neues BigInteger, dann hätten wir ein Konsistenzproblem.

Wir sehen an diesen Beispielen, dass es gute Gründe dafür gibt, Attribute zu privatisieren und öffentliche Methoden zum Lesen und Schreiben anzubieten. Da diese Methoden auf die Attribute zugreifen, nennen sie sich auch Zugriffsmethoden. Für jedes Attribut wird eine Schreib- und Lesemethode definiert, für die es auch ein Namensschema gibt. Lesemethoden beginnen mit get-, Schreibmethoden mit set-. Hinter die Vorsilbe wird der Name des Attributs gesetzt. Bei boolean-Attributen darf es statt getXXX() auch isXXX() heißen. Da die Programmentwicklung in der Regel mit englischen Bezeichnernamen erfolgt, kommt es nicht zu unschönen Bezeichnern wie getAlter().

Beispiel   Das bisher öffentliche Attribut age soll entfernt und durch Zugriffsmethoden ersetzt werden. Eine Konsistenzprüfung soll verhindern, dass es ein Alter kleiner Null gibt.

Listing 6.8 HeyAlter.java

class HeyAlter
{
  private int age;
 
  public int getAge() { return age; }
 
  public void setAge( int age )
 {
    if ( age >= 0 )
      this.age = age;
  }
}

An den Methoden wird eine weitere Konvention sichtbar. Die Hole-Methode getXXX() besitzt keinen Parameter, und der Typ vom Rückgabewert ist der gleiche wie der von der Variablen alter. Die set-Methode hat keinen Rückgabewert, aber genau einen Parameter vom Typ des Attributs. Letzteres gilt für die anfängliche Programmerstellung. Wird später bei der Weiterentwicklung des Programms eine Änderung nötig, beispielsweise muss das Alter ja nicht ganzzahlige Werte annehmen, kann der Typ der internen Variablen geändert werden und die Welt draußen bekommt davon nichts mit. Lediglich eine kleine Typanpassung muss in der Implementierung von setAge() und getAge() vorgenommen werden. Sicherlich ist es eine gute Idee, sich bei ungültigen Werten nicht taub zu stellen, sondern eine Fehlermeldung zu produzieren. Das kann etwa in Form einer Ausnahme geschehen.

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

elipse

Steht der Cursor auf einer Objekteigenschaft, so fördert Source, Generate Setter und Getter ein Dialogfenster vor, mit dem Eclipse automatisch die setXXX()- und getXXX()-Methoden einfügen kann.


Galileo Computing

6.3.4 Zusammenfassung zur Sichtbarkeit downtop

1. Die mit public deklarierten Methoden und Variablen sind überall dort sichtbar, wo auch die Klasse verfügbar ist. Natürlich kann auch eine erweiternde Klasse (Unterklasse) auf alle Elemente zugreifen.
2. Die mit private deklarierten Methoden und Variablen sind nur innerhalb der sie definierenden Klasse sichtbar. Auch wenn diese Klasse erweitert wird, sind die Elemente nicht sichtbar. 3. Wird eine Klasse erweitert, so sind die mit protected deklarierten Variablen und Methoden in der Unterklasse sichtbar, aber nicht außerhalb. Zudem gilt die Erweiterung, dass alle Klassen im gleichen Paket auch den Zugriff bekommen. Ein Paket besteht aus einer Gruppe von Klassen, die im Dateisystem normalerweise in demselben Verzeichnis liegen.

Der Einsatz der Sichtbarkeitsstufen über die Schlüsselworte public, private und protected und der Standard »paketsichtbar« ohne explizites Schlüsselwort sollte überlegt erfolgen. Objektorientierte Programmierung zeichnet sich durch durchdachten Einsatz von Klassen und deren Beziehungen aus. Am besten ist die einschränkendste Beschreibung, also nie mehr Öffentlichkeit als notwendig.


Galileo Computing

6.3.5 Sichtbarkeit in der UMLtoptop

Für die Sichtbarkeit von Attributen und Operationen sieht die UML unterschiedliche Symbole vor, die vor die jeweilige Eigenschaft gesetzt werden:

SymbolSichtbarkeit
+ Öffentlich
- Privat
# Geschützt (protected)
~ Paketsichtbar1

Hinweis    Wenn in der UML kein Sichtbarkeitsmodifizierer steht, so heißt das nicht, dass dies paketsichtbar bedeutet. Es heißt nur, dass dies noch nicht definiert ist!





Copyright © Galileo Press GmbH 2003
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] [Buchkatalog] [Neue Bücher] [Vorschau]

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