Является ли Java «передачей по ссылке» или «передачей по значению»?

6584

Я всегда думал, что Java была по ссылке .

Тем не менее, я видел пару постов в блоге (например, этот блог ), которые утверждают, что это не так.

Я не думаю, что понимаю разницу, которую они проводят.

Какое объяснение?

user4315
источник
706
Я считаю, что большая путаница в этом вопросе связана с тем, что разные люди имеют разные определения термина «ссылка». Люди из C ++ полагают, что «ссылка» должна означать то же, что и в C ++, люди из C полагают, что «ссылка» должна быть такой же, как «указатель» в их языке, и так далее. Правильно ли говорить, что Java проходит по ссылке, на самом деле зависит от того, что подразумевается под «ссылкой».
Гравитация
119
Я стараюсь последовательно использовать терминологию, найденную в статье « Стратегия оценки» . Следует отметить, что, хотя в статье указывается, что термины сильно различаются в зависимости от сообщества, в ней подчеркивается, что семантика для call-by-valueи call-by-referenceотличается очень важным образом . (Лично я предпочитаю использовать call-by-object-sharingэти дни более call-by-value[-of-the-reference], поскольку это описывает семантику на высоком уровне и не создает конфликта call-by-value, который является базовой реализацией.)
59
@Gravity: Вы можете пойти и поместить свой комментарий на ОГРОМНЫЙ рекламный щит или что-то? Вот и вся проблема в двух словах. И это показывает, что все это семантика. Если мы не согласны с базовым определением ссылки, мы не согласимся с ответом на этот вопрос :)
MadConan
30
Я думаю, что путаница заключается в «передаче по ссылке» против «ссылочной семантики». Java передается по значению со ссылочной семантикой.
spraff
11
@Gravity, хотя вы абсолютно правы в том, что у людей, пришедших из C ++, инстинктивно будет другой набор интуиции в отношении термина «ссылка», я лично считаю, что тело более скрыто в «by». «Передача» сбивает с толку тем, что она абсолютно отличается от «Передачи» в Java. В C ++, однако, в разговорной речи это не так. В C ++ вы можете сказать «передача ссылки», и понятно, что она пройдет swap(x,y)тест .

Ответы:

5846

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это имя собаки после вызова, foo(aDog)потому что имя объекта было установлено внутри foo(...). Любые операции , которые fooвыполняют на dтаковы , что для всех практических целей, они выполняются на aDog, но это не возможно изменить значение переменного aDogсам.

erlando
источник
263
Разве это не немного путает проблему с внутренними деталями? Нет никакой концептуальной разницы между «передачей ссылки» и «передачей значения ссылки», при условии, что вы подразумеваете «значение внутреннего указателя на объект».
IZB
383
Но есть небольшая разница. Посмотрите на первый пример. Если бы он был просто передан по ссылке, aDog.name был бы "Fifi". Это не так - ссылка, которую вы получаете, является ссылкой на значение, которая при перезаписи будет восстановлена ​​при выходе из функции.
Эрландо
300
@Lorenzo: Нет, в Java все передается по значению. Примитивы передаются по значению, а ссылки на объекты передаются по значению. Сами объекты никогда не передаются методу, но объекты всегда находятся в куче, и в метод передается только ссылка на объект.
Эско Луонтола
295
Моя попытка найти хороший способ визуализации прохождения объекта: представьте воздушный шар. Вызов fxn подобен привязке второй строки к воздушному шару и передаче линии fxn. Параметр = new Balloon () обрежет эту строку и создаст новый шарик (но это не влияет на исходный шарик). Параметр.pop () будет по-прежнему отображать его, потому что он следует за строкой в ​​тот же самый оригинальный шарик. Java передается по значению, но передаваемое значение не является глубоким, оно находится на самом высоком уровне, то есть примитив или указатель. Не путайте это с глубокой передачей по значению, когда объект полностью клонируется и передается.
Дакнер
150
Что сбивает с толку, так это то, что ссылки на объекты на самом деле являются указателями. В начале SUN называл их указателями. Тогда маркетинг сообщил, что «указатель» был плохим словом. Но вы все еще видите «правильную» номенклатуру в исключении NullPointerException.
Проф. Фалькен нарушил контракт
3036

Я только что заметил, что вы ссылались на мою статью .

Спецификация Java говорит, что все в Java передается по значению. В Java нет такого понятия, как «передача по ссылке».

Ключом к пониманию этого является то, что

Dog myDog;

это не собака; это на самом деле указатель на собаку.

Что это значит, когда у вас есть

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
  • на линии "ААА"
    • someDogследует к Dogнему указывает ( Dogобъект по адресу 42)
    • что Dog(тот, по адресу 42) попросили изменить его имя на Макс
  • на линии "ВВВ"
    • новый Dogсоздан. Допустим, он по адресу 74
    • мы назначаем параметр someDog74
  • на линии "CCC"
    • SomeDog следует на Dogэто указывает ( Dogобъект по адресу 74)
    • что Dog(тот по адресу 74) попросили изменить его имя на Rowlf
  • тогда мы вернемся

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

Изменился myDog?

Там ключ.

Помня, что myDogэто указатель , а не факт Dog, ответ НЕТ. myDogпо-прежнему имеет значение 42; он по-прежнему указывает на оригинал Dog(но обратите внимание, что из-за строки «AAA» его имя теперь «Макс» - все тот же Dog; myDogзначение не изменилось.)

Совершенно верно следовать за адресом и изменять то, что в конце этого; это не меняет переменную, однако.

Java работает точно так же, как C. Вы можете назначить указатель, передать указатель на метод, следовать указателю в методе и изменить данные, на которые он указывал. Однако вы не можете изменить, куда указывает этот указатель.

В C ++, Ada, Pascal и других языках, которые поддерживают передачу по ссылке, вы действительно можете изменить переданную переменную.

Если бы у Java была семантика передачи по ссылке, то fooметод, который мы определили выше, изменился бы там, куда myDogуказывал, когда он был назначен someDogв строке BBB.

Представьте, что ссылочные параметры являются псевдонимами для передаваемой переменной. Когда этот псевдоним назначен, переменная также передается.

Скотт Стэнчфилд
источник
164
Вот почему распространенный рефрен «У Java нет указателей» вводит в заблуждение.
Беска
135
Вы не правы, имхо. «Помня, что myDog - это указатель, а не настоящий Dog, ответ НЕТ. MyDog по-прежнему имеет значение 42; он по-прежнему указывает на исходный Dog». myDog имеет значение 42, но его аргумент name теперь содержит «Max», а не «Rover» в строке // AAA.
Озгюр
180
Подумайте об этом таким образом. У кого-то есть адрес Энн Арбор, Мичиган (мой родной город, GO СИНИЙ!) На листке бумаги под названием «annArborLocation». Вы копируете его на лист бумаги под названием «myDestination». Вы можете доехать до «myDestination» и посадить дерево. Возможно, вы изменили что-то о городе в этом месте, но это не изменит LAT / LON, который был написан на любой бумаге. Вы можете изменить LAT / LON на «myDestination», но это не изменит «annArborLocation». Это помогает?
Скотт Стэнчфилд,
43
@ Скотт Стэнчфилд: Я прочитал вашу статью около года назад, и это действительно помогло мне прояснить ситуацию. Спасибо! Позвольте мне смиренно предложить небольшое дополнение: вы должны упомянуть, что на самом деле существует определенный термин, описывающий эту форму «вызов по значению, где значение является ссылкой», который был изобретен Барбарой Лисков для описания стратегии оценки ее языка CLU в 1974, чтобы избежать путаницы, подобной той, о которой говорится в вашей статье: вызов посредством совместного использования (иногда называемый вызовом посредством совместного использования объекта или просто вызов по объекту ), который в значительной степени прекрасно описывает семантику.
Йорг Миттаг
29
@Gevorg - Языки C / C ++ не владеют понятием «указатель». Существуют другие языки, которые используют указатели, но не допускают тех же типов манипулирования указателями, которые разрешены в C / C ++. У Java есть указатели; они просто защищены от вреда.
Скотт Стэнчфилд
1741

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указывает ссылка .

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

Надеюсь, теперь вы понимаете, как передача объектов в качестве аргументов работает в Java :)

Eng.Fouad
источник
82
+1 Хороший материал. хорошие диаграммы. Я также нашел хорошую лаконичную страницу здесь adp-gmbh.ch/php/pass_by_reference.html Хорошо, я признаю, что она написана на PHP, но это - основа понимания разницы, которую я считаю важной (и как манипулировать этой разницей твои нужды).
DaveM
5
@ Eng.Fouad Это хорошее объяснение, но если aуказывает на тот же объект, что и f(и никогда не получает свою собственную копию объекта, fуказывает на), любые изменения в объекте, сделанные с использованием, aдолжны измениться так же fхорошо (так как они оба работают с одним и тем же объектом ), поэтому в какой-то момент aдолжна получить собственную копию объекта, на которую fуказывает.
0x6C38
14
@MrD, когда «a» указывает на тот же объект, «f» также указывает на, тогда любое изменение, внесенное в этот объект через «a», также можно наблюдать через «f», НО ЭТО НЕ ИЗМЕНИЛО «f». 'f' все еще указывает на тот же объект. Вы можете полностью изменить объект, но вы никогда не сможете изменить то, на что указывает «f». Это фундаментальный вопрос, который по какой-то причине некоторые люди просто не могут понять.
Майк Браун
@MikeBraun ... что? Теперь вы меня смутили: S. Разве то, что вы только что написали, противоречит тому, что показывают 6. и 7.?
Злая стиральная машина
6
Это лучшее объяснение, которое я нашел. Кстати, а как насчет основной ситуации? Например, аргумент требует тип int, передает ли он копию переменной int в аргумент?
allenwang
733

