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




java reference passed by value (20)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

https://code.i-harness.com

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

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

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

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


Java передает параметры по VALUE и ТОЛЬКО по значению .

Короче говоря:

Для тех, кто приходит из C #: НЕТ параметра "out".

Для тех, кто прибывает из PASCAL: НЕТ параметра "var" .

Это означает, что вы не можете изменить ссылку на сам объект, но вы всегда можете изменить свойства объекта.

Обходной путь должен использовать StringBuilder параметр вместо String . И вы всегда можете использовать массивы!


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

Как это устроено

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

В tricky методе:

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

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

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

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

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

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

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

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

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

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

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

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

Печать:

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

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


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

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

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

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


Несколько исправлений к некоторым постам.

C не поддерживает передачу по ссылке. ВСЕГДА передается по значению. C ++ поддерживает передачу по ссылке, но не используется по умолчанию и довольно опасен.

Неважно, какое значение в Java: примитив или адрес (примерно) объекта, оно ВСЕГДА передается по значению.

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

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


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

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

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


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

/ **

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

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

* /

Пример 1:

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

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

    }

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

Результат

output : output
value : Nikhil
valueflag : false

Пример 2:

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

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

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

    }

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

Результат

output : output
value : Nikhil
valueflag : false

Пример 3:

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

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

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

* /

public class PassByValueObjectCase1 {

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

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

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

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

Результат

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

Пример 4:

/ **

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

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

* /

public class PassByValueObjectCase2 {

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

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

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

}

Результат

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

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


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

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

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:

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

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


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


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

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

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

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

Позвольте мне объяснить это на 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 создает новое назначение для ссылки a , а не f , объекта, чей атрибут "b" .

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

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

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


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

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

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

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

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

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


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


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

Спецификация 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.

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


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

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

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

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

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

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

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

class Test {
    String name;
}

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

changevalue

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

Test t = new Test();

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

new PassByValue().changeValue(t);

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

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

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

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

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

class Test {
    String name;
}

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

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


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

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

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

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

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

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

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

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








pass-by-value