Verwendung von "static"

Wann soll/darf ich static verwenden? Wozu ist static gut?

Kurz gesagt: In einem Programm eines eher unerfahrenen Programmierers sollte am Besten nur eines static sein, und zwar die main-Methode. Ansonsten ist mit hoher Wahrscheinlichkeit davon auszugehen, dass weitgehend prozedural und nicht objektorientiert programmiert wurde. Man sollte sich klar machen, dass statische Elemente im Grunde den globalen Prozeduren und Variablen der prozeduralen Programmierung entsprechen, nur dass sie in Java Klassen zugeordnet sind, wobei diese Zuordnung in erster Linie dazu dient, den Namensraum „thematisch“ zu unterteilen. Prinzipiell ist eine statische Methode oder Variable von überall her zugreifbar, indem man ihrem Bezeichner den Klassennamen voranstellt (eventuelle Sichtbarkeitsmodifikatoren mal außen vor gelassen). Exemplare der betreffenden Klasse sind dazu nicht notwendig.

Leider erlaubt Java es, statische Elemente scheinbar über Exemplare der betreffenden Klasse anzusprechen, also z.B. statische Methoden scheinbar auf Exemplaren aufzurufen. In Wirklichkeit werden solche Aufrufe aber vom Compiler in Aufrufe über den Klassennamen umgewandelt. Aus einem

    Thread t = myThread.currentThread();

wird also im Bytecode das gleiche wie bei

    Thread t = Thread.currentThread();

denn die statische Methode currentThread() ist völlig unabhängig von der Existenz irgendeines konkreten Thread-Exemplars.

Man sollte sich unbedingt abgewöhnen, statische Zugriffe „über Exemplare“ durchzuführen, weil es vernebelt, was man da eigentlich tut und damit das Verständnis erschwert. Gute IDEs warnen einen in solchen Fällen mit „The static method currentThread() from the type Thread should be accessed in a static way“ oder ähnlichen Meldungen.

Eine Ursache für den Missbrauch von „static“ liegt wohl in Compilerfehlermeldungen à la „Cannot make a static reference to the non-static ...“. Nehmen wir folgenden Code:

public class Motorrad {

    private boolean motorLaeuft;

    public void start() {
        motorLaeuft = true;
    }

    public static void main(String[] args) {
        start();
    }
}

Der Compiler meldet: „Cannot make a static reference to the non-static method start() from the type Motorrad.“ Die Fehlermeldung legt die Vermutung nahe, dass die Lösung darin bestünde, die Methode start() static zu machen. Gesagt, getan. Nun meldet der Compiler einen anderen Fehler: „Cannot make a static reference to the non-static field motorLaeuft“. Und schon wird auch die Variable static gemacht. Und immer fröhlich so weiter. Das ist FALSCH.

Einer der Grundgedanken objektorientierter Programmierung ist es, Daten und die auf ihnen möglichen Operationen in einer Struktur zusammenzufassen, einem Objekt. Man kann dem Objekt Nachrichten senden (Methoden aufrufen) und dann tut es etwas, typischerweise verändert es seinen Zustand, welcher durch seine Attribute repräsentiert wird. Wenn ich auf den Starterknopf meines Motorrads drücke, dann rödelt der Anlasser und wenn alles gut geht, ändert sich der Zustand meines Motorrades signifikant. Ich sende also durch den Knopfdruck eine Nachricht an ein ganz bestimmtes Motorrad und dieses eine Motorrad reagiert darauf.

Dem entspricht in einer ganz primitiven Simulation der Aufruf einer Methode starte() auf einem Motorrad-Exemplar, welches daraufhin ein boolsches Attribut „motorLaeuft“ von false auf true setzt.

