Part 5

Primitive und Referenzvariablen

Variablen in Java werden in primitive und Referenzvariablen unterteilt. Aus Sicht der Programmierenden wird die Information einer primitiven Variablen als Wert dieser Variablen gespeichert, während eine Referenzvariable einen Verweis auf Informationen enthält, die mit dieser Variablen in Zusammenhang stehen. Referenzvariablen sind in Java praktisch immer Objekte. Schauen wir uns beide Typen anhand von zwei Beispielen an.

int value = 10;
System.out.println(value);
Beispielausgabe

10

public class Name {
    private String name;

    public Name(String name) {
        this.name = name;
    }
}
Name luke = new Name("Luke");
System.out.println(luke);
Beispielausgabe

Name@4aa298b7

Im ersten Beispiel erstellen wir eine primitive int-Variable, und die Zahl 10 wird als deren Wert gespeichert. Wenn wir die Variable an die Methode System.out.println übergeben, wird die Zahl 10 ausgegeben. Im zweiten Beispiel erstellen wir eine Referenzvariable namens luke. Der Konstruktor der Klasse Name gibt beim Aufruf eine Referenz auf ein Objekt zurück, und diese Referenz wird als Wert der Variablen gespeichert. Wenn wir die Variable ausgeben, erhalten wir Name@4aa298b7 als Ausgabe. Was verursacht das?

Der Methodenaufruf System.out.println gibt den Wert der Variablen aus. Der Wert einer primitiven Variablen ist konkret, während der Wert einer Referenzvariablen eine Referenz ist. Wenn wir versuchen, den Wert einer Referenzvariablen auszugeben, enthält die Ausgabe den Typ der Variablen und eine von Java erstellte Kennung: Die Zeichenfolge Name@4aa298b7 zeigt uns, dass die gegebene Variable vom Typ Name ist und ihre Kennung 4aa298b7 lautet.

Das vorherige Beispiel gilt immer dann, wenn die Programmierenden das Standardausgabeformat eines Objekts nicht verändert haben. Sie können die Standardausgabe ändern, indem Sie die Methode toString innerhalb der Klasse des betreffenden Objekts definieren, in der Sie angeben, wie die Ausgabe des Objekts aussehen soll. Im folgenden Beispiel haben wir die Methode public String toString() innerhalb der Klasse Name definiert, die die Instanzvariable name zurückgibt. Wenn wir nun ein Objekt der Klasse Name mit dem Befehl System.out.println ausgeben, wird die Zeichenfolge, die von der Methode toString zurückgegeben wird, ausgegeben.

public class Name {
    private String name;

    public Name(String name) {
        this.name = name;
    }

    public String toString() {
        return this.name;
    }
}
Name luke = new Name("Luke");
System.out.println(luke); // gleichbedeutend mit System.out.println(luke.toString());
Beispielausgabe

Luke

Primitive Variablen

Java hat acht verschiedene primitive Variablen. Diese sind: boolean (ein Wahrheitswert: entweder true oder false), byte (ein Byte mit 8 Bit, Wertebereich -128 bis 127), char (ein 16-Bit-Wert, der ein einzelnes Zeichen darstellt), short (ein 16-Bit-Wert, der eine kleine Ganzzahl darstellt, Wertebereich -32768 bis 32767), int (ein 32-Bit-Wert, der eine mittelgroße Ganzzahl darstellt, Wertebereich -231 bis 231-1), long (ein 64-Bit-Wert, der eine große Ganzzahl darstellt, Wertebereich -263 bis 263-1), float (eine Gleitkommazahl, die 32 Bit verwendet), und double (eine Gleitkommazahl, die 64 Bit verwendet).

Von all diesen haben wir hauptsächlich die Wahrheitswertvariable (boolean), Ganzzahlvariable (int) und Gleitkommazahlvariable (double) verwendet.

boolean truthValue = false;
int integer = 42;
double floatingPointNumber = 4.2;

System.out.println(truthValue);
System.out.println(integer);
System.out.println(floatingPointNumber);
Beispielausgabe

false 42 4.2

Die Deklaration einer primitiven Variablen führt dazu, dass der Computer etwas Speicher reserviert, in dem der der Variablen zugewiesene Wert gespeichert werden kann. Die Größe des reservierten Speicherbereichs hängt vom Typ der primitiven Variablen ab. Im folgenden Beispiel erstellen wir drei Variablen. Jede hat ihren eigenen Speicherort, an dem der zugewiesene Wert gespeichert wird.

int first = 10;
int second = first;
int third = second;
System.out.println(first + " " + second + " " + third);
second = 5;
System.out.println(first + " " + second + " " + third);
10 10 10
10 5 10

Der Name der Variablen gibt den Speicherort an, an dem ihr Wert gespeichert ist. Wenn Sie einer primitiven Variablen mit einem Gleichheitszeichen einen Wert zuweisen, wird der Wert auf der rechten Seite in den durch den Namen der Variablen angegebenen Speicherort kopiert. Zum Beispiel reserviert die Anweisung int first = 10 einen Speicherort namens first für die Variable und kopiert den Wert 10 in diesen Speicherort.

