Java-Kurs: Geltung von Variablen
Willemers Informatik-Ecke
Strings Klassen als Datenstruktur

Variablen in Blockgrenzen

Eine Variable existiert, sobald sie definiert wurde. Sie bleibt in dem Block, in dem Sie definiert wurde, zugreifbar und wird am Ende des Blocks aufgelöst.
/* Was auch immer vor der geschweiften Klammer steht */ {
    int meineVar;   // Variable wird bei Definition geboren
    meineVar = 12;  // Variable wird verändert
                    // Egal, was hier passiert, die Variable lebt!
}                   // Variable stirbt am Ende des Blocks
Variablen, die in einem äußeren Block oder im gleichen Block definiert wurden, dürfen in einem inneren Block nicht noch einmal definiert werden.

Das folgende Beispiel spielt die Gültigkeit in zwei verschachtelten Blöcken durch:

{
    int meineVar = 12;
    {
         int meineVar = 3; // nein, die Variable gibt es schon!
         if (meineVar<20) {  // meineVar ist bekannt!
         // ...
         int innereVar = 5;
         // ...  innereVar lebt bis zum Blockende
    }
    if (innereVar < 20) { // Variable unbekannt - Fehler
    if (meineVar < 20) { // Variable bekannt - alles gut
}

Fallstrick bei do-while-Schleife

Bei der do-while-Schleife fällt man leicht darauf herein, dass der Block des Schleifeninhalts vor der Bedingung endet, Variablen aus dem Schleifenblock also gar nicht abgefragt werden können.
do {
    int meineVar = 12;
    // ...
} while (meineVar<20); // meineVar unbekannt - Fehler
Lösung: Die Variable muss vor der Schleife definiert werden:
int meineVar;
do {
    meineVar = 12;
    // ...
} while (meineVar<20); // meineVar bleibt erhalten! Alles gut.

Referenzen und Daten

Referenzen sind Variablen, die selbst keine Daten enthalten, sondern auf solche verweisen. Beispielsweise ist eine Array-Variable eine Referenz, genau wie String- und Datenobjektvariablen Referenzen sind.
int[] meinArray = new int[4];

Die Variable meinArray selbst ist eine Referenz, die auf die eigentlichen Daten zeigt, die durch den Befehl new irgendwo im Speicher erzeugt werden.

Jedes new erzeugt einen eigenen Speicher für die Referenz. Im folgenden Beispiel haben meinArray und deinArray je einen eigenen Speicher.

int[] meinArray = new int[4];
int[] deinArray = new int[4];

Bei einer Zuweisung einer Referenz wird nur der Zeiger kopiert. Es entsteht kein neuer Speicher. So können beliebig viele Referenzen auf ein und denselben Speicher verweisen.

int[] meinArray = new int[4];
int[] deinArray = meinArray;

Für eine Referenzvariable gelten die gleichen Geltungsregeln wie für jede Variable. Am Ende des Blocks, in dem sie definiert wurde, stirbt sie.

Die Daten, auf die eine Referenz verweist, überleben nur dann, wenn es noch mindestens eine Referenz darauf gibt, denn nur dann sind die Daten für das Programm noch erreichbar.

Ein Datenbereich, auf das keine Referenzvariable mehr verweist, schwebt unerreichbar durch den Speicherraum. Dieser Speicher wird irgendwann von der Müllabfuhr beseitigt, der sogenannten Garbage Collection. Die Müllabfuhr läuft mit geringer Priorität ständig im Hintergrund.

Variablen und Methoden

Rücksprungadressen auf dem Stack

Beim Aufruf einer Methode legt der Aufrufer seine aktuelle Position als Rücksprungadresse auf den Stack, einen speziellen Speicherbereich. Kommt die Methode an ihr Ende, schaut sie auf dem Stack nach, wohin sie zurückspringen muss und beseitigt den vom Aufrufer abgelegten Speicher vom Stack. Ruft die Methode ihrerseits wieder eine Methode, legt sie ebenfalls ihre Position als Rücksprungadresse auf den Stack. Dies kann beliebig tief verschachtelt werden. Die Methoden kommen so immer zu der Position zurück, von der sie aufgerufen wurden.

Parametervariablen und lokale Variablen

Werden beim Aufruf Parameter übergeben, werden diese zusätzlich auf den Stack gelegt. Die Methode kann sie dort zugreifen. Auf diese Weise kann jeder Aufruf seine eigenen Parameter auf den Stack legen. Die Methode bekommt immer die Parameter ihres Aufrufs.

Werden innerhalb der Methode lokale Variablen angelegt, werden auch diese auf dem Stack gespeichert.

So kann die Methode immer auf ihren aktuellen Status zurückgreifen, auch wenn sie zwischendurch eine andere Methode aufruft. Springt die Methode in eine andere Methode, bleiben diese Daten auf dem Stack.

Läuft die Methode zum Ende des Blocks, kehrt das Programm zum Aufrufpunkt und löscht zuvor seinen kompletten Eintrag auf dem Stack, inklusive der lokalen Variablen und der übergebenen Parameter.

Globale Variablen

Es können Variablen auch außerhalb von Methoden angelegt werden. Allerdings müssen sie sich in einer Klasse befinden. Diese Variablen sind von allen Methoden der Klasse ansprechbar. Darum werden sie auch als globale Variablen bezeichnet.

Die Klassenvariable muss static deklariert sein, wenn sie von einer als static deklarierten Methode zugegriffen werden soll. Solange Sie nicht objektorientiert programmieren, sind solche Klassenvariablen immer static.

Insbesondere Konstanten werden oft sinnvollerweise als Klassenvariablen angelegt. Konstanten werden durch das Schlüsselwort final ausgezeichnet.

Das folgende Beispiel zeigt, wie die Methoden main und meineFunktion über die globale Variable klassenVar an den Parametern vorbei miteinander kommunizieren.

public class MeineKlasse {

    static int klassenVar = 12; // wird statt Parameter missbraucht
    
    static void meineFunktion() {
        klassenVar = 4; // Schweinerei! Gehört in die Parameter
    }
    
    public static void main(String[] args) {
        System.out.println(klassenVar); // 12
        meineFunktion();
        System.out.println(klassenVar); // 4
    }
}
Solche Zugriffe auf globale Variablen gilt es unbedingt zu vermeiden. Ordentliche Methoden definieren in Parametern oder Rückgabevariablen alle Daten, die hinein oder herausgehen.

Methoden dürfen den Namen ihrer lokalen Variablen oder ihrer Parameter frei wählen. Diese dürfen den gleichen Namen haben wir globale Variablen. Dadurch werden die globalen Variablen überdeckt.

Video


Strings Klassen als Datenstruktur