[an error occurred while processing this directive]

Call by Reference in Java: Einführung in Referenzbezeichner als Argument in Java im Rahmen der Lehre des Programmierens mit der Programmiersprache Java. [] (call by reference in java call by value variable, Parameter, Argumente, Referenz, Referenzen, Variable ), Lektion, Seite 721515
http://www.purl.org/stefan_ram/pub/java_referenzvariablen_als_argument_de (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram

Referenzeffekte in Java

Diese Lektion behandelt in ihrem zweiten Teil die Frage der Übergabe von Referenzargumenten an Parameter. Im ersten Teil wird dieses Thema zunächst mit der Erklärung allgemeiner Eigenschaften von Referenzen vorbereitet.

In Java  können Werte von Variablen oder Ausdrücken neben den primitiven Werten immer nur Referenzen auf Objekte  oder die Nullreferenz sein und nie Objekte selber. Dadurch ergeben sich einige Effekte, die in dieser Lektion beschrieben werden sollen.

Änderungen von Variablen

In der Methode "main" der Klasse "BindingModification" wird die Bindung der Inhalte der lokalen Variablen "s" verändert. Sie enthält zuerst eine Referenz auf einen Textspeicher mit dem Text "a" und später dann eine Referenz auf einen Textspeicher mit dem Text "b". So kommt die Ausgabe "ab" mit zwei verschiedenen Zeichen zustande, obwohl zweimal der gleiche Bezeichner als Argument der Ausgabeoperation angegeben ist.

BindingModification.java
public class BindingModification 
{ public static void main( final java.lang.String[] args ) 
{ final java.lang.StringBuilder a = new java.lang.StringBuilder( "a" ); 
final java.lang.StringBuilder b = new java.lang.StringBuilder( "b" ); 
java.lang.StringBuilder s; 
s = a; java.lang.System.out.print ( s ); 
s = b; java.lang.System.out.println( s ); }}

System.out
ab

Die Diagramme veranschaulichen die Situation nach den beiden Zuweisungen. Es ist die dargestellte Änderung der Referenz in einer Variablen, die durch den Variablenmodifizierer "final" verhindert worden wäre, der deswegen in dem Beispielprogramm fehlt.

In den Abbildungen dieser Lektion stellen die Rechtecke Variablen dar, eine Zahl oder ein Doppelkreuz "#" symbolisiert eine Referenz, und ein Kreis steht für ein Objekt.

Situation nach "s=a"
              Quelltextmodell : Laufzeitmodell                 _____ 
: .-' '-. 
: .' '. 
: / \  
: .---. ; ; 
s =======================>| 0 ------------>0| "a" | 
Bezeichner Bindung : '---' Referenz ; ; 
: Variable \ / 
: '. .' 
: '-._____.-'  
: Objekt  
: _____ 
: .-' '-. 
: .' '. 
: / \  
: ; ; 
: 1| "b" | 
: ; ; 
: \ / 
: '. .' 
: '-._____.-'  
: Objekt  
:

Situation nach "s=b"
              Quelltextmodell : Laufzeitmodell                 _____ 
: .-' '-. 
: .' '. 
Bezeichner : / \  
: .---. ; ; 
s =======================>| 1 | 0| "a" | 
Bindung : '-|-' ; ; 
: Vari|able \ / 
: | '. .' 
: | '-._____.-'  
: | Objekt  
: | _____ 
: | .-' '-. 
: | .' '. 
: | / \  
: | ; ; 
: '------------->1| "b" | 
: Referenz ; ; 
: \ / 
: '. .' 
: '-._____.-'  
: Objekt  
:

Änderungen von Objekten

Auch in der Methode "main" der Klasse "StateModification" wird durch zweifache Ausgabe des lokalen Bezeichners "s" Text »ab« ausgegeben, aber hier geschieht dies auf eine ganz andere Weise. Die Variable »s« enthält die ganze Zeit lang dieselbe Referenz, verweist also auf dasselbe Objekt. Zwischen den beiden Ausgabeoperationen wird hier nicht der Inhalt der Variablen »s«, sondern der Zustand des Objektes verändert, der von diesem Inhalte referenziert wird.

StateModification.java
public class StateModification 
{ public static void main( final java.lang.String[] args ) 
{ final java.lang.StringBuilder s = new java.lang.StringBuilder( "-" ); 
s.setCharAt( 0, 'a' ); java.lang.System.out.print ( s ); 
s.setCharAt( 0, 'b' ); java.lang.System.out.println( s ); }}