Das Attribut motorLaeuft darf natürlich nicht static sein, sonst wäre es ein gemeinsames Attribut aller Motorräder, d.h. die wären alle gleichzeitig entweder an oder aus. Das will man nicht. Und damit darf auch die Methode starte() nicht static sein, denn eine statische Methode wird nicht auf einem Exemplar aufgerufen (hat keinen impliziten Parameter), sie kennt überhaupt keine Exemplare.

Kurz gesagt: Wenn ich ein Motorrad starten will, brauche ich auch ein Motorrad, also ein Exemplar. Und damit sähe der Code fürs Motorrad sinnvollerweise etwa so aus:

public class Motorrad {

    private boolean motorLaeuft;

    public void start() {
        motorLaeuft = true;
    }

    public static void main(String[] args) {
        Motorrad m = new Motorrad();
        m.start();
    }
}

Eine weitere missbräuchliche Verwendung von „static“ besteht darin, dass irgendwelche Werte, die in einer anderen Klasse benötigt werden, als der, in der sie deklariert wurden, static gemacht werden, weil man sie dann von überallher mit vorangestelltem Klassennamen ansprechen kann. Das ist im Allgemeinen nicht sinnvoll. Wenn ein Objekt eine Information benötigt, dann sollte man ihm eine Referenz auf das Objekt verschaffen, welches die Information hat, indem man z.B. eine solche Referenz im Konstruktor übergibt. Sehr oft ist aber auch das Design als solches das Problem, und es wäre besser, den Job, zu dem die Information benötigt wird, eben dort erledigen zu lassen, wo diese vorhanden ist. Wenn man in seltenen Fällen wirklich einmal einen Weg braucht, variable Informationen programmweit verfügbar zu machen, dann kann ein sog. Singleton ein möglicher Weg sein, eine Klasse, von der es genau ein Exemplar gibt, welches man sich mit eine statischen Methode der Klasse besorgen kann (das kann eine sinnvolle Anwendung von „static“ sein).

Vielleicht sollte ich aber - ohne Anspruch auf Vollständigkeit – auch noch einige Beispiele für sinnvolle Verwendungen des Schlüsselworts „static“ nennen:

  1. Da wäre zum einen natürlich die main-Methode. Diese dient als Einstiegspunkt des Programms und ist static, denn sie ist nicht an Exemplare gebunden. Dass sie sich - wie auch in meinem Motorradbeispiel - oft in einer Klasse befindet, von der man auch Exemplare erzeugen will, ist im Grunde reiner Zufall. Man kann sie problemlos in eine andere Klasse auslagern und diese nur dazu nutzen, das Programm zu starten und vielleicht sollte man das auch prinzipiell tun, zumindest so lange, bis einem klar ist, dass es mehr oder weniger nur eine Frage der Optik ist, wo sie steht:

    public class Start {
        public static void main(String[] args) {
            Motorrad m = new Motorrad();
            m.start();
        }
    }
    
    public class Motorrad {
    
        private boolean motorLaeuft;
    
        public void start() {
            motorLaeuft = true;
        }
    }
  2. Eine weitere sinnvolle Verwendung von „static“ sind die Methoden reiner Dienstleistungsklassen. Solche Klassen bündeln Funktionalität, die in prozeduralen Sprachen typischerweise in Form von globalen Funktionen realisiert ist. Ein typisches Beispiel wäre die Klasse Math. Von dieser Klasse sollen und können keine Exemplare erzeugt werden, sie stellt aber Methoden zur Verfügung, die z.B. den Sinus eines übergebenen Wertes zurückliefern. Außerdem enthält die Klasse Math Konstanten, und zwar wieder solche, die in einem prozeduralen Programm globale Konstanten wären, wie z. B. Math.PI oder Math.E. Womit wir eine weitere sinnvolle Verwendung für „static“ haben, nämlich eben solche global gültigen Konstanten.

  3. Im Zusammenhang mit Factories findet man oft statische Methoden, so genannte Factory-Methoden. Eine solche Methode wird dann statt eines Konstruktors verwendet, wenn man ein Exemplar eines bestimmten Typs benötigt. Einer der Vorteile ggü. einem Konstruktor besteht darin, dass die Factory-Methode - abhängig von irgendwelchen Faktoren - auch ein Exemplar eines Subtyps des Typs liefern kann, den sie als Rückgabetyp deklariert. Ein Beispiel wäre die Methode getInstance() der abstrakten Klasse Calendar, welche abhängig von der verwendeten Locale und Zeitzone vorkonfigurierte Exemplare einer Subklasse von Calendar liefert.

