Чи Java "прохідна посилання" чи "пропускна вартість"?


6577

Я завжди думав, що Java проходить через посилання .

Однак я бачив пару публікацій в блозі (наприклад, цей блог ), які стверджують, що це не так.

Я не думаю, що я розумію різницю, яку вони роблять.

Яке пояснення?


705
Я вважаю, що велика частина плутанини в цьому питанні пов'язана з тим, що різні люди мають різні визначення терміна "довідка". Люди, що походять із походження C ++, припускають, що "посилання" повинно означати, що воно означало на C ++, люди з фонів С припускають, що "посилання" має бути таким же, як "вказівник" у їхній мові тощо. Чи правильно сказати, що Java передає посилання дійсно, залежить від того, що означає "посилання".
Гравітація

119
Я намагаюся послідовно використовувати термінологію, яку ви знайдете в статті Стратегії оцінювання . Слід зазначити, що, хоча ця стаття вказує на те, що терміни сильно різняться залежно від спільноти, вона підкреслює, що семантика call-by-valueі call-by-referenceвідрізняються дуже важливим чином . (Особисто я вважаю за краще використовувати call-by-object-sharingце сьогодні call-by-value[-of-the-reference], оскільки це описує семантику на високому рівні і не створює конфлікту з call-by-value, що є основою реалізації.)

59
@ Гравітація: Ви можете піти і помістити свій коментар на ВЕЛИЧЕЗНУ білборд чи щось таке? Ось у двох словах. І це показує, що вся ця річ - семантика. Якщо ми не погоджуємось з базовим визначенням посилання, то ми не погодимось відповіді на це питання :)
MadConan

29
Я думаю, що плутанина - це "пройти через посилання" проти "референтну семантику". Java є прохідним значенням з опорною семантикою.
шприц

11
@ Гравітація, хоча ти абсолютно правильний у тому, що люди, які походять із С ++, інстинктивно матимуть іншу інтуїцію щодо терміна "посилання", я особисто вважаю, що тіло більше закопане у "від". "Пройти повз" заплутано тим, що він абсолютно відрізняється від "Проходження" в Java. Однак у C ++ це розмовно - ні. У C ++ ви можете сказати "проходження посилання", і розуміється, що він пройде swap(x,y)тест .

Відповіді:


5838

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()в 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сам.


263
Це не трохи плутає проблему з внутрішніми деталями? Не існує концептуальної різниці між "передачею посилання" та "передачею значення посилання", якщо припустити, що ви маєте на увазі "значення внутрішнього вказівника на об'єкт".
izb

383
Але є тонка різниця. Подивіться перший приклад. Якби це було лише прохідним посиланням, aDog.name було б "Fifi". Це не так - посилання, яке ви отримуєте, - це посилання на значення, яке при перезаписі буде відновлено при виході з функції.
erlando

300
@Lorenzo: Ні, у Java все передається за значенням. Примітиви передаються за значенням, а посилання на об'єкти передаються за значенням. Самі об'єкти ніколи не передаються методу, але об'єкти завжди знаходяться в купі і лише посилання на об'єкт передається методу.
Еско Луонтола,

294
Моя спроба візуалізувати проходження предмета: Уявіть собі повітряну кулю. Виклик fxn - це як прив’язання другої струни до повітряної кулі та передача лінії до fxn. Параметр = new Balloon () вирізає цей рядок і створить нову кульку (але це не вплине на початковий куля). Параметр.pop () все ще з’явиться, хоча він слідує за рядком до того самого оригінального кулі. Java передається за значенням, але передане значення не є глибоким, воно знаходиться на найвищому рівні, тобто примітиві або покажчику. Не плутайте це з глибоким прохідним значенням, коли об'єкт повністю клонований і переданий.
dhackner

150
Що бентежить, що посилання на об'єкти - це фактично вказівники. На початку SUN називав їх покажчиками. Тоді маркетинг повідомив, що "покажчик" було поганим словом. Але ви все ще бачите "правильну" номенклатуру в NullPointerException.
Контракт професора Фолкена порушено

3035

Я щойно помітив, що ти посилався на мою статтю .

Java Spec каже, що все на 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
  • на лінії "AAA"
    • someDogслідує до цього, на Dogякий вказує ( Dogоб’єкт за адресою 42)
    • що Dog(той за адресою 42) просять змінити своє ім'я на Макс
  • на лінії "BBB"
    • створюється новий Dog. Скажімо, він за адресою 74
    • параметр присвоюємо someDog74
  • на лінії "CCC"
    • someDog дотримується Dogйого вказує на ( Dogоб’єкт за адресою 74)
    • що Dog(той за адресою 74) просять змінити своє ім'я на Роульф
  • тоді ми повертаємось

Тепер давайте подумаємо, що відбувається поза методом:

Чи myDogзмінилися?

Там ключ.

Маючи на увазі, що myDogце покажчик , а не фактичний Dog, відповідь - НІ. myDogяк і раніше має значення 42; він все ще вказує на оригінал Dog(але зауважте, що через рядок "AAA" його назва тепер "Макс" - все одно та сама собака; myDogзначення не змінилося.)

Цілком справедливо дотримуватися адреси та змінювати те, що знаходиться наприкінці; але це не змінює змінну.

Java працює точно так само, як C. Ви можете призначити покажчик, передати покажчик методу, слідувати за вказівником у методі та змінити дані, на які вказували. Однак ви не можете змінити, де цей вказівник вказує.

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