System.out
ab

Die Abbildungen stellen die Situation bei den beiden Ausgabeoperationen bildlich dar. Man erkennt, daß zwar der Zustand des von der Referenz in der Variablen referenzierten Objekts verändert wird, aber sich die Variable selber nicht verändert. Sie zeigt immer auf dasselbe Objekt. Daher konnte die Variable in diesem Programm sogar mit dem Variablenmodifizierer »final« deklariert werden. (Man vergleiche diese Situation mit der Situation, die in dem vorigen Abschnitt „Änderungen von Variablen “ beschrieben wurde.)

Situation nach "s.setCharAt( 0, 'a' )"
              Quelltextmodell : Laufzeitmodell                 _____ 
: .-' '-. 
: .' '. 
: / \  
: .---. ; ; 
s =======================>| # ------------->| "a" | 
Bezeichner Bindung : '---' Referenz ; ; 
: Variable \ / 
: '. .' 
: '-._____.-'  
: Objekt  
:

Situation nach "s.setCharAt( 0, 'b' )"
              Quelltextmodell : Laufzeitmodell                 _____ 
: .-' '-. 
: .' '. 
: / \  
: .---. ; ; 
s =======================>| # ------------->| "b" | 
Bezeichner Bindung : '---' Referenz ; ; 
: Variable \ / 
: '. .' 
: '-._____.-'  
: Objekt  
:

Aliaseffekte

In der Methode "main" der Klasse "Aliasing" wird eine Referenz auf einen Textspeicher einer Variablen "s" und gleichzeitig auch einer anderen Variablen "t" zugewiesen. Da beide Referenzen dasselbe Objekt  referenzieren, läßt dieses sich dann in dem Gültigkeitsbereich dieser beiden Bezeichner mit jedem der beiden Bezeichner  ansprechen. So wird das Objekt dann unter Verwendung des Bezeichners "t" verändert, aber unter Verwendung des Bezeichners "s" ausgelesen.

Aliasing.java
public class Aliasing 
{ public static void main( final java.lang.String[] args ) 
{ final java.lang.StringBuilder s = new java.lang.StringBuilder( "-" ); 
final java.lang.StringBuilder t = s; 
t.setCharAt( 0, 'a' ); java.lang.System.out.print ( s ); 
t.setCharAt( 0, 'b' ); java.lang.System.out.println( s ); }}

System.out
ab

Das Verwirrende daran kann es sein, daß man bei oberflächlicher Betrachtung vermeint, daß die Variable »s« zwischen den beiden Ausgaben nicht verändert wird und daher die beiden Ausgaben gleich sein müßten. Dieses Beispiel illustriert Anweisungen mit einer versteckten Wirkung, die beim Programmieren manchmal zu beobachten sind: Eine Veränderung eines Zustandes erfolgt durch eine Maßnahme, der man dies zunächst nicht ansieht. Hier hat die Anweisung »t.setCharAt( 0, 'b' );« die versteckte Wirkung der „Veränderung von s“. Die Wirkung ist versteckt, da der Name »s« in der Anweisung selber nicht vorkommt. Solche versteckten Wirkungen lassen sich nicht immer ganz vermeiden, aber sie können Ursachen für fehlerhafte Interpretationen von Programmen sein und damit zu Fehlern führen.

Solche Aliassituationen in einem Block wird man selten bewußt herstellen, weil sie die Situation nur unnötig verkomplizieren. Aliassituationen können aber unbeabsichtigt auftreten, etwa wenn ein Methode zwei Referenzen verarbeiten soll, und beispielsweise von dem einen referenzierten Objekt in das andere kopieren soll. Es könnte dabei aber auch einmal vorkommen, daß beide Referenzen dasselbe Objekt referenzieren. Die Methode sollte sich dann auch in diesem Fall so wie beabsichtigt (wie gewünscht) verhalten. Der Programmierer muß also stets auch an die Möglichkeit denken, daß zwei auf verschiedenen Wegen erhaltene Referenzen einander gleich sein könnten.

Die beiden Abbildungen zum Aliaseffekt machen die Situation noch einmal deutlich.

Situation nach "t.setCharAt( 0, 'a' )"
              Quelltextmodell : Laufzeitmodell       
:  
: .---. 
t =======================>| # -----------------------. 
Bezeichner Bindung : '---' Referenz | 
: Variable | 
: __V__ 
: .-' '-. 
: .' '. 
: / \  
: .---. ; ; 
s =======================>| # ------------->| "a" | 
Bezeichner Bindung : '---' Referenz ; ; 
: Variable \ / 
: '. .' 
: '-._____.-'  
: Objekt