Aber wenn ich einfach nur Code aus einer Klasse verwenden will…

Oft taucht die Frage auf, wie man eine Methode irgendeines Objektes einer Klasse benutzt, die mit diesem Objekt als solchem nichts zu tun hat, sondern einfach gut passender Code ist, der halt in der Klasse von m steht steht. Wenn es nur darum ginge, dann könnte man die Methode ja wirklich „static“ machen, weil es dann eine reine Diestleistungsmethode wäre, analog etwa zu Math.pow().

Der Normalfall ist aber, dass man aus einem ganz bestimmten Objekt a einem ganz bestimmten Objekt b eine Nachricht senden will, um von diesem speziellen Objekt etwas zu erfahren oder den Zustand dieses Objekts zu ändern. Dann benötigt man aber in a eine Referenz auf b. Ich erweitere mein Motorrad-Beispiel…

Disclaimer: Das folgende Beispiel ist konstruiert und wie alle Beispiele mit Bedacht zu genießen. Es trifft eine Reihe impliziter Annahmen, um es sinnvoll erscheinen zu lassen. Es soll natürlich nicht zeigen, wie man üblicherweise die Vorgänge der Motorradfabrikation oder der Teilebestellung realisiert. Sowas läuft ganz anders und ist um einige Größenordnungen komplexer. Das Beispiel dient lediglich dazu, auf einem stark vereinfachten Modell bestimmte Problemstrukturen zu verdeutlichen, (die einem in der Realität aber durchaus begegnen) und typische Lösungen zu zeigen.

Also: Ich habe ein „richtiges“ Objekt, welches bestimmte veränderliche Daten hat, also einen Zustand, und Methoden, mit denen man den Zustand abfragen oder verändern kann. Dieses Objekt soll so gestaltet werden, dass es eine dauerhafte Beziehung zu einem anderen Objekt bekommt, das es braucht, um korrekt zu funktionieren. Dann ist eine typisches Struktur die weiter unten gezeigte, wobei ich das jetzt so modelliere, dass lediglich das Motorrad seinen Motor und seinen Anlasser kennt, diese sich aber nicht gegenseitig. Das ist sinnvoll, denn so kann man eins der Teile austauschen und muss das nur einer Stelle mitteilen. Ein ganz bestimmter Anlasser und ein ganz bestimmter Motor gehören also zum Objektzustand genau des Motorrads, welches ich in der main-Methode erzeuge:

public class Start {

   private Start() {}

   public static void main(String[] args) {
       Motorrad m = new Motorrad();
       m.starte();
   }
}

public class Motorrad {

   private Anlasser anlasser;

   private Motor motor;

   public void starte() {

       anlasse.dreheMotor(motor);
   }
}

So... wie bekomme ich denn nun aber den Motor und den Anlasser beim Bau des Motorrades in Beziehung zum Motorrad? Ein gängiger Weg ist die Übergabe im Konstruktor:

public class Start {

   private Start() {}

   public static void main(String[] args) {
       Anlasser anl = YamahaZentralLager.getAnlasser("XJ900 Diversion", 1995);
       Motor mot = YamahaZentralLager.getMotor("XJ900 Diversion", 1995);
       Motorrad meinMotorrad = new Motorrad(anl, mot);
       meinMotorrad.starten();
   }
}

public class Motorrad {

   private Anlasser anlasser;

   private Motor motor;