Это даст вам некоторое представление о том, как на самом деле работает Java, до такой степени, что в вашем следующем обсуждении передачи Java по ссылке или по значению вы просто улыбнетесь :-)

Шаг первый, пожалуйста, удалите из памяти это слово, которое начинается с 'p' "_ _ _ _ _ _ _", особенно если вы пришли из других языков программирования. Java и 'p' не могут быть записаны в одной книге, на форуме или даже в txt.

Шаг второй Помните, что когда вы передаете Object в метод, вы передаете ссылку на Object, а не сам объект.

  • Ученик : Магистр, означает ли это, что Java передается по ссылке?
  • Мастер : Кузнечик, №

Теперь подумайте о том, что ссылка / переменная объекта делает / является:

  1. Переменная содержит биты, которые сообщают JVM, как добраться до ссылочного объекта в памяти (кучи).
  2. При передаче аргументов методу вы НЕ передаете ссылочную переменную, а копируете биты в ссылочной переменной . Как то так: 3bad086a. 3bad086a представляет способ добраться до пропущенного объекта.
  3. Итак, вы просто передаете 3bad086a, что это значение ссылки.
  4. Вы передаете значение ссылки, а не саму ссылку (и не объект).
  5. Это значение фактически копируется и передается методу .

В следующем (пожалуйста, не пытайтесь скомпилировать / выполнить это ...):

1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7.     anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }

Что просходит?

  • Переменная person создается в строке # 1 и в начале имеет значение null.
  • Новый объект Person создается в строке # 2, сохраняется в памяти, а переменной person дается ссылка на объект Person. То есть его адрес. Допустим, 3bad086a.
  • Переменная person, содержащая адрес объекта, передается функции в строке # 3.
  • В строке № 4 вы можете прослушать звук тишины
  • Проверьте комментарий в строке № 5
  • Локальная переменная метода - anotherReferenceToTheSamePersonObject - создается, а затем появляется волшебство в строке # 6:
    • Переменная / эталонное лицо копируется побитно и передается в другойReferenceToTheSamePersonObject внутри функции.
    • Новые экземпляры Person не создаются.
    • И " person ", и " anotherReferenceToTheSamePersonObject " содержат одно и то же значение 3bad086a.
    • Не пытайтесь это сделать, но person == anotherReferenceToTheSamePersonObject будет истинным.
    • Обе переменные имеют ИДЕНТИЧНЫЕ КОПИИ ссылки, и они обе ссылаются на один и тот же объект Person, ОДИН ЖЕ объект в куче и НЕ КОПИЮ.

Одна картинка стоит тысячи слов:

Передать по значению

Обратите внимание, что стрелки anotherReferenceToTheSamePersonObject направлены на объект, а не на переменную person!

Если вы не получили его, просто поверьте мне и помните, что лучше сказать, что Java передается по значению . Ну, перейдите по ссылке . Ну что ж, еще лучше, если передать значение переменной! ;)

Теперь не стесняйтесь ненавидеть меня, но обратите внимание, что с учетом этого нет разницы между передачей примитивных типов данных и объектов при обсуждении аргументов метода.

Вы всегда передаете копию битов значения ссылки!

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

Java передается по значению, потому что внутри метода вы можете изменять ссылочный объект столько раз, сколько хотите, но как бы вы ни старались, вы никогда не сможете изменить переданную переменную, которая будет продолжать ссылаться (не p _ _ _) _ _ _ _) один и тот же объект, несмотря ни на что!


Приведенная выше функция changeName никогда не сможет изменить фактическое содержимое (значения битов) переданной ссылки. Другими словами, changeName не может заставить человека ссылаться на другой объект.


Конечно, вы можете сократить его и просто сказать, что Java передается по значению!

Gevorg
источник
5
Вы имеете в виду указатели? .. Если я его правильно, в public void foo(Car car){ ... }, carявляется локальной fooи содержит расположение кучи Объекта? Так что, если я изменю carзначение на car = new Car(), оно будет указывать на другой объект в куче? и если я изменю значение carсвойства на car.Color = "Red", объект в указанной куче carбудет изменен. Кроме того, то же самое в C #? Ответьте, пожалуйста! Спасибо!
ДПФ
11
@ domanokz Ты убиваешь меня, пожалуйста, не говори это слово снова! ;) Обратите внимание, что я мог бы ответить на этот вопрос, не сказав при этом также и «ссылку». Это проблема терминологии, и с ней дела обстоят хуже. У меня и Скотта разные взгляды на это, к сожалению. Я думаю, что вы поняли, как это работает в Java, теперь вы можете называть это передачей по значению, разделением объекта, копированием значения переменной или не стесняйтесь придумывать что-то еще! Мне все равно, пока вы понимаете, как это работает и что находится в переменной типа Object: просто адрес почтового ящика! ;)
Марселлус Уоллес
9
Похоже, вы только что передали ссылку ? Я собираюсь отстаивать тот факт, что Java все еще является скопированным языком передачи по ссылке. Тот факт, что это скопированная ссылка, не меняет терминологию. Обе ссылки все еще указывают на один и тот же объект. Это аргумент пуриста ...
Джон Стриклер,
3
Итак, загляните в теоретическую строку № 9, System.out.println(person.getName());что появится? "Том" или "Джерри"? Это последнее, что поможет мне преодолеть эту путаницу.
TheBrenny
1
«Ценность ссылки » - вот что мне наконец-то объяснили.
Александр Шуберт
689

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

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

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

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

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

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

SCdF
источник
7
Поскольку значения примитивов являются неизменяемыми (например, String), разница между этими двумя случаями на самом деле не имеет значения.
Paŭlo Ebermann
4
Точно. Для всего, что вы можете сказать через наблюдаемое поведение JVM, примитивы могут передаваться по ссылке и могут жить в куче. Они не делают, но это на самом деле не наблюдается в любом случае.
Гравитация
6
примитивы неизменны? это новое в Java 7?
user85421
4
Указатели неизменны, примитивы вообще изменчивы. Строка тоже не примитив, это объект. Более того, базовая структура String является изменяемым массивом. Единственная неизменная вещь об этом - длина, которая является неотъемлемой природой массивов.
kingfrito_5005
3
Это еще один ответ, указывающий на семантическую природу аргумента. Определение ссылки, предоставленное в этом ответе, сделало бы Java «передачей по ссылке». Автор, по сути, признает то же самое в последнем абзаце, заявляя, что он «неотличим на практике» от «передачи по ссылке». Я сомневаюсь, что OP спрашивает из-за желания понять реализацию Java, а скорее понять, как правильно использовать Java. Если бы это было неразличимо на практике, тогда не было бы никакого смысла в заботе, и даже думать об этом было бы пустой тратой времени.
Loduwijk
331

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

Таким образом, вы не можете изменить ссылку, которая будет передана.

ScArcher2
источник
27
Но то, что постоянно повторяется: «Вы не можете изменить значение объектов, переданных в аргументах», явно ложно. Возможно, вы не сможете заставить их ссылаться на другой объект, но вы можете изменить их содержимое, вызвав их методы. ИМО это означает, что вы теряете все преимущества ссылок и не получаете никаких дополнительных гарантий.
Тимммм
34
Я никогда не говорил: «Вы не можете изменить значение объектов, переданных в аргументах». Я скажу: «Вы не можете изменить значение ссылки на объект, переданной в качестве аргумента метода», что является истинным утверждением о языке Java. Очевидно, что вы можете изменить состояние объекта (если оно не является неизменным).
ScArcher2
20
Имейте в виду, что вы не можете передавать объекты в Java; объекты остаются в куче. Указатели на объекты могут быть переданы (которые копируются в кадр стека для вызываемого метода). Таким образом, вы никогда не изменяете переданное значение (указатель), но вы можете следить за ним и изменять объект в куче, на который он указывает. Это передача по значению.
Скотт Стэнчфилд
10
Похоже, вы только что передали ссылку ? Я собираюсь отстаивать тот факт, что Java все еще является скопированным языком передачи по ссылке. Тот факт, что это скопированная ссылка, не меняет терминологию. Обе ссылки все еще указывают на один и тот же объект. Это аргумент пуриста ...
Джон Стриклер,
3
Java не передает объект, он передает значение указателя на объект. Это создает новый указатель на эту ячейку памяти исходного объекта в новой переменной. Если вы измените значение (адрес памяти, на который он указывает) этой переменной указателя в методе, то исходный указатель, используемый в вызывающей стороне метода, останется неизменным. Если вы называете параметр ссылкой, то тот факт, что это копия исходной ссылки, а не самой исходной ссылки, так что теперь имеется две ссылки на объект, означает, что он передается по значению
theferrit32
238

Я чувствую, что спор о «передача по ссылке против передачи по значению» не является супер-полезным.

Если вы говорите: «Java является передачей по любому (ссылка / значение)», в любом случае вы не дадите полного ответа. Вот некоторая дополнительная информация, которая, надеюсь, поможет понять, что происходит в памяти.

