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

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

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

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

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

Ответы [ 79 ]

5 голосов
/ 26 января 2019

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

Несколько баллов:

  1. Термин ссылка перегружен. В Java это просто означает указатель. В термине «Передача по ссылке» это означает ссылку на исходную переменную, которая была передана.
  2. Java является передачей по значению , но позволяет нам эмулировать передачу по ссылке, передавая ссылку на Java (то есть указатель) по значению. Это означает, что он передает копию ссылки Java.
  3. C ++ допускает передачу по ссылке , объявляя опорный параметр с помощью символа «&» (который является тем же символом, который используется для обозначения «адреса переменной» как в C, так и в C ++ ). Например, если мы передадим указатель, параметр и аргумент не просто указывают на один и тот же объект, но на то, что они являются одной и той же переменной. Если один из них настроен на другой адрес или имеет значение NULL, то же самое можно сказать и о другом.
  4. В приведенном ниже примере C ++ я передаю указатель на завершенную нулем строку по ссылке . И в приведенном ниже примере с Java я передаю ссылку на Java на строку (опять же, так же, как указатель на строку) по значению. Обратите внимание на вывод в комментариях.

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

using namespace std;
#include <iostream>

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

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

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

public class ValueDemo{

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

     public static void main(String []args){
        ValueDemo vd = new ValueDemo();
        String str = "not null";
        vd.change(str);
        System.out.println("str is " + str);        // ==> str is not null
     }
}

EDIT

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4 голосов
/ 11 июля 2014

Понять это за 2 шага:

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

Если вы хотите изменить значение за ссылкой, вы объявите только новую переменную в стеке с тем же именем 'd'. Посмотрите на ссылки со знаком @, и вы обнаружите, что ссылка была изменена.

public static void foo(Dog d) {
  d.Name = "belly";
  System.out.println(d); //Reference: Dog@1540e19d

  d = new Dog("wuffwuff");
  System.out.println(d); //Dog@677327b6
}
public static void main(String[] args) throws Exception{
  Dog lisa = new Dog("Lisa");
  foo(lisa);
  System.out.println(lisa.Name); //belly
}
4 голосов
/ 12 мая 2016

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

Хорошее описание обоих можно найти здесь .

Pass By Value Значение, полученное функцией, является копией объекта, который использует вызывающая сторона. Он полностью уникален для функции, и все, что вы делаете с этим объектом, будет видно только внутри функции.

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

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

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

И нет, специальный синтаксис C ++ «передача по ссылке» не является исключительным определением передачи по ссылке. Это просто удобный синтаксис, предназначенный для того, чтобы вам не нужно было использовать синтаксис указателя после передачи указателя. Он по-прежнему передает указатель, компилятор просто скрывает этот факт от вас. Он также все еще передает этот указатель BY VALUE, компилятор просто скрывает это от вас.

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

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

4 голосов
/ 22 января 2014

Мистер @ Скотт Стэнчфилд написал отличный ответ. Вот класс, который вы бы проверить именно то, что он имел в виду:

public class Dog {

    String dog ;
    static int x_static;
    int y_not_static;

    public String getName()
    {
        return this.dog;
    }

    public Dog(String dog)
    {
        this.dog = dog;
    }

    public void setName(String name)
    {
        this.dog = name;
    }

    public static void foo(Dog someDog)
    {
        x_static = 1;
        // y_not_static = 2;  // not possible !!
        someDog.setName("Max");     // AAA
        someDog = new Dog("Fifi");  // BBB
        someDog.setName("Rowlf");   // CCC
    }

    public static void main(String args[])
    {
        Dog myDog = new Dog("Rover");
        foo(myDog);
        System.out.println(myDog.getName());
    }
}

Итак, мы передаем из main () собаку с именем Rover, затем присваиваем новый адрес указателю, который мы передали, но в конце имя собаки не Rover и не Fifi и, конечно, не Rowlf, а Max.

4 голосов
/ 17 мая 2018

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

package com.asok.cop.example.task;
public class Example {
    int data = 50;

    void change(int data) {
        data = data + 100;// changes will be in the local variable 
        System.out.println("after add " + data);
        }

    public static void main(String args[]) {
        Example op = new Example();
        System.out.println("before change " + op.data);
        op.change(500);
        System.out.println("after change " + op.data);
    }
}

Выход:

before change 50
after add 600
after change 50

как говорит Михаил в комментариях:

объекты по-прежнему передаются по значению, хотя операции с ними ведут себя как передача по ссылке. Предположим, что void changePerson(Person person){ person = new Person(); } ссылка вызывающего абонента на объект person останется неизменной. Сами объекты передаются по значению, но изменения могут повлиять на их членов. Чтобы быть истинной передачей по ссылке, мы должны были бы иметь возможность переназначить аргумент новому объекту и отразить изменение в вызывающей стороне.

3 голосов
/ 05 сентября 2013

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

Я взял этот код и объяснение из книги по сертификации Java и внес некоторые незначительные изменения.
Я думаю, что это хорошая иллюстрация к передаче по значению объекта. В приведенном ниже коде переназначение g не переназначает f! В конце метода bar () два объекта Foo были созданы, одна ссылается на локальную переменную f и одна ссылается на локальная (аргументная) переменная g.

Поскольку метод doStuff () имеет копию ссылочной переменной, у него есть способ получить к исходному объекту Foo, например, для вызова метод setName (). Но метод doStuff () не может ссылочная переменная f. Таким образом, doStuff () может изменять значения в объекте f to, но doStuff () не может изменить фактическое содержимое (битовый шаблон) для f. В других словами, doStuff () может изменить состояние объекта, на который ссылается f, но не может заставить f ссылаться на другой объект!

