Передача переменных по значению и по ссылке

Переменные могут передаваться двумя способами: по значению и по ссылке. Кроме того, в Java существует два типа данных: примитивные и ссылочные. Давайте разберёмся, что это значит и как это работает. Ведь в этом легко запутаться. Особенно учитывая, что в разных языках программирования принципы различаются. Рассматривать будем в контексте передачи переменных в методы.

Что такое передача по значению и по ссылке

В теории, параметры могут передаваться в метод по значению (by value) и по ссылке (by reference).

При передаче параметров по значению метод получает копию значения переменной. Изменения, производимые с переданной в метод переменной, никак не влияют на ту, что осталась снаружи. Почему? Потому что в методе значения переданных параметров копируются. То есть с исходными переменными нет никакого взаимодействия – только с их копиями.

При передаче параметров по ссылке передаётся ссылка на объект. Через неё можно обращаться к исходному объекту, а не к его копии. Ссылочный параметр связан с исходным объектом, поэтому через ссылку можно менять сам объект.

Если пока что сложно, подождите примеров. Всё будет продемонстрировано – в том числе, как получается/не получается повлиять из метода на передаваемые переменные.

Передача примитивных типов

Как в Java передаются примитивные типы? По ссылке или по значению? Давайте выясним опытным путём. Передача по ссылке означает следующее. Что бы мы с переданной переменной ни делали, это отразится на исходной переменной. Поэтому проверим, повлияют ли наши махинации с переменной примитивного типа int в методе на переданную извне переменную.

Скриншот консоли IntelliJ IDEA со значением переменной age в методах main() и increment()

Сначала переменная имела значение 18. Для неё был вызван метод increment(), куда была передана копия этой переменной. Копия, увеличившись на 1, получила значение 19. Когда же метод завершился и управление вернулось в main(), выведено было 18 для переменной age, так как сама эта переменная в методе increment() не затрагивалась – затронута была только её копия.

Ровно то же самое будет происходить и с переменными других примитивных типов: byte, short, long, char, boolean, float и double.

Передача ссылочных типов

Как в Java передаются ссылочные типы (объекты, а также массивы)? По ссылке или по значению? Давайте так же, как и с примитивными типами, выясним опытным путём. Для того, чтобы продемонстрировать, как передаётся объект в метод, придётся использовать объект. Есть такой класс – StringBuilder – его объект мы и создадим. StringBuilder позволяет строить строки.

Здесь есть переменная класса FirstProject private static final String SEPARATOR = “\n”. Final означает, что значение переменной больше менять нельзя. Эту строку мы будем использовать для разделения разных добавлений в объект StringBuilder. Метод appendText() взаимодействует с переданным в него параметром и вызывает метод append объекта StringBuilder внутри себя. Посмотрим, отразится ли это на внешней переменной. Внешнее по отношению к методу appendText() – это содержимое метода main(), так как метод appendText() вызывается из метода main().

Итак, первый вывод, этот когда метод appendText() ещё не был вызван. А вот второй уже поинтереснее. StringBuilder аккумулировал первую добавленную строку из метода main() и ту строку, что была применена к нему в методе appendText()! Он сохранил это значение! Выходит, удалось повлиять на внешний объект изнутри метода appendText()!

Скриншот консоли IntelliJ IDEA с выводом из методов main() и appendText()

Означает ли это, что ссылочные переменные в Java передаются по ссылке? Нет. Тут вот в чём дело. Когда мы обращаемся к объектам, на самом деле мы оперируем ссылками на них. И когда мы пытаемся передать в метод ссылку на объект, на самом деле берётся значение этой ссылки – то есть её копия. Это означает, что ссылка на объект StringBuilder в методе main() и ссылка на объект StringBuilder в методе appendText() – это две разные ссылки! Но дальше – больше. Обе эти ссылки указывают на один и тот же объект. Поэтому получается влиять на него из метода appendText().

Как можно проверить, передаётся ссылка на объект или же копия ссылки? Очень легко. Давайте попробуем изменить изнутри метода не объект, как мы уже проделали со StringBuilder, а саму ссылку. Это можно сделать, создав новый объект StringBuilder с помощью ключевого слова new и положив его в переданную ссылку.

Скриншот консоли IntelliJ IDEA с выводом из метода main()

Что.. Это невероятно! Куда же пропала надпись, гласящая о методе appendText()? Мы попытались внутри этого метода изменить переданную ссылку. Мы попробовали положить туда новый объект StringBuilder. Но поскольку передана в метод была не сама ссылка на внешний объект, а лишь её копия, изменение внутренней ссылки не повлияло на внешнюю. В итоге, во внешней ссылке лежит один объект, а во внутренней – уже совершенно другой.

Если бы объект StringBuilder передавался в метод по ссылке, результат выполнения кода совпал бы с предыдущим примером. То есть была бы передана его реальная ссылка, а не её копия. Однако это не так. Изменение копии ссылки никак не отразилось на исходной ссылке.

Именно поэтому запомните: все переменные в Java передаются по значению. В случае примитивных типов это копия их значения вроде 8, ‘c’, 10000 или true. В случае же ссылочных типов это – копия ссылки на объект. Так что даже при передаче ссылок параметров, метод получает копии (копии этих ссылок). Как передать переменную по ссылке в Java? Никак. Но вот повлиять на исходную ссылочную переменную через копию ссылки – можно.

Неизменяемые объекты (классы String, Integer, Boolean и другие)

String – это же класс. По крайней мере, он пишется с большой буквы. Да и Integer, Boolean, Character, Byte и прочие классы-обёртки примитивных типов в Java. Почему же тогда при передаче их в метод и изменении объектов через копии ссылок не меняются внешние объекты?

Скриншот консоли IntelliJ IDEA с выводом строк из методов main() и appendText()

Ответ неочевиден: объекты этих классов являются неизменяемыми. Это означает, что после создания объекта этих классов его значение нельзя изменить. То есть при любом изменении объекта на самом деле исходный объект не изменяется. По факту создаётся новый объект с новым значением. Таким образом, когда мы пытаемся присоединить к строке String новый текст при помощи оператора +=, на самом деле мы создаём новый объект String. Он инициализируется значением предыдущего значения плюс того, которое мы хотели присоединить.

Null

В Java null – это специальное значение, которое на самом деле представляет собой отсутствие значения. Null можно присвоить любой ссылочной переменной. Это будет означать, что переменная не указывает на какой-либо объект. То ест ссылка пуста. Обратите внимание, что примитивным типам строго-настрого такое запрещено. Им нельзя содержать в себе значение null.

Строка emptyReference равна null – эта ссылка не указывает ни на какой объект класса String. При попытке вывести emptyReference на консоль выводится null. Проверка этой строки на null проходит успешно. Но если бы мы попытались обратиться к какому-нибудь из методов emptyReference через точку, в программе возникла бы ошибка (исключение).

Скриншот консоли IntelliJ IDEA с выводом значения null

Очень важно понимать, как работает передача переменных различных типов в методы или в другие места. Незнание этой тонкости не раз путало мне карты при разработке. Здесь вроде объект должен изменяться, а здесь.. вроде не должен? Все эти “вроде” не позволяют сфокусироваться на концепции программы, заставляя переживать о деталях. Между тем как этого можно избежать – достаточно изучить эту тему.

Добавить комментарий