methods int is - Является ли Java «pass-by-reference» или «pass-by-value»?





15 Answers

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

Java Spec говорит, что все в Java является передачей по значению. В Java нет такой вещи, как «pass-by-reference».

Ключом к пониманию этого является то, что что-то вроде

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

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

myDog ли myDog ?

Есть ключ.

Помня о том, что myDog - это указатель , а не настоящая Dog , ответ НЕТ. myDog все еще имеет значение 42; он все еще указывает на оригинальную Dog (но обратите внимание, что из-за строки «AAA» ее имя теперь «Макс» - все тот же Dog, значение myDog не изменилось.)

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

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

В C ++, Ada, Pascal и других языках, поддерживающих pass-by-reference, вы можете фактически изменить переданную переменную.

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

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

dammit how to

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

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

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

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




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

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

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

  • Студент : Учитель, означает ли это, что 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. }

Что просходит?

  • Человек переменной создается в строке # 1, а в начале он имеет значение null.
  • Новый объект Person создается в строке # 2, хранящейся в памяти, а человеку переменной присваивается ссылка на объект Person. То есть, его адрес. Скажем, 3bad086a.
  • Переменная, содержащая адрес объекта, передается функции в строке # 3.
  • В строке №4 вы можете слушать звук тишины
  • Проверьте комментарий на строке # 5
  • Локальная переменная метода - anotherReferenceToTheSamePersonObject - создается, а затем появляется волшебство в строке # 6:
    • Переменная / ссылочный человек копируется по битам и передается в другойReferenceToTheSamePersonObject внутри функции.
    • Никаких новых экземпляров Person не создается.
    • И « person », и « anotherReferenceToTheSamePersonObject » имеют одинаковое значение 3bad086a.
    • Не пытайтесь это, но человек == anotherReferenceToTheSamePersonObject будет правдой.
    • Обе переменные имеют ИДЕНТИФИКАЦИОННЫЕ КОПИИ ссылки, и оба они относятся к одному объекту Person, SAME Object on the Heap и NOT A COPY.

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

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

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

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

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

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

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

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

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




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

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




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




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

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.




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

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




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

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

Данные в памяти имеют местоположение и в этом месте есть значение (байт, слово, что угодно). В сборке у нас есть удобное решение, чтобы дать имя определенному местоположению (aka variable), но при компиляции кода ассемблер просто заменяет Name на указанное место так же, как ваш браузер заменяет имена доменов 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 копируется в один из регистров CPU (т.е. EAX).
  2. 5 получает PUSHd в стек.
  3. 47 копируется в один из регистров CPU
  4. 47 PUSHd в стек.
  5. 223 копируется в один из регистров CPU.
  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 для выполнения программы (aka. Segfault), потому что вы будете писать в другую память, чем ожидалось, вы даже можете изменить область, предназначенную для хранения исполняемого файла программа и запись на нее изменят текущий код (Foo теперь не включен 47). НО значение Foo47не изменился глобально, а только один внутри метода, потому что 47был также копией метода.
  • в случае 5 и 6. если вы изменяете 223внутри метода, он создает тот же хаос, что и в 3. или 4. (указатель, указывающий на теперь плохое значение, которое снова используется как указатель), но это все еще локальный проблема, поскольку 223 была скопирована . Однако, если вы можете разыменовать Ref2Foo(то есть 223), дойти до и изменить указанное значение 47, скажем, 49оно повлияет на Foo глобально , потому что в этом случае методы получили копию, 223но ссылка 47существует только один раз, и изменение этого чтобы 49приведет каждое Ref2Fooдвойное разыменование к неправильному значению.

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

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

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




Насколько мне известно, 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 вы используете копии, которые не влияют на ссылки в основном. Но если ваши объекты не являются неизменными, вы можете изменить его, например:

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

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

edit: Я считаю, что это также причина использования StringBuffer, когда дело доходит до «добавления» двух строк, потому что вы можете модифицировать исходный объект, который не может быть с неизменяемыми объектами, такими как String.




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

/ ** * * Pass By Value * * /

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», а объекты «проходят по ссылке».

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

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

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

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

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




Различие, или, возможно, только то, как я помню, поскольку я был под тем же впечатлением, что и оригинальный плакат, таков: 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:

name = Maxx
name = Fido

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




Несколько исправлений для некоторых сообщений.

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

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

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

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




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

Сократить длинный рассказ короткий:

Для тех, кто приходит из C #: нет параметра «out».

Для тех, кто прибывает из PASCAL: нет параметра «var» .

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

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




Related