Ускоренный курс по стеку / куче, прежде чем мы перейдем к реализации Java: значения идут и выходят из стека хорошим упорядоченным образом, как стопка тарелок в кафетерии. Память в куче (также известная как динамическая память) является случайной и дезорганизованной. JVM просто находит место, где только может, и освобождает его, поскольку переменные, которые его используют, больше не нужны.

Ладно. Во-первых, локальные примитивы идут в стек. Итак, этот код:

int x = 3;
float y = 101.1f;
boolean amIAwesome = true;

Результаты в этом:

примитивы в стеке

Когда вы объявляете и создаете экземпляр объекта. Фактический объект идет в кучу. Что идет в стек? Адрес объекта в куче. Программисты C ++ назвали бы это указателем, но некоторые Java-разработчики против слова «указатель». Без разницы. Просто знайте, что адрес объекта уходит в стек.

Вот так:

int problems = 99;
String name = "Jay-Z";

ab * 7ch не один!

Массив - это объект, поэтому он также попадает в кучу. А как насчет объектов в массиве? Они получают свое собственное пространство кучи, и адрес каждого объекта идет внутри массива.

JButton[] marxBros = new JButton[3];
marxBros[0] = new JButton("Groucho");
marxBros[1] = new JButton("Zeppo");
marxBros[2] = new JButton("Harpo");

братья маркс

Итак, что передается, когда вы вызываете метод? Если вы передаете объект, на самом деле вы передаете адрес объекта. Некоторые могут сказать «значение» адреса, а некоторые говорят, что это просто ссылка на объект. Это происхождение священной войны между сторонниками «ссылки» и «ценности». То, что вы называете, не так важно, как то, что вы понимаете, что то, что передается, - это адрес объекта.

private static void shout(String name){
    System.out.println("There goes " + name + "!");
}

public static void main(String[] args){
    String hisName = "John J. Jingleheimerschmitz";
    String myName = hisName;
    shout(myName);
}

Создается одна строка, и место для нее выделяется в куче, а адрес строки сохраняется в стеке и получает идентификатор hisName, поскольку адрес второй строки совпадает с первым, новая строка не создается и новое пространство кучи не выделяется, но в стеке создается новый идентификатор. Затем мы вызываем shout(): создается новый кадр стека и создается новый идентификатор, nameи ему присваивается адрес уже существующей строки.

ла да ди да да да да

Итак, ценность, ссылка? Вы говорите "картофель".

cutmancometh
источник
7
Однако вам следовало бы привести более сложный пример, где появляется функция для изменения переменной, на адрес которой она имеет ссылку.
Брайан Петерсон
34
Люди не "танцуют вокруг реальной проблемы" стека против кучи, потому что это не настоящая проблема. В лучшем случае это деталь реализации, а в худшем - совершенно неверная. (Для объектов вполне возможно жить в стеке; google "escape анализ". И огромное количество объектов содержит примитивы, которые, вероятно , не живут в стеке.) Реальная проблема заключается именно в разнице между ссылочными типами и типами значений - в частности, что значение переменной ссылочного типа является ссылкой, а не объектом, на который она ссылается.
Цао
8
Это «деталь реализации» в том, что Java никогда не обязана фактически показывать вам, где объект находится в памяти, и на самом деле, кажется, полна решимости избежать утечки этой информации. Это может положить объект в стек, и вы никогда не узнаете. Если вы заботитесь, вы сосредотачиваетесь не на том - и в этом случае это означает игнорирование реальной проблемы.
Цао
10
И в любом случае, «примитивы идут в стек» неверно. Примитивные локальные переменные идут в стек. (Если они не были оптимизированы, конечно.) Но тогда, то же самое делают и с локальными ссылочными переменными . И примитивные члены, определенные в объекте, живут там, где он живет.
cHao
9
Согласитесь с комментариями здесь. Стек / куча является побочной проблемой и не имеет отношения к делу. Некоторые переменные могут находиться в стеке, некоторые находятся в статической памяти (статические переменные), а многие хранятся в куче (все переменные-члены объекта). Ни одна из этих переменных не может быть передана по ссылке: из вызываемого метода НИКОГДА не возможно изменить значение переменной, переданной в качестве аргумента. Поэтому в Java нет передачи по ссылке.
fishinear
195

Просто чтобы показать контраст, сравните следующие фрагменты C ++ и Java :

В C ++: Примечание: плохой код - утечки памяти! Но это демонстрирует суть.

void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
{
    val = 7; // Modifies the copy
    ref = 7; // Modifies the original variable
    obj.SetName("obj"); // Modifies the copy of Dog passed
    objRef.SetName("objRef"); // Modifies the original Dog passed
    objPtr->SetName("objPtr"); // Modifies the original Dog pointed to 
                               // by the copy of the pointer passed.
    objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                   // leaving the original object alone.
    objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to 
                                    // by the original pointer passed. 
    objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
}

int main()
{
    int a = 0;
    int b = 0;
    Dog d0 = Dog("d0");
    Dog d1 = Dog("d1");
    Dog *d2 = new Dog("d2");
    Dog *d3 = new Dog("d3");
    cppMethod(a, b, d0, d1, d2, d3);
    // a is still set to 0
    // b is now set to 7
    // d0 still have name "d0"
    // d1 now has name "objRef"
    // d2 now has name "objPtr"
    // d3 now has name "newObjPtrRef"
}

В Java

public static void javaMethod(int val, Dog objPtr)
{
   val = 7; // Modifies the copy
   objPtr.SetName("objPtr") // Modifies the original Dog pointed to 
                            // by the copy of the pointer passed.
   objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                  // leaving the original object alone.
}

public static void main()
{
    int a = 0;
    Dog d0 = new Dog("d0");
    javaMethod(a, d0);
    // a is still set to 0
    // d0 now has name "objPtr"
}

Java имеет только два типа передачи: по значению для встроенных типов и по значению указателя для типов объектов.

Eclipse
источник
7
+1 Я бы также добавил Dog **objPtrPtrк примеру C ++, чтобы мы могли изменить то, на что «указывает» указатель.
Amro
1
Но это не отвечает на данный вопрос в Java. На самом деле, все в методе Java - Pass By Value, не более того.
tauitdnmd
2
Этот пример просто доказывает, что Java использует очень эквивалентный указатель в C нет? Где проходить по значению в C в основном только для чтения? В конце концов вся эта тема непостижима
amdev
179

Java передает ссылки на объекты по значению.

Джон Ченнинг
источник
это лучший ответ. Коротко и правильно.
woens
166

По сути, переназначение параметров объекта не влияет на аргумент, например,

private void foo(Object bar) {
    bar = null;
}

public static void main(String[] args) {
    String baz = "Hah!";
    foo(baz);
    System.out.println(baz);
}

распечатает "Hah!"вместо null. Причина, по которой это работает, заключается в том, что barэто копия значения baz, которое является просто ссылкой на "Hah!". Если бы это было само собой фактическое задание, тогда fooбы переопределена bazв null.

Hank Gay
источник
8
Я бы скорее сказал, что bar - это копия ссылочной базы (или псевдонима базы), которая изначально указывает на один и тот же объект.
MaxZoom
не немного ли отличается между классом String и всеми другими классами?
Мехди Карамослы
152

Я не могу поверить, что никто еще не упомянул Барбару Лисков. Когда она разработала CLU в 1974 году, она столкнулась с той же проблемой терминологии, и она изобрела термин вызов путем разделения (также известный как вызов посредством разделения объектов и вызов по объекту ) для этого конкретного случая «вызов по значению, где значение равно ссылка".

Йорг Миттаг
источник
3
Мне нравится это различие в номенклатуре. К сожалению, Java поддерживает вызовы с помощью общего доступа для объектов, но не вызов по значению (как это делает C ++). Java поддерживает вызов по значению только для примитивных типов данных, а не для составных типов данных.
Дерек Махар
2
Я действительно не думаю, что нам нужен был дополнительный термин - это просто передача по значению для определенного типа значения. Будет ли добавление «вызов по примитиву» добавить какие-либо разъяснения?
Скотт Стэнчфилд
8
Call by Sharing
wulfgarpro
Таким образом, я могу передать по ссылке, поделившись некоторым глобальным контекстным объектом или даже передавая контекстный объект, который содержит другие ссылки? Я все еще передаю по значению, но, по крайней мере, у меня есть доступ к ссылкам, которые я могу изменить и заставить их указывать на что-то еще.
YoYo
1
@Sanjeev: разделение по вызовам является частным случаем передачи по значению. Тем не менее, многие люди яростно утверждают, что Java (и подобные языки, такие как Python, Ruby, ECMAScript, Smalltalk) передаются по ссылке. Я бы предпочел также называть это передачей по значению, но разделение по объектам кажется разумным термином, с которым могут согласиться даже люди, которые утверждают, что это не передача по значению.
Jörg W Mittag
119

Суть дела в том, что слово « ссылка» в выражении «передача по ссылке» означает нечто совершенно отличное от обычного значения слова « ссылка» в Java.

Обычно в Java ссылка означает ссылку на объект . Но технические термины, передаваемые по ссылке / значению из теории языка программирования, говорят о ссылке на ячейку памяти, содержащую переменную , что является чем-то совершенно другим.