Situation nach "t.setCharAt( 0, 'b' )"
              Quelltextmodell : Laufzeitmodell       
:  
: .---. 
t =======================>| # -----------------------. 
Bezeichner Bindung : '---' Referenz | 
: Variable | 
: __V__ 
: .-' '-. 
: .' '. 
: / \  
: .---. ; ; 
s =======================>| # ------------->| "b" | 
Bezeichner Bindung : '---' Referenz ; ; 
: Variable \ / 
: '. .' 
: '-._____.-'  
: Objekt

Referenzvariablen und Referenzwerte (Referenzen)

Referenzvariablen  und Referenzwerte  werden oft nicht deutlich unterschieden, indem beide einfach nur als „Referenzen“ bezeichnet werden. Eine „Referenz“ ist aber eigentlich ein Referenzwert  und keine Referenzvariable.

Ein Referenzwert —oder, kurz, eine Referenz —ist eine Angabe, die es ermöglicht, ein bestimmtes Objekt zu erreichen (oder es ist die Nullreferenz).

Eine Referenzvariable  ist eine Speicher, der zu einem Zeitpunkt genau einen Referenzwert enthalten kann, aber im Laufe der Zeit kann sich der enthaltene Referenzwert auch verändern.

Den Unterschied zwischen Referenzvariablen und Referenzwerten  kann man jetzt verstehen, wenn man daran denkt, daß im Abschnitt „Änderungen von Variablen dieselbe  Referenzvariable zwei verschiedene  Referenzwerte enthielt (zu zwei verschiedenen Zeitpunkten). Umgekehrt findet man in dem letzten Abschnitt „Aliaseffekte “ die Situation dargestellt, daß zwei verschiedene  Referenzvariablen denselben  Referenzwert enthalten. (Zum Verständnis dieses Unterschiedes betrachte man noch einmal die Schaubilder dieser beiden Abschnitte.)

Referenzargumente

In Java  werden Argumente eines Aufrufs immer als Wert  an die angewendete Methode übergeben. (Das bedeutet, daß der Parameter an der Wert des jeweiligen Argumentausdrucks gebunden wird.) Dies gilt sowohl für Argumentwerte primitiver Typen  (wie des Typs "int") als auch für Argumentwerte von Referenztypen, also beispielsweise, wenn ein Bezeichner als Argument verwendet wird, dessen Typ eine Klasse ist.

Eine Variable enthält einen Wert  eines primitiven Typs oder eine Referenz, aber nie ein Objekt  selber. Daher werden also stets primitive Werte  oder Referenzen, aber nie Objekte  übergegeben. (Primitive Werte und Referenzwerte werden manchmal zusammen auch als „Skalare“ bezeichnet.)

Nach der Anweisung "java.lang.StringBuilder va = new java.lang.StringBuilder();" enthält die Variable "va" beispielsweise kein  Objekt, sondern eine Referenz auf ein Objekt.

Der Bezeichner "va" hat den Typ "java.lang.StringBuilder" und ist an eine Variable gebunden, die eine Referenz auf ein Objekt der Klasse "java.lang.StringBuilder" enthalten kann—diese Variable enthält tatsächlich eine Referenz auf ein soeben erzeugtes Objekt der Klasse "java.lang.StringBuilder".

Ein Referenzvariablenbezeichner bezieht sich auf ein Objekt


Quelltextmodell : Laufzeitmodell 



: Objekt 
: _____ 
: .-' '-. 
: .' '. 
: / \  
: .---. ; ; 
va ======================>| # ------------->| | 
Bezeichner Bindung : '---' Referenz ; ; 
: Variable \ / 
: '. .' 
: '-._____.-'  
:  
:  
Typ: : Typ: 
java.lang.StringBuilder : java.lang.StringBuilder 



:

Änderung eines Objekts

In der Übersetzungseinheit "RefArg.java" wird in der Methode "main" ein Objekt der Klasse "java.lang.StringBuilder" erzeugt. Die Methode "modify" kann dann mit der Variablen "va" aufgerufen werden, die eine Referenz auf dieses Objekt enthält. Da die Methode "modify" über diese Referenz auf das Objekt zugreifen kann, ist sie auch in der Lage, es zu verändern. Das liegt aber nicht  daran, daß etwa eine Referenz auf die Variable  "va" übergeben wurde: Es wurde vielmehr auch in diesem Fall der Wert der Variablen  "va" übergeben, nur daß dieser Wert eben eine Referenz auf ein Objekt ist.

