1. Wprowadzenie
W różnych językach programowania istnieją dwa podstawowe sposoby przekazywania parametrów do funkcji (lub metod):
- Przez wartość: funkcja otrzymuje kopię wartości zmiennej. Jeśli funkcja zmienia parametr, oryginał się nie zmienia.
- Przez referencję: funkcja otrzymuje „referencję” (wskaźnik) do oryginalnego obiektu. Zmiany wewnątrz funkcji wpływają na oryginał.
W Java WSZYSTKIE parametry są przekazywane przez wartość!
Ale jest tu haczyk: gdy przekazujemy typy prymitywne (int, double, boolean itd.), kopiowana jest sama wartość. A gdy przekazujemy typy referencyjne (np. tablice lub obiekty), kopiowana jest wartość referencji (czyli „adres” obiektu w pamięci), a nie sam obiekt.
Jeśli czujesz, że mózg zaczyna się topić, spokojnie — za chwilę wszystko uporządkujemy i pokażemy na przykładach!
2. Typy prymitywne: kopiowana jest wartość
Zacznijmy od prostego przypadku: co się stanie, jeśli spróbujemy zmienić wartość zmiennej typu int wewnątrz metody?
public class Demo
{
public static void main(String[] args)
{
int number = 5;
changeValue(number);
System.out.println(number); // Co zostanie wypisane?
}
public static void changeValue(int n)
{
n = 42;
}
}
Wynik: na ekran trafi 5.
Dlaczego?
Do metody changeValue przekazywana jest KOPIA wartości zmiennej number. Wewnątrz metody zmieniamy tylko kopię (n), a oryginalna zmienna number w main pozostaje nienaruszona.
Analogia
Wyobraź sobie, że dałeś koledze kserokopię paszportu, a nie oryginał. Kolega może rysować wąsy na kopii ile chce — twój prawdziwy paszport pozostanie bez zmian!
3. Typy referencyjne: kopiowana jest referencja
Teraz spróbujmy tego samego z tablicą:
public class Demo
{
public static void main(String[] args)
{
int[] numbers = {1, 2, 3};
changeFirst(numbers);
System.out.println(numbers[0]); // Co zostanie wypisane?
}
public static void changeFirst(int[] arr)
{
arr[0] = 99;
}
}
Wynik: na ekran trafi 99.
Dlaczego?
Do metody changeFirst przekazywana jest kopia REFERENCJI do tablicy numbers. Obie nazwy (numbers w main i arr w metodzie) wskazują na tę samą tablicę w pamięci. Dlatego jeśli zmienimy element tablicy wewnątrz metody, zmiana jest „widoczna” na zewnątrz.
Wizualizacja
main: numbers ──► [1, 2, 3]
▲
│
changeFirst: arr ──┘
Zmieniając arr[0], w rzeczywistości zmieniamy numbers[0], ponieważ oba wskazują na ten sam obiekt.
4. Ale jeśli zmienisz samą referencję — obiekt źródłowy się nie zmieni!
Spróbujmy teraz zrobić tak:
public class Demo
{
public static void main(String[] args)
{
int[] numbers = {1, 2, 3};
replaceArray(numbers);
System.out.println(numbers[0]); // Co zostanie wypisane?
}
public static void replaceArray(int[] arr)
{
arr = new int[] {10, 20, 30};
}
}
Wynik: wypisze 1.
Dlaczego?
W metodzie replaceArray zmienna arr zaczyna wskazywać na nową tablicę, ale nie wpływa to w żaden sposób na zmienną numbers w main.
Były powiązane jedną referencją, lecz gdy wewnątrz metody przypisujemy arr = ..., jest to tylko lokalna zmiana.
Analogia
To tak, jakbyś ty i twój znajomy mieli ten sam klucz do mieszkania. Znajomy dorobił sobie nowy klucz, ale twój stary klucz pozostał taki sam, a drzwi są te same.
5. Przykłady na utrwalenie: nie da się „zamienić miejscami” dwóch int przez metodę
Wielu początkujących próbuje napisać metodę zamieniającą miejscami dwie liczby, ale im się to nie udaje:
public class Demo
{
public static void main(String[] args)
{
int a = 5, b = 10;
swap(a, b);
System.out.println(a + " " + b); // Oczekiwanie: 10 5, a w rzeczywistości?
}
public static void swap(int x, int y)
{
int temp = x;
x = y;
y = temp;
}
}
Wynik: na ekran trafi 5 10.
Dlaczego?
Ponieważ do swap trafiają kopie zmiennych a i b. Wszystko, co robimy wewnątrz swap, dotyczy tylko tych kopii, a nie oryginałów.
6. Ale w przypadku tablic można zmieniać zawartość!
Spójrzmy na poprawny sposób zamiany wartości w tablicy:
public class Demo
{
public static void main(String[] args)
{
int[] arr = {5, 10};
swap(arr);
System.out.println(arr[0] + " " + arr[1]); // Oczekiwanie: 10 5
}
public static void swap(int[] arr)
{
int temp = arr[0];
arr[0] = arr[1];
arr[1] = temp;
}
}
Wynik: wypisze 10 5.
Dlaczego?
Ponieważ zmieniamy zawartość obiektu (tablicy), na który wskazuje zarówno oryginalna zmienna, jak i parametr metody.
7. Demonstracja: obiekty i ich pola
Z tablicami już jasne. A co z obiektami własnych klas? Dokładnie tak samo!
class Box
{
int value;
}
public class Demo
{
public static void main(String[] args)
{
Box box = new Box();
box.value = 7;
changeBox(box);
System.out.println(box.value); // Co zostanie wypisane?
}
public static void changeBox(Box b)
{
b.value = 42;
}
}
Wynik: wypisze 42.
Dlaczego?
Ponieważ zmieniamy pole obiektu, na który wskazuje referencja.
Co dzieje się przy przekazywaniu parametrów do metod
| Co przekazujemy | Jak jest kopiowane | Czy można zmienić wewnątrz metody? | Czy zmiany są widoczne na zewnątrz? |
|---|---|---|---|
| int, double i in. | Kopia wartości | Tak, ale tylko kopię | Nie |
| Tablica | Kopia referencji | Tak, jeśli zmieniasz elementy | Tak |
| Obiekt (klasa) | Kopia referencji | Tak, jeśli zmieniasz pola | Tak |
| Tablica/obiekt | Kopia referencji | Nie, jeśli przypiszesz nową referencję | Nie |
8. Typowe błędy
Błąd nr 1: oczekiwanie, że int zmieni się po wywołaniu metody. Wielu początkujących sądzi, że jeśli przekaże do metody zmienną typu int, to można zmienić jej wartość wewnątrz metody. To nieprawda: zmienia się tylko kopia!
Błąd nr 2: próba zamiany miejscami dwóch int przez metodę. Jak już widzieliśmy, to niemożliwe — zamieniają się tylko kopie zmiennych, oryginały pozostają nietknięte.
Błąd nr 3: próba „podmiany” tablicy przez metodę. Jeśli wewnątrz metody przypiszesz parametrowi-tablicy nową tablicę (arr = new int[]{...}), nie wpłynie to na oryginalną tablicę. Aby „podmienić” tablicę — trzeba zwrócić nową tablicę z metody i przypisać ją zmiennej w kodzie wywołującym.
Błąd nr 4: zapominanie, że można zmieniać zawartość obiektu. Jeśli przekazujesz obiekt (lub tablicę) — pamiętaj, że wszelkie zmiany jego pól/elementów będą widoczne na zewnątrz!
Błąd nr 5: zamieszanie z null. Jeśli przekażesz do metody referencję równą null i spróbujesz odwołać się do jej pól — otrzymasz NullPointerException. Bądź ostrożny!
GO TO FULL VERSION