Schlüsselwort static
Schlüsselwort static
Man kann das Schlüsselwort static
verwenden für
- Variablen
- Methoden
- Innere Klassen
Es bedeutet, dass
- eine Methode losgelöst von einer Instanz existiert
- eine Variable zur Klasse und nicht zu den Instanzen gehört
- eine innere Klasse ohne dazugehöriges Objekt existieren kann
Statische Methoden und Attribute
Statische Methoden (static methods)
- können ohne Objekt direkt auf der Klasse aufgerufen werden
- können selbst nur auf statische Methoden und statische Attribute zugreifen
- können nicht überschrieben, sondern nur überlagert werden
Statische Attribute (static attributes)
- existieren nur einmal pro Klasse
- können direkt über die Klasse angesprochen werden
- können überlagert (verdeckt) werden
Da eine Klasse in Java durchaus mehrmals vorkommen kann, nämlich dann, wenn sie durch verschiedene Classloader geladen wurde, muss man korrekterweise sagen, dass statische Attribute pro Instanz der Klasse (d. h. pro Classloader und Klasse) nur einmal vorkommen. Dieses Detail wollen wir hier aber ausblenden und sagen, dass sie nur einmal existieren.
Das Überschreiben von Methoden ist eine Eigenschaft der objektorientierten Programmierung, die den virtuellen Methodenaufruf, spätes Binden und Polymorphie voraussetzt. Für statische Variablen und Methoden existieren diese Mechanismen aber in Java nicht, d. h. obwohl man glaubt die Methode überschrieben zu haben, hat man sie tatsächlich einfach nur verdeckt bzw. überlagert. Leider erlaubt der Java-Compiler das Verdecken von statischen Methoden und Variablen, obwohl hier ein großes Gefahrenpotential einem geringen Nutzen gegenübersteht.
public class A {
public static void tuWas() {
System.out.println("A:tuWas()");
}
}
public class B extends A {
public static void tuWas() {
System.out.println("B:tuWas()");
}
}
public class Test {
public static void main(String[] args) {
A a1 = new A();
B b = new B();
A a2 = new B();
a1.tuWas();
b.tuWas();
a2.tuWas();
}
}
Die Ausgabe ist:
A:tuWas()
B:tuWas()
A:tuWas()
Beispiel: Statische Attribute
public class Ticket {
public static int zaehler = 0;
private int serienNummer;
private String spiel;
public Ticket(String spiel) {
zaehler++;
serienNummer = zaehler;
this.spiel = spiel;
}
}
Im vorliegenden Beispiel ist die Variable zaehler
statisch deklariert und existiert nur einmal pro Klasse. Die Variablen serienNummer
und spiel
sind aber normale Instanzvariablen, die im Objekt gespeichert werden.
Immer wenn ein neues Objekt angelegt wird, wird die statische Variable zaehler
hochgezählt und der serienNummer
zugewiesen. So bekommt jedes Objekt vom Typ Ticket
eine eindeutige Seriennummer.
In der UML werden statische Attribute und Methoden durch Unterstreichung gekennzeichnet.
Beispiel: Statische Methode
public class Ticket {
private static int zaehler = 0;
private int serienNummer;
private String spiel;
public Ticket(String spiel) {
zaehler++;
serienNummer = zaehler;
this.spiel = spiel;
}
public static int getZaehler() {
return zaehler;
}
}
Dieses Beispiel ist analog zum vorhergehenden. Der Unterschied besteht nur darin, dass der Wert von zaehler
nicht mehr direkt von außen gelesen werden kann, sondern über die statische Methode getZaehler()
.
public class TestTicket {
public static void main(String[] args) {
Ticket t1 = new Ticket("Dortmund-Schalke");
System.out.println(Ticket.getZaehler());
Ticket t2 = new Ticket("Hoffenheim-Leverkusen");
System.out.println(Ticket.getZaehler());
}
}
1
2
Praxis-Tipp: Zugriff auf Klassenvariablen
Verwenden Sie beim Zugriff auf statische Variablen und Methoden immer den Klassennamen und nie eine Objektreferenz
Variablen.klassenVariable = 10;
// Schlechter Stil!!
Variablen v = new Variablen();
v.klassenVariable = 15;
Der Java-Compiler bindet die Klassenvariable und die Methode an den Typ der Objektreferenz und nicht an den Typ des Objekts (statische Bindung)
Die statische Bindung kann zu sehr unerwarteten Effekten führen, da statische Variablen von Subklassen überdeckt werden können. Das folgende Beispiel zeigt, warum man auf keinen Fall über Objekt-Referenzen auf Klassen-Variablen zugreifen sollte:
class A {
static int wert = 5;
}
class B extends A {
static int wert = 6;
}
public class C {
void method(A a) {
System.out.println(a.wert);
}
public static void main(String[] args) {
new C().method(new B());
}
}
Das Beispiel liefert als Ausgabe 5
, da der Java-Compiler den Zugriff auf das statische Feld wert
anhand des Typs des Parameters a
der Methode method()
bestimmt und nicht anhand des Typs des Objekts, dass method()
übergeben wird.
Statische Blöcke
Statische Blöcke (static initializer)
- existieren außerhalb einer Methode
- dienen der Initialisierung der Klasse
- werden exakt in der Reihenfolge ihrer Deklaration ausgeführt
- werden exakt einmal beim Laden der Klasse ausgeführt
- können an beliebiger Stelle in der Klasse stehen
- können mehrfach pro Klasse vorkommen
- haben Zugriff auf die statischen Attribute und statischen Methoden der Klasse
Statische Blöcke kann man sich als Klassen-Konstruktoren vorstellen. In der erzeugten Klasse werden sie in einer speziellen Methode namens <clinit>
gesammelt. Im Gegensatz zu normalen Konstruktoren können sie aber keine Parameter bekommen und werden automatisch beim Laden der Klasse durch die Java-Virtual-Machine ausgeführt.
Innerhalb statischer Blöcke können Fehler auftreten. Da der statische Block aber nicht explizit aufgerufen wird, sondern implizit durch den Classloader, hat man als Programmierer:in keine Möglichkeit Fehler außerhalb eines statischen Blocks explizit zu fangen. Daher muss man besonders defensiv in statischen Blöcken programmieren. Kommt es trotzdem zu einem Fehler, so wird das Laden der Klasse abgebrochen und sie steht dann dem Programm nicht zur Verfügung, was im Allgemeinen zu einem Programmabsturz führt. Das folgende Beispiel zeigt, was passiert, wenn eine Ausnahme in einem statischen Block auftritt.
public class Crash {
static {
int x = 5 / 0;
}
public static int add(int a, int b) {
return a + b;
}
}
public class CrashUser {
public static void main(String[] args) {
System.out.println(Crash.add(5, 5));
}
}
Der Fehler im statischen Block führt dazu, dass die Klasse nicht geladen werden kann und damit der main
-Methode nicht zur Verfügung steht. Bei dem Versuch, die add
-Methode zu nutzen, wird eine Ausnahme geworfen.
Exception in thread "main" java.lang.ExceptionInInitializerError
at pr2.oo2.statisch.initializer.CrashUser.main(CrashUser.java:7)
Caused by: java.lang.ArithmeticException: / by zero
at pr2.oo2.statisch.initializer.Crash.<clinit>(Crash.java:7)
... 1 more
public class Ticket {
public static int zaehler;
private int serienNummer;
private String spiel;
static {
zaehler = Integer.parseInt(System.getProperty("zaehler_start"));
}
public Ticket(String spiel) {
zaehler++;
serienNummer = zaehler;
this.spiel = spiel;
}
static {
System.out.println("Ticket initialisiert: zaehler=" + getZaehler());
}
}
Das Beispiel liest den Anfangswert für den Seriennummern-Zähler aus einer System-Property, die der VM beim Start durch die -D-Option übergeben werden kann.
Der Code ist insofern naiv, als dass er nicht damit umgehen kann, dass die Property eventuell nicht gesetzt wurde, sondern in diesem Fall mit einer NullPointerException
abstürzt.
public class TestTicket {
public static void main(String[] args) {
Ticket t1 = new Ticket("Dortmund-Schalke");
System.out.println(Ticket.getZaehler());
Ticket t2 = new Ticket("Hoffenheim-Leverkusen");
System.out.println(Ticket.getZaehler());
}
}
>java -Dzaehler_start=100 TestTicket
Ticket initialisiert: zaehler=100
101
102
Das Singleton Pattern
Zweck des Singletons
- es soll genau eine einzige Instanz eines Objektes geben
- es soll einen einzigen, globalen Zugriffspunkt auf dieses Objekt geben
Das Singleton erschwert aber auch
- Threadsicherheit
- Rekonfiguration
- Multi-user, multi-client Betrieb
Beispiel: Singleton
public class Firma {
private static final Firma INSTANZ = new Firma();
public static Firma getInstance() {
return INSTANZ;
}
private Firma() {
// Keine Instanzen dieser Klasse!
}
}
public class Firma {
private static final Firma instanz;
static {
instanz = new Firma();
}
public static Firma getInstance() {
return instanz;
}
private Firma() {
// Keine Instanzen dieser Klasse!
}
}
Das Static Factory Pattern
Zweck der Static Factory
- Kontrollierte Erzeugung von Objekten, ohne dass der Verwender den konkreten Typ kennt
- Verwendung anstatt Konstruktoren
- Klassen können sich selbst erzeugen
public class Erzeugter {
int wert;
Erzeugter(int wert) {
this.wert = wert;
}
}
public class Factory {
public static Erzeugter create(int wert) {
return new Erzeugter(wert);
}
}
public class SelbstErzeuger {
private SelbstErzeuger(int wert) {
this.wert = wert;
}
public static SelbstErzeuger create() {
return new SelbstErzeuger();
}
}
Trennung von API und Implementierung
Ein gutes Design trennt API (Application Programming Interface) und Implementierung. Unter API versteht man die Schnittstelle zu einem Softwaremodul. Eine solche Trennung können wir mit den bisher vorgestellten Techniken und Entwurfsmustern (Pattern) herbeiführen. Die Schnittstelle zur Funktionalität wird durch ein Interface beschrieben, das zusammen mit einer statischen Fabrik in einem speziellen API-Paket liegt. Die Implementierung des Interfaces (Funktionalität des API) liegt in einem anderen Paket und kann nur über die Factory erzeugt werden.
Mit diesem Design hat man ein Maximum an Flexibilität:
- Der Verwender kennt nur eine Schnittstelle, weiß aber nicht welche Klasse diese implementiert.
- Die genaue Implementierung ist komplett vor dem Verwender verborgen und kann jederzeit geändert werden. Noch nicht einmal die Klasse, die das API implementiert, ist dem Verwender, dank der statischen Fabrik, bekannt.
Beispiel: API/IMPL-Trennung
public interface Calculator {
public abstract int add(int a, int b);
public abstract int sub(int a, int b);
}
public class CalculatorImpl implements Calculator {
public int add(int a, int b) {
return a + b;
}
public int sub(int a, int b) {
return a - b;
}
}
public class CalculatorFactory {
private CalculatorFactory() {}
public static Calculator createCalculator() {
return new CalculatorImpl();
}
}
public class Verwender {
public static void main(String[] args) {
Calculator calculator = CalculatorFactory.createCalculator();
int ergebnis = calculator.add(1, 5);
System.out.println(ergebnis);
}
}
Statische Methoden in Interfaces
- Seit Java 8 dürfen Interfaces statische Methoden enthalten
- Unterscheiden sich nicht von statischen Methoden in Klassen (siehe oben)
public interface Rechner {
public int apply(int a, int b);
public static int rechne(Rechner r, int a, int b) {
return r.apply(a, b);
}
}
Statische Methoden an Interfaces sind sinnvoll, wenn man Hilfsfunktionen anbieten möchte, die eng mit dem Interface verbunden sind. Bisher musste man immer eine weitere Klasse angeben, die die statischen Methoden enthielt. Jetzt können diese direkt am Interface verbleiben.