Является ли Java "передачей по ссылке" или "передачей по значению"?
Я всегда думал, что Java использует передачу по ссылке. Однако я прочитал сообщение в блоге, в котором утверждается, что Java использует передачу по значению. Не думаю, что понимаю различие, проводимое автором.
Каково объяснение?
Переведено автоматически
Ответ 1
Термины "передача по значению" и "передача по ссылке" имеют особые, точно определенные значения в информатике. Эти значения отличаются от того, что многие люди понимают, когда впервые слышат термины. Большая часть путаницы в этом обсуждении, похоже, проистекает из этого факта.
Термины "передача по значению" и "передача по ссылке" относятся к переменным. Передача по значению означает, что значение переменной передается функции / методу. Передача по ссылке означает, что функции передается ссылка на эту переменную. Последнее дает функции возможность изменять содержимое переменной.
Согласно этим определениям, Java всегда передается по значению. К сожалению, когда мы имеем дело с переменными, содержащими объекты, мы на самом деле имеем дело с дескрипторами объектов, называемыми ссылками, которые также передаются по значению. Эта терминология и семантика легко сбивают с толку многих новичков.
Это выглядит следующим образом:
public static void main(String[] args) {
Dog aDog = new Dog("Max");
Dog oldDog = aDog;
// we pass the object to foo
foo(aDog);
// aDog variable is still pointing to the "Max" dog when foo(...) returns
aDog.getName().equals("Max"); // true
aDog.getName().equals("Fifi"); // false
aDog == oldDog; // true
}
public static void foo(Dog d) {
d.getName().equals("Max"); // true
// change d inside of foo() to point to a new Dog instance "Fifi"
d = new Dog("Fifi");
d.getName().equals("Fifi"); // true
}
В приведенном выше примере aDog.getName()
все равно вернет "Max"
. Значение aDog
внутри main
не изменяется в функции foo
с помощью Dog
"Fifi"
, поскольку ссылка на объект передается по значению. Если бы это было передано по ссылке, то aDog.getName()
in main
возвращался бы "Fifi"
после вызова foo
.
Аналогично:
public static void main(String[] args) {
Dog aDog = new Dog("Max");
Dog oldDog = aDog;
foo(aDog);
// when foo(...) returns, the name of the dog has been changed to "Fifi"
aDog.getName().equals("Fifi"); // true
// but it is still the same dog:
aDog == oldDog; // true
}
public static void foo(Dog d) {
d.getName().equals("Max"); // true
// this changes the name of d to be "Fifi"
d.setName("Fifi");
}
В приведенном выше примере Fifi
это имя dog после вызова foo(aDog)
, потому что имя объекта было задано внутри foo(...)
. Любые операции, которые foo
выполняются с d
, таковы, что для всех практических целей они выполняются с aDog
, но не возможно изменить значение самой переменной aDog
.
Для получения дополнительной информации о передаче по ссылке и передаче по значению обратитесь к следующему ответу: https://javalang.ru/a/430958/6005228. Это более подробно объясняет семантику и историю, стоящую за этими двумя, а также объясняет, почему Java и многие другие современные языки, похоже, выполняют и то, и другое в определенных случаях.
Ответ 2
Я только что заметил, что вы ссылались на мою статью.
В спецификации Java говорится, что все в Java передается по значению. В Java нет такого понятия, как "передача по ссылке".
Ключом к пониманию этого является что-то вроде
Dog myDog;
это не Dog; на самом деле это указатель на Dog. Использование термина "ссылка" в Java сильно вводит в заблуждение и является причиной большей части путаницы здесь. То, что они называют "ссылками", больше похоже на то, что мы назвали бы "указателями" в большинстве других языков.
Это означает, что у вас есть
Dog myDog = new Dog("Rover");
foo(myDog);
по сути, вы передаете адрес созданного Dog
объекта foo
методу.
(Я говорю по существу, потому что указатели / ссылки Java не являются прямыми адресами, но проще всего думать о них таким образом.)
Предположим, что Dog
объект находится по адресу памяти 42. Это означает, что мы передаем методу 42.
если бы метод был определен как
public void foo(Dog someDog) {
someDog.setName("Max"); // AAA
someDog = new Dog("Fifi"); // BBB
someDog.setName("Rowlf"); // CCC
}
давайте посмотрим, что происходит.
- параметру
someDog
присвоено значение 42 - в строке "AAA"
someDog
далее следует то, наDog
что указывает (Dog
объект по адресу 42)- это
Dog
(тот, что по адресу 42) просят изменить его имя на Max
- в строке "BBB"
- создается новый
Dog
. Допустим, он находится по адресу 74 - мы присваиваем параметру
someDog
значение 74
- создается новый
- в строке "CCC"
- За someDog следует тот, на
Dog
который он указывает (Dog
объект по адресу 74) - этого
Dog
(того, кто находится по адресу 74) просят изменить его имя на Rowlf
- За someDog следует тот, на
- затем мы возвращаем
Теперь давайте подумаем о том, что происходит вне метода:
myDog
Изменилось?
Вот ключ.
Имея в виду, что myDog
это указатель, а не фактическое значение Dog
, ответ - НЕТ. myDog
по-прежнему имеет значение 42; оно по-прежнему указывает на оригинал Dog
(но обратите внимание, что из-за строки "AAA" его имя теперь "Max" - все тот же Dog; myDog
значение не изменилось.)
Вполне допустимо следовать по адресу и изменять то, что находится в его конце; однако это не меняет переменную.
Java работает точно так же, как C. Вы можете назначить указатель, передать указатель методу, следовать указателю в методе и изменять данные, на которые указывалось. Однако вызывающий объект не увидит никаких изменений, которые вы вносите в то место, куда указывает этот указатель. (В языке с семантикой передачи по ссылке функция метода может изменять указатель, и вызывающий объект увидит это изменение.)
В C ++, Ada, Pascal и других языках, поддерживающих передачу по ссылке, вы действительно можете изменить переданную переменную.
Если бы Java имела семантику передачи по ссылке, foo
метод, который мы определили выше, изменил бы то, куда myDog
указывал, когда он назначал someDog
в строке BBB.
Считайте ссылочные параметры псевдонимами для передаваемой переменной. Когда присваивается этот псевдоним, присваивается и переменная, которая была передана.
Обновить
Обсуждение в комментариях требует некоторых разъяснений...
На C вы можете написать
void swap(int *x, int *y) {
int t = *x;
*x = *y;
*y = t;
}
int x = 1;
int y = 2;
swap(&x, &y);
Это не особый случай в C. Оба языка используют семантику передачи по значению. Здесь сайт вызова создает дополнительную структуру данных, чтобы помочь функции получать доступ к данным и манипулировать ими.
Функции передаются указатели на данные, и она следует этим указателям для доступа к этим данным и их изменения.
Аналогичный подход в Java, где вызывающая сторона устанавливает вспомогательную структуру, может быть:
void swap(int[] x, int[] y) {
int temp = x[0];
x[0] = y[0];
y[0] = temp;
}
int[] x = {1};
int[] y = {2};
swap(x, y);
(или, если вы хотите, чтобы оба примера демонстрировали функции, которых нет в другом языке, создайте изменяемый класс IntWrapper для использования вместо массивов)
В этих случаях и C, и Java имитируют передачу по ссылке. Они по-прежнему передают значения (указатели на целые числа или массивы) и следуют за этими указателями внутри вызываемой функции для манипулирования данными.
Передача по ссылке - это все о объявлении / определении функции и о том, как она обрабатывает свои параметры. Ссылочная семантика применяется к каждому вызову этой функции, и сайту вызова требуется передавать только переменные, никакой дополнительной структуры данных.
Для этих симуляций требуется взаимодействие сайта вызова и функции. Без сомнения, это полезно, но это все еще передача по значению.
Ответ 3
Java всегда передает аргументы по значению, А НЕ по ссылке.
Позвольте мне объяснить это на примере:
public class Main {
public static void main(String[] args) {
Foo f = new Foo("f");
changeReference(f); // It won't change the reference!
modifyReference(f); // It will modify the object that the reference variable "f" refers to!
}
public static void changeReference(Foo a) {
Foo b = new Foo("b");
a = b;
}
public static void modifyReference(Foo c) {
c.setAttribute("c");
}
}
Я объясню это поэтапно:
Объявляем ссылку с именем
f
типаFoo
и присваиваем ей новый объект типаFoo
с атрибутом"f"
.Foo f = new Foo("f");
Со стороны метода объявляется ссылка типа
Foo
с именемa
, и она изначально присваиваетсяnull
.public static void changeReference(Foo a)
При вызове метода
changeReference
ссылкеa
будет присвоен объект, который передается в качестве аргумента.changeReference(f);
Объявляем ссылку с именем
b
типаFoo
и присваиваем ей новый объект типаFoo
с атрибутом"b"
.Foo b = new Foo("b");
a = b
выполняет новое присвоение ссылкеa
, неf
, объекта, атрибутом которого является"b"
.При вызове
modifyReference(Foo c)
метода создается ссылкаc
, которой присваивается объект с атрибутом"f"
.c.setAttribute("c");
изменит атрибут объекта, на который указывает ссылкаc
, и это тот же объект, на который указывает ссылкаf
.
Ответ 4
Java всегда передается по значению, без каких-либо исключений, никогда.
Так как же получается, что кто-то вообще может быть сбит с толку этим и полагать, что Java передается по ссылке, или думать, что у них есть пример Java, действующий как передача по ссылке? Ключевым моментом является то, что Java никогда не предоставляет прямой доступ к значениям самих объектов, ни при каких обстоятельствах. Единственный доступ к объектам осуществляется через ссылку на этот объект. Поскольку доступ к объектам Java всегда осуществляется через ссылку, а не напрямую, принято говорить о полях и переменных и аргументах метода как о объектах, хотя педантично они являются всего лишь ссылками на объекты. Путаница возникает из-за этого (строго говоря, некорректного) изменения номенклатуры.
Итак, при вызове метода
- Для аргументов примитива (
int
,long
и т.д.) значение передачи по является фактическим значением примитива (например, 3). - Для объектов значение передачи по - это значение ссылки на объект.
Итак, если у вас есть doSomething(foo)
и public void doSomething(Foo foo) { .. }
два Foo скопировали ссылки, которые указывают на одни и те же объекты.
Естественно, передача по значению ссылки на объект очень похожа (и на практике неотличима от) передачи объекта по ссылке.