JacquesB
источник
7
@Gevorg - Тогда что такое «NullPointerException»?
Hot Licks
5
@Hot: К сожалению, названное исключение до того, как Java установил четкую терминологию. Семантически эквивалентное исключение в c # называется NullReferenceException.
JacquesB
4
Мне всегда казалось, что использование «ссылки» в терминологии Java - это аффект, препятствующий пониманию.
Hot Licks
Я научился называть эти так называемые «указатели на объекты» как «дескриптор объекта». Это уменьшило двусмысленность.
moronkreacionz
86

В Java все ссылки, поэтому, когда у вас есть что-то вроде: Point pnt1 = new Point(0,0);Java делает следующее:

  1. Создает новый объект Point
  2. Создает новую ссылку Point и инициализирует эту ссылку точкой (ссылкой) на ранее созданный объект Point.
  3. Отсюда, через жизнь объекта Point, вы получите доступ к этому объекту через ссылку pnt1. Таким образом, мы можем сказать, что в Java вы манипулируете объектом через его ссылку.

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

Java не передает аргументы метода по ссылке; он передает их по значению. Я буду использовать пример с этого сайта :

public static void tricky(Point arg1, Point arg2) {
  arg1.x = 100;
  arg1.y = 100;
  Point temp = arg1;
  arg1 = arg2;
  arg2 = temp;
}
public static void main(String [] args) {
  Point pnt1 = new Point(0,0);
  Point pnt2 = new Point(0,0);
  System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
  System.out.println(" ");
  tricky(pnt1,pnt2);
  System.out.println("X1: " + pnt1.x + " Y1:" + pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);  
}

Ход программы:

Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);

Создание двух разных объектов Point с двумя разными ссылками. введите описание изображения здесь

System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
System.out.println(" ");

Ожидаемый результат будет:

X1: 0     Y1: 0
X2: 0     Y2: 0

В этой строке «передача по значению» входит в игру ...

tricky(pnt1,pnt2);           public void tricky(Point arg1, Point arg2);

Список литература pnt1и pnt2которые передаются по значению к хитрому способу, что означает , что теперь ваши ссылки pnt1и pnt2имеют их copiesимени arg1и arg2.so pnt1и arg1 точки на тот же объект. (То же самое для pnt2и arg2) введите описание изображения здесь

В trickyметоде:

 arg1.x = 100;
 arg1.y = 100;

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

Далее в trickyметоде

Point temp = arg1;
arg1 = arg2;
arg2 = temp;

Здесь вы сначала создаете новую tempссылку на точку, которая будет указывать на то же место, что и arg1ссылка. Затем вы перемещаете ссылку, arg1чтобы указать на то же место, что и arg2ссылка. Наконец arg2будет указывать на то же место , как temp.

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

Отсюда сфера trickyметода нет , и вы не имеете доступа больше к ссылкам: arg1, arg2, temp. Но важно отметить, что все, что вы делаете с этими ссылками, когда они «в жизни», будет постоянно влиять на объект, на который они указывают .

Итак, после выполнения метода tricky, когда вы вернетесь к main, у вас будет такая ситуация: введите описание изображения здесь

Итак, теперь полностью выполнение программы будет:

X1: 0         Y1: 0
X2: 0         Y2: 0
X1: 100       Y1: 100
X2: 0         Y2: 0
Srle
источник
7
В Java, когда вы говорите «В Java все является ссылкой», вы подразумеваете, что все объекты передаются по ссылке. Примитивные типы данных не передаются по ссылке.
Эрик
Я могу напечатать замененное значение в методе main. в методе trickey добавьте следующее утверждение arg1.x = 1; arg1.y = 1; arg2.x = 2; arg2.y = 2;так, чтобы arg1 теперь содержал pnt2 refrence, а arg2 держал теперь pnt1 ссылку, поэтому его печатьX1: 2 Y1: 2 X2: 1 Y2: 1
Shahid Ghafoor
85

Java всегда передается по значению, а не по ссылке

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

Передача по значению означает, что вы делаете копию в памяти фактического значения параметра, которое передается. Это копия содержимого фактического параметра .

Передача по ссылке (также называемая передачей по адресу) означает, что копия адреса фактического параметра сохраняется .

Иногда Java может дать иллюзию передачи по ссылке. Давайте посмотрим, как это работает, используя пример ниже:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeValue(t);
        System.out.println(t.name);
    }

    public void changeValue(Test f) {
        f.name = "changevalue";
    }
}

class Test {
    String name;
}

Выход этой программы:

changevalue

Давайте разберемся шаг за шагом:

Test t = new Test();

Как мы все знаем, он создаст объект в куче и вернет значение ссылки обратно в t. Например, предположим, что значение t равно 0x100234(мы не знаем фактическое внутреннее значение JVM, это только пример).

первая иллюстрация

new PassByValue().changeValue(t);

При передаче ссылки t в функцию она не будет напрямую передавать фактическое значение ссылки объекта test, но создаст копию t и затем передаст ее функции. Поскольку он передается по значению , он передает копию переменной, а не фактическую ссылку на нее. Поскольку мы сказали, что значение t было 0x100234, и t, и f будут иметь одинаковое значение, и, следовательно, они будут указывать на один и тот же объект.

вторая иллюстрация

Если вы измените что-либо в функции, используя ссылку f, это изменит существующее содержимое объекта. Вот почему мы получили вывод changevalue, который обновляется в функции.

Чтобы понять это более четко, рассмотрим следующий пример:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeRefence(t);
        System.out.println(t.name);
    }

    public void changeRefence(Test f) {
        f = null;
    }
}

class Test {
    String name;
}

Будет ли это бросить NullPointerException? Нет, потому что он передает только копию ссылки. В случае передачи по ссылке он мог бы выбросить NullPointerException, как показано ниже:

третья иллюстрация

Надеюсь, это поможет.

Ганеша
источник
71

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

Получив внешний вид коробки, давайте посмотрим на сборку или некоторое низкоуровневое управление памятью. На уровне ЦП ссылка на что-либо сразу становится значением, если оно записывается в память или в один из регистров ЦП. (Вот почему указатель является хорошим определением. Это значение, которое одновременно имеет цель).

Данные в памяти имеют местоположение, и в этом месте есть значение (байт, слово, что угодно). В Assembly у нас есть удобное решение для присвоения имени определенному местоположению (он же переменная), но при компиляции кода ассемблер просто заменяет имя указанным местоположением, как ваш браузер заменяет доменные имена на IP-адреса.

Вплоть до сути технически невозможно передать ссылку на что-либо на любом языке, не представляя его (когда это сразу становится значением).

Допустим, у нас есть переменная Foo, ее Location находится на 47-м байте в памяти, а ее значение равно 5. У нас есть другая переменная Ref2Foo, которая на 223-м байте в памяти, и ее значение будет 47. Этот Ref2Foo может быть технической переменной , явно не созданный программой. Если вы просто посмотрите на 5 и 47 без какой-либо другой информации, вы увидите только два значения . Если вы используете их в качестве ссылок, чтобы добраться до 5нас, мы должны путешествовать:

(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223]  -> 47
(Foo)[47]       -> 5

Вот как работают таблицы переходов.

Если мы хотим вызвать метод / функцию / процедуру со значением Foo, существует несколько возможных способов передачи переменной в метод, в зависимости от языка и нескольких режимов вызова метода:

  1. 5 копируется в один из регистров процессора (т. Е. EAX).
  2. 5 получает PUSHd в стек.
  3. 47 копируется в один из регистров процессора
  4. 47 PUSHd в стек.
  5. 223 копируется в один из регистров ЦП.
  6. 223 получает PUSHd в стек.

В каждом случае выше значения - копия существующего значения - была создана, теперь это должен получить метод обработки. Когда вы пишете «Foo» внутри метода, он либо считывается из EAX, либо автоматически разыменовывается , либо разыменовывается дважды, процесс зависит от того, как работает язык и / или от того, что диктует тип Foo. Это скрыто от разработчика, пока она не обходит процесс разыменования. Таким образом, ссылка - это значение, когда оно представлено, потому что ссылка - это значение, которое должно быть обработано (на уровне языка).

Теперь мы передали Foo методу:

  • в случаях 1. и 2. если вы измените Foo ( Foo = 9), это повлияет только на локальную область, поскольку у вас есть копия значения. Изнутри метода мы даже не можем определить, где в памяти находился оригинальный Foo.
  • в случаях 3. и 4. если вы используете языковые конструкции по умолчанию и изменяете Foo ( Foo = 11), это может изменить Foo глобально (зависит от языка, т. е. Java или типа procedure findMin(x, y, z: integer;var m Паскаля : integer);). Однако, если язык позволяет обойти процесс разыменования, вы можете изменить его 47, скажем, на 49. В этот момент кажется, что Foo изменился, если вы прочитали его, потому что вы изменили локальный указатель на него. И если вы захотите изменить этот Foo внутри метода ( Foo = 12), вы, вероятно, откажетесь от выполнения программы (aka. Segfault), поскольку вы будете писать в память, отличную от ожидаемой, вы даже можете изменить область, предназначенную для хранения исполняемого файла. Программа и запись в нее изменят выполняемый код (Foo сейчас нет 47). НО ценность Фу47не изменился глобально, только один внутри метода, потому что 47был также копией метода.
  • в случаях 5. и 6. если вы изменяете 223внутри метода, он создает тот же хаос, что и в 3. или 4. (указатель, указывающий на неверное значение, которое снова используется в качестве указателя), но это все еще локальный проблема, так как 223 было скопировано . Однако, если вы сможете разыменовать Ref2Foo(то есть 223), достигнуть и изменить указанное значение 47, скажем, чтобы 49, это повлияет на Foo глобально , потому что в этом случае методы получили копию, 223 но указанная ссылка 47существует только один раз, и изменение этого чтобы 49приведет каждое Ref2Fooдвойное разыменование к неправильному значению.