package test.abc;

public class TestObject {

    /**
     * @param args
     */
    public static void main(String[] args) {
        bar();
    }

    static void bar() {
        Foo f = new Foo();
        System.out.println("Object reference for f: " + f);
        f.setName("James");
        doStuff(f);
        System.out.println(f.getName());
        //Can change the state of an object variable in f, but can't change the object reference for f.
        //You still have 2 foo objects.
        System.out.println("Object reference for f: " + f);
        }

    static void doStuff(Foo g) {
            g.setName("Boo");
            g = new Foo();
            System.out.println("Object reference for g: " + g);
        }
}


package test.abc;

public class Foo {
    public String name = "";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Обратите внимание, что ссылка на объект не изменилась в выводе консоли ниже:

Выход на консоль:

Ссылка на объект для f: test.abc.Foo@62f72617

Ссылка на объект для g: test.abc.Foo@4fe5e2c3

Boo Ссылка на объект для f: test.abc.Foo@62f72617

3 голосов
/ 26 февраля 2015

Java передает все по значению !!

// создаем объект, передавая имя и возраст:

PersonClass variable1 = new PersonClass("Mary", 32);

PersonClass variable2;

// Переменная2 и переменная1 теперь ссылаются на один и тот же объект

variable2 = variable1; 


PersonClass variable3 = new PersonClass("Andre", 45);

// переменная1 теперь указывает на переменную3

variable1 = variable3;

// ЧТО ЭТО ВЫХОД?

System.out.println(variable2);
System.out.println(variable1);

Mary 32
Andre 45

если бы вы могли понять этот пример, мы сделали. в противном случае, пожалуйста, посетите эту веб-страницу для подробного объяснения:

WebPage

3 голосов
/ 06 октября 2014

Существует обходной путь в Java для справки. Позвольте мне объяснить на этом примере:

public class Yo {
public static void foo(int x){
    System.out.println(x); //out 2
    x = x+2;
    System.out.println(x); // out 4
}
public static void foo(int[] x){
    System.out.println(x[0]); //1
    x[0] = x[0]+2;
    System.out.println(x[0]); //3
}
public static void main(String[] args) {
    int t = 2;
    foo(t);
    System.out.println(t); // out 2 (t did not change in foo)

    int[] tab = new int[]{1};
    foo(tab);
    System.out.println(tab[0]); // out 3 (tab[0] did change in foo)
}}

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

3 голосов
/ 26 сентября 2016

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

Возьмем метод badSwap (), например:

    public void badSwap(int var1, int
 var2{ int temp = var1; var1 = var2; var2 =
 temp; }

Когда возвращается badSwap (), переменные, переданные в качестве аргументов, по-прежнему сохраняют свои исходные значения. Метод также потерпит неудачу, если мы изменим тип аргументов с int на Object, поскольку Java также передает ссылки на объекты по значению. Теперь вот, где это становится сложным:

public 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("X:
 " + pnt1.x + " Y: " +pnt1.y);

     System.out.println("X: " + pnt2.x + " Y:
 " +pnt2.y); System.out.println(" ");

     tricky(pnt1,pnt2);
 System.out.println("X: " + pnt1.x + " Y:" + pnt1.y);

     System.out.println("X: " + pnt2.x + " Y: " +pnt2.y); }

Если мы выполним этот метод main (), мы увидим следующий вывод:

X: 0 Y: 0 X: 0 Y: 0 X: 100 Y: 100 X: 0 Y: 0

Метод успешно изменяет значение pnt1, даже если оно передается по значению; однако обмен pnt1 и pnt2 завершается неудачно! Это основной источник путаницы. В методе main () pnt1 и pnt2 являются не чем иным, как ссылками на объекты. Когда вы передаете ppnt1 и pnt2 методу tricky (), Java передает ссылки по значению, как и любой другой параметр. Это означает, что ссылки, переданные методу, на самом деле являются копиями исходных ссылок. На рисунке 1 ниже показаны две ссылки, указывающие на один и тот же объект после того, как Java передает объект методу.

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

3 голосов
/ 16 ноября 2016

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

Класс-S

class S{
String name="alam";
public void setName(String n){
this.name=n; 
}}

Класс-Sample

    public class Sample{
    public static void main(String args[]){
    S s=new S();
    S t=new S();
    System.out.println(s.name);
    System.out.println(t.name);
    t.setName("taleev");
    System.out.println(t.name);
    System.out.println(s.name);
    s.setName("Harry");
    System.out.println(t.name);
    System.out.println(s.name);
    }}

выход

Алам

Алам

taleev

1024 * Алам *

taleev

гарри

Поскольку мы определили класс S с именем переменной экземпляра со значением taleev , то для все объекты, которые мы инициализируем из него, будут иметь переменную name со значением taleev , но если мы изменим значение имени любого объекта, то это изменит имя только той копии класса (Object), а не для каждого класса, после этого также, когда мы делаем System.out.println (s.name) , это печатает taleev только мы не можем изменить значение имени, которое мы определили первоначально и значение, которое мы меняем, является значением объекта, а не значением переменной экземпляра, поэтому, как только мы определим переменную экземпляра, мы не сможем ее изменить

Так что я думаю, что именно так видно, что Java имеет дело с значениями , но не с ссылками

Распределение памяти для примитивных переменных можно понять как this

...