Является ли Java «передачей по ссылке» или «передачей по значению»? - PullRequest
5963 голосов
/ 03 сентября 2008

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

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

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

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

Ответы [ 79 ]

5299 голосов
/ 03 сентября 2008

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 .

2844 голосов
/ 16 сентября 2008

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

Спецификация 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
    • мы присваиваем параметр someDog 74
  • в строке "CCC"
    • someDog следует за Dog, на который он указывает (объект Dog по адресу 74)
    • что Dog (тот, что по адресу 74) попросили изменить его имя на Rowlf
  • потом возвращаем

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

Изменилось ли myDog? ​​

Вот ключ.

Учитывая, что myDog является указателем , а не фактическим Dog, ответ НЕТ. myDog все еще имеет значение 42; он по-прежнему указывает на исходный Dog (но учтите, что из-за строки «AAA» его имя теперь «Max» - все тот же Dog; значение myDog не изменилось.)

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

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

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

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

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

1573 голосов
/ 14 сентября 2012

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

    enter image description here

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

    public static void changeReference(Foo a)
    

    enter image description here

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

    changeReference(f);
    

    enter image description here

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

    Foo b = new Foo("b");
    

    enter image description here

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

    enter image description here


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

    enter image description here

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

    enter image description here

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

696 голосов
/ 12 августа 2011

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

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

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

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

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

Pass by Value

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

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

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

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

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

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


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


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

620 голосов
/ 03 сентября 2008

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

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

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

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

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

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

300 голосов
/ 03 сентября 2008

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

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

217 голосов
/ 11 сентября 2013

Я чувствую, что спорить о "передаче по ссылке против передачи по значению" не очень полезно.

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

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

Хорошо. Прежде всего, локальные примитивы идут в стек. Итак, этот код:

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

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

primitives on the stack

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

Вот так:

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

a b*7ch aint one!

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

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

marx brothers

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

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, которому присваивается адрес уже существующей строки.

la da di da da da da

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

176 голосов
/ 17 сентября 2008

Чтобы показать контраст, сравните следующие фрагменты 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 имеет только два типа передачи: по значению для встроенных типов и по значению указателя для типов объектов.

158 голосов
/ 03 сентября 2008

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

153 голосов
/ 03 сентября 2008

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

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.

...