double
-Feld
als Parameter und einer double
-Zahl als Rückgabewert.
static double mittelwert(double[] x){ int i, n; n=x.length; double w=0; for (i=0;i<n;i++) w+=x[i]; w/=n; return w; }//Ende mittelwert()
i, n, w
, aber auch der
Parameter x sind lokale Variable dieser Methode.
Für sie wird erst Platz geschaffen, wenn die Methode aufgerufen
wird. Vorher und nachher sind diese Variablen bzw. ihre Namen unbekannt.
(Globale) Variable der Applikationsklasse (diese müssen
als static
deklariert werden), die diese Methode enthält,
können ohne weiteres dieselben Bezeichner als die
lokalen Variablen haben (auch wenn dies nicht immer sinnvoll ist):
Wir haben hier unnötiger Weise eine globale und eine lokale Variable gleichen Namensclass MW{ static double v, w; static double mittelwert(double[] x){ int i, n; n=x.length; double w=0; for (i=0;i<n;i++) w+=x[i]; w/=n; return w; //w=berechneter Mittelwert }//Ende mittelwert() static void main(String[] args){ double[] y={1, 2.1, 3}; w=y[0]; //hat nichts mit dem Mittelwert zu tun v=mittelwert(y); //Hier wird der Mittelwert mit v bezeichnet System.out.println("w="+w+" Mittelwert="+v); //Ausgabe: w=1.0 Mittelwert=2.033333333333333 }//Ende main() }//Ende class MW
w
, die unterschiedliche Werte haben. Ausgegeben wird im
main()
-Block die globale Variable. Die lokalen Variablen der Methode
mittelwert()
sind im main()
-Block unbekannt. Wollte man auf
sie (z.B. auf n
) im main()
-Block zugreifen, so gäbe es eine Syntax-Fehlermeldung
Undefined variable: n
.
Globale Variable können aber sehr wohl in einem Methodenblock verändert werden, wenn es keine lokalen Variablen gleichen Namens gibt. Denselben Zweck wie das letzte Programm erfüllt
Dieses Programm ist sogar kürzer, aber weniger gut strukturiert. Der Mittelwert wird jetzt implizit berechnet, die Methodeclass MW2{ static double w;//global static void mittelwert(double[] x){ int i, n; n=x.length; for (i=0;i<n;i++) w+=x[i]; w/=n; }//Ende mittelwert() static void main(String[] args){ double[] y={1, 2.1, 3}; mittelwert(y); System.out.println("Mittelwert="+w); //Ausgabe: Mittelwert=2.033333333333333 }//Ende main() }//Ende class MW2
mittelwert()
hat keinen Rückgabewert.
Übrigens hätte die lokale Feldvariable double[]
y
der Methode main()
durchaus den gleichen Namen wie die Parametervariable
double[] x
der Methode mittelwert() haben können, ohne
dass hierdurch Speicherplatz gespart worden wäre!
Jetzt machen wir Gleiches mit Zeichenketten und Feldern:class lokal{ static void lift(double x){ x+=5; } static void main(String[] args){ double y=1; lift(y); System.out.println(y); //Ergebis: 1, nicht 6 } }
Nur bei demclass lokal{ static void lift(double x){ x+=5; } static void lift(String s){ s+=" !!!!"; } static void lift(double[] x){ for (int i=0;i<x.length;i++) x[i]+=5; } static void main(String[] args){ double y=1; lift(y); System.out.println(y); String s="Java"; lift(s); System.out.println(s); double[] z={1,2,3}; lift(z); for(int i=0;i<3;i++) System.out.println(z[i]); } }
double
- Feld wirkt sich die Methode lift()
aus!
(Übrigens: Es gibt hier drei verschiedene Methoden gleichen
Namens: man spricht von Überladung!). Der Grund
hierfür soll jetzt erarbeitet werden:
x
ist eine lokale double
-Variable der Methode
lift(double x)
, während y
eine lokale double
-Variable
der Methode main()
ist. Bei Aufruf von
lift(y);
wird y
(inkl. Wert) kopiert,
die Kopie wird mit x
bezeichnet und deren Wert (aber
nicht der von y
!) um 5
erhöht. Dies bleibt ohne Folgen nach Beendigung des
Methodenaufrufs! Aus dem gleichen Grund bleibt auch die Stringvariable
s
unbeeindruckt von lift(s)
, hier wird die
(Zeiger-)Variable samt Inhalt kopiert. Nur das
komponentenweise Liften des double-Feldes z
hat
geklappt. Warum? Wieder wird bei Aufruf von lift(z)
eine
Kopie von z
namens x
angelegt - aber nur von
der Zeigervariablen, nicht von dem Inhalt, auf den sie zeigt.
Beide Variable - das Original und die Kopie - zeigen auf denselben
Inhalt, nämlich dem
von z
. Im Methodenblock von lift(double[] x)
wird nun der Inhalt von x
verändert und damit
gleichzeitig der von z
.
Jetzt wenden wir uns den Parametern einer Methode genauer zu wie dem
Parameter double[] x
in der oberen Methode mittelwert()
. Wir wollen
eine Methode diskutieren, die das Feld vom Kopf auf die
Füße stellt:
Der invertierte Vektor ist hier der Rückgabewert der Methodeclass KopfFuss{ static double[] umkehren(double[] x){ int i, n; //lokale Variablen von umkehren() n=x.length; double[] w=new double[n]; for (i=0;i<n;i++) w[i]=x[n-i-1]; return w; }//Ende umkehren() static void main(String[] args){ double[] y={1, 2.1, 3}; y=umkehren(y); //y={3, 2.1, 1} for (int i=0;i<3;i++) System.out.println(y[i]); }//Ende main() }//Ende class KopfFuss
umkehren()
. Die Zeile y=umkehren(y);
überschreibt das Feld y
durch das
invertierte. Alles klappt wie es soll.
Alternativ kann man auch versuchen, keinen
Rückgabewert vorzusehen und den Parameter double[] x
der Methode zu überschreiben. Das sähe so aus:
Dies klappt in dem Sinne nicht, dass die Anweisung umkehren(y); gar keine Umkehr bewirkt! Der Inhalt der lokalenclass KopfFuss2{ static void umkehren(double[] x){ int i, n; n=x.length; double[] w=new double[n]; // x und w sind lokale Variable dieser Methode for (i=0;i<n;i++) w[i]=x[n-i-1]; x=w; //bleibt ohne Wirkung nach außen! }//Ende umkehren() static void main(String[] args){ double[] y={1, 2.1, 3}; umkehren(y); //tut nicht, was es soll! for (int i=0;i<3;i++) System.out.println(y[i]); }//Ende main() }//Ende class KopfFuss2
double[]
-Variable w
des
Methodenblocks von umkehren()
hat zwar die
gewünschte Gestalt und
die letzte Zeile der Methode (x=w;)
bewirkt, dass die lokale double[]
-Variable
x
(Parameter von umkehren()
auf den
"richtigen" Inhalt zeigt, aber von dem
double[]
-Parameter y
(einer
Zeigervariable!) beim Aufruf von
umkehren(y);
wird nur eine Kopie angelegt, die nach Abarbeit der
Methode gelöscht wird: y
zeigt immer noch auf den
alten Inhalt.
Um die Verwirrung zu vergrößern: Das Programm
leistet das, was es soll! Offensichtlich sind die beiden Zeilen for (i=0; i<n;i++) x[i]=w[i]; und x=w; nicht gleichwertig. Um dies zu verstehen, muss verstanden werden, was call by value und Zeigervariable bedeuten:class KopfFuss3{ static void umkehren(double[] x){ int i, n; n=x.length; double[] w=new double[n]; for (i=0;i<n;i++) w[i]=x[n-i-1]; for (i=0; i<n;i++) x[i]=w[i]; }//Ende umkehren() static void main(String[] args){ double[] y={1, 2.1, 3}; umkehren(y); for (int i=0;i<3;i++) System.out.println(y[i]); }//Ende main() }//Ende class KopfFuss3
Grundsätzlich werden Methodenparameter beim Aufruf einer Methode
stets kopiert, sie werden zu einem
lokalen Parameter der Methode. Nach
Beendigung der Methode wird die Kopie wieder gelöscht. Bei
Feldern (wie auch bei anderen Objekten) sind die zugehörigen
Variablen jedoch Zeigervariable. Ich
wiederhole (s. auch Kap.4.3): Das
muss man sich so vorstellen, dass eine solche Variable ihren
Bezeichner abspeichert und als Information den Ort des Speicherplatzes
enthält, wo das Objekt (hier das gesamte Feld, sein Inhalt) abgespeichert wird. Die Variable
zeigt also nur auf den Ort des eigentlichen Inhalts. Die
Zeigervariable beansprucht vergleichsweise wenig Speicher im Gegensatz
zu dem (vielleicht sehr langen) Feld.
Beim Aufruf von umkehren(y);
in der Klasse
KopfFuss2
wird zunächst Platz gemacht für eine lokale
Zeigervariable namens x
und mit der Zeigervariablen
y
des main()
-Blocks belegt - x
ist eine Kopie von
y
. Beide zeigen jetzt auf denselben Speicher, wo das double
-Feld der
Länge drei gespeichert ist. Sodann wird innerhalb der Methode
eine Zeigervariable namens w
deklariert, die nach ihrer
Initialisierung ebenfalls auf ein double
-Feld der
Länge drei zeigt. Dieser Speicherbereich wird danach durch den
invertierten Vektor besetzt (nach der Initialisierung mit Hilfe des
new-Operators haben alle Komponenten
den Wert Null). Die Zeile x=w;
verändert jetzt nur
die Zeigervariable x
: sie zeigt jetzt auf den Inhalt von
w
, der den gewünschten invertierten Vektor
enthält, und nicht mehr auf den Inhalt von y
. Nach
Beendigung der Methode jedoch stehen die Zeigervariablen x,
w
sowie der Inhalt von w
nicht mehr zur Verfügung, sie wurden "gelöscht", die
Information ging verloren.
Anders in der Klasse KopfFuss3.java
. Hier wird durch for (i=0; i<n;i++) x[i]=w[i]; der Inhalt
von x
und damit auch der von y
in der
gewünschten Weise
verändert. Nach Beendigung der Methode bleibt es bei diesem
invertierten Inhalt von y
.
Das Anlegen einer Kopie der beim Aufruf der Methode verwendeten Methodenparameter wird mit call by value bezeichnet, während ein call by reference es ermöglicht, eine Veränderung des Parameters innerhalb des Methodenblocks zu bewirken, die auch nach Beendigung der Methode wirksam bleibt. Sind Parameter einer Methode Zeigervariable, so ist es trotz des call by value-Prinzips möglich, die Inhalte der Speicherbereiche, auf die der Zeiger zeigt, zu verändern. Die Wirkung der Methode ist dann - was den Inhalt betrifft - identisch mit einer Call by reference-Wirkung. Man nennt daher Zeigervariable auch Referenzvariable.
Im Gegensatz zu C++ "verschleiert" Java die Tatsache, dass alle Objektvariable (Sie kennen bisher nur Felder!) Zeigervariable sind. Dadurch bleibt der unerfahrenen ProgrammiererIn manche Wirkung kryptisch, z.B. die Fehlermeldung java.lang.NullpointerException.
Variable vom Grunddatentyp sind keine Zeigervariable, sie sind unauflöslich mit ihrem Inhalt verbunden. Treten sie als Parameter einer Methode auf, wird stets eine Kopie angelegt und alle Änderungen betreffen nur die Kopie, niemals die Variable, die an die Methode übergeben wurde. So bleibt die folgende Methode, obwohl syntaktisch richtig, wirkungslos:
Es gibt im Gegensatz anderer Programmiersprachen wie Pascal und C keine einfache Möglichkeit, Variable eines Grunddatentyps als Referenzvariable zu behandeln. Vor einem Jahr hatte ich einen Preis für ein einfaches Vertausche-Programm ausgesetzt - ohne Ergebnis.//Bsp Vertausche static void vertausche(double x, double y){ double h=x; x=y; y=h; }//Ende vertausche()
Einen letzten Satz zu String-Variablen. Man kann durch sie eine NullPointerException hervorrufen, was auf den Zeigercharakter hinweist. Was jedoch ihre Verwendung als Paramter von Methoden betrifft, ähneln sie eher den Grunddatentypen: es wird eine Kopie auch des Inhalts angelegt, d.h. (Zeiger-)Variable und Inhalt sind nicht zu trennen (anders bei der Klasse StringBuffer.class VertauscheFelder{ static void vertausche(double[] x, double[] y){ int n=x.length; for (int i=0;i<n;i++){ double h=x[i]; x[i]=y[i]; y[i]=h; } }//Ende vertausche() static void main(String[] args){ double[] x={1,2}; double[] y={-1,-2}; vertausche(x,y); System.out.println("x(0]="+x[0]+" x[1]="+x[1]); }//Ende main() }//Ende class
Dieser Abschnitt ist für ProgrammieranfängerInnen nicht einfach. Also nicht verzagen, wenn Sie ihn nicht auf Anhieb verstehen. Es wird noch viele Gelegenheiten geben, auf das Phänomen Zeiger- oder Referenzvariable hinzuweisen.
Nehmen wir an, wir wollen mit Hilfe einer Methode die p
-te Wurzel aus einer Zahl
x
ziehen. Die Parameter der Methode wären dann
x
und p
. Wir erwarten jedoch, dass der
Nutzer dieser Methode in den meisten Fällen eine Quadratwurzel
(p=2
) ziehen will. Für diesen Fall stellen wir eine
Methode gleichen Namens mit nur einem Parameter zur Verfügung:
Jetzt kann die Methode//Wurzel static double wurzel(double x){ return Math.sqrt(x); }//Ende 1 static double wurzel(double x, int p){ return Math.pow(x,(double) 1/p);//Casting!! }//Ende 2
wurzel()
mit einem oder auch mit zwei
Parametern aufgerufen werden! Man sagt, die Methode 2 hat die erste
überladen - allerdings behält die erste Methode ihre
Gültigkeit, beide können gleichberechtigt eingesetzt werden.