Якби Java мала семантику проходження посилань, fooметод, який ми визначили вище, змінив би місце myDogвказівки при призначенні someDogна лінії BBB.

Розгляньте контрольні параметри як псевдоніми для змінної, переданої в. Коли цей псевдонім призначений, так це і змінна, яка була передана в


164
Ось чому загальний рефрен "Java не має покажчиків" настільки оманливий.
Беска

134
Ви помиляєтесь, імхо. "Маючи на увазі, що myDog - це вказівник, а не фактична собака, відповідь" НІ ". MyDog все ще має значення 42; він все ще вказує на оригінальну собаку." myDog має значення 42, але аргумент його імені тепер містить "Max", а не "Rover" в // AAA рядку.
Özgür

180
Подумайте про це таким чином. У когось є адреса Ен-Арбор, штат Мічиган (мій рідний місто, Ідіть БЛАГО!) На аркуші паперу під назвою "annArborLocation". Ви копіюєте його на аркуші паперу під назвою "myDestination". Ви можете заїхати на "myDestination" і посадити дерево. Можливо, ви змінили щось про місто в цьому місці, але це не змінює LAT / LON, що було написано на будь-якому папері. Ви можете змінити LAT / LON на "myDestination", але це не змінить "annArborLocation". Чи допомагає це?
Скотт Стенчфілд

43
@Scott Stanchfield: Я прочитав вашу статтю близько року тому, і це дійсно допомогло мені зрозуміти речі. Дякую! Чи можу я покірно запропонувати невелике доповнення: ви повинні зазначити, що насправді існує специфічний термін, який описує цю форму "виклику за значенням, де значення є посиланням", яку винайшла Барбара Лісков для опису стратегії оцінювання її мови CLU у 1974 р., Щоб уникнути плутанини, наприклад, та, до якої звертається ваша стаття: виклик за допомогою спільного доступу (іноді викликається викликом шляхом спільного використання об'єктів або просто дзвінок за об'єктом ), що майже чудово описує семантику.
Йорг W Міттаг

29
@Gevorg - мови C / C ++ не володіють поняттям "покажчик". Є й інші мови, які використовують покажчики, але не дозволяють використовувати ті ж типи маніпулювання покажчиками, які дозволяють C / C ++. У Java є вказівники; вони просто захищені від пустощів.
Скотт Стенчфілд

1740

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 :)


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

732

Це дасть вам трохи уявлення про те, як Java насправді працює, до того, що в наступній дискусії про те, як Ява проходить посилання або проходить повз значення, ви просто посміхніться :-)

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

Крок другий пам’ятайте, що при передачі Об'єкта в метод ви передаєте посилання на об’єкт, а не сам об’єкт.

  • Студент : Майстер, це означає, що Java проходить посилання?
  • Майстер : Коник, Ні.

Тепер подумайте, що робить посилання / змінна об'єкта:

  1. Змінна містить біти, які вказують JVM, як дістатися до згаданого Об'єкта в пам'яті (Heap).
  2. При передачі аргументів методу ви передаєте не опорну змінну, а копію бітів у контрольній змінній . Щось подібне: 3bad086a. 3bad086a являє собою спосіб дістатися до переданого об'єкта.
  3. Таким чином, ви просто передаєте 3bad086a, що це значення посилання.
  4. Ви передаєте значення посилання, а не самого посилання (а не об'єкта).
  5. Це значення фактично COPIED і надається методу .

У наступному (будь ласка, не намагайтеся компілювати / виконувати це ...):

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. }

Що сталося?

  • Змінний людина створюється в рядку # 1 , і це нуль на початку.
  • Новий об'єкт Person створюється у рядку №2, зберігається в пам'яті, а змінній особі надається посилання на об'єкт Person. Тобто його адреса. Скажімо, 3bad086a.
  • Змінна особа, що містить адресу Об'єкта, передається функції у рядку №3.
  • У рядку №4 ви можете слухати звук тиші
  • Перевірте коментар у рядку №5
  • Локальна змінна методу - AnotherReferenceToTheSamePersonObject - створюється, а потім вводиться магія в рядок №6:
    • Мінлива / посилання люди копіюються біт за бітом і передаються anotherReferenceToTheSamePersonObject всередині функції.
    • Не створюються нові екземпляри Person.
    • І " person ", і " anotherReferenceToTheSamePersonObject " мають однакове значення 3bad086a.
    • Не намагайтеся цього, але person == elseReferenceToTheSamePersonObject було би правдою.
    • Обидві змінні мають ІДЕНТИЧНІ КОПІЇ посилання, і обидві вони посилаються на один і той же Об'єкт Особи, ОДНІЙ Об'єкт на Купі, А НЕ КОПІЯ.

Малюнок вартує тисячі слів:

Передайте значення

Зауважте, що стрілки AnotherReferenceToTheSamePersonObject спрямовані до Об'єкта, а не до змінної особи!

Якщо ви цього не отримали, просто довіртеся мені і пам’ятайте, що краще сказати, що Java передається за значенням . Ну, передайте по еталонному значенню . Ну добре, ще краще - пропуск-за-копією змінного значення! ;)

Тепер не соромтесь ненавидіти мене, але зауважте, що враховуючи це , немає різниці між передачею примітивних типів даних та Об'єктами, коли йдеться про аргументи методу.

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

  • Якщо це примітивний тип даних, ці біти будуть містити значення самого примітивного типу даних.
  • Якщо це "Об'єкт", біти будуть містити значення адреси, яка повідомляє JVM, як дістатися до "Об'єкта".

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