Ebenso reserviert die Anweisung int second = first; in diesem Fall einen Speicherort namens second für die erstellte Variable und kopiert den in der Speicheradresse von first gespeicherten Wert hinein.

Die Werte von Variablen werden auch dann kopiert, wenn sie in Methodenaufrufen verwendet werden. Dies bedeutet praktisch, dass der Wert einer Variablen, die als Parameter in einem Methodenaufruf übergeben wird, in der aufrufenden Methode nicht durch die aufgerufene Methode geändert wird. Im folgenden Beispiel deklarieren wir eine Variable number in der Hauptmethode, deren Wert als Parameter des Methodenaufrufs kopiert wird. In der aufgerufenen Methode wird der Wert, der über den Parameter kommt, ausgegeben, dann wird sein Wert um eins erhöht. Der Wert der Variablen wird dann noch einmal ausgegeben, und die Programmausführung kehrt schließlich zur Hauptmethode zurück. Der Wert der Variablen number in der Hauptmethode bleibt unverändert, da er nichts mit der Variablen number zu tun hat, die als Parameter der aufgerufenen Methode definiert ist.

Loading...

Referenzvariablen

Alle von Java bereitgestellten Variablen (mit Ausnahme der oben erwähnten acht primitiven Variablen) sind Referenztypen. Programmierende können auch eigene Variablentypen erstellen, indem sie neue Klassen definieren. Praktisch jedes Objekt, das aus einer Klasse instanziiert wird, ist eine Referenzvariable.

Betrachten wir das Beispiel vom Anfang des Kapitels, in dem wir eine Variable namens leevi vom Typ Name erstellt haben.

Name leevi = new Name("Leevi");

Der Aufruf hat folgende Bestandteile:

  • Wann immer eine neue Variable deklariert wird, muss zuerst ihr Typ angegeben werden. Unten deklarieren wir eine Variable vom Typ Name. Damit das Programm erfolgreich ausgeführt werden kann, muss eine Klasse Name vorhanden sein, die vom Programm verwendet werden kann.
Name ...
  • Wir geben den Namen der Variablen an, wenn sie deklariert wird. Sie können später auf den Wert der Variablen über ihren Namen zugreifen. Unten wird die Variable leevi genannt.
Name leevi...
  • Variablen können Werte zugewiesen werden. Objekte werden aus Klassen durch Aufruf des Klassenkonstruktors erstellt. Dieser Konstruktor definiert die Werte, die den Instanzvariablen des erstellten Objekts zugewiesen werden. Im folgenden Beispiel nehmen wir an, dass die Klasse Name einen Konstruktor hat, der eine Zeichenkette als Parameter entgegennimmt.
... new Name("Leevi");
  • Der Konstruktorruf gibt einen Wert zurück, der eine Referenz auf das neu erstellte Objekt ist. Das Gleichheitszeichen sagt dem Programm, dass der Wert des Ausdrucks auf der rechten Seite als Wert der Variablen auf der linken Seite kopiert werden soll. Die Referenz auf das neu erstellte Objekt, die vom Konstruktorruf zurückgegeben wird, wird als Wert der Variablen leevi kopiert.
Name leevi = new Name("Leevi");

Der wichtigste Unterschied zwischen primitiven und Referenzvariablen besteht darin, dass primitive Variablen (in der Regel Zahlen) unveränderlich sind. Der interne Zustand von Referenzvariablen kann hingegen typischerweise verändert werden. Dies liegt daran, dass der Wert einer primitiven Variablen direkt in der Variablen gespeichert wird, während der Wert einer Referenzvariablen eine Referenz auf die Daten der Variablen, also deren internen Zustand, darstellt.

Arithmetische Operationen wie Addition, Subtraktion und Multiplikation können mit primitiven Variablen verwendet werden – diese Operationen ändern die ursprünglichen Werte der Variablen nicht. Arithmetische Operationen erzeugen neue Werte, die bei Bedarf in Variablen gespeichert werden können. Im Gegensatz dazu können die Werte von Referenzvariablen durch diese arithmetischen Ausdrücke nicht geändert werden.

Der Wert einer Referenzvariablen – also die Referenz – zeigt auf einen Speicherort, der Informationen über die gegebene Variable enthält. Nehmen wir an, dass uns eine Klasse Person zur Verfügung steht, die eine Instanzvariable age enthält. Wenn wir

ein Personenobjekt aus der Klasse instanziiert haben, können wir auf die Variable age zugreifen, indem wir der Referenz des Objekts folgen. Der Wert dieser Variablen kann dann nach Bedarf geändert werden.

Primitive und Referenzvariablen als Methodenparameter

Wir haben bereits erwähnt, dass der Wert einer primitiven Variablen direkt in der Variablen gespeichert wird, während der Wert einer Referenzvariablen eine Referenz auf ein Objekt enthält. Wir haben auch erwähnt, dass das Zuweisen eines Wertes mit dem Gleichheitszeichen den Wert auf der rechten Seite (möglicherweise einer anderen Variablen) kopiert und ihn als Wert der Variablen auf der linken Seite speichert.