   public Motorrad(Anlasser anlasser, Motor motor) {
       this.anlasser = anlasser;
       this.motor = motor;
   }

   public void starten() {
       anlasser.dreheMotor(motor);
   }
}

Und so baut sich nach und nach ein objektorientiertes Programm auf, in dem das Starten eines Motorrades genau den Anlasser dieses Motorrades dazu bringt, den Motor dieses Motorrades zu drehen. Aber stop... YamahaZentralLager ist offenbar eine Klasse (Damit man das auf den ersten Blick sieht, gibt es die Konvention, Klassen groß zu beginnen, Methoden hingegen klein), und getAnlasser() und getMotor() sind statische Methoden. Offenbar gibt es nur ein einziges YamahaZentralLager und da hab ich mir gedacht, ich kann die Methoden ja auch static machen. ;-)

Aber will man das wirklich so bauen? Betrachten wir den Fall genauer. Ich ändere das Beispiel ein bisschen um: Nehmen wir zunächst an, Yamaha hat genau ein Zentrallager in Japan, aus dem Besteller Teile anfordern können, wobei dem Zentrallager mitgeteilt wird, wer der Besteller ist, damit die Ware dorthin ausgeliefert wird. Dann könnte die zu obigem Beispiel passende Klasse YamahaZentralLager etwa so aussehen:

public class YamahaZentralLager {

    private static int motorVorrat;

    public static boolean liefereMotor(Besteller b) {
       if (motorVorrat > 0) {
           motorVorrat --;
           spedition.liefereAus(einMotor, b);
           return true;
       }
       return false;
    }
 }

Und irgendwo in einer Methode eines Bestellers könnte es einen Aufruf geben, der so aussieht:

    YamahaZentralLager.liefereMotor(this);  // this ist der Besteller

Und schon schickt das Zentrallager einen Motor auf die Reise, und zwar zu dem als Parameter übergebenen Besteller. Das entspricht strukturell dem, was ich im ersten Beispiel gemacht habe, nur dass ich im folgenden irrelevante Details wie etwa das Baujahr weggelassen habe und stattdessen den Besteller übergebe.

Schön ist das aber nicht. Stattdessen wird man das Zentrallager eher als Singleton modellieren. Einer der Hauptgründe: Angenommen, irgendwann wird dann doch ein weiteres Lager in Europa gebaut. Dann müssen alle, die bisher Motoren angefordert haben, ihre Anforderungsmethode ändern. Das mag der Realität in vielen Bereichen der Wirtschaft entsprechen, aber man kann ja beim Modellieren auch einmal etwas besser machen als in der Realität.

Habe ich das Lager von vornherein als Singleton realisiert, dann kann ein Besteller so arbeiten:

    YamahaZentralLager.getInstance(this).liefereMotor();

Und das funktioniert auch noch, wenn es später doch mehrere ZentralLager gibt. Dann kann ich nämlich einfach die Methode getInstance() so umbauen, dass sie je nach Standort des Bestellers unterschiedliche ZentralLager-Exemplare liefert. Und auch in der Klasse ZenralLager halten sich die Änderungen in Grenzen. Insbesondere müssen nicht alle Attribute von static auf nichtstatic geändert werden.

Wird static in Java nicht auch verwendet, um Aufzählungstypen zu modellieren?

So hat man das in der Tat vor 1.5 gemacht. Das ist letztlich ein Sonderfall der von mir erwähnten sinnvollen Verwendung von static für global gültige Konstanten. Allerdings hat diese Vorgehensweise reichlich Nachteile, weswegen es seit Java 1.5 glücklicherweise typsichere Aufzählungstypen (enum) gibt. Diese werden im Kurs in Abschnitt 3.2.6.2 behandelt und auch hier in der FAQ gibt es ein Kapitel zu Enums. Eine ausführlichere (über den Kurs hinausgehende) Einführung mit Beispielen findet sich hier.