RefArg.java
public class RefArg  
{  
public static void show( final java.lang.StringBuilder vc ) 
{ System.out.println( vc ); }
public static void modify( final java.lang.StringBuilder vb ) 
{ vb.setCharAt( 0, 'b' ); }
public static void main( final String[] args ) 
{ final java.lang.StringBuilder va =  
new java.lang.StringBuilder( "a" );
show( va ); modify( va ); show( va ); }}

System.out

b

Die zweite Ausgabe des von der Variablen "va" referenzierten Objektes zeigt, daß dieses Objekt von der Methode "modify" verändert werden konnte und, daß diese Veränderung auch in der aufrufenden Methode "main" beobachtbar ist.

Die Variable "va" enthält eine Referenz auf das erzeugte Objekt  und die Methode "modify" erhält eine Kopie dieses Wertes, also eine Kopie dieser Referenz  auf das erzeugte Objekt, aber keine Kopie dieses Objektes. Bei Eintritt in den Rumpf der Methode "modify" haben die Variable "va" und die Variable "vb" nun denselben  Wert, sie referenzieren beide dasselbe  Objekt "O".

Zwei Referenzen auf dasselbe Objekt
              Quelltextmodell : Laufzeitmodell       

Gültigkeitsbereich "main" :  
: .---. 
va =======================>| # -----------------------. 
Bezeicher Bindung : '---' Referenz | 
: Variable | 
..............................: __V__ 
: .-' '-. 
: .' '. 
Gültigkeitsbereich "modify" : / \  
: .---. ; ; 
vb =======================>| # ------------->| "b" | 
Bezeichner Bindung : '---' Referenz ; ; 
: Variable \ / 
: vb.setCharAt( 0, 'b' ) '. .' 
: -------------------------> '-._____.-'  
: Operation Objekt

Nachbildung der Übergabe von Objektwerten (Call-by-Object-Value )

Wenn Änderungen an einem Objekt nicht möglich sein sollen, so kann entweder eine Kopie des Objekts (ein Clone ) übergeben werden oder ein unveränderliches Objekt verwendet werden.

Nachbildung der Variablenübergabe (Call-by-Reference )

Die Übergabe einer Variablen, so daß deren Inhalt verändert werden kann, kann auf verschiedenen Wegen nachgebildet werden, etwas durch Übergabe einer Reihung. Dabei entspricht die Reihung der Variablen, und dem Inhalt der Variablen entspricht der Inhalt der ersten Komponente der Reihung.

Main.java
public class Main
{ static void method( final java.lang.String[] variable )
{ variable[ 0 ]= "beta"; } public static void main( final java.lang.String[] commandLineArguments )
{ final java.lang.String[] variable = new java.lang.String[]{ "alpha" };
method( variable );
java.lang.System.out.println( variable[ 0 ] ); }}

Änderung trotz des Schlüsselwortes "final"

Man beachte, daß auch das Schlüsselwort "final" in der Parameterdeklaration "final java.lang.StringBuilder vb" die Veränderung des von der Variablen "vb" referenzierten Objekts nicht untersagt. Dieses "final" untersagt nur Veränderungen des Inhalts der Variablen "vb", also das Schreiben eines neuen Referenzwertes in die Variable "vb", nicht jedoch Veränderungen des von der enthaltenen Referenz referenzierten Objektes. (Um solche Veränderungen zu verhindern, kann eine Schnittstelle [oder Klasse] verwendet werden, die keine Methoden enthält, deren Aufruf solche Veränderungen vornehmen kann.)

Änderung einer Referenzvariablen

Wird der Inhalt der Variablen "vb" in der Methode "modify", z.B. durch "vb = new java.lang.StringBuilder();" jedoch so verändert, daß er ein anderes  Objekt referenziert, dann wird das von der Variablen "va" in der aufrufenden Methode "main" referenzierte Objekt durch anschließende Zugriffe auf das von dem Parameter "vb" referenzierte Objekt natürlich auch nicht mehr  verändert.

Die Zuweisung eines Wertes an den Parameter "vb" in der Methode "modify" verändert also nicht  das bisher von dem Parameter "vb" referenzierte Objekt "Objekt 0".

