Deklaration statischer Klassenkonstanten und -variablen in Java
Das folgende Programm zeigt zwei Methoden, welche dieselben Konstanten benötigen und sie daher mit einer gewissen Redundanz doppelt deklarieren.
Main.java
public final class Main
{public static void umfang()
{ final double pi = java.lang.Math.PI;
final double r = 9.80665;java.lang.System.out.print( "Der Umfang eines Kreises mit dem Radius " + r + " ist " );
java.lang.System.out.print( 2 * pi * r );
java.lang.System.out.println( "." ); }public static void flaeche()
{ final double pi = java.lang.Math.PI;
final double r = 9.80665;java.lang.System.out.print( "Die Flaeche eines Kreises mit dem Radius " + r + " ist " );
java.lang.System.out.print( pi * r * r );
java.lang.System.out.println( "." ); }public static void main( final java.lang.String[] args )
{ umfang();
flaeche(); }}transcript
Der Umfang eines Kreises mit dem Radius 9.80665 ist 61.616999192652685.
Die Flaeche eines Kreises mit dem Radius 9.80665 ist 302.1281725663137.
Eine statische Klassenkonstante kann von mehreren Methoden einer Klasse verwendet werden. Sie wird im Rumpf einer Klassendeklaration geschrieben (nicht im Rumpf einer Methodendeklaration).
Der Gültigkeitsbereich einer statischen Klassenkonstanten ist die gesamte Klasse. Durch Voranstellung des Namens der Klasse kann eine Klassenkonstante aber auch in anderen Klassen verwendet werden – insofern ist sie eine „globale Konstante“.
Die Lebensdauer einer statischen Klassenkonstanten ist die gesamte Laufzeit des Programms. Eine Klassenkonstante existiert also unabhängig von Inkarnationen von Methoden. Daher kommt auch die Bezeichnung als „statisch “ – sie existiert unveränderlich.
Im allgemeinen wird den Klassenkonstanten der Name der Klasse als Qualifikation vorangestellt, wie schon bei den statischen Methoden einer Klasse gesehen.
Statische Klassenkonstanten werden auch als statische Felder ihrer Klasse bezeichnet, oder kurz einfach nur als Felder. (Dabei stellt man sich die Klasse als einen Datensatz oder als ein Formular vor, dessen Bestandteile traditionell „Felder“ genannt werden.)
In der folgenden Klasse »Main« werden beispielsweise zwei Konstanten definiert, die dann von Methoden verwendet werden können.
Main.java
public final class Main
{public static final double pi = java.lang.Math.PI;
public static final double r = 9.80665;public static void umfang()
{ java.lang.System.out.print( "Der Umfang eines Kreises mit dem Radius " + Main.r + " ist " );
java.lang.System.out.print( 2 * Main.pi * Main.r );
java.lang.System.out.println( "." ); }public static void flaeche()
{ java.lang.System.out.print( "Die Flaeche eines Kreises mit dem Radius " + Main.r + " ist " );
java.lang.System.out.print( Main.pi * Main.r * Main.r );
java.lang.System.out.println( "." ); }public static void main( final java.lang.String[] args )
{ umfang();
flaeche(); }}java.lang.System.out
Der Umfang eines Kreises mit dem Radius 9.80665 ist 61.616999192652685.
Die Flaeche eines Kreises mit dem Radius 9.80665 ist 302.1281725663137.
Die Deklaration einer statischen Klassenkonstanten erfolgt wie die Deklaration einer Blockkonstanten nur mit einem vorangestellten »public static«. Die Feldmodifizierer »public static« werden dabei aus denselben Gründen verwendet, aus denen sie auch bei einer Deklaration einer statischen Methode verwendet werden. »public« bedeutet, daß das Feld nicht versteckt werden soll (versteckte Felder werden später behandelt werden). »static« bedeutet, daß das Feld nur einmal in der Klasse existiert und nicht zu einem Objekt gehören soll.
Nichtkonstante Statische Klassenvariablen
Nichtkonstante Statische Klassenvariablen lassen sich wie statische Klassenkonstanten deklarieren, nur daß das »final« weggelassen werden muß. Für solche nichtkonstanten statischen Klassenvariablen gilt dann das eben für statische Klassenkonstanten Gesagte, nur daß sie auch verändert werden dürfen.
Technisch gesehen ist eine statische Klassenkonstante auch eine statische Klassenvariable, nur eben eine final-Variable. In diesem Kurs bezeichnen wir final-Variablen auch manchmal als Konstanten.
Wie statische Klassenkonstanten werden auch statische Klassenvariablen als statische Felder oder Felder ihrer Klasse bezeichnet.
Vergleich mit Standardkonstanten
Das folgende Programm zeigt, daß eine in der Klasse »Main« deklarierte Konstante wie eine Konstante aus der Standardbibliothek verwendet werden kann.
Main.java
public final class Main
{public static final double pi = java.lang.Math.PI;
public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( Main.pi );
java.lang.System.out.println( Math.PI ); }}transcript
3.141592653589793
3.141592653589793
Unqualifizierte Namen
Wie bei Methoden so ist es auch bei statischen Feldern einer Klasse erlaubt, bei Bezügen innerhalb derselben Klasse auf die Qualifikation zu verzichten.
Main.java
public final class Main
{public static final double pi = java.lang.Math.PI;
public static final double r = 9.80665;public static void umfang()
{ java.lang.System.out.print( "Der Umfang eines Kreises mit dem Radius " + r + " ist " );
java.lang.System.out.print( 2 * pi * r );
java.lang.System.out.println( "." ); }public static void flaeche()
{ java.lang.System.out.print( "Die Flaeche eines Kreises mit dem Radius " + r + " ist " );
java.lang.System.out.print( pi * r * r );
java.lang.System.out.println( "." ); }public static void main( final java.lang.String[] args )
{ umfang();
flaeche(); }}java.lang.System.out
Der Umfang eines Kreises mit dem Radius 9.80665 ist 61.616999192652685.
Die Flaeche eines Kreises mit dem Radius 9.80665 ist 302.1281725663137.
Reihenfolge der Deklarationen
Wie eine Methodendeklaration kann auch eine statische Variablendeklaration an irgendeiner Stelle innerhalb des Klassenrumpfs stehen (vor oder hinter einer anderen Deklaration), ohne daß dies die Bedeutung des Programms beeinflußt.
Main.java
public final class Main
{ public static int i = 4;
public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( i ); }}java.lang.System.out
4
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( i ); }
public static int i = 4; }java.lang.System.out
4
Verdeckung
Eine statische Konstante wird durch einen gleichnamigen Parameter verdeckt.
Main.java
public final class Main
{ public static final int a = 8;public static void ausgeben( final int a )
{ java.lang.System.out.println( a ); }public static void main( final java.lang.String[] args )
{ ausgeben( 9 ); }}java.lang.System.out
9
Lokale Variablen
Es ist erlaubt, daß eine lokale Variable denselben Namen trägt wie eine Klassenvariable. Falls der Name der lokalen Variable ohne Qualifikation im Gültigkeitsbereich dieser lokalen Variablen verwendet wird, so bezieht sich dieser Name dann auf die lokale Variable und nicht auf die Klassenvariable.
Main.java
public final class Main
{
public static final int i = 1;
public static void method()
{ final int i = 2;
java.lang.System.out.println( i );
java.lang.System.out.println( Main.i ); }
public static void main( final java.lang.String[] args )
{ method(); }}java.lang.System.out
2
1
Das folgende Beispiel illustriert, daß der Gültigkeitsbereich der lokalen Konstanten i bereits direkt nach dem Gleichheitszeichen in der ihrer Deklaration beginnt, sonst würde nämlich dort der Wert 1 der Klassenkonstanten i verwendet werden.
Main.java
public final class Main
{public static final int i = 1;
public static void method()
{ final int i = i;
java.lang.System.out.println( i );
java.lang.System.out.println( Main.i ); }
public static void main( final java.lang.String[] args )
{ method(); }}- Konsole
Main.java:5: error: variable i might not have been initialized
{ final int i = i;
^
Methoden
Der Name eines Feldes darf dem Namen einer Methode gleichen. Die folgende Klasse enthält eine Feld namens »f« und eine Methode namens »f«.
Main.java
public final class Main
{public static int f;
public static void f()
{ java.lang.System.out.println( f ); }public static void main( final java.lang.String[] args )
{ f = 5;
f(); }}java.lang.System.out
5
Auch der Name »main« ist erlaubt.
Main.java
public final class Main
{public static int main;
public static void main( final java.lang.String[] args )
{ main = 26;
java.lang.System.out.println( main ); }}transcript
26
Auch der Name der Klasse (»Main«) ist erlaubt. (Jedoch sollten Feldnamen in der Regel nicht so mit großem Anfangsbuchstaben geschrieben werden.)
Main.java
public final class Main
{public static int Main;
public static void main( final java.lang.String[] args )
{ Main = 11;
java.lang.System.out.println( Main ); }}transcript
11
Initialisierung
Während lokale Variablen ohne ausdrückliche Initialisierung als nicht initialisiert gelten, erhalten statische Klassenvariablen automatisch den Wert 0 (int) bzw 0.0 (double), false (boolean) oder null (Referenztyp).
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ int i;
java.lang.System.out.println( i ); }}- Konsole
Main.java:36: error: variable i might not have been initialized
java.lang.System.out.println( i ); }}
^Main.java
public final class Main
{ public static int i;
public static double j;
public static boolean k;
public static java.lang.String l;
public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( i );
java.lang.System.out.println( j );
java.lang.System.out.println( k );
java.lang.System.out.println( l ); }}transcript
0
0.0
false
null
Die Lebensdauer
Die Lebensdauer von Blockvariablen ist die Dauer der Ausführung ihres Blocks. Diese stimmt bei Hauptblöcken von Methoden mit der Lebensdauer der Inkarnation der Methode überein.
Daher ist es nicht möglich, daß eine Methode mit Hilfe einer Blockvariablen die Anzahl ihrer Aufrufe zählt. Die Blockvariable wird am Ende jeder Inkarnation aufgelöst und kann daher nicht verwendet werden, um eine Zahl über mehrere Aufrufe hinweg zu speichern.
Main.java
public final class Main
{public static void aufrufzaehler()
{ int i = 0;
i = i + 1;
java.lang.System.out.println( i + " Aufruf(e)." ); }public static void main( final java.lang.String[] args )
{ aufrufzaehler();
aufrufzaehler();
aufrufzaehler(); }}java.lang.System.out
1 Aufruf(e).
1 Aufruf(e).
1 Aufruf(e).
Das folgende Programm zeigt, daß die Anzahl der Aufrufe einer Methode aber mit einem statischen Feld gezählt werden kann, da solch ein Feld während der ganzen Zeit der Ausführung des Programms existiert.
Main.java
public final class Main
{public static int i;
public static void aufrufzaehler()
{ i = i + 1;
java.lang.System.out.println( i + " Aufruf(e)." ); }public static void main( final java.lang.String[] args )
{ aufrufzaehler();
aufrufzaehler();
aufrufzaehler(); }}java.lang.System.out
1 Aufruf(e).
2 Aufruf(e).
3 Aufruf(e).
Der Zeitpunkt der Initialisierung statischer Felder
Vor der Verwendung einer Klasse, beispielsweise vor der Ausführung einer statischen Methode dieser Klasse, wird die Klasse initialisiert (falls sie nicht schon initialisiert wurde) (JLS8:12.4.1). Bei der Initialisierung einer Klasse werden die statischen Felder dieser Klasse initialisiert. Das folgende Programm zeigt, daß das Feld »a« vor Ausführung der Methode »main« initialisiert wird.
Main.java
public final class Main
{public static int a = a();
public static int a(){ java.lang.System.out.println( "a" ); return 7; }
public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( "Hello, World" );
java.lang.System.out.println( a ); }}java.lang.System.out
a
Hello, World
7
Das Teilen eines Zustandes zwischen Methoden
Eine statische Klassenvariable eröffnet auch die Möglichkeit des Teilens eines Zustandes zwischen mehreren Methoden, das soll bedeuten, daß mehrere Methoden auf dieselbe Variable zugreifen können, welche ja zu jedem Zeitpunkt einen besteimmten Zustand hat. So können mehrere Methoden „zusammenarbeiten“, um gemeinsam eine „Methode mit Gedächtnis“ zu bilden, die man gedanklich gemeinsam als eine „Maschine“ auffassen kann. Die zusammenarbeitenden Methoden teilen Informationen in Form von Variablen, wobei jede dann für eine mit Hilfe dieser Variablen zu erledigenden Aufgabe zuständig sein kann.
So teilen in dem folgendenden Beispielprogramm die Methoden »inkrementieren« und »zuruecksetzen« das Feld »i«, um damit gemeinsam einen Zähler zu realisieren.
Main.java
public final class Main
{public static int i;
public static void inkrementieren()
{ i = i + 1;
java.lang.System.out.println( i ); }public static void zuruecksetzen()
{ i = 0; }public static void main( final java.lang.String[] args )
{inkrementieren();
inkrementieren();
inkrementieren();zuruecksetzen();
inkrementieren();
inkrementieren(); }}java.lang.System.out
1
2
3
1
2- Die Methode »main« nutzt den Zähler, indem sie dessen Methoden aufruft, ohne dabei direkt auf die Variable i zuzugreifen. Die Variable »i« ist ein Internum des Zählers.
Zaehler
.---------------------.---------------------.
Methode "main" | inkrementieren | Variable i |
| | |
| | |
-----------------> | | |
| | |
| | |
| | |
|---------------------| |
| zuruecksetzen | |
| | |
| | |
-----------------> | | |
| | |
| | |
| | |
'-------------------------------------------'
Dieses Beispiel diente zur Veranschaulichung der Einsatzmöglichkeiten statischer Felder. Um solche „simulierten Maschinen“ zu realisieren sollten aber im allgemeinen Objekte mit nicht-statischen Feldern und Methoden bevorzugt werden.
Rückgabe mit Klassenvariablen
Eine Methode kann eine Information auch an eine Aufrufer übertragen, indem sie die Information in eine statische Klassenvariable schreibt. Der Aufrufer kann diese Information dann wieder aus der statischen Klassenvariablen auslesen.
Main.java
public final class Main
{public static int anzahlDerTageEinerWoche;
public static void anzahlDerTageEinerWocheFestlegen()
{ anzahlDerTageEinerWoche = 7; }public static void main( final java.lang.String[] args )
{ anzahlDerTageEinerWocheFestlegen();
java.lang.System.out.println( 2 * anzahlDerTageEinerWoche ); }}
Diese Vorgehensweise ist aber komplizierter und fehlerträchtiger als die Verwendung der Rückgabeanweisung und gilt deswegen zu Recht als schlechter Stil. Sie sollte also im allgemeinen vermieden werden. Sie wurde hier nur gezeigt, weil sie helfen kann, statische Klassenvariablen zu verstehen.
Lokale Variablen bevorzugen!
Der Gültigkeitsbereich einer Variablen sollte immer so klein wie möglich sein.
Die Lebensdauer einer Variablen sollte immer so kurz wie möglich sein.
Daher sollten lokale Variablen gegenüber statischen Klassenvariablen bevorzugt werden, falls statische Klassenvariable zur Erfüllung einer Aufgabe nicht unbedingt nötig sind oder einen überwältigenden Vorteil bieten.
Konstanten bevorzugen!
Die Rechte zum Zugriff auf eine Variablen sollten immer so gering wie möglich sein.
Falls globale Variablen benötigt werden, sollten diese möglichst mit »final« deklariert werden, da globale Konstanten weniger problematisch sind als globale Variablen.
Stapelberichte
Der folgende Stapelbericht zeigt die Tiefe der Verschachtelung an und hilft damit, den mit dem statischen Feld zusammenhängenden Fehler zu finden.
Main.java
public final class Main
{ public static int i = 3;public static void beta()
{ final int j = 1/( i = i - 1 );
beta(); }public static void main( final java.lang.String[] args )
{ beta(); }}- Konsole
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Main.beta(Main.java:4)
at Main.beta(Main.java:5)
at Main.beta(Main.java:5)
at Main.main(Main.java:7)- Konsole (übersetzt)
Ausnahme in Verlauf "main" java.lang.ArithmetischeAusnahme: / durch 0
at Main.beta(Main.java:4)
at Main.beta(Main.java:5)
at Main.beta(Main.java:5)
at Main.main(Main.java:7)
Zunächst wird »beta()« in Zeile 7 aufgerufen. Dies findet sich in der letzten Zeile des Stapelberichts. Dann ruft »beta()« sich in Zeile 5 zweimal selber auf. Schließlich kommt es in Zeile 4 zu einem Laufzeitfehler. Wir erkennen also, daß der Fehler während der dritten Inkarnation der Methode »beta()« begangen wird.
- Aussprachehinweis
- arithmetic ˌærɪθˈmɛtɪk (die voranstehende Aussprache gilt für das Adjektiv, die Aussprach des Substantivs lautet /əˈrɪθmətɪk/)
Übungsfragen
? Ausgabe vorhersagen (0)
Was ist die Ausgaben des folgenden Programms?
- Main.java
public final class Main
{ public static int i;
public static void main( final java.lang.String[] args )
{ i = 2;
java.lang.System.out.println( i ); }}
? Ausgabe vorhersagen (1)
Was ist die Ausgaben des folgenden Programms?
- Main.java
public final class Main
{ public static int i;
public static void main( final java.lang.String[] args )
{ int i = 2;
java.lang.System.out.println( Main.i ); }}
? Ausgabe vorhersagen (2)
Was ist die Ausgaben des folgenden Programms?
- Main.java
public final class Main
{ public static int i;
public static void m()
{ int i; i = 3; }
public static void main( final java.lang.String[] args )
{ m();
java.lang.System.out.println( i ); }}
? Ausgabe vorhersagen (3)
Was ist die Ausgaben des folgenden Programms?
- Main.java
public final class Main
{ public static int i;
public static void m()
{ i = 3; }
public static void main( final java.lang.String[] args )
{ m();
java.lang.System.out.println( i ); }}
? Ausgabe vorhersagen (4)
Was ist die Ausgaben des folgenden Programms?
- Main.java
public final class Main
{ public static void m(){ i = 5; }
public static int i = 7;
public static void p(){ java.lang.System.out.println( i ); }
public static void main( final java.lang.String[] args )
{ m();
p(); }}
Übungsaufgaben
/ Refaktor
Schreiben Sie das folgende Programm zu um, daß die Variable »i« keine Klassenvariable mehr ist, sondern als lokale Variable deklariert wird. Alles andere im Programm soll nicht verändert werden.
(Bei diesem Programm ist die Verwendung einer lokalen Variablen besserer Stil.)
- Main.java
public final class Main
{ public static int i;
public static void main( final java.lang.String[] args )
{ i = 0; while( i < 2 )java.lang.System.out.println( i = i + 1 ); }}java.lang.System.out
1
2