Наведена вище функція changeName ніколи не зможе змінити фактичний вміст (бітові значення) переданої посилання. Іншими словами, ChangeName не може змусити особу людини звертатися до іншого Об'єкту.


Звичайно, ви можете скоротити це і просто сказати, що Java є прохідною цінністю!


5
Ви маєте на увазі покажчики? .. Якщо я правильно зрозумів, в public void foo(Car car){ ... }, carє локальним, fooі він містить розташування об'єкта в купі? Отже, якщо я зміню carзначення на car = new Car(), воно вкаже на різні Об'єкти на купі? і якщо я зміню carвартість власності на car.Color = "Red", Об'єкт у купі, на яку вказує, carбуде змінено. Також те саме в C #? Будь-ласка дайте відповідь! Дякую!
dpp

11
@domanokz Ти мене вбиваєш, будь ласка, більше не вимовляй цього слова! ;) Зауважте, що я міг би відповісти на це питання, не кажучи також "посилання". Це питання термінології, і це робить його гірше. На жаль, я та Скотт мають різні погляди на це. Я думаю, ви зрозуміли, як це працює в Java, тепер ви можете назвати його передавати за значенням, за допомогою спільного використання об'єкта, за допомогою копії змінного значення або не соромтеся придумати щось інше! Мені не дуже важливо, доки ти зрозумів, як це працює і що є в змінній типу Об'єкт: просто адреса PO Box! ;)
Марселл Уоллес

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, скопіювали посилання, які вказують на однакові об’єкти.

Звичайно, передача за значенням посилання на об'єкт виглядає дуже (як і не відрізняється на практиці від) передачі об'єкта за посиланням.


7
Оскільки значення примітивів незмінні (як String), різниця між двома випадками насправді не має значення.
Paŭlo Ebermann

4
Саме так. При всьому, що ви можете сказати за допомогою спостережуваної поведінки JVM, примітиви можуть бути передані за посиланням і можуть жити на купі. Вони цього не роблять, але це насправді не спостерігається.
Гравітація

6
примітиви незмінні? це нове в Java 7?
користувач85421

4
Покажчики незмінні, примітиви взагалі мінливі. Рядок також не є примітивом, це об'єкт. Більше того, основна структура String є змінним масивом. Єдина незмінна річ у цьому - це довжина, яка є притаманною природою масивів.
kingfrito_5005

3
Це ще одна відповідь, яка вказує на значно семантичний характер аргументу. Визначення посилання, що надається у цій відповіді, зробить Яву "прохідною посиланням". Автор, по суті, визнає це в останньому абзаці, заявляючи, що це "не відрізняється на практиці" від "пропускного посилання". Я сумніваюся, що ОП запитує через бажання зрозуміти реалізацію Java, а швидше зрозуміти, як правильно використовувати Java. Якби це було нерозрізнено на практиці, тоді не було б сенсу дбати і навіть думати про це було б марною тратою часу.
Loduwijk

331

Java передає посилання за значенням.

Таким чином, ви не можете змінити посилання, яке передається.


27
Але те, що постійно повторюється, "ви не можете змінити значення об'єктів, переданих аргументами", явно помилкове. Можливо, ви не зможете змусити їх посилатися на інший об’єкт, але ви можете змінити їх вміст, викликавши їх методи. IMO це означає, що ви втрачаєте всі переваги посилань і не отримуєте додаткових гарантій.
Timmmm

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створюється адреса вже наявного рядка.

ла-да-ді-да-да-да

Отже, значення, довідка? Ви кажете «картопля».


7
Однак вам слід було би скористатися більш складним прикладом, коли з'являється функція для зміни змінної, на адресу якої вона має посилання.
Брайан Петерсон

34
Люди не "танцюють навколо справжнього питання" стека проти купи, тому що це не справжня проблема. Це деталізація щодо здійснення в кращому випадку, а в гіршому - неправильно (Цілком можливо, що об’єкти можуть жити на стеку; google "аналіз втечі". І величезна кількість об'єктів містять примітиви, які, ймовірно , не живуть у стеці.) Справжня проблема полягає саме в різниці між типовими типами та типами значень. - зокрема, що значення змінної типу посилання є посиланням, а не об'єктом, на який вона посилається.
cHao

8
Це "деталізація реалізації" в тому, що Java ніколи не вимагає показувати вам, де об'єкт живе в пам'яті, і насправді здається рішучим уникнути протікання цієї інформації. Це може поставити об'єкт у стек, і ви ніколи не дізнаєтесь. Якщо вам байдуже, ви зосереджуєтесь на неправильній речі - і в цьому випадку це означає ігнорування реальної проблеми.
cHao

10
І в будь-якому випадку, «примітиви йдуть на стек» - неправильно. Примітивні локальні змінні йдуть на стек. (Якщо, звичайно, їх не було оптимізовано.) Але тоді так само робити і місцеві змінні . І примітивні члени, визначені в межах об'єкта, живуть, де б він не жив.
cHao

9
Погодьтеся з коментарями тут. Стек / купа - це бічна проблема, а не актуальна. Деякі змінні можуть бути у стеці, деякі - у статичній пам'яті (статичні змінні), а багато - у купі (усі змінні об'єктні елементи). НІКОЛИ з цих змінних не можна передавати за посиланням: із названого методу НІКОЛИ не можна змінювати значення змінної, яка передається як аргумент. Тому в Java немає прохідних посилань.
риболовля

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 має лише два типи передачі: за значенням для вбудованих типів та за значенням вказівника для типів об'єктів.


