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


Java ist auch eine Insel (2. Aufl.) von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Java ist auch eine Insel (2. Auflage)
gp Kapitel 6 Eigene Klassen schreiben
  gp 6.1 Eigene Klassen definieren
  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.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 Stringobjekte
    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.4 Statische Methoden und Variablen  downtop

Exemplarvariablen sind eng mit ihrem Objekt verbunden. Wird ein Objekt geschaffen, erhält es einen eigenen Satz von Exemplarvariablen, die zusammen den Zustand des Objekts repräsentieren. Ändert eine Objektmethode den Wert einer Exemplarvariablen in einem Objekt, so hat dies keine Auswirkungen auf die Daten der anderen Objekte; jedes Objekt speichert eine individuelle Belegung. Es gibt jedoch auch Situationen, in denen Eigenschaften oder Methoden nicht direkt einem individuellen Objekt zugeordnet werden. Dazu gehören zum Beispiel die Methoden:

gp  sin(), etwa in Math.sin(Math.PI/2.0)
gp  max(), etwa in Math.max(1, -2)
gp  Color.HSBtoRGB(), zum Konvertieren von Farben in Farbräumen
gp  Integer.parseInt(), Umwandeln von einem String in den Integer oder Variablen, die sogar konstant sein können
gp  MAX_INTEGER, die größte darstellbare Zahl
gp  PI aus Math bestimmt die Zahl 3,1415 ...

Diese genannten Eigenschaften sind weniger einem konkreten Objekt mit seinem ureigenen Objektzustand zuzuordnen, sondern vielmehr der Klasse. Diese Art von Zugehörigkeit wird in Java durch statische Eigenschaften unterstützt. Da sie nicht zu einem Objekt gehören wie Objekteigenschaften, nennen wir sie auch Klasseneigenschaften. Die Sinus-Funktion ist ein Beispiel für eine statische Methode der Mathe-Klasse und MAX_INTEGER ein statisches Attribut der Klasse Integer.


Galileo Computing

6.4.1 Warum statische Eigenschaften sinnvoll sind  downtop

Statische Eigenschaften haben gegenüber Objekteigenschaften den Vorteil, dass sie im Programm ausdrücken, keinen Zustand vom Objekt zu nutzen. Betrachten wir noch einmal Funktionen aus der Mathe-Klasse. Wenn sie Objektmethoden wären, so würden sie in der Regel mit einem Objektzustand arbeiten. Die Funktionen nähmen dann keinen Parameter, sondern arbeiteten mit dem internen Zustand des aufrufenden Objekts. Das macht aber keine Mathe-Funktion. Um den Sinus eines Winkels zu berechnen, benötigen wir kein spezifisches Mathe-Objekt. Statische Funktionen sind aus diesem Grunde häufiger als statische Variablen, da sie ihre Arbeitswerte ausschließlich aus den Parametern ziehen. Statische Variablen werden in erster Linie als Konstanten verwendet.


Galileo Computing

6.4.2 Statische Eigenschaften mit static  downtop

Um statische Eigenschaften in Java umzusetzen, fügen wir vor der Definition einer Variablen oder einer Methode das Schlüsselwort static hinzu. Für den Zugriff verwenden wir einfach den Klassennamen, den wir wie eine Referenz verwenden. In der UML können statische Eigenschaften durch Unterstreichen markiert werden.

Abbildung

Beispiel   Eine statische Funktion und eine statische Variable

Listing 6.9   LittleHelpers.java

class LittleHelpers
{
  static double PI2 = Math.PI*Math.PI;

  static double half( double x, double y )
  {
    return ( x + y ) / 2.0;
  }
}

class StaticUser
{
  public static void main( String args[] )
  {
     System.out.println(
         LittleHelpers.half( LittleHelpers.PI2, Math.E )
     );
  }
}

Wir haben in der main()-Funktion das Attribut und die Eigenschaft mit dem Namen LittleHelpers angesprochen. Auch bei Math.PI sehen wir, dass PI ein statisches Attribut der Mathe-Klasse ist.



Galileo Computing

6.4.3 Statische Eigenschaften als Objekteigenschaften nutzen  downtop

Besitzt eine Klasse eine Klasseneigenschaft, so kann es auch wie ein Objektattribut über die Referenz angesprochen werden. Das bedeutet, dass es zwei Möglichkeiten gibt, wenn ein Objektexemplar existiert und die Klasse ein statisches Attribut hat. Bleiben wir bei unserem obigen Beispiel mit der Klasse LittleHelpers. Jetzt können wir für den Zugriff auf PI2 schreiben:

LittleHelpers l = new LittleHelpers();
System.out.println( l.PI2 );
System.out.println( LittleHelpers.PI2 );

Die unteren beiden Anweisungen sind identisch. Betrachten wir alleine dieses Codesegment, so ist für uns nicht sichtbar, dass PI2 eine statische Eigenschaft ist. Aus diesem Grund sei der Tipp gegeben, statische Eigenschaften über ihren Klassennamen anzusprechen.


Galileo Computing

6.4.4 Statische Eigenschaften und Objekteigenschaften  downtop

