Avatar ·

В Java передаются параметры по ссылке или по значению?

Всем привет! Я только начал изучать Java и столкнулся с непониманием. В книжках пишут, что в Java всё передаётся по значению, но я на практике вижу что-то странное. Вот пример. Когда я передаю в метод объект (например, список) и меняю его внутри метода, изменения сохраняются и после выхода из метода: ```java public class Test { public static void main(String[] args) { StringBuilder sb = new StringBuilder("Hello"); changeString(sb); System.out.println(sb); // Вывод: Hello World } public static void changeString(StringBuilder s) { s.append(" World"); } } ``` Здесь sb изменился. Я думал, что если передаётся копия, то изменения должны быть локальными. Получается, что объекты передаются по ссылке? Но если я попробую присвоить новый объект внутри метода, то снаружи это не отразится: ```java public class Test { public static void main(String[] args) { StringBuilder sb = new StringBuilder("Hello"); changeReference(sb); System.out.println(sb); // Вывод: Hello } public static void changeReference(StringBuilder s) { s = new StringBuilder("World"); } } ``` Почему так? В первом случае объект поменялся, во втором — нет. Это какая-то смесь передачи по значению и по ссылке? Объясните, пожалуйста, как это работает на самом деле. А то я уже голову сломал. <<>>

Короткий ответ: в Java всегда передача по значению

Запомните главное правило: Java всегда передаёт параметры по значению. Никакой передачи по ссылке в Java нет. Путаница возникает из-за того, что начинающие программисты неправильно понимают, что именно является "значением" при работе с объектами.

Что такое "значение" для примитивов и объектов?

Для примитивных типов (int, double, boolean и т.д.) значение — это сам байт данных. Вы передаёте копию числа, и изменения внутри метода не влияют на оригинал:

public class Test {
    public static void main(String[] args) {
        int x = 10;
        changePrimitive(x);
        System.out.println(x); // Вывод: 10 (не изменилось)
    }

    public static void changePrimitive(int value) {
        value = 20; // меняем копию
    }
}

Для ссылочных типов (объекты, массивы, строки) "значением" является ссылка (адрес в памяти), а не сам объект. Когда вы передаёте объект в метод, вы передаёте копию этой ссылки. Обе ссылки (оригинальная и копия) указывают на один и тот же объект в куче.

Почему первый пример работает?

В первом примере вы передаёте копию ссылки на StringBuilder. Оригинальная ссылка sb и копия s указывают на один и тот же объект. Вызов s.append(" World") изменяет содержимое этого объекта. Поскольку оригинальная ссылка sb всё ещё указывает на тот же объект, изменения видны снаружи.

Почему второй пример не работает?

Во втором примере вы присваиваете копии ссылки s новый объект: s = new StringBuilder("World"). Это не меняет объект, на который указывает s, а заставляет s указывать на совершенно другой объект. Оригинальная ссылка sb продолжает указывать на старый объект "Hello".

Ключевой момент: вы не можете изменить саму ссылку снаружи. Вы можете только изменить объект, на который ссылка указывает (если объект мутабельный).

Наглядная аналогия

  • Передача по значению — это как дать кому-то копию адреса вашего дома. Этот человек может прийти и перекрасить стены внутри дома (изменить объект), но не может заставить вас переехать в другой дом (изменить вашу ссылку).
  • Передача по ссылке (как в C++) — это дать кому-то сам ключ от вашего дома. Этот человек может не только перекрасить стены, но и сказать: "Ты теперь живёшь в другом доме", и вы действительно переедете.

Как проверить себя?

Если бы Java поддерживала передачу по ссылке, то второй пример изменил бы sb на новый объект. Этого не происходит, значит, передача только по значению.

Итог

Запомните три правила:

  1. Java всегда передаёт параметры по значению (копию значения).
  2. Для примитивов значением является само число.
  3. Для объектов значением является ссылка (адрес в памяти).

Когда вы говорите "передача по ссылке" в контексте Java — это ошибка. Правильно говорить: "передача ссылки по значению".

<<>>

Дополнение: как это связано с неизменяемыми объектами

Путаница часто усиливается при работе с неизменяемыми (immutable) объектами, например, со строками String. Поскольку String нельзя изменить, любые "изменения" на самом деле создают новый объект:

public class Test {
    public static void main(String[] args) {
        String str = "Hello";
        changeString(str);
        System.out.println(str); // Вывод: Hello
    }

    public static void changeString(String s) {
        s = s + " World"; // Создаётся новый объект, s теперь указывает на него
    }
}

Здесь s = s + " World" создаёт новый объект String и присваивает его копии ссылки s. Оригинальная ссылка str по-прежнему указывает на "Hello". Это работает точно так же, как и с мутабельными объектами — вы не можете изменить ссылку снаружи.

Совет для новичков: если хотите изменить объект внутри метода и чтобы изменения были видны снаружи, убедитесь, что:

  • Объект мутабельный (можно изменять его содержимое).
  • Вы не присваиваете параметру новый объект, а вызываете методы, изменяющие состояние текущего объекта.

Если нужно изменить саму ссылку (например, заменить объект на новый), придётся возвращать новое значение из метода или использовать обёртки/массивы.