7
+1 Я також додав Dog **objPtrPtrби до прикладу C ++, щоб ми могли змінити те, на що вказує вказівник.
Amro

1
Але це не відповідає на задане питання на Java. По суті, все в методі Java - це Pass By Value, нічого більше.
tauitdnmd

2
Цей приклад просто доводить, що java використовує той самий еквівалент покажчика в C ні? Де в основному прочитуються значення на значення C? Зрештою, вся ця нитка незрозумілий
amdev


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.


8
Я скоріше скажу, що бар - це копія опорного база (або псевдонім baz), який спочатку вказує на один і той же об'єкт.
MaxZoom

Чи не існує незначного відмінності між класом String та всіма іншими класами?
Мехді Карамослі

152

Я не можу повірити, що ще ніхто не згадував Барбару Лісков. Коли вона розробила CLU у 1974 році, вона зіткнулася з цією ж проблемою термінології, і вона винайшла термін виклик шляхом спільного використання (також відомий як виклик шляхом спільного використання об'єктів та виклик за об'єктом ) для цього конкретного випадку "виклику за значенням, де значення посилання ".


3
Мені подобається ця відмінність у номенклатурі. Прикро, що Java підтримує дзвінки шляхом спільного використання об'єктів, але не за значенням (як C ++). Java підтримує дзвінок за значенням лише для примітивних типів даних, а не для складених типів даних.
Дерек Махар

2
Я дійсно не думаю, що нам потрібен був додатковий термін - це просто прохідне значення для конкретного типу значення. Чи додає "виклик примітивом", додасть якісь пояснення?
Скотт Стенчфілд


Тож я можу пройти посилання за допомогою спільного використання якогось глобального контекстного об'єкта або навіть передавати контекстний об'єкт, який містить інші посилання? Я все ще проходжую цінність, але принаймні у мене є доступ до посилань, які я можу змінити і зробити їх вказівками на щось інше.
YoYo

1
@Sanjeev: обмін дзвінками по об'єктах - це особливий випадок передачі значення. Однак дуже багато людей твердо стверджують, що Java (і подібні мови, такі як Python, Ruby, ECMAScript, Smalltalk) є прохідними посиланнями. Я також вважаю за краще би називати це "перехідною вартістю", але обмін дзвінками за об'єктом представляється розумним терміном, на який можуть погодитися навіть люди, які стверджують, що це не прохідна вартість.
Йорг W Міттаг

119

Суть у тому, що посилання на слово у виразі "пройти посилання" означає щось зовсім інше, ніж звичайне значення слова посилання на Java.

Зазвичай у Java посилання означає посилання на об'єкт . Але технічні терміни, що передаються за посиланням / значенням з теорії мови програмування, говорять про посилання на осередок пам'яті, що містить змінну , що є чимось зовсім іншим.


7
@Gevorg - Тоді що таке "NullPointerException"?
Гарячі лизи

5
@Hot: На жаль, названий виняток до того, як Java оселилася на чіткій термінології. Семантично еквівалентний виняток у c # називається NullReferenceException.
ЖакБ

4
Мені завжди здавалося, що використання «посилання» в термінології Java - це афект, який заважає зрозуміти.
Гарячі лизання

Я навчився називати ці так звані "покажчики на об'єкти" як "ручка до об'єкта". Це зменшило неоднозначність.
moronkreacionz

86

У Java все посилається, тому коли у вас є щось на кшталт: Point pnt1 = new Point(0,0);Java робить наступне:

  1. Створює новий об'єкт Point
  2. Створює нову посилання на точку та ініціалізує це посилання на точку (посилання на) раніше створеного об’єкта 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

7
У Java, коли ви говорите "У java все є посиланням", ви маєте на увазі, що всі об'єкти передаються за посиланням. Примітивні типи даних не передаються посиланням.
Ерік

Я в змозі надрукувати замінене значення в основному методі. у методі trickkey додайте наступне твердження arg1.x = 1; arg1.y = 1; arg2.x = 2; arg2.y = 2;так, як arg1 тепер тримає prent2 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 на функцію вона безпосередньо не передає фактичне опорне значення тесту об'єкта, але створить копію 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

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

Оглядаючи зовнішній вигляд вікна, давайте подивимось на складання або деяке управління пам'яттю низького рівня. На рівні процесора посилання на що-небудь відразу стає значенням, якщо воно записується в пам'ять або в один з регістрів процесора. (Ось чому вказівник - це хороше визначення. Це значення, яке одночасно має мету).

Дані в пам'яті мають місцеположення, і в цьому місці є значення (байт, слово, що завгодно). У Асамблеї у нас є зручне рішення надати ім’я певній локації (він же змінна), але при складанні коду асемблер просто замінює ім'я на визначене місце так само, як ваш браузер замінює доменні імена з IP-адресами.

До основи технічно неможливо передавати посилання на будь-яку мову будь-якою мовою, не представляючи її (коли це відразу стає значенням).

Скажімо, у нас є змінна Foo, її розташування знаходиться на 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;вар-м Паскаля : integer);). Однак якщо мова дозволяє вам обійти процес відміни, ви можете змінити 47, скажімо, на 49. У той момент Foo, здається, був змінений, якщо ви його прочитали, тому що ви змінили локальний вказівник на нього. І якщо ви повинні змінити цей Foo всередині методу ( Foo = 12), ви, ймовірно, FUBAR виконання програми (ака. Segfault), тому що ви будете писати в іншій пам'яті, ніж очікувалося, ви навіть можете змінити область, призначену для зберігання виконуваного файлу програма і запис до неї змінить запущений код (Foo зараз не в 47). АЛЕ значення Foo47не змінився глобально, лише той, який знаходиться всередині методу, тому що 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", звичайно, але ...
supercat