Не обращая внимания на незначительные детали, даже языки, которые передают по ссылке, будут передавать значения в функции, но эти функции знают, что они должны использовать его для разыменования. Эта передача-ссылка-как-значение просто скрыта от программиста, потому что она практически бесполезна, а терминология - только передача по ссылке .

Строгая передача по значению также бесполезна, это будет означать, что 100-мегабайтный массив должен копироваться каждый раз, когда мы вызываем метод с массивом в качестве аргумента, поэтому Java не может строго передаваться по значению. Каждый язык передает ссылку на этот огромный массив (в качестве значения) и использует механизм копирования при записи, если этот массив можно изменить локально внутри метода, или позволяет методу (как это делает Java) изменять массив глобально (из представление вызывающего абонента) и несколько языков позволяет изменять значение самой ссылки.

Короче говоря, и в собственной терминологии Java, Java является передачей по значению, где значением может быть: либо реальное значение, либо значение , представляющее ссылку .

каратедог
источник
1
В языке с передачей по ссылке вещь, которая передается (ссылка), эфемерна; получатель не должен «копировать» его. В Java передача переданного массива является «идентификатором объекта» - эквивалентно листку бумаги, на котором написано «Объект № 24601», когда созданный 24601-й объект был массивом. Получатель может скопировать «Объект № 24601» куда угодно, и любой, у кого есть бумажка с надписью «Объект № 24601», может сделать все, что захочет, с любым из элементов массива. Шаблон битов, который передается, на самом деле не говорит «Объект № 24601», конечно, но ...
суперкат
... ключевой момент заключается в том, что получатель идентификатора объекта, который идентифицирует массив, может хранить этот идентификатор объекта в любом месте и передавать его тому, кому захочет, и любой получатель сможет получить доступ к массиву или изменить его всякий раз, когда хочет. Напротив, если массив передавался по ссылке в языке, подобном Pascal, который поддерживает такие вещи, вызываемый метод мог делать с массивом все, что ему нужно, но не мог сохранять ссылку таким образом, чтобы позволить коду изменять массив после того как он вернулся.
суперкат
Действительно, в Pascal вы можете получить адрес каждой переменной, будь то по ссылке или локально скопированной, addrнетипизированной, @с типом, и вы можете изменить указанную переменную позже (кроме локально скопированных). Но я не вижу смысла, почему вы это сделали. Этот клочок бумаги в вашем примере (объект № 24601) является справочным, его цель - помочь найти массив в памяти, он не содержит в себе никаких данных массива. Если вы перезапустите свою программу, тот же массив может получить другой идентификатор объекта, даже если его содержимое будет таким же, как и в предыдущем запуске.
каратэдог
Я думал, что оператор @ не был частью стандартного Паскаля, но был реализован как общее расширение. Это часть стандарта? Моя точка зрения заключалась в том, что в языке с истинной передачей по ссылке и отсутствием возможности создать неэфемерный указатель на эфемерный объект, код, который содержит единственную ссылку на массив в любом месте вселенной, до передачи массива с помощью ссылка может знать, что если получатель не "обманет", он все равно будет содержать единственную ссылку после этого. Единственный безопасный способ сделать это в Java - это создать временный объект ...
суперкат
... который инкапсулирует AtomicReferenceи не раскрывает ни ссылку, ни ее цель, но вместо этого включает методы для выполнения действий с целью; как только код, на который был передан объект, возвращается, AtomicReference[на который его создатель хранил прямую ссылку] должен быть признан недействительным и отменен. Это обеспечило бы правильную семантику, но это было бы медленно и неприлично.
суперкат
70

Java это вызов по значению

Как это работает

  • Вы всегда передаете копию битов значения ссылки!

  • Если это примитивный тип данных, эти биты содержат значение самого примитивного типа данных, поэтому, если мы изменим значение заголовка внутри метода, то это не будет отражать изменения снаружи.

  • Если это тип данных объекта, такой как Foo, foo = new Foo (), то в этом случае копия адреса объекта проходит как ярлык файла, предположим, что у нас есть текстовый файл abc.txt в C: \ desktop, и предположим, что мы сделали ярлык тот же файл и поместите его в C: \ desktop \ abc-ярлык, чтобы при доступе к файлу из C: \ desktop \ abc.txt и записи «Переполнение стека» и закрытии файла снова открывался файл из ярлыка, затем вы напишите «самое большое онлайн-сообщество для изучения программистами», тогда общее изменение файла будет «Stack Overflow - это крупнейшее онлайн-сообщество для обучения программистов»это означает, что не имеет значения, откуда вы открываете файл, каждый раз, когда мы обращались к одному и тому же файлу, здесь мы можем принять Foo в качестве файла и предположить, что foo хранится в 123hd7h (исходный адрес, такой как C: \ desktop \ abc.txt ) address и 234jdid (скопированный адрес, такой как C: \ desktop \ abc-ярлык, который на самом деле содержит оригинальный адрес файла внутри). Так что для лучшего понимания создайте ярлык файла и почувствуйте ..

Химаншу Арора
источник
66

Уже есть отличные ответы, которые охватывают это. Я хотел внести небольшой вклад, поделившись очень простым примером (который будет скомпилирован), сравнивающим поведение между передачей по ссылке в c ++ и передачей по значению в Java.

Несколько моментов:

  1. Термин «ссылка» перегружен двумя значениями. В Java это просто означает указатель, но в контексте «Передачи по ссылке» это означает дескриптор исходной переменной, которая была передана.
  2. Java это передача по значению . Java является потомком C (среди других языков). До C некоторые (но не все) более ранние языки, такие как FORTRAN и COBOL, поддерживали PBR, но C - нет. PBR позволил этим другим языкам вносить изменения в передаваемые переменные внутри подпрограмм. Чтобы выполнить то же самое (т.е. изменить значения переменных внутри функций), программисты на Си передавали указатели на переменные в функции. Языки, вдохновленные C, такие как Java, заимствовали эту идею и продолжают передавать указатели на методы, как это делал C, за исключением того, что Java вызывает свои указатели References. Опять же, это слово используется по-другому, чем в «Pass-By-Reference».
  3. C ++ допускает передачу по ссылке , объявляя ссылочный параметр с помощью символа «&» (который является тем же символом, который используется для обозначения «адреса переменной» как в C, так и в C ++). Например, если мы передаем указатель по ссылке, параметр и аргумент не просто указывают на один и тот же объект. Скорее, это одна и та же переменная. Если один из них настроен на другой адрес или имеет значение NULL, то же самое можно сказать и о другом.
  4. В приведенном ниже примере C ++ я передаю указатель на завершенную нулем строку по ссылке . И в приведенном ниже примере с Java я передаю ссылку Java на строку (опять же, как указатель на строку) по значению. Обратите внимание на вывод в комментариях.

C ++ передаем по справочному примеру:

using namespace std;
#include <iostream>

void change (char *&str){   // the '&' makes this a reference parameter
    str = NULL;
}

int main()
{
    char *str = "not Null";
    change(str);
    cout<<"str is " << str;      // ==>str is <null>
}

Java передает "Java-ссылку" на примере значения

public class ValueDemo{

    public void change (String str){
        str = null;
    }

     public static void main(String []args){
        ValueDemo vd = new ValueDemo();
        String str = "not null";
        vd.change(str);
        System.out.println("str is " + str);    // ==> str is not null!!
                                                // Note that if "str" was
                                                // passed-by-reference, it
                                                // WOULD BE NULL after the
                                                // call to change().
     }
}

РЕДАКТИРОВАТЬ

Несколько человек написали комментарии, которые, кажется, указывают, что либо они не смотрят на мои примеры, либо не получают пример c ++. Не уверен, где разъединение, но угадать пример с ++ не ясно. Я выкладываю тот же пример на паскале, потому что думаю, что переход по ссылке выглядит на Паскале чище, но я могу ошибаться. Я мог бы просто запутывать людей больше; Надеюсь нет.

В паскале параметры, передаваемые по ссылке, называются «параметрами вар». В процедуре setToNil ниже обратите внимание на ключевое слово «var», которое предшествует параметру «ptr». Когда указатель передается этой процедуре, он передается по ссылке . Обратите внимание на поведение: когда эта процедура устанавливает для ptr значение nil (это говорит на паскале для NULL), она устанавливает для аргумента значение nil - вы не можете сделать это в Java.

program passByRefDemo;
type 
   iptr = ^integer;
var
   ptr: iptr;

   procedure setToNil(var ptr : iptr);
   begin
       ptr := nil;
   end;

begin
   new(ptr);
   ptr^ := 10;
   setToNil(ptr);
   if (ptr = nil) then
       writeln('ptr seems to be nil');     { ptr should be nil, so this line will run. }
end.

РЕДАКТИРОВАТЬ 2

Некоторые выдержки из «Языка программирования Java» Кена Арнольда, Джеймса Гослинга (парня, который изобрел Java) и Дэвида Холмса, глава 2, раздел 2.6.5

Все параметры в методы передаются «по значению» . Другими словами, значения переменных параметров в методе являются копиями инициатора, указанного в качестве аргументов.

Он продолжает делать то же самое в отношении объектов. , ,