Wie wir oben gesehen haben, können wir über eine Objektreferenz auch statische Eigenschaften nutzen. Wir wollen uns aber noch einmal vergewissern, wie Objekteigenschaften und statische Eigenschaften gemischt werden können. Erinnern wir uns daran, dass unsere ersten Programme aus der main()-Methode bestanden, aber unsere anderen Methoden auch static sein mussten. Dies ist sinnvoll, denn eine statische Methode kann – ohne explizite Angabe eines aufrufenden Objekts – nur andere statische Methoden aufrufen. Wie sollte auch eine statische Methode eine Objektmethode aufrufen können, wenn es kein dazugehöriges Objekt gibt? Statische Methoden gibt es immer, wenn es die Klasse gibt. Andersherum kann aber jede Objektmethode eine beliebige statische Methode direkt aufrufen. Genauso verhält es sich mit Attributen. Eine statische Methode kann keine Objektattribute nutzen, da es kein implizites Objekt gibt, auf dessen Eigenschaften zugegriffen werden könnte.

this-Referenzen und statische Eigenschaften

Auch der Einsatz der this-Referenz ist bei statischen Eigenschaften nicht möglich. Das trifft in erster Linie statische Methoden, die eine this-Referenz verwenden wollen.

Hinweis   In statischen Methoden gibt es kein this.

class InStaticNoThis
{
  int a;

  static void martina()
  {
    this.a = 1;           // Compilerfehler genauso wie a = 1?
  }
}

Galileo Computing

6.4.5 Statische Variablen zum Datenaustausch  downtop

Der Wert einer statischen Variablen wird bei dem Klassenobjekt gespeichert und nicht bei einem Exemplar der Klasse. Wie wir aber gesehen haben, kann jedes Exemplar einer Klasse auch auf die statischen Variablen der Klasse zugreifen. Da eine statische Variable aber nur einmal pro Klasse vorliegt, führt dies dazu, dass mehrere Objekte sich eine Variable teilen. Somit wird ein Austausch von Informationen über die Objektgrenze hinaus erlaubt. Doch kein Vorteil ohne Nachteil. Es kann bei nebenläufigen Zugriffen zu Problemen kommen. Deshalb müssen wir spezielle Synchronisationsmechanismen nutzen.

Abbildung

Beispiel   Objekte tauschen Daten über eine gemeinsame statische Variable.

Listing 6.10   ShareData.java

class ShareData
{
  private static int share;

  public void memorize( int i )
  {
    share = i;
  }

  public int retrieve ()
  {
    return share;
  }
  public static void main( String args[] )
  {
    ShareData s1 = new ShareData();
    ShareData s2 = new ShareData();

    s1.memorize( 2 );
    System.out.println( s2.retrieve() );    // ist 2
  }
}


Galileo Computing

6.4.6 Warum die Groß- und Kleinschreibung wichtig ist  downtop

Die Vorgabe der Namenskonvention sagt, Klassennamen sind mit Großbuchstaben zu vergeben und Variablennamen mit Kleinbuchstaben. Treffen wir auf eine Anweisung wie Math.max(a, b), so wissen wir sofort, dass max() eine statische Methode sein muss, weil davor ein Bezeichner steht, der großgeschrieben ist. Dieser kennzeichnet also keine Referenz, sondern einen Klassenamen. Daher sollten wir in unseren Programmen großgeschriebene Objektnamen meiden.

Beispiel   Warum Referenzen mit Kleinbuchstaben beginnen sollten.
String StringModifier = "What is the Matrix?";
String t = StringModifier.trim();

Die trim()-Methode ist nicht statisch, wie die Anweisung durch die Großschreibung suggeriert.

Beispiel   Das gleiche Problem haben wir, wenn wir Klassen mit Kleinbuchstaben benennen. Auch das kann irritieren.
class modifier
{
  static void move() { ... }
}

Jetzt könnte jemand modifier.move() schreiben, und der Leser würde annehmen, dass modifier eine Referenz ist, da sie kleingeschrieben ist und move() eine Objektmethode. Wir sehen an diesem Beispiel, dass es wichtig ist, die Namensgebung zu verfolgen.


Galileo Computing

6.4.7 Konstanten mit dem Schlüsselwort final bei Variablen  downtop

Statische Variablen werden auch verwendet, um Konstanten zu definieren. Dazu dient zusätzlich das Schlüsselwort final. Damit wird dem Compiler mitgeteilt, dass dieser Variablen nur einmal ein Wert zugewiesen werden darf. Für Variablen bedeutet dies: Es sind Konstanten, jeder spätere Schreibzugriff wäre ein Fehler.

class Sockentyp
{
  static final int PUNKTIERT = 1,
                   GEFLECKT  = 2,
                   GESTREIFT = 3;
}

In der Klasse Sockentyp werden drei Konstanten definiert. Ein Aufzählungstyp mit enum wie in C++ gibt es in Java nicht. Für Konstanten ist es bedenkenswert, die Konstanten relativ zum Vorgänger zu wählen, um das Einfügen zu vereinfachen. Dann wäre etwa GEFLECKT=PUNKTIERT+1.