... ключовим моментом є те, що одержувач ідентифікатора об'єкта, який ідентифікує масив, може зберігати цей об’єкт-ідентифікатор, де він хоче, і надавати його кому хоче, і будь-який одержувач матиме змогу отримати доступ або змінити масив, коли він хоче. Навпаки, якщо масив передавались посиланням на такій мові, як Pascal, яка підтримує такі речі, викликаний метод може робити все, що завгодно з масивом, але не може зберігати посилання таким чином, щоб дозволити коду змінювати масив. після повернення.
supercat

Дійсно, у Pascal ви можете отримати адресу кожної змінної, будь то перехідна посилання чи локально скопійована, addrчи не типізована, @чи введена вона з типом, а ви можете змінити пізню змінну пізніше (крім локально скопійованих). Але я не бачу сенсу, чому ви це зробите. Цей листок паперу у вашому прикладі (Об'єкт №24601) - це посилання, його мета - допомогти знайти масив у пам'яті, він не містить в собі даних масиву. Якщо перезапустити програму, той самий масив може отримати інший об'єкт-ідентифікатор, навіть якщо його вміст буде таким, як був у попередньому запуску.
karatedog

Я вважав, що оператор "@" не є частиною стандартного Pascal, але реалізований як загальне розширення. Це частина стандарту? Моя думка полягала в тому, що в мові з істинним проходом-перемиканням та відсутністю здатності побудувати неефемерний покажчик ефемерного об'єкта, код якого містить єдине посилання на масив у будь-якій точці Всесвіту, перш ніж передавати масив посилання може знати, що якщо одержувач не "обдурить", він все одно буде мати єдину посилання після цього. Єдиним безпечним способом досягти цього на Яві було б побудувати тимчасовий об’єкт ...
supercat

... який інкапсулює AtomicReferenceі не розкриває посилання, ані його ціль, а натомість включає методи, щоб робити речі до цілі; Після повернення коду, якому було передано об'єкт, AtomicReference[[на який його творець зберігав пряме посилання] слід визнати недійсним та скасованим. Це забезпечило б належну семантику, але це було б повільно і невдало.
supercat

70

Java - це дзвінок за значенням

Як це працює

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

  • Якщо це примітивний тип даних, ці біти містять значення самого типу примітивних даних. Ось чому, якщо ми змінимо значення заголовка всередині методу, він не відображає зміни зовні.

  • Якщо це такий тип даних об'єкта, як Foo foo = new Foo (), то в цьому випадку копія адреси об'єкта передається як ярлик файлу, припустимо, у нас є текстовий файл abc.txt на C: \ desktop і, припустимо, ми робимо ярлик той самий файл і помістіть його всередині C: \ desktop \ abc-ярлик, тож коли ви отримуєте доступ до файлу з C: \ desktop \ abc.txt і пишете "Переповнення стека", закриваєте файл, і ви знову відкриваєте файл із ярлика, тоді ви написати "є найбільшою інтернет-спільнотою для програмістів, які навчаються", то загальна зміна файлів буде "Переповнення стека - найбільше інтернет-співтовариство для вивчення програмістів"це означає, що неважливо, звідки ви відкриваєте файл, кожен раз, коли ми отримували доступ до одного і того ж файлу, тут ми можемо вважати Foo як файл, і припустимо, foo зберігається на 123hd7h (оригінальна адреса на зразок C: \ desktop \ abc.txt ) адреса та 234jdid (скопійована адреса на зразок C: \ desktop \ abc-ярлик, який насправді містить оригінальну адресу файлу всередині). Тож для кращого розуміння зробіть файл ярлика та відчуйте ..


66

Вже є чудові відповіді, які висвітлюють це. Я хотів зробити невеликий внесок, поділившись дуже простим прикладом (який складе), протиставляючи поведінку між Pass-by-reference у c ++ та Pass-by-value на Java.

Кілька пунктів:

  1. Термін "посилання" - це перевантажене двома окремими значеннями. У Java це просто означає вказівник, але в контексті "Pass-by-reference" це означає ручку до оригінальної змінної, яка була передана в.
  2. Java є Pass-by-value . Java - нащадок С (серед інших мов). До C декілька (але не всі) попередніх мов, як FORTRAN та COBOL підтримували PBR, але C - не. PBR дозволив цим іншим мовам вносити зміни до переданих змінних всередині підпрограм. Для того, щоб виконати те саме (тобто змінити значення змінних всередині функцій), програмісти C передавали покажчики на змінні у функції. Мови, натхнені C, наприклад, Java, запозичили цю ідею і продовжують передавати покажчик методам, як це робив C, за винятком того, що Java називає свої вказівники Посиланнями. Знову ж таки, це слово вживання "Посилання" по-іншому, ніж у "Pass-By-Reference".
  3. C ++ дозволяє пропускати посилання шляхом оголошення опорного параметра за допомогою символу "&" (який трапляється тим самим символом, який використовується для позначення "адреси змінної" як у C, так і в C ++). Наприклад, якщо ми передаємо вказівник за посиланням, параметр і аргумент не просто вказують на один і той же об'єкт. Скоріше, вони однакові. Якщо один встановлюється на іншу адресу або на нуль, це робить і інший.
  4. У наведеному нижче прикладі C ++ я передаю вказівник на нульову закінчену рядок за посиланням . І в наведеному нижче прикладі Java я передаю посилання Java на String (знову ж, те саме, що вказівник на String) за значенням. Помітьте результат у коментарях.

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().
     }
}

EDIT

Кілька людей написали коментарі, які, схоже, вказують на те, що або вони не дивляться на мої приклади, або не отримують приклад c ++. Не впевнений, де знаходиться відключення, але вгадати приклад c ++ не зрозуміло. Я розміщую той самий приклад у мові pascal, тому що я думаю, що пропуск посилання виглядає чистішим у Pascal, але я можу помилитися. Я можу просто більше бентежити людей; Я сподіваюся, що не.

У паскалі параметри, передані посиланням, називаються "параметрами var". У нижченаведеній процедурі setToNil зверніть увагу на ключове слово "var", яке передує параметру "ptr". Коли вказівник передається на цю процедуру, він передається за посиланням . Зверніть увагу на поведінку: коли ця процедура встановлює ptr до нуля (це паскаль говорить для NULL), вона встановить аргумент на нуль - цього не можна робити на 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.

EDIT 2

Деякі уривки «Мова програмування Java» Кен Арнольд, Джеймс Гослінг (хлопець, який винайшов Java) та Девід Холмс, глава 2, розділ 2.6.5

Усі параметри методам передаються "за значенням" . Іншими словами, значення змінних параметрів у методі є копіями збудника, вказаними як аргументи.

Він продовжує робити те саме, що стосується предметів. . .

Ви повинні зауважити, що коли параметр є посиланням на об'єкт, "значення" передається саме посиланням на об'єкт, а не самим об'єктом .

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

Мова програмування Java не передає об'єкти за посиланням; він передає посилання на об'єкт за значенням . Оскільки дві копії одного і того ж посилання стосуються одного і того ж фактичного об'єкта, зміни, зроблені через одну опорну змінну, видно через іншу. Існує рівно один параметр, що проходить режим переходу за значенням, і це допомагає робити прості речі.

У цьому розділі книги є велике пояснення проходження параметрів на Java та різниці між прохідними посиланнями та прохідними значеннями, а також розробниками Java. Я б закликав когось прочитати його, особливо якщо ви все ще не впевнені.

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

Я сподіваюся, що це врегулює дискусію, але, мабуть, не буде.

EDIT 3

Я, можливо, трохи одержимий цією посадою. Можливо, тому, що я відчуваю, що виробники Java ненавмисно поширюють дезінформацію. Якби замість того, щоб використовувати вказівники слово "посилання", вони використовували щось інше, скажімо, брусниця, не було б жодних проблем. Можна сказати: "Java передає чорницю за значенням, а не за посиланням", і ніхто не буде плутати.

Ось чому лише розробники Java мають проблеми з цим. Вони дивляться на слово "посилання" і думають, що точно знають, що це означає, тому навіть не намагаються розглянути протилежний аргумент.

У будь-якому випадку я помітив коментар у старшому дописі, який зробив аналогію на повітряній кулі, яка мені дуже сподобалась. Настільки, що я вирішив склеїти разом деякі картинки, щоб зробити набір мультфільмів, щоб проілюструвати суть.

Передача посилання за значенням - Зміни до посилання не відображаються в області виклику, але зміни в об'єкті є. Це тому, що посилання скопійовано, але і оригінал, і копія посилаються на один і той же об'єкт. Передача посилань на об'єкти за значенням

Перейти за посиланням - копії посилання немає. Єдина посилання поділяється як абонентом, так і функцією, що викликається. Будь-які зміни в посиланні або на дані об'єкта відображаються в області дії абонента. Перейти за посиланням

РЕДАКТ 4

Я бачив публікації на цю тему, в яких описується низький рівень реалізації параметрів, що проходять через Java, що, на мою думку, є великим і дуже корисним, оскільки це робить конкретну абстрактну ідею. Однак для мене питання стосується скоріше поведінки, описаної в мовній специфікації, ніж про технічну реалізацію поведінки. Це вказівка ​​зі специфікації мови Java, розділ 8.4.1 :

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

Що означає, java створює копію переданих параметрів перед виконанням методу. Як і більшість людей, які вивчали компіляторів у коледжі, я використав "Книгу Драконів", яка є THE THE компіляторами. У ній є хороший опис "Дзвінок за вартістю" та "Заклик за посиланням" у Розділі 1. Опис за замовчуванням за значенням точно відповідає Java Specs.

Ще коли я вивчав компіляторів - у 90-х я використав перше видання книги з 1986 року, яке раніше датувало Яву приблизно на 9 або 10 років. Однак я просто наткнувся на копію другого видання з 2007 року, де фактично згадується Java! Розділ 1.6.6 з позначкою "Механізми передачі параметрів" описує параметри проходження досить добре. Ось уривок під заголовком "Call-by-value", де згадується Java:

У значенні виклику фактичний параметр оцінюється (якщо це вираз) або копіюється (якщо він є змінною). Значення розміщується у розташуванні, що належить відповідному формальному параметру викликаної процедури. Цей метод використовується в C та Java, і є поширеним варіантом у C ++, а також у більшості інших мов.


4
Чесно кажучи, ви можете спростити цю відповідь, сказавши, що Java передається за значенням лише для примітивних типів. Все, що успадковується від Object, фактично передається шляхом посилання, де посилання - це вказівник, який ви передаєте.
Скуба Стів

1
@JuanMendes, я просто використав C ++ як приклад; Я можу навести вам приклади іншими мовами. Термін "пройти посилання" існував задовго до існування C ++. Це термін підручника з дуже конкретним визначенням. І за визначенням, Java не проходить через посилання. Ви можете продовжувати використовувати термін таким чином, якщо хочете, але ваше використання не буде відповідати визначенню підручника. Це не просто вказівник на вказівник. Це конструкція, надана мовою, щоб дозволити "Пройти посилання". Будь ласка, уважно подивіться на мій приклад, але, будь ласка, не сприймайте моє слово за це, а перегляньте його самі.
Санджив

2
@AutomatedMike Я думаю, що опис Java як "прохідної посилання" також вводить в оману, їх посилання є не що інше, як покажчики. Як Сандєєв писав значення, посилання та вказівник - це терміни підручника, які мають власне значення незалежно від того, чим користуються творці Java.
Jędrzej Dudkiewicz

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, оскільки у функції swap ви використовуєте 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 на зразок цієї, яка дозволила б використовувати її з Strings:

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.


+1 для тесту своп - мабуть, найпростіший і відносний спосіб розрізнити між проходженням посилання і передачею посилання за значенням . Якщо ви можете легко записати функцію, swap(a, b)яка (1) здійснює обмін aі bз POV, що викликає, (2) є типово-агностичною, наскільки дозволяє статичне введення тексту (тобто використання її з іншим типом не вимагає нічого іншого, як зміна оголошених типів aта b) , і (3) не вимагає, щоб абонент явно передав вказівник або ім'я, тоді мова підтримує передачу посилання.
cHao

"..для примітивних типів даних ви будете працювати з копією, а для об'єктів - з копією посилання на об'єкти" - ідеально написано!
Раул

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" - "pass by value", а об'єкти - "pass mimo reference".

Але з цього прикладу ми можемо зрозуміти, що це фактично передається лише за значенням, маючи на увазі, що тут ми передаємо посилання як значення. тобто: посилання передається за значенням. Ось чому ми можемо змінитись і досі це стосується місцевих масштабів. Але ми не можемо змінити фактичну посилання поза початкової області. що це означає, демонструється наступним прикладом 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:

/ **

На додаток до того, що було згадано в Example3 (PassByValueObjectCase1.java), ми не можемо змінити фактичну посилання за межами початкової області. "

Примітка. Я не вставляю код для private class Student. Визначення класу для Studentтаке ж, як Example3.

* /

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 Virtual Machine тут . Значення типу - це відповідне інтегральне або значення з плаваючою точкою, кодоване відповідним чином (8, 16, 32, 64 і т.д. біт).


42

У Java передаються лише посилання та передаються за значенням:

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

У випадку примітивних типів поведінка Java проста: значення копіюється в інший екземпляр примітивного типу.

У випадку з об’єктами це те саме: Змінні об'єкти - це вказівники (відра), що містять лише адресу Об'єкта, створену за допомогою "нового" ключового слова, і копіюються як примітивні типи.

Поведінка може виявитися різною від примітивних типів: Тому що скопійована об'єктна змінна містить однакову адресу (до того ж Об'єкта). Вміст / члени об'єкта все ще можуть бути змінені в межах методу та пізніше отримати доступ назовні, створюючи ілюзію, що сам (що містить) Об'єкт переданий шляхом посилання.

"Строкові" Об'єкти здаються хорошим зустрічним прикладом міської легенди, що "Об'єкти передаються посиланням":

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

String Object, містить символи масиву, оголошеного остаточним, який неможливо змінити. Тільки адреса Об'єкта може бути замінена іншою, використовуючи "нову". Використання "нового" для оновлення змінної не дозволить отримати доступ до Об'єкта ззовні, оскільки змінна спочатку була передана за значенням і скопійована.


Так це ByRef щодо об'єктів і byVal щодо примітивів?
Мокс

@mox будь ласка, прочитайте: Об'єкти не передаються посиланням, це уступ: String a = new String ("незмінний");
користувач1767316

1
@Aaron "Перейти за посиланням" не означає "передавати значення, яке є членом типу, який у Java називається" посиланням ". Два використання "посилання" означають різні речі.
Філіпсі

2
Досить заплутана відповідь. Причина, чому ви не можете змінити об'єкт рядка в методі, який передається посиланням, полягає в тому, що об'єкти String є незмінними за конструкцією, і ви не можете робити такі речі strParam.setChar ( i, newValue ). З цього приводу рядки, як і все інше, передаються за значенням, і оскільки String є непомітивним типом, це значення є посиланням на те, що було створено за допомогою нового, і ви можете перевірити це за допомогою String.intern () .
zakmck

1
Натомість ви не можете змінити рядок через param = "інший рядок" (еквівалентно новому рядку ("інший рядок")), оскільки опорне значення парами (яке тепер вказує на "інший рядок") не може повернутися з методу тіло. Але це справедливо для будь-якого іншого об'єкта, відмінність полягає в тому, що, коли інтерфейс класу дозволяє це, ви можете зробити param.changeMe (), і об'єкт верхнього рівня, який посилається, зміниться, оскільки парам вказує на нього, незважаючи на саме параметричне значення парамуса (адресоване місце розташування, в термінах С), не може з'явитися назад із методу.
zakmck

39

Відмінність чи, можливо, лише те, що я пам’ятаю, коли я був під тим же враженням, що й оригінальний плакат, це таке: Java завжди передає значення. Усі об'єкти (на Java, що завгодно, крім примітивів) на Java - це посилання. Ці посилання передаються за значенням.


2
Я вважаю, що ваше друге в останнє речення є дуже оманливим. Неправда, що "всі об'єкти на Java - це посилання". Це лише посилання на ті об'єкти, які є посиланнями.
Давуд ібн Карім

37

Як багато людей згадували про це раніше, Java завжди проходить через цінність

Ось ще один приклад, який допоможе вам зрозуміти різницю ( класичний приклад swap ):

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

Друкує:

До: a = 2, b = 3
Після: a = 2, b = 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();
}

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:

name = ім'я Maxx
= Fido

Класи примітивної обгортки та рядки незмінні, тому будь-який приклад, що використовує ці типи, не працюватиме так само, як інші типи / об'єкти.


5
"пропустити копію" - це те, що означає прохідна вартість .
TJ Crowder

@TJCrowder "Передати копію" може бути більш підходящим способом вираження того самого поняття для цілей Java: тому що семантично це робить дуже важким замах на ідею, схожу на проходження посилання, що саме ви хочете в Java.
мійський гризун

@mikerodent - Вибачте, я не дотримувався цього. :-) "Pass-by-value" і "pass-by-reference" - правильні умови мистецтва для цих понять. Ми повинні їх використовувати (надаючи їх визначення, коли люди потребують), а не складати нові. Вище я говорив, що "пройти за копією" - це те, що означає "прохід за вартістю", але насправді не зрозуміло, що означає "пройти за копією" - копію того, що? Значення? (Наприклад, посилання на об'єкт.) Сам об’єкт? Тому я дотримуюся ст. :-)
TJ Crowder

28

На відміну від деяких інших мов, Java не дозволяє вибирати між прохідними значеннями та пропускними посиланнями - всі аргументи передаються за значенням. Виклик методу може передавати методу два типи значень - копії примітивних значень (наприклад, значення int та double) та копії посилань на об'єкти.

Коли метод модифікує параметр примітивного типу, зміни параметра не впливають на вихідне значення аргументу в методі виклику.

Що стосується об'єктів, то самі об'єкти не можуть бути передані методам. Отже ми передаємо посилання (адресу) об’єкта. Ми можемо маніпулювати оригінальним об'єктом за допомогою цього посилання.

Як Java створює та зберігає об’єкти: Коли ми створюємо об’єкт, ми зберігаємо адресу об'єкта у довідковій змінній. Проаналізуємо наступне твердження.

Account account1 = new Account();

"Рахунок рахунку1" - це тип і назва довідкової змінної, "=" - оператор призначення, "новий" запитує необхідну кількість місця в системі. Конструктор праворуч від ключового слова new, який створює об'єкт, неявно викликається ключовим словом new. Адреса створеного об'єкта (результат правого значення, яке є виразом під назвою "вираження створення екземпляра класу") присвоюється лівому значенню (яке є посилальною змінною з вказаним іменем та типом) за допомогою оператора присвоєння.

Хоча посилання на об'єкт передається за значенням, метод все ще може взаємодіяти з посилається об'єктом, викликаючи його загальнодоступні методи, використовуючи копію посилання на об'єкт. Оскільки посилання, що зберігається в параметрі, є копією посилання, яке було передано як аргумент, параметр у викликаному методі та аргумент в методі виклику посилаються на один і той же об'єкт у пам'яті.

Передача посилань на масиви замість самих об’єктів масиву має сенс з міркувань продуктивності. Оскільки все в Java передається за значенням, якщо об’єкти масиву передавались, копія кожного елемента буде передана. Для великих масивів це би втрачало час і вимагало значного зберігання для копій елементів.

На зображенні нижче ви бачите, що у нас є дві опорні змінні (Ці називаються покажчиками в C / C ++, і я думаю, що цей термін полегшує розуміння цієї функції.) В основному методі. Примітивні та еталонні змінні зберігаються в пам'яті стека (ліва частина зображень внизу). 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 в основному методі, array1 в основному почне вказувати на масив 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. array1 почне вказувати на b.


27

Я створив нитку, присвячену таким питанням для будь-яких мов програмування тут .

Також згадується Java . Ось короткий підсумок:

  • Java передає йому параметри за значенням
  • "за значенням" - єдиний спосіб в java передавати параметр методу
  • використання методів об'єкта, заданих як параметр, змінить об'єкт як посилання на початкові об'єкти. (якщо цей метод сам змінює деякі значення)

27

Якщо коротко розповісти, об’єкти Java мають деякі дуже своєрідні властивості.

Загалом, Java , має примітивні типи ( int, bool, char, doubleі т.д.), які передаються безпосередньо за значенням. Тоді у Java є об’єкти (все, що випливає з java.lang.Object). Об'єкти насправді завжди обробляються за допомогою посилання (посилання - це вказівник, до якого не можна торкатися). Це означає, що фактично об'єкти передаються посиланням, оскільки посилання зазвичай не цікаві. Однак це означає, що ви не можете змінити, на який об’єкт вказується, оскільки саме посилання передається за значенням.

Це звучить дивно і заплутано? Розглянемо, як C-реалізація проходить за посиланням та передає значення. У C умовна умова передається за значенням. void foo(int x)передає int за значенням. void foo(int *x)це функція , яка не бажає , щоб int a, але покажчик на міжнар: foo(&a). Можна було б скористатися цим &оператором для передачі змінної адреси.

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


Я думаю, що це дуже близько до мого розуміння об’єкта Java та його посилання. Об'єкт в Java передається методу за допомогою еталонної копії (або псевдоніма).
MaxZoom
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.