Eine ähnliche Art von Kopie erfolgt während eines Methodenaufrufs. Unabhängig davon, ob es sich um eine primitive oder eine Referenzvariable handelt, wird der Wert, der als Argument an die Methode übergeben wird, für die aufgerufene Methode kopiert. Bei primitiven Variablen wird der Wert der Variablen an die Methode übergeben. Bei Referenzvariablen ist es eine Referenz.

Schauen wir uns das in der Praxis an und nehmen wir an, dass uns die folgende Klasse Person zur Verfügung steht.

public class Person {
    private String name;
    private int birthYear;

    public Person(String name) {
        this.name = name;
        this.birthYear = 1970;
    }

    public int getBirthYear() {
        return this.birthYear;
    }

    public void setBirthYear(int birthYear) {
        this.birthYear = birthYear;
    }

    public String toString() {
        return this.name + " (" + this.birthYear + ")";
    }
}

Wir werden die Programmausführung Schritt für Schritt untersuchen.

public class Example {
    public static void main(String[] args) {
        Person first = new Person("First");

        System.out.println(first);
        youthen(first);
        System.out.println(first);

        Person second = first;
        youthen(second);

        System.out.println(first);
    }

    public static void youthen(Person person) {
        person.setBirthYear(person.getBirthYear() + 1);
    }
}
Beispielausgabe

First (1970) First (1971) First (1972)

Die Ausführung des Programms beginnt mit der ersten Zeile der main-Methode. In der ersten Zeile der main-Methode wird eine Variable vom Typ Person deklariert, und der vom Konstruktor der Klasse Person zurückgegebene Wert wird als ihr Wert kopiert. Der Konstruktor erstellt ein Objekt, dessen Geburtsjahr auf 1970 gesetzt wird und dessen Name auf den als Parameter empfangenen Wert gesetzt wird. Der Konstruktor gibt eine Referenz zurück. Nachdem die Zeile ausgeführt wurde, sieht der Zustand des Programms wie folgt aus – ein Person-Objekt wurde im Speicher erstellt, und die in der main-Methode definierte Variable first enthält eine Referenz auf dieses Objekt.

Im folgenden verwenden wir pythontutor.com zur Visualiserung des Ablaufes, insbesondere Sehen Sie die Veränderungen des Aufrufstapel und des Hauptspeichers auf der rechten Seite.

In der dritten Zeile der main-Methode (Zeile 5 in der Visualisierung) geben wir den Wert der Variablen first aus. Der Methodenaufruf System.out.println sucht nach der toString-Methode der Referenzvariablen, die ihm als Parameter übergeben wurde. Die Klasse Person hat die Methode toString, sodass diese Methode auf dem Objekt aufgerufen wird, auf das die Variable first verweist. Der Wert der Variablen name in diesem Objekt ist "First", und der Wert der Variablen birthYear ist 1970. Die Ausgabe lautet "First (1970)".

Danach ruft das Programm die Methode youthen auf, der die Variable first als Argument übergeben wird. Wenn die Methode youthen aufgerufen wird, wird der Wert der Parametervariablen für die Verwendung durch die Methode youthen kopiert. Die Ausführung der youthen-Methode erzeugt einen neuen frame im Aufrufstapel. Da die Variable first ein Referenztyp ist, wird die zuvor erstellte Referenz für die Verwendung durch die Methode kopiert. Am Ende der Methodenausführung ist das Geburtsjahr des Objekts auf 1971 erhöht, und das frame wird vom Aufrufstapel genommen.

Nachdem wir vom Methodenaufruf zurückgekehrt sind, geben wir erneut den Wert der Variablen first aus. Das Objekt, auf das die Variable first verweist, wurde während des Aufrufs der Methode youthen verändert: Die Variable birthYear des Objekts wurde um eins erhöht. Der endgültige Wert, der ausgegeben wird, ist "First (1971)".

Dann wird im Programm eine neue Variable vom Typ Person namens second deklariert. Der Wert der Variablen first wird in die Variable second kopiert, d.h. der Wert der Variablen second wird zu einer Referenz auf das bereits vorhandene Person-Objekt.

Beachten Sie das first und second auf das gleiche Objekt im SPeicher verweisen.

Danach wird die Methode youthen aufgerufen, der die Variable second als Parameter übergeben wird. Der Wert der während des Methodenaufrufs übergebenen Variablen wird als Wert für die Methode kopiert, d.h. die Methode erhält die in der Variablen second enthaltene Referenz zur Verwendung. Nach der Ausführung der Methode ist das Geburtsjahr des Objekts, auf das die Methode verweist, um eins auf 1972 erhöht.

Schließlich endet die Methodenausführung, und das Programm kehrt zur main-Methode zurück, wo der Wert der Variablen first ein letztes Mal ausgegeben wird. Das endgültige Ergebnis der Ausgabe ist "First (1972)".

Quiz

Loading...

Quiz

Loading...

Sie haben das Ende dieses Abschnitts erreicht! Weiter zum nächsten Abschnitt: