1. Вступ
У різних мовах програмування існує два основних способи передавання параметрів у функції чи методи:
- За значенням: функція отримує копію значення змінної. Якщо функція змінює параметр, оригінал не змінюється.
- За посиланням: функція отримує «посилання» (вказівник) на оригінальний обʼєкт. Зміни усередині функції відображаються на оригіналі.
У Java усі параметри передаються за значенням!
Є нюанс: коли ми передаємо примітивні типи (int, double, boolean тощо), копіюється саме значення. Натомість коли передаємо посилальні типи (наприклад, масиви або обʼєкти), копіюється значення посилання (тобто «адреса» обʼєкта в памʼяті), а не сам обʼєкт.
Якщо це здається заплутаним — не хвилюйтеся: зараз усе розкладемо по поличках і проілюструємо прикладами!
2. Примітивні типи: копіюється значення
Почнімо з простого: що станеться, якщо ми спробуємо змінити значення змінної типу int усередині методу?
public class Demo
{
public static void main(String[] args)
{
int number = 5;
changeValue(number);
System.out.println(number); // Що буде виведено?
}
public static void changeValue(int n)
{
n = 42;
}
}
Результат: на екран буде виведено 5.
Чому?
У метод changeValue передається копія значення змінної number. Усередині методу ми змінюємо лише копію (n), а оригінальна змінна number у main залишається незмінною.
Аналогія
Уявіть, що ви дали другові ксерокопію паспорта, а не оригінал. Друг може малювати вуса на копії скільки завгодно — ваш справжній паспорт залишиться незмінним!
3. Посилальні типи: копіюється посилання
Тепер спробуймо те саме з масивом:
public class Demo
{
public static void main(String[] args)
{
int[] numbers = {1, 2, 3};
changeFirst(numbers);
System.out.println(numbers[0]); // Що буде виведено?
}
public static void changeFirst(int[] arr)
{
arr[0] = 99;
}
}
Результат: на екран буде виведено 99.
Чому?
У метод changeFirst передається копія посилання на масив numbers. Обидва імені (numbers у main і arr у методі) вказують на один і той самий масив у памʼяті. Тому, якщо ми змінюємо елемент масиву усередині методу, зміна буде помітна ззовні.
Візуалізація
main: numbers ──► [1, 2, 3]
▲
│
changeFirst: arr ──┘
Змінивши arr[0], ми насправді змінюємо numbers[0], бо обидва вказують на один і той самий обʼєкт.
4. Але якщо поміняти саме посилання — початковий обʼєкт не зміниться!
Спробуймо тепер зробити так:
public class Demo
{
public static void main(String[] args)
{
int[] numbers = {1, 2, 3};
replaceArray(numbers);
System.out.println(numbers[0]); // Що буде виведено?
}
public static void replaceArray(int[] arr)
{
arr = new int[] {10, 20, 30};
}
}
Результат: буде виведено 1.
Чому?
У методі replaceArray змінна arr починає вказувати на новий масив, але це жодним чином не впливає на змінну numbers у main.
Вони були повʼязані одним посиланням, але коли усередині методу ми присвоюємо arr = ..., це лише локальна зміна.
Аналогія
Це якби у вас і в друга був один і той самий ключ від квартири. Ваш друг зробив собі новий ключ, але ваш старий ключ залишився тим самим, а двері — незмінні.
5. Приклади для закріплення: не вийде «поміняти місцями» два int за допомогою методу
Багато початківців намагаються написати метод, щоб поміняти місцями два числа, та нічого не виходить:
public class Demo
{
public static void main(String[] args)
{
int a = 5, b = 10;
swap(a, b);
System.out.println(a + " " + b); // Очікування: 10 5, а насправді?
}
public static void swap(int x, int y)
{
int temp = x;
x = y;
y = temp;
}
}
Результат: на екран буде виведено 5 10.
Чому?
Тому що в swap потрапляють копії змінних a та b. Усе, що ми робимо усередині swap, впливає лише на ці копії, а не на оригінали.
6. Але з масивами можна міняти вміст!
Подивімося на правильний спосіб поміняти місцями значення в масиві:
public class Demo
{
public static void main(String[] args)
{
int[] arr = {5, 10};
swap(arr);
System.out.println(arr[0] + " " + arr[1]); // Очікування: 10 5
}
public static void swap(int[] arr)
{
int temp = arr[0];
arr[0] = arr[1];
arr[1] = temp;
}
}
Результат: буде виведено 10 5.
Чому?
Бо ми змінюємо вміст обʼєкта (масиву), на який вказує і оригінальна змінна, і параметр методу.
7. Демонстрація: обʼєкти та їхні поля
З масивами розібралися. А що щодо обʼєктів власних класів? Усе так само!
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); // Що буде виведено?
}
public static void changeBox(Box b)
{
b.value = 42;
}
}
Результат: буде виведено 42.
Чому?
Бо ми змінюємо поле обʼєкта, на який вказує посилання.
Що відбувається під час передавання параметрів у методи
| Що передаємо | Як копіюється | Можна змінити усередині методу? | Зміни видно ззовні? |
|---|---|---|---|
| int, double тощо | Копія значення | Так, але лише копію | Ні |
| Масив | Копія посилання | Так, якщо змінювати елементи | Так |
| Обʼєкт (клас) | Копія посилання | Так, якщо змінювати поля | Так |
| Масив/обʼєкт | Копія посилання | Ні, якщо присвоїти нове посилання | Ні |
8. Типові помилки
Помилка № 1: очікування, що int зміниться після виклику методу. Багато початківців думають, що якщо передати змінну типу int у метод, то можна змінити її значення усередині методу. Це хибно: змінюється лише копія!
Помилка № 2: спроба поміняти місцями два int за допомогою методу. Як ми вже бачили, це неможливо — лише копії змінних змінюються місцями, оригінали не зачіпаються.
Помилка № 3: спроба «замінити» масив за допомогою методу. Якщо ви усередині методу присвоїте параметру масиву новий масив (arr = new int[]{...}), це не вплине на оригінальний масив. Щоб «замінити» масив, треба повернути новий масив із методу й присвоїти його змінній у коді, що його викликає.
Помилка № 4: забули, що можна змінювати вміст обʼєкта. Якщо ви передаєте обʼєкт (або масив), не забувайте: будь-які зміни його полів/елементів будуть помітні ззовні!
Помилка № 5: плутанина з null. Якщо передати в метод посилання, що дорівнює null, і спробувати звернутися до його полів — отримаєте NullPointerException. Будьте уважні!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