Tipp   Es ist eine gute Idee, die Namen von Konstanten vollständig großzuschreiben, um deren Bedeutung hervorzuheben.


Galileo Computing

6.4.8 Typsicherere Konstanten  downtop

Konstanten sind eine wertvolle Möglichkeit, den Quellcode aussagekräftiger zu machen. Der herkömmliche Weg geht über Ganzzahl-Konstanten:

public final int  KONSTANTE1 = 0;
public final int  KONSTANTE2 = 1;
public final int  KONSTANTE3 = 2;

Dieser Weg bringt den Nachteil mit sich, dass die Konstanten nicht unbedingt von jedem angewendet werden müssen und ein Programmierer eventuell direkt die Zahlen oder Zeichenketten einsetzt. Dieses Problem kommt zum Beispiel auf, wenn ein Font-Objekt für die grafische Oberfläche angelegt werden soll, aber unser Gedächtnis versagt, in welcher Reihenfolge die Parameter einzugeben sind. Ein Fallbeispiel:

Font f = new Font( "Dialog",  12, Font.BOLD ):

Leider ist dieses falsch, denn die Parameter für die Zeichensatzgröße und den Schriftstil sind vertauscht. Das Problem ist, dass die Konstanten nur Namen für Werte eines frei zugänglichen Grundtyps sind, und nur der Wert an die Funktion übergeben wird. Niemand kann verbieten, dass direkt die Werte eingetragen werden. Das führt dann zu Fehlern, wie im oberen Fall. In diesem ist 12 die Ganzzahl für den Schriftstil, obwohl es dafür nur die Werte 0, 1, 2 geben sollte. Mit Zeichenketten als Wert der Konstanten kommen wir der Lösung auch nicht näher. Eine gute Möglichkeit von Ganzzahlen wegzukommen ist, Objekte einer Klasse als Konstanten einzusetzen. Folgendes bietet sich an:

Listing 6.11   TypsichereKonstanten.java

public class TypsichereKonstanten
{
  static void func( Muster k )
  {
    if ( k == Muster.GEKRINGELT )
      System.out.println( "GEKRINGELT" );
    if ( k == Muster.GESTRICHELT )
      System.out.println( "GESTRICHELT" );
  }

  public static void main( String args[] )
  {
    func( Muster.GESTRICHELT );
  }
}

final class Muster
{
  static final Muster GEKRINGELT = new Muster();

  static final Muster GESTRICHELT = new Muster();

  private Muster()    // von außen lassen sich keine weiteren
  {                   // Objekte erzeugen
    id = nextId++;
  }

  private int id;

  private static int nextId = 0;  // der Deutlichkeit halber
}

Die Klasse Muster definiert die Konstanten als statische Attribute vom Typ Muster. Da die Objekte für die Konstanten aber nur einmal vorliegen, lassen sie sich einfach mit ==, wie in func() gezeigt, vergleichen.


Galileo Computing

6.4.9 Statische Blöcke  toptop

Eine Art Konstruktor für das Klassenobjekt selbst (nicht die Exemplare der Klasse) ist ein static-Block, der in jede Klasse gesetzt werden kann. Der Block wird genau dann ausgeführt, wenn die Klasse vom Klassenlader in die virtuelle Maschine geladen wird. In der Regel geschieht das nur einmal während eines Programmlaufs. Unter gewissen Umständen kann jedoch eine Klasse auch zwischenzeitlich aus dem Speicher entfernt werden.

Beispiel   Zwei statische Blöcke mit einer Hauptfunktion.

Listing 6.12   StaticBlock.java

class StaticBlock
{
  static
{
System.out.println( "Eins" ); }
static
{
System.out.println( "Zwei" ); }

public static void main( String args[] ) { System.out.println( "Jetzt geht's los." ); } }

Lädt der Klassenlader die Klasse StaticBlock, so führt er zuerst den ersten Block mit der Ausgabe »Eins« aus und dann den Block mit der Ausgabe »Zwei«. Da die Klasse StaticBlock auch das main() besitzt, führt die virtuelle Maschine anschließend die Start-Funktion aus.

Beispiel   Mit diesem Trick lassen sich auch Programme ohne main()-Funktion schreiben. In den statischen Block wird einfach das Hauptprogramm geschrieben. Da jedoch die virtuelle Maschine immer noch nach dem main() sucht, müssen wir die Laufzeitumgebung schon vorher beenden. Dies geschieht dadurch, dass mit System.exit() die Bearbeitung abgebrochen wird:

Listing 6.13   StaticNowMain.java

class StaticNowMain
{
  static
  {
    System.out.println( "Jetzt bin ich das Hauptprogramm" );
    System.exit( 0 );
  }
}

Mit diesem Vorgehen ist jedoch der Nachteil verbunden, dass bei Ausnahmen im versteckten Hauptprogramm die JVM unsinnige Fehler meldet. Etwa dass die Klasse StaticNowMain nicht gefunden wurde oder auch eine ExceptionInInitializerError, die statt einer vernünftigen Exception kommt.






1   Leider konnte eine frühe Version des freien IBM-Compiler Jikes nicht erkennen, dass PUNKTIERT+1 zur Übersetzungszeit eine Konstante 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]

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