Вопрос-ответ

Is Java "pass-by-reference" or "pass-by-value"?

Является ли 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



  • затем мы возвращаем

Теперь давайте подумаем о том, что происходит вне метода:

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");
}

}

Я объясню это поэтапно:


  1. Объявляем ссылку с именем f типа Foo и присваиваем ей новый объект типа Foo с атрибутом "f".


    Foo f = new Foo("f");

    введите описание изображения здесь



  2. Со стороны метода объявляется ссылка типа Foo с именем a, и она изначально присваивается null.


    public static void changeReference(Foo a)

    введите описание изображения здесь



  3. При вызове метода changeReferenceссылке a будет присвоен объект, который передается в качестве аргумента.


    changeReference(f);

    введите описание изображения здесь



  4. Объявляем ссылку с именем b типа Foo и присваиваем ей новый объект типа Foo с атрибутом "b".


    Foo b = new Foo("b");

    введите описание изображения здесь



  5. a = b выполняет новое присвоение ссылке a, не f , объекта, атрибутом которого является "b".


    введите описание изображения здесь



  6. При вызове modifyReference(Foo c) метода создается ссылка c, которой присваивается объект с атрибутом "f".


    введите описание изображения здесь



  7. c.setAttribute("c"); изменит атрибут объекта, на который указывает ссылка c, и это тот же объект, на который указывает ссылка f.


    введите описание изображения здесь



Ответ 4

Java всегда передается по значению, без каких-либо исключений, никогда.

Так как же получается, что кто-то вообще может быть сбит с толку этим и полагать, что Java передается по ссылке, или думать, что у них есть пример Java, действующий как передача по ссылке? Ключевым моментом является то, что Java никогда не предоставляет прямой доступ к значениям самих объектов, ни при каких обстоятельствах. Единственный доступ к объектам осуществляется через ссылку на этот объект. Поскольку доступ к объектам Java всегда осуществляется через ссылку, а не напрямую, принято говорить о полях и переменных и аргументах метода как о объектах, хотя педантично они являются всего лишь ссылками на объекты. Путаница возникает из-за этого (строго говоря, некорректного) изменения номенклатуры.

Итак, при вызове метода


  • Для аргументов примитива (int, long и т.д.) значение передачи по является фактическим значением примитива (например, 3).

  • Для объектов значение передачи по - это значение ссылки на объект.

Итак, если у вас есть doSomething(foo) и public void doSomething(Foo foo) { .. } два Foo скопировали ссылки, которые указывают на одни и те же объекты.

Естественно, передача по значению ссылки на объект очень похожа (и на практике неотличима от) передачи объекта по ссылке.

java