RefArg1.java
public class RefArg1 
{
public static void show( final java.lang.StringBuilder vc ) 
{ System.out.println( vc ); }
public static void modifyParameter( java.lang.StringBuilder vb ) 
{ vb = /* vb erhaelt eine Referenz auf ein neues Objekt 1 */ 
new java.lang.StringBuilder( "a" ); /* Objekt 1 wird erzeugt */ 
vb.setCharAt( 0, 'b' ); /* Stelle 0, aendert Objekt 1 */ }
public static void main ( final String[] args ) 
{ final java.lang.StringBuilder va =  
new java.lang.StringBuilder( "a" ); /* Objekt 0 wird erzeugt */
show( va ); modifyParameter( va ); show( va ); }}

System.out

a

Wenn die Kontrolle die mit dem Kommentar "/* Stelle 0, aendert Objekt 1 */" markierte Zeile erreicht, dann ist der Parameter "vb" an ein anderes  Objekt "Objekt 1" gebunden worden, und die Zugriffe auf dieses Objekt können das erste Objekt "Objekt 0" dann natürlich nicht mehr verändern.

Die beiden Variablen referenzieren an Stelle 0 verschiedene Objekte
              Quelltextmodell : Laufzeitmodell                 _____ 
: .-' '-. 
Gültigkeitsbereich "main" : .' '. 
: / \  
: .---. ; ; 
va =======================>| 0 ------------>0| "a" | 
Bezeichner Bindung : '---' Referenz ; ; 
: Variable \ / 
: '. .' 
..............................: '-._____.-'  
: Objekt  
: _____ 
: .-' '-. 
am Ende von "modifyParameter" : .' '. 
: / \  
: .---. ; ; 
vb =======================>| 1 ------------>1| "b" | 
Bezeichner Bindung : '---' Referenz ; ; 
: Variable \ / 
: vb.setCharAt( 0, 'b' ) '. .' 
: -------------------------> '-._____.-'  
: Operation Objekt

Der Parameter "vb" wird in der Methodendeklaration verändert, indem ihm eine neue Referenz zugewiesen wird. So etwas ist selten sinnvoll, da ein Parameter die Werte der Argumente enthalten sollte und diese Voraussetzung nicht mehr erfüllt wäre, wenn er verändert werden würde. Daher sollten Parameter in der Regel mit "final" deklariert werden, das hat auch den Vorteil, daß der Übersetzer dann bei einer möglicherweise nicht den Absichten des Autors entsprechenden Zuweisung (wie in der Methode "modifyParameter") eine Fehlermeldung anzeigte, die den Programmierer auf die fragwürdige Zuweisung aufmerksam machte.

modifyParameter [Methodendeklaration]
public static void modifyParameter( java.lang.StringBuilder vb ) 

vb = /* vb erhaelt eine Referenz auf ein neues Objekt 1 * 
new java.lang.StringBuilder( "a" ); /* Objekt 1 wird erzeugt */
vb.setCharAt( 0, 'b' ); /* Stelle 0, aendert Objekt 1 */ 
}

Das hier für den Parameter "vb" Gesagte gilt entsprechend auch für andere Typen von Variablenbezeichnern.

Nullreferenzen

Bei Ausdrücken mit »null« ist es offensichtlich, daß eine Nullreferenz verwendet wird. Bei Parametern ist dies jedoch auch immer möglich, ohne daß es so offensichtlich ist.

Für jede Operation sollte spezifiziert sein, ob eine Nullreferenz als Argument zulässig ist und welches Verhalten die Operation dann haben soll. Ist eine Nullreferenz nicht zulässig, dann muß der Aufrufer einer Operation diese unbedingt vermeiden. Ist sie zulässig, dann muß die Operation sicherstellen, daß sie auch bei Übergabe einer Nullreferenz das spezifizierte Verhalten zeigt.

Quellen zum Thema

Parameter passing in Java —by reference or by value?
http://www.yoda.arachsys.com/java/passing.html
Pass-by-value semantics in Java  applications
http://www-128.ibm.com/developerworks/java/library/j-passbyval/
Pass-by-value semantics in Java applications
http://www-128.ibm.com/developerworks/library/j-praxis/pr1.html
Fun with pointers
http://cslibrary.stanford.edu/104/

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Von der Stefan-Ram-Startseite ausgehend finden sich oft noch mehr Informationen zu Themen, die auf einer Seite angesprochen wurden. (Eine Verbindung zur Stefan-Ram-Startseite befindet sich ganz oben auf dieser Seite.)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. slrprd, PbclevtugFgrsnaEnz