Parameteröverföring och returvärden
Denna minilektion beskriver hur information överförs till och från metoder. Överföringen till metoder sker genom parametrar och överföringen från metoder genom returvärden.
Parameteröverföring
Parametrar används till föra över information från den som anropar en metod till själva
metoden.
Till skillnad från en del andra programmeringsspråk (t ex C++) finns det bara en mekanism
för parameteröverföring i Java.
Mekanismen kallas "värdeanrop" ("call by value").
Effekten är dock olika beroende på om
parametern är av primitiv typ (int
, double
,
char
, ...) eller av referenstyp (array, String
, Turtle
, ...).
Värdeanrop innebär att värdet av argumentet (den aktuella parametern) dvs det som står i metodanropet först ska beräknas.
Antag t ex att vi har satsen
Argumentet dvs uttrycket (int)(Math.random()*10) + 4
kommer först att beräknas och resultera i ett heltalsvärde som,
beroende på vad
Math.random()
returnerar, ligger mellan 4 och 14.
Det resulterande värdet kopieras till den
formella parametern (dvs den som är deklarerad i metodhuvudet).
Därefter fungerar den formella parametern precis som en lokal variabel i metoden
dvs den kan både
refereras och ändras. Inget som görs med den parametern kan påverka
argumentet.
Således:
När parametern är en primitiv datatyp
Exempel: Givet följande metod metod:
Processen vid det första anropet till
increase
kan illustreras nedan.
Rutorna till höger innehåller koden utan utskriftssatser.
Den röda pilen visar var exekveringen är.
Före första anropet ser det ut så här: |
![]() |
public static void main(...)
int p = 1;
→
increase(p);
increase(5*p+10);
}
void increase(int p) {
p = 2*p + 1;
}
|
När vi kommit in i increase ser det ut så här.
Den tjocka, röda pilen visar överföringen av parametervärdet.
|
![]() |
public static void main(...)
int p = 1;
increase(p);
increase(5*p+10);
}
void increase(int p) {
→
p = 2*p + 1;
}
|
Vid utgången av increase ser det ut så här:
|
![]() |
public static void main(...)
int p = 1;
increase(p);
increase(5*p+10);
}
void increase(int p) {
p = 2*p + 1;
→
}
|
När vi lämnat increase så ser det ut
som före anropet.
|
![]() |
public static void main(...)
int p = 1;
increase(p);
→
increase(5*p+10);
}
void increase(int p) {
p = 2*p + 1;
}
|
Slutsats: |
Ingenting som vi gör med formella parametern påverkar argumentet (den aktuella parametern) eller
något annat i den anropande koden.
En effekt av detta är att det inte går att skicka tillbaka någon information till anroparen med hjälp av sådana parametrar. |
När parametern är en referenstyp
Samma mekanism används dvs den aktuella parameterns värde kopieras till den formella. Här måste man komma ihåg att parametern inte innehåller själva objektet utan är en referens (adress, pekare) till objektet. Det är således adressen som kopieras och inte objektet. Om metoden ändrar i objektet så har det alltså effekt i omvärlden.
Följande artificiella exempel avser illustrera detta
Vi nu lägger till och kör följande main
-program i klassen:
Kod | Utskrift |
---|---|
public static void main(String[] args) { A p = new A(); System.out.println("p before first call: " + p); increase(p, 3); System.out.println("p after first call : " + p); increase(p, 5); System.out.println("p after second call: " + p); } | p before first call: (1, 2) p after first call : (4, 5) p after second call: (9, 10) |
Nedan är en grafisk illustration av förloppet. Koden bantad på "ointressanta" delar.
Situationen när vi kommit in i
increase
för första gången.
|
![]() |
public class A { private int x = 1; private int y = 2; public void increase(A p, int i) { → p.x += i; p.y += i; p = null; // Just for fun i++; // Just for fun } } public static void main(...) { A p = new A(); increase(p, 3); increase(p, 5); } |
Omedelbart före return från increase
ser det ut så här:
Förändrade värden är inringade med rött. |
![]() |
public class A { private int x = 1; private int y = 2; public void increase(A p, int i) { p.x += i; p.y += i; p = null; // Just for fun i++; // Just for fun → } } public static void main(...) { A p = new A(); increase(p, 3); increase(p, 5); } |
Observera att variablerna hör till (existerar i) en metod medan objektet inte gör det. Objekten ligger i en värld för sig och är åtkomliga för alla som har referenser till dem.
Arrayer
Eftersom arrayer är objekt och hanteras med referenser kan en metod som tar emot en array ändra i denna och detta har effekt i omvärlden som nästa exempel visar.
Antag att vi har följande metod som tar emot en array-referens som parameter och negerar värden i arrayen:
Antag att vi har följande kod (i main
eller i någon annan metod):
När satsenint b[] = {1, 2, 3, 4}; är utför ser det ut så här: |
![]() |
När vi kommit in i negate men innan
for -satsen påbörjats ser det ut så här:
|
![]() |
Så här ser det ut vid slutet av negate :
|
![]() |
När vi lämnat negate :
|
![]() |
Instansvariabler som initieras med objekt från parametrar
Om man skickar en objektreferens till en klass och klassen sparar den i en instansvariabel uppstår en speciell situation. Även om instansvariabeln är deklareradprivate
kan alla
som har en referens till objektet påverka detta.
Exempel: | public class B { private String name; private int[] arr; public B(String name, int[] arr) { this.name = name; this.arr = arr; } } |
Antag att vi har följande kod t ex i main :
|
String n = "Kim"; int[] a = {1, 2, 3}; B b = new B(n, a); |
Omdelbart före anropet av konstruktorn ser det ut så här:
I objektvärden finns således ett |
![]() |
String n = "Kim";
int[] a = {1, 2, 3};
→
B b = new B(n, a);
|
Omedelbart efter anropet av konstruktorn ser det ut så här:
Nu har ett |
![]() |
String n = "Kim";
int[] a = {1, 2, 3};
B b = new B(n, a);
→
|
Med denna konstruktor har klassen B
ingen kontroll över vad som händer
med arrayen trots att instansvariabeln är deklarerad private
.
Alla i hela världen som har en referens till array-objektet kan ändra i det.
Om man vill garantera att någon utifrån inte kan peta på arrayen måste man i konstruktorn göra en kopia av den: |
public B(String name, int[] arr) {
this.name = name;
this.arr = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
this.arr[i] = arr[i];
}
}
|
Med denna konstruktor blir den avslutande bilden så här: |
![]() |
String
-objekt är oföränderliga behöver inte name
kopieras.
Om arrayparametern i sin tur innehåller objekt (t ex Turtle[]
) så måste man kopiera
även dessa för att vara säker på att ingen utifrån kommer åt dem. Detta kallas djup kopiering.
Observera också att det inte är säkert att man alltid vill göra djup kopiering. Om man t ex har objekt med personuppgifter vill man troligen inte manipulera kopior dessa (man vill t ex inte göra en adressändring i en kopia av ett personobjekt). Att bara kopiera referensen kallas grund kopiering.
Returvärden
Returvärden hanteras på ett liknande sätt: värdet av uttrycket som står ireturn
-satsen
kopieras ut till mottagaren. Om metoden returnerar en referens till ett objekt som också
refereras av en instansvariabel måste man vara medveten om att objektet utelämnas till anroparens våld
även om instansvariabeln är private
.
Exempel: Antag att vi vill ha metoden int[] getArr()
i klassen B
ovan.
Två varianter:
public int[] getArr() {
return this.arr;
}
|
Lämnar ut instansvariabelns objekt till omvärldens godtycke (grund kopering).
|
public int[] getArr() {
int[] result = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
result[i] = arr[i];
}
return result;
}
|
Ett "säkert" utlämnande. Gör en kopia av
arrayen och lämnar ut referensen till den.
Instansvariabelns objekt fortfarande skyddat (djup kopiering).
|
Det är viktigt att förstå skillnaden mellan dessa. Vilken variant man använder beror på vad man vill uppnå.
En getName
-metod kan riskfritt skrivas som
eftersom String
-objekt är oföränderliga.