Вы должны заметить, что когда параметр является ссылкой на объект, передается «по значению» ссылка на объект, а не на сам объект .

И ближе к концу того же раздела он делает более широкое утверждение о том, что java передается только по значению и никогда не передается по ссылке.

Язык программирования Java не передает объекты по ссылке; он передает ссылки на объекты по значению . Поскольку две копии одной и той же ссылки ссылаются на один и тот же фактический объект, изменения, внесенные через одну ссылочную переменную, видны через другую. Существует ровно один способ передачи параметров - передача по значению - и это помогает упростить процесс.

В этом разделе книги дается отличное объяснение передачи параметров в Java, а также различия между передачей по ссылке и передачей по значению, созданной Java. Я бы посоветовал любому прочитать его, особенно если вы все еще не убеждены.

Я думаю, что разница между этими двумя моделями очень тонкая, и если вы не занимались программированием, где фактически использовали переход по ссылке, легко пропустить, где две модели отличаются.

Я надеюсь, что это решит спор, но, вероятно, не будет.

РЕДАКТИРОВАТЬ 3

Я мог бы быть немного одержим этим постом. Вероятно, потому что я чувствую, что создатели Java непреднамеренно распространяют дезинформацию. Если бы вместо использования слова «ссылка» для указателей они использовали что-то еще, скажем, Dingleberry, не было бы никаких проблем. Вы можете сказать: «Java передает dingleberry по значению, а не по ссылке», и никто не будет смущен.

Вот почему только разработчики Java имеют проблемы с этим. Они смотрят на слово «ссылка» и думают, что точно знают, что это значит, поэтому они даже не удосуживаются рассмотреть противоположный аргумент.

Во всяком случае, я заметил комментарий в более старом посте, который сделал аналогию с воздушным шаром, которая мне действительно понравилась. Настолько, что я решил склеить несколько клипов, чтобы сделать набор мультфильмов, чтобы проиллюстрировать это.

Передача ссылки по значению - Изменения в ссылку не отражаются в области видимости вызывающего, но изменения в объекте. Это потому, что ссылка копируется, но и оригинал, и копия ссылаются на один и тот же объект. Передача ссылок на объекты по значению

Передача по ссылке - Копия ссылки отсутствует. Одиночная ссылка совместно используется как вызывающей, так и вызываемой функцией. Любые изменения в ссылке или данных объекта отражаются в области действия вызывающей стороны. Передать по ссылке

РЕДАКТИРОВАТЬ 4

Я видел посты на эту тему, которые описывают низкоуровневую реализацию передачи параметров в Java, что я считаю замечательным и очень полезным, потому что оно делает абстрактную идею конкретной. Однако для меня вопрос скорее в поведении, описанном в спецификации языка, чем в технической реализации поведения. Это выдержка из спецификации языка Java, раздел 8.4.1 :

Когда метод или конструктор вызывают (§15.12), значения фактических выражений аргумента инициализируют вновь созданные переменные параметра, каждый из объявленного типа, перед выполнением тела метода или конструктора. Идентификатор, который появляется в DeclaratorId, может использоваться как простое имя в теле метода или конструктора для ссылки на формальный параметр.

Это означает, что java создает копию переданных параметров перед выполнением метода. Как и большинство людей , которые изучали компилятор в колледже, я использовал «Книгу дракона» , который составители книга. В главе 1 есть хорошее описание «Call-by-value» и «Call-by-Reference». Описание Call-by-value точно соответствует спецификациям Java.

Еще в 90-е годы, когда я изучал компиляторы, я использовал первое издание книги 1986 года, которое предшествовало Java примерно на 9 или 10 лет. Тем не менее, я только что натолкнулся на копию 2- й редакции 2007 года, в которой на самом деле упоминается Java! Раздел 1.6.6, озаглавленный «Механизмы передачи параметров», описывает передачу параметров довольно красиво. Вот выдержка под заголовком «Call-by-value», в которой упоминается Java:

В вызове по значению фактический параметр оценивается (если это выражение) или копируется (если это переменная). Значение помещается в местоположение, принадлежащее соответствующему формальному параметру вызываемой процедуры. Этот метод используется в C и Java и является распространенным вариантом в C ++, а также в большинстве других языков.

Санджив
источник
4
Честно говоря, вы можете упростить этот ответ, сказав, что Java передается по значению только для примитивных типов. Все, что наследуется от Object, эффективно передается по ссылке, где ссылка - это указатель, который вы передаете.
Скуба Стив
1
@JuanMendes, я просто использовал C ++ в качестве примера; Я мог бы дать вам примеры на других языках. Термин «передача по ссылке» существовал задолго до появления C ++. Это термин из учебника с очень конкретным определением. И по определению, Java не передается по ссылке. Вы можете продолжать использовать термин таким образом, если хотите, но его использование не будет соответствовать определению из учебника. Это не просто указатель на указатель. Это конструкция, предоставляемая языком для разрешения «Передача по ссылке». Пожалуйста, внимательно посмотрите на мой пример, но, пожалуйста, не верьте мне на слово и посмотрите сами.
Санджив
2
@AutomatedMike Я думаю, что описание Java как «передача по ссылке» также вводит в заблуждение, их ссылки - не более чем указатели. Как писал Санджив, значение, ссылка и указатель - это термины из учебника, которые имеют свое значение независимо от того, что используют создатели Java.
Енджей Дудкевич
1
@ArtanisZeratul дело тонкое, но не сложное. Пожалуйста, посмотрите на мультфильм, который я разместил. Я чувствовал, что это было довольно просто следовать. То, что Java передается по значению, это не просто мнение. Это верно по определению учебника передачи по значению. Кроме того, к «людям, которые всегда говорят, что это обходится по значению», относятся такие ученые, как Джеймс Гослинг, создатель Java. Пожалуйста, смотрите цитаты из его книги в разделе «Редактировать 2» в моем посте.
Санджив
1
Какой замечательный ответ, «передача по ссылке» не существует в Java (и других языках, таких как JS).
новая папка
56

Насколько я знаю, Java знает только вызов по значению. Это означает, что для примитивных типов данных вы будете работать с копией, а для объектов вы будете работать с копией ссылки на объекты. Однако я думаю, что есть некоторые подводные камни; например, это не будет работать:

public static void swap(StringBuffer s1, StringBuffer s2) {
    StringBuffer temp = s1;
    s1 = s2;
    s2 = temp;
}


public static void main(String[] args) {
    StringBuffer s1 = new StringBuffer("Hello");
    StringBuffer s2 = new StringBuffer("World");
    swap(s1, s2);
    System.out.println(s1);
    System.out.println(s2);
}

Это заполнит Hello World, а не World Hello, потому что в функции подкачки вы используете copys, которые не влияют на ссылки в основном. Но если ваши объекты не являются неизменяемыми, вы можете изменить это, например:

public static void appendWorld(StringBuffer s1) {
    s1.append(" World");
}

public static void main(String[] args) {
    StringBuffer s = new StringBuffer("Hello");
    appendWorld(s);
    System.out.println(s);
}

Это заполнит Hello World в командной строке. Если вы измените StringBuffer на String, он выдаст только Hello, потому что String неизменен. Например:

public static void appendWorld(String s){
    s = s+" World";
}

public static void main(String[] args) {
    String s = new String("Hello");
    appendWorld(s);
    System.out.println(s);
}

Однако вы можете создать оболочку для String, например, такую, которая позволит использовать ее со строками:

class StringWrapper {
    public String value;

    public StringWrapper(String value) {
        this.value = value;
    }
}

public static void appendWorld(StringWrapper s){
    s.value = s.value +" World";
}

public static void main(String[] args) {
    StringWrapper s = new StringWrapper("Hello");
    appendWorld(s);
    System.out.println(s.value);
}

редактировать: я считаю, что это также причина для использования StringBuffer, когда дело доходит до «добавления» двух строк, потому что вы можете изменить исходный объект, что вы не можете с неизменными объектами, такими как String.

kukudas
источник
+1 для теста подкачки - возможно, самый простой и удобный способ различить передачу по ссылке и передачу ссылки по значению . Если вы можете легко написать функцию, swap(a, b)которая (1) меняет местами aи bиз POV вызывающей стороны, (2) не зависит от типа в той степени, в которой это позволяет статическая типизация (то есть использование ее с другим типом не требует ничего, кроме изменения объявленных типов aи b) и (3) не требует, чтобы вызывающая сторона явно передавала указатель или имя, тогда язык поддерживает передачу по ссылке.
Чао
«... для примитивных типов данных вы будете работать с копией, а для объектов вы будете работать с копией ссылки на объекты» - отлично написано!
Рауль
55

Нет, это не передача по ссылке.

Java передается по значению в соответствии со спецификацией языка Java:

Когда метод или конструктор вызывается (§15.12), значения фактических выражений аргумента инициализируют вновь созданные переменные параметра , каждая из объявленного типа, перед выполнением тела метода или конструктора. Идентификатор, который появляется в DeclaratorId, может использоваться как простое имя в теле метода или конструктора для ссылки на формальный параметр .

роррохпрог
источник
52

Позвольте мне попытаться объяснить мое понимание с помощью четырех примеров. Java передается по значению, а не по ссылке

/ **

Передать по значению

В Java все параметры передаются по значению, т. Е. Назначение аргумента метода не отображается вызывающей стороне.

* /

Пример 1:

public class PassByValueString {
    public static void main(String[] args) {
        new PassByValueString().caller();
    }

