30. Oktober 2024

Java: Unterschied zwischen Wertesemantik und Referenzsemantik

Wertesemantik und Referenzsemantik sind zwei Paar Schuhe, die man bei der Programmierung in Java strikt trennen muss, um Fehler im Code zu vermeiden. Doch was genau verbirgt sich hinter diesen Begriffen?

In diesem Artikel werden beide anhand von Beispielen genauer erläutert.

Primitive und komplexe Datentypen

Um den Unterschied zwischen Werte- und Referenzsemantik zu verstehen muss man sich zunächst den Unterschied zwischen primitiven und komplexen Datentypen in Java verdeutlichen.

Primitive Datentypen oder auch elementare Datentypen sind die Datentypen, die in Java (und so gut wie jeder anderen Programmiersprache) immer gegeben sind. Dazu zählen z.B.:

  • int
  • boolean
  • char (nur Einzelzeichen!)

Komplexe Datentypen sind meist zusammengesetzte primitive Datentypen. Zu den komplexen Datentypen zählen z.B.:

  • Arrays
  • Strings
  • struct
  • Objekte beliebiger Klassen

Schon hier wird deutlich welchen unterschiedlichen Speicheraufwand diese Datentypen haben können. Ein Integer beispielsweise hat immer die selbe Größe im Speicher, da es einen festen Wertebereich gibt. Ein String hingegen kann unterschiedliche Längen annehmen und so unterschiedlich viel Speicherplatz beanspruchen. Es ist sogar ein Platzanspruch im Megabyte-Bereich möglich. Bei sehr komplexen Objekten einer großen Klasse kann es beispielsweise bei wissenschaftlichen Anwendungen auf Großrechnern zu einem Speicherplatzanspruch im Giga- oder sogar Terabyte-Bereich kommen.

Auch wenn moderne Rechner sehr große und immer schnellere Hauptspeicher besitzen und dieser auch immer billiger werden, liegt es auf der Hand, dass man primitive und komplexe Datentypen aus Performancegründen semantisch anders behandeln sollte.

Wertesemantik

Die Wertesemantik gilt für primitive Datentypen. Wenn man einer Funktion einen Übergabeparameter gibt, der ein beliebiger primitiver Datentyp ist, so wird von diesem im Speicher eine Kopie erstellt und die übergebene Variable bleibt unangetastet, wie folgendes Codebeispiel zeigt:

public class Example {

    public static int exampleMethod(int primitive)
    {
        primitive++; //Uebergabeparameter um 1 erhoehen
        return primitive; //und zurueckgeben
    }
    
    public static void main(String[] args)
    {
        int testvariable = 1;
        
        System.out.println("Testvariable: "+testvariable+" vor dem Ausfuehren der Methode");
        System.out.println("beispielMethode: "+exampleMethod(testvariable)+ " Ausfuehren mit testvariable als Parameter");
        System.out.println("Testvariable: "+testvariable+" nach dem Ausfuehren der Methode");
        
    }

}

Der Code führt zu dieser Ausgabe:

Testvariable: 1 vor dem Ausfuehren der Methode
beispielMethode: 2 Ausfuehren mit testvariable als Parameter
Testvariable: 1 nach dem Ausfuehren der Methode

Wie man erkennen kann, wurde die übergebene Variable nicht verändert, sondern es wurde nur auf eine Kopie gearbeitet, die nach dem Abarbeiten der Funktion zurückgegeben und gelöscht wird.

Referenzsemantik

Die Referenzsemantik gilt für komplexe Datentypen. Der Unterschied zur Wertesemantik liegt darin, dass das zu bearbeitende Objekt/Array/… nicht kopiert wird, sondern lediglich eine Referenz, also die Speicheradresse zum Objekt übergeben wird. Dadurch muss das Objekt/Array/… nicht erst kopiert werden, was Speicherplatz und außerdem Zeit spart.

Wie sich die Referenzsemantik beim Javaprogrammieren bemerkbar macht zeigt folgendes Codebeispiel:

public class Example {

    public static int[] exampleMethod(int[] compArr)
    {
        for(int i=0;i<compArr.length; ++i)
        {
            compArr[i] = compArr[i] + 1; //1 auf jede Stelle addieren
        }
        return compArr;
    }
    
    public static void main(String[] args)
    {
        int anyArray[] = { 1, 42, 73, 1337};
        
        System.out.println("Array vor dem Ausfuehren der Methode");
        for(int i=0; i<anyArray.length; ++i)
        {
            System.out.println("  ["+i+"]: "+anyArray[i]);
        }
        
        System.out.println("Ausfuehren der Methode exampleMethod(anyArray) ...");
        exampleMethod(anyArray);
        
        System.out.println("Array nach dem Ausfuehren der Methode");
        for(int i=0; i<anyArray.length; ++i)
        {
            System.out.println("  ["+i+"]: "+anyArray[i]);
        }
        
    }

}

Der Code führt zu dieser Ausgabe:

Array vor dem Ausfuehren der Methode
 [0]: 1
 [1]: 42
 [2]: 73
 [3]: 1337
Ausfuehren der Methode exampleMethod(anyArray) ...
Array nach dem Ausfuehren der Methode
 [0]: 2
 [1]: 43
 [2]: 74
 [3]: 1338

Man erkennt hier, dass das Übergabearray selbst verändert wurde, also nicht nur der Rückgabewert der Funktion die Veränderung enthält. Das liegt daran, dass der Funktion lediglich eine Referenz, also die Speicheradresse zum Array gegeben wurde und die Funktion auch tatsächlich auf diesem ausgeführt wurde.

Ein Gedanke zu “Java: Unterschied zwischen Wertesemantik und Referenzsemantik

  1. Ich bin mir nicht sicher ob du in deinem Artikel Werte/Verweissemantik mit Call by Value und Call by Reference verwechselt. Letztere regeln m.E. ob tatsächlichen Parameter kopiert oder Referncen an den Methodenrumpf übergeben werden. Erstere inwiefern Objekte oder Refernzen in Variablen (nach Zuweisungen) gespeichert werden. Grüßé

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert