java in - Является ли Java «pass-by-reference» или «pass-by-value»?




15 Answers

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

Это происходит так:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    // 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 
}

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");
    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // 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 (кроме случаев, когда d изменяется, чтобы указывать на другой экземпляр Dog такой как d = new Dog("Boxer") ).

out method

Я всегда считал, что Java является сквозной ссылкой .

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

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

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




Java всегда передает аргументы по значению NOT по ссылке.

Позвольте мне объяснить это на example :

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 переназначает ссылку NOT f объекту, чей атрибут "b" .

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

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

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




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

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

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

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

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

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




Я чувствую, что спорить о «pass-by-reference vs pass-by-value» не очень полезно.

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

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

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

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

приводит к следующему:

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

Вот так:

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

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

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

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

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




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




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




В 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

В этой строке «pass-by-value» переходит в игру ...

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



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

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

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

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

Иногда 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 было 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, как показано ниже:

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




Нет, это не проходит по ссылке.

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

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




Вы никогда не сможете передавать по ссылке в 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.




Я думал, что внес свой ответ, чтобы добавить более подробные сведения из Спецификаций.

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

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

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

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

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

И по вопросу о пропуске

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

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

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

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

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

Рассмотрим ссылочные типы, в Java Virtual Machine Спецификация состояний

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

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

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

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

Так

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

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

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

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

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

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




Как уже упоминалось ранее, Java всегда имеет значение pass-by-value

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

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 будет меняться только в локальной области, а не за пределами этого метода.




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

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

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

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

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

Объекты «String» кажутся идеальным контр-примером для городской легенды, говорящей, что «Объекты передаются по ссылке»:

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

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




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

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

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



Короче говоря, объекты 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 ++.




Related

java methods parameter-passing pass-by-reference pass-by-value