    public void caller() {
        String value = "Nikhil";
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}

Результат

output : output
value : Nikhil
valueflag : false

Пример 2:

/ ** * * Передать по значению * * /

public class PassByValueNewString {
    public static void main(String[] args) {
        new PassByValueNewString().caller();
    }

    public void caller() {
        String value = new String("Nikhil");
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}

Результат

output : output
value : Nikhil
valueflag : false

Пример 3:

/ ** Этот «Pass By Value» имеет ощущение «Pass By Reference»

Некоторые люди говорят, что примитивные типы и «String» - это «передача по значению», а объекты - «передача по ссылке».

Но из этого примера мы можем понять, что это только передача по значению, имея в виду, что здесь мы передаем ссылку как значение. т.е. ссылка передается по значению. Вот почему могут измениться, и все же это справедливо после локальной области. Но мы не можем изменить фактическую ссылку за пределы исходной области. что это означает, продемонстрировано на следующем примере PassByValueObjectCase2.

* /

public class PassByValueObjectCase1 {

    private class Student {
        int id;
        String name;
        public Student() {
        }
        public Student(int id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "Student [id=" + id + ", name=" + name + "]";
        }
    }

    public static void main(String[] args) {
        new PassByValueObjectCase1().caller();
    }

    public void caller() {
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student);
    }

    public String method(Student student) {
        student.setName("Anand");
        return "output";
    }
}

Результат

output : output
student : Student [id=10, name=Anand]

Пример 4:

/ **

В дополнение к тому, что было упомянуто в примере 3 (PassByValueObjectCase1.java), мы не можем изменить фактическую ссылку за пределы исходной области. "

Примечание: я не вставляю код для private class Student. Определение класса для Studentтакое же, как в примере 3.

* /

public class PassByValueObjectCase2 {

    public static void main(String[] args) {
        new PassByValueObjectCase2().caller();
    }

    public void caller() {
        // student has the actual reference to a Student object created
        // can we change this actual reference outside the local scope? Let's see
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student); // Will it print Nikhil or Anand?
    }

    public String method(Student student) {
        student = new Student(20, "Anand");
        return "output";
    }

}

Результат

output : output
student : Student [id=10, name=Nikhil]
паук
источник
49

Вы никогда не можете перейти по ссылке в Java, и один из очевидных способов - это когда вы хотите вернуть более одного значения из вызова метода. Рассмотрим следующий фрагмент кода на C ++:

void getValues(int& arg1, int& arg2) {
    arg1 = 1;
    arg2 = 2;
}
void caller() {
    int x;
    int y;
    getValues(x, y);
    cout << "Result: " << x << " " << y << endl;
}

Иногда вы хотите использовать тот же шаблон в Java, но не можете; по крайней мере, не напрямую. Вместо этого вы можете сделать что-то вроде этого:

void getValues(int[] arg1, int[] arg2) {
    arg1[0] = 1;
    arg2[0] = 2;
}
void caller() {
    int[] x = new int[1];
    int[] y = new int[1];
    getValues(x, y);
    System.out.println("Result: " + x[0] + " " + y[0]);
}

Как было объяснено в предыдущих ответах, в Java вы передаете указатель на массив как значение в getValues. Этого достаточно, потому что метод затем модифицирует элемент массива, и по соглашению вы ожидаете, что элемент 0 будет содержать возвращаемое значение. Очевидно, что вы можете сделать это другими способами, такими как структурирование вашего кода, так что в этом нет необходимости, или создание класса, который может содержать возвращаемое значение или разрешить его установку. Но простой шаблон, доступный вам в C ++ выше, недоступен в Java.

Джаред Оберхаус
источник
48

Я думал, что добавлю этот ответ, чтобы добавить больше деталей из Спецификаций.

Во-первых, в чем разница между передачей по ссылке и передачей по значению?

Передача по ссылке означает, что параметр вызываемых функций будет таким же, как переданный аргумент вызывающего (не значение, а идентификатор - сама переменная).

Передача по значению означает, что параметр вызываемых функций будет копией переданного аргумента вызывающей стороны.

Или из википедии, на тему передачи по ссылке

При оценке по ссылке (также называемой передачей по ссылке) функция получает неявную ссылку на переменную, используемую в качестве аргумента, а не на копию ее значения. Как правило, это означает, что функция может модифицировать (т.е. назначать) переменную, используемую в качестве аргумента - то, что увидит ее вызывающая сторона.

И на предмет передачи по стоимости

В вызове по значению выражение аргумента оценивается, и результирующее значение привязывается к соответствующей переменной в функции [...]. Если функция или процедура может назначать значения своим параметрам, назначается только ее локальная копия [...].

Во-вторых, нам нужно знать, что Java использует в своих вызовах методов. В языке спецификация Java состояния

Когда метод или конструктор вызывается (§15.12), значения фактических выражений аргумента инициализируют вновь созданные переменные параметра , каждая из объявленного типа, перед выполнением тела метода или конструктора.

Таким образом, он присваивает (или связывает) значение аргумента соответствующей переменной параметра.

Какова ценность аргумента?

Давайте рассмотрим ссылочные типы, в Java Virtual Machine Specification состояния

Существует три типа ссылочных типов : типы классов, типы массивов и типы интерфейсов. Их значения являются ссылками на динамически создаваемые экземпляры классов, массивы или экземпляры классов или массивы, которые реализуют интерфейсы соответственно.

Спецификация языка Java также говорится ,

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

Значением аргумента (некоторого ссылочного типа) является указатель на объект. Обратите внимание, что переменная, вызов метода с возвращаемым типом ссылочного типа и выражение создания экземпляра ( new ...) разрешаются в значение ссылочного типа.

Так

public void method (String param) {}
...
String var = new String("ref");
method(var);
method(var.toString());
method(new String("ref"));

все связывают значение ссылки на Stringэкземпляр для вновь созданного параметра метода, в param. Это именно то, что описывает определение передачи по значению. Таким образом, Java передается по значению .

Тот факт, что вы можете следовать по ссылке, чтобы вызвать метод или получить доступ к полю ссылочного объекта, совершенно не имеет отношения к беседе. Определение передачи по ссылке было

Как правило, это означает, что функция может модифицировать (т.е. назначать) переменную, используемую в качестве аргумента - то, что увидит ее вызывающая сторона.

В Java изменение переменной означает ее переназначение. В Java, если вы переназначите переменную в методе, она останется незамеченной для вызывающей стороны. Модификация объекта, на который ссылается переменная, совершенно другая концепция.


Примитивные значения также определены в Спецификации виртуальной машины Java, здесь . Значением типа является соответствующее целочисленное значение или значение с плавающей запятой, кодированное соответствующим образом (8, 16, 32, 64 и т. Д. Биты).

Сотириос Делиманолис
источник
42

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

Все аргументы Java передаются по значению (ссылка используется при использовании метода):

В случае примитивных типов поведение Java простое: значение копируется в другой экземпляр примитивного типа.

В случае объектов это тоже самое: переменные объекта - это указатели (сегменты), содержащие только адрес объекта, который был создан с использованием ключевого слова «new», и копируются как примитивные типы.

Поведение может отличаться от примитивных типов: поскольку скопированная переменная объекта содержит один и тот же адрес (для одного и того же объекта). Содержимое / члены объекта все еще могут быть изменены внутри метода, а затем и доступ снаружи, создавая иллюзию, что сам (содержащий) Объект был передан по ссылке.

«Строковые» объекты кажутся хорошим контрпримером к городской легенде о том, что «объекты передаются по ссылке»:

Фактически, используя метод, вы никогда не сможете обновить значение строки, переданной в качестве аргумента:

String Object, содержит символы в массиве, объявленном как final, который нельзя изменить. Только адрес объекта может быть заменен другим, используя «новый». Использование «new» для обновления переменной не позволит получить доступ к объекту извне, поскольку переменная изначально была передана по значению и скопирована.

user1767316
источник
Таким образом, это byRef в отношении объектов и byVal в отношении примитивов?
Мокс
@mox, пожалуйста, прочитайте: Объекты не передаются по ссылке, это уступка: String a = new String ("неизмененный");
user1767316
1
@Aaron «Передача по ссылке» не означает «передавать значение, являющееся членом типа, называемого« ссылкой »в Java». Два использования «ссылки» означают разные вещи.
Филипп
2
Довольно запутанный ответ. Причина, по которой вы не можете изменить строковый объект в методе, который передается по ссылке, заключается в том, что строковые объекты являются неизменяемыми по конструкции, и вы не можете делать такие вещи, как strParam.setChar ( i, newValue ). Тем не менее, строки, как и все остальные, передаются по значению, и, поскольку String является не примитивным типом, это значение является ссылкой на объект, созданный с помощью new, и вы можете проверить это с помощью String.intern (). ,
zakmck
1
Вместо этого вы не можете изменить строку с помощью param = "Другая строка" (эквивалент новой строки ("другая строка")), поскольку ссылочное значение параметра "Пара" (которое теперь указывает на "другую строку") не может возвращаться из метода тело. Но это верно для любого другого объекта, разница в том, что, когда интерфейс класса это позволяет, вы можете сделать param.changeMe (), и объект верхнего уровня, на который вы ссылаетесь, изменится, потому что param указывает на него, несмотря на само ссылочное значение param (адресное местоположение, в терминах C) не может всплыть обратно из метода.
Zakmck
39

Различие, или, может быть, просто то, что я помню, как у меня было такое же впечатление, как у оригинального плаката, заключается в следующем: Java всегда передается по значению. Все объекты (в Java, все, кроме примитивов) в Java являются ссылками. Эти ссылки передаются по значению.

штеймер
источник
2
Я нахожу ваше предпоследнее предложение очень вводящим в заблуждение. Это неправда, что «все объекты в Java являются ссылками». Это только ссылки на те объекты, которые являются ссылками.
Дауд ибн Карим
37

Как уже упоминали многие люди, Java всегда передается по значению

Вот еще один пример, который поможет вам понять разницу ( классический пример обмена ):

public class Test {
  public static void main(String[] args) {
    Integer a = new Integer(2);
    Integer b = new Integer(3);
    System.out.println("Before: a = " + a + ", b = " + b);
    swap(a,b);
    System.out.println("After: a = " + a + ", b = " + b);
  }

  public static swap(Integer iA, Integer iB) {
    Integer tmp = iA;
    iA = iB;
    iB = tmp;
  }
}

Печать:

До: а = 2, б = 3
После: а = 2, б = 3

Это происходит потому, что iA и iB являются новыми локальными ссылочными переменными, которые имеют одинаковое значение переданных ссылок (они указывают на a и b соответственно). Таким образом, попытка изменить ссылки iA или iB изменится только в локальной области, а не вне этого метода.

пекин
источник
32

У Java есть только передача по значению. Очень простой пример, чтобы подтвердить это.

public void test() {
    MyClass obj = null;
    init(obj);
    //After calling init method, obj still points to null
    //this is because obj is passed as value and not as reference.
}
private void init(MyClass objVar) {
    objVar = new MyClass();
}
Gaurav
источник
4
Это самый простой и простой способ увидеть, что Java проходит по значению. Значение obj( null) было передано init, а не ссылка на obj.
Дэвид Шварц
31

Я всегда думаю об этом как о «проходе копией». Это копия значения, будь то примитив или ссылка. Если это примитив, то это копия битов, являющихся значением, а если это объект, то это копия ссылки.

public class PassByCopy{
    public static void changeName(Dog d){
        d.name = "Fido";
    }
    public static void main(String[] args){
        Dog d = new Dog("Maxx");
        System.out.println("name= "+ d.name);
        changeName(d);
        System.out.println("name= "+ d.name);
    }
}
class Dog{
    public String name;
    public Dog(String s){
        this.name = s;
    }
}

вывод Java PassByCopy:

имя = макс
имя = фидо

Примитивные классы-обертки и строки неизменны, поэтому любой пример, использующий эти типы, не будет работать так же, как другие типы / объекты.

SWD
источник
5
«передача по копии» - это то, что означает передача по значению .
TJ Crowder
@TJCrowder. «Передача копией» может быть более подходящим способом выражения той же концепции для целей Java: потому что семантически это очень затрудняет использование идеи, сродни передаче по ссылке, что вам и нужно в Java.
Майк Грызун
@mikerodent - Извините, я не следил за этим. :-) «Передача по значению» и «передача по ссылке» являются правильными терминами искусства для этих понятий. Мы должны использовать их (предоставляя их определения, когда они нужны людям), а не придумывать новые. Выше я сказал, что «передача по копии» означает «передача по значению», но на самом деле неясно, что означает «передача по копии» - копия чего? Значение? (Например, ссылка на объект.) Сам объект? Поэтому я придерживаюсь условий искусства. :-)
TJ Crowder
29

В отличие от некоторых других языков, Java не позволяет выбирать между передачей по значению и передачей по ссылке - все аргументы передаются по значению. Вызов метода может передавать в метод два типа значений - копии примитивных значений (например, значений типа int и double) и копии ссылок на объекты.

Когда метод изменяет параметр типа примитива, изменения параметра не влияют на исходное значение аргумента в вызывающем методе.

Когда дело доходит до объектов, сами объекты не могут быть переданы в методы. Таким образом, мы передаем ссылку (адрес) объекта. Мы можем манипулировать исходным объектом, используя эту ссылку.

Как Java создает и сохраняет объекты: когда мы создаем объект, мы сохраняем адрес объекта в ссылочной переменной. Давайте проанализируем следующее утверждение.

Account account1 = new Account();

«Account account1» - это тип и имя ссылочной переменной, «=» - оператор присваивания, «new» запрашивает необходимый объем пространства в системе. Конструктор справа от ключевого слова new, который создает объект, неявно вызывается ключевым словом new. Адрес созданного объекта (результат правильного значения, которое является выражением, называемым «выражением создания экземпляра класса») присваивается левому значению (которое является ссылочной переменной с указанным именем и типом) с помощью оператора присвоения.

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

Передача ссылок на массивы вместо самих объектов массива имеет смысл с точки зрения производительности. Поскольку в Java все передается по значению, при передаче объектов массива будет передана копия каждого элемента. Для больших массивов это приведет к потере времени и значительному хранению копий элементов.

На рисунке ниже вы можете видеть, что у нас есть две ссылочные переменные (они называются указателями в C / C ++, и я думаю, что этот термин облегчает понимание этой функции.) В методе main. Примитивные и эталонные переменные хранятся в памяти стека (левая сторона на изображениях ниже). ссылочные переменные array1 и array2 "точка" (как ее называют программисты C / C ++) или ссылки на массивы a и b соответственно, которые являются объектами (значения, которые эти ссылочные переменные содержат адреса объектов) в динамической памяти (справа на изображениях ниже) ,

Передайте по значению пример 1

Если мы передаем значение ссылочной переменной array1 в качестве аргумента методу reverseArray, в методе создается ссылочная переменная, и эта ссылочная переменная начинает указывать на тот же массив (a).

public class Test
{
    public static void reverseArray(int[] array1)
    {
        // ...
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        reverseArray(array1);
    }
}

Передайте по значению пример 2

Итак, если мы скажем

array1[0] = 5;

в методе reverseArray он внесет изменение в массив a.

У нас есть другая ссылочная переменная в методе reverseArray (array2), которая указывает на массив c. Если бы мы сказали

array1 = array2;

в методе reverseArray ссылочная переменная array1 в методе reverseArray перестает указывать на массив a и начинает указывать на массив c (пунктирная линия на втором изображении).

Если мы возвращаем значение ссылочной переменной array2 в качестве возвращаемого значения метода reverseArray и присваиваем это значение ссылочной переменной array1 в main методе, array1 в main начнет указывать на массив c.

Итак, давайте напишем все, что мы сделали сразу.

public class Test
{
    public static int[] reverseArray(int[] array1)
    {
        int[] array2 = { -7, 0, -1 };

        array1[0] = 5; // array a becomes 5, 10, -7

        array1 = array2; /* array1 of reverseArray starts
          pointing to c instead of a (not shown in image below) */
        return array2;
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        array1 = reverseArray(array1); /* array1 of 
         main starts pointing to c instead of a */
    }
}

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

И теперь, когда метод reverseArray завершен, его ссылочные переменные (array1 и array2) исчезли. Это означает, что теперь у нас есть только две ссылочные переменные в основном методе array1 и array2, которые указывают на массивы c и b соответственно. Ссылочная переменная не указывает на объект (массив) a. Так что он имеет право на сборку мусора.

Вы также можете присвоить значение array2 в main для array1. массив1 начнет указывать на б.

Michael
источник
27

Я создал ветку, посвященную этим вопросам для любых языков программирования здесь .

Ява также упоминается . Вот краткое резюме:

  • Java передает его параметры по значению
  • «по значению» это единственный способ в Java передать параметр методу
  • использование методов из объекта, заданного в качестве параметра, изменит объект, так как ссылки указывают на исходные объекты. (если этот метод сам изменяет некоторые значения)
свен
источник
27

Короче говоря, у объектов Java есть некоторые очень специфические свойства.

В общем, Java , имеет примитивные типы ( int, bool, char, doubleи т.д.), которые передаются непосредственно по значению. Тогда у Java есть объекты (все, что происходит от java.lang.Object). Объекты на самом деле всегда обрабатываются с помощью ссылки (ссылка - это указатель, который вы не можете коснуться). Это означает, что, по сути, объекты передаются по ссылке, так как ссылки обычно не интересны. Это, однако, означает, что вы не можете изменить объект, на который указывает объект, так как сама ссылка передается по значению.

Это звучит странно и сбивает с толку? Давайте рассмотрим, как C реализует передачу по ссылке и передачу по значению. В С соглашением по умолчанию является передача по значению. void foo(int x)передает int по значению. void foo(int *x)это функция , которая не желает , чтобы int a, но указатель на междунар: foo(&a). Можно использовать это с &оператором для передачи адреса переменной.

Отнесите это на C ++, и у нас есть ссылки. Ссылки в основном (в этом контексте) являются синтаксическим сахаром, который скрывает указатель части уравнения: void foo(int &x)вызывается foo(a), когда сам компилятор знает, что это ссылка, и адрес не-ссылки aдолжен быть передан. В Java все переменные, ссылающиеся на объекты, на самом деле относятся к ссылочному типу, фактически вызывая вызов по ссылке для большинства целей и задач без детального контроля (и сложности), предоставляемого, например, C ++.

Paul de Vrieze
источник
Я думаю, что это очень близко к моему пониманию объекта Java и его ссылки. Объект в Java передается методу посредством эталонной копии (или псевдонима).
MaxZoom