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

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

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

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

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

Ответы [ 79 ]

13 голосов
/ 22 октября 2017

Java строго передается по значению

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

Важным моментом было бы знать, что даже язык C строго передается только по значению:
т.е.: Данные копируются с вызывающего абонента на вызываемого, и более того, когда выполняемая вызывающим оператором операция находится в одной и той же ячейке памяти, мы передаем им адрес того места, которое мы получаем из оператора (&), а идентификатор, используемый в формальных параметрах, объявляется переменной-указателем (*), с помощью которой мы можем попасть внутрь ячейки памяти для доступа к данным в это.

Следовательно, здесь формальный параметр - это просто псевдонимы для этого местоположения. И любые изменения, сделанные в этом местоположении, видны везде, где область действия переменной (которая идентифицирует это местоположение) жива.

В Java нет понятия указателей (, т. Е. нет ничего, называемого переменной-указателем), хотя мы можем рассматривать ссылочную переменную в качестве указателя, технически в Java мы называем ее дескриптором. Причина, по которой мы называем указатель на адрес в качестве дескриптора в Java, заключается в том, что переменная-указатель способна выполнять не просто разыменование, а множественную разыменование например: int *p; в P означает, что p указывает на целое число и int **p; в C означает p - указатель на указатель на целое число у нас нет этого средства в Java, поэтому его абсолютно правильно и технически обоснованно называть его дескриптором, также есть правила арифметики для указателей на C. Это позволяет выполнять арифметические операции над указателями с ограничениями на них.

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

(и если мы передаем данные непосредственно в C, мы называем это передачей по значению.)

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

Следовательно, Java строго использует механизм передачи по значению

12 голосов
/ 09 августа 2010

Посмотрите на этот код. Этот код не будет выбрасывать NullPointerException ... Он напечатает "Vinay"

public class Main {
    public static void main(String[] args) {
        String temp = "Vinay";
        print(temp);
        System.err.println(temp);
    }

    private static void print(String temp) {
        temp = null;
    }
}

Если Java передается по ссылке, то она должна выдать NullPointerException, поскольку для ссылки установлено значение Null.

12 голосов
/ 14 ноября 2016

Основным краеугольным знанием должно быть цитируемое,

Когда ссылка на объект передается методу, сама ссылка передается с помощью call-by-value . Тем не менее, так как значение передается относится к объекту, копия этого значения будет по-прежнему ссылаться на тот же объект, на который ссылается соответствующий аргумент.

Java: руководство для начинающих, шестое издание, Герберт Шильдт

11 голосов
/ 29 сентября 2016

Простой тест, чтобы проверить, поддерживает ли язык передачу по ссылке, - это просто написать традиционный обмен. Можете ли вы написать традиционный метод / функцию подкачки (a, b) в Java?

Традиционный метод или функция подкачки принимает два аргумента и заменяет их так, что переменные, передаваемые в функцию, изменяются вне функции. Его основная структура выглядит как

(не Java) Структура базовой функции подкачки

swap(Type arg1, Type arg2) {
    Type temp = arg1;
    arg1 = arg2;
    arg2 = temp;
}

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

Type var1 = ...;
Type var2 = ...;
swap(var1,var2);

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

8 голосов
/ 25 июля 2018

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

Теперь людям нравится бесконечно спорить о том, является ли «передача по ссылке» правильным способом описания того, что Java et al. на самом деле Дело в следующем:

  1. Передача объекта не копирует объект.
  2. Объект, переданный в функцию, может иметь свои члены, модифицированные функцией.
  3. Примитивное значение, переданное функции, не может быть изменено функцией. Копия сделана.

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

- Брайан Би - Какие языки программирования передаются по ссылке?

8 голосов
/ 25 июня 2013

Пытаясь добавить еще больше к этому, я подумал, что включу раздел «Учебное пособие по SCJP» по этой теме. Это из руководства, предназначенного для прохождения теста Sun / Oracle на поведение Java, поэтому это хороший источник для использования в этом обсуждении.

Передача переменных в методы (цель 7.3)

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

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

Передача переменных ссылки на объект

Когда вы передаете переменную объекта в метод, вы должны иметь в виду, что вы передаете ссылку на объект, а не на сам объект. Помните, что ссылочная переменная содержит биты, которые представляют (для базовой ВМ) способ добраться до определенного объекта в памяти (в куче). Что еще более важно, вы должны помнить, что вы даже не передаете фактическую ссылочную переменную, а скорее копию ссылочной переменной. Копия переменной означает, что вы получаете копию битов в этой переменной, поэтому, когда вы передаете ссылочную переменную, вы передаете копию битов, представляющих, как добраться до определенного объекта. Другими словами, и вызывающий, и вызываемый метод теперь будут иметь идентичные копии ссылки, и, таким образом, оба будут ссылаться на один и тот же точный (не копируемый) объект в куче.

В этом примере мы будем использовать класс Dimension из пакета java.awt:

1. import java.awt.Dimension;
2. class ReferenceTest {
3.     public static void main (String [] args) {
4.         Dimension d = new Dimension(5,10);
5.         ReferenceTest rt = new ReferenceTest();
6.         System.out.println("Before modify() d.height = " + d.height);
7.         rt.modify(d);
8.         System.out.println("After modify() d.height = "
9.     }
10.
11.
12.
13.   }
14. }

Когда мы запустим этот класс, мы увидим, что метод modify () действительно смог изменить исходный (и единственный) объект Dimension, созданный в строке 4.

C:\Java Projects\Reference>java ReferenceTest
Before modify() d.height = 10
dim = 11
After modify() d.height = 11

Обратите внимание, что когда объект Dimension в строке 4 передается методу modify (), любые изменения в объекте, происходящие внутри метода, вносятся в объект, ссылка на который была передана. В предыдущем примере ссылочные переменные d и dim указывают на один и тот же объект.

Использует ли Java семантику передачи по значению?

Если Java передает объекты, передавая переменную-ссылку, означает ли это, что Java использует передачу по ссылке для объектов? Не совсем, хотя вы часто будете слышать и читать, что это так. Java фактически передается по значению для всех переменных, работающих в одной виртуальной машине. Передача по значению означает передачу по переменной. А это значит, передача-копия-переменной! (Это слово снова скопировано!)

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

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

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

        void bar() {
           Foo f = new Foo();
           doStuff(f);
        }
        void doStuff(Foo g) {
           g.setName("Boo");
           g = new Foo();
        }

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

Передача примитивных переменных

Давайте посмотрим, что происходит, когда примитивная переменная передается методу:

class ReferenceTest {
    public static void main (String [] args) {
      int a = 1;
      ReferenceTest rt = new ReferenceTest();
      System.out.println("Before modify() a = " + a);
      rt.modify(a);
      System.out.println("After modify() a = " + a);
    }
    void modify(int number) {
      number = number + 1;
      System.out.println("number = " + number);
    }
}

В этой простой программе переменная a передается в метод, называемый modify (), который увеличивает переменную на 1. Результирующий вывод выглядит так:

  Before modify() a = 1
  number = 2
  After modify() a = 1

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

7 голосов
/ 03 сентября 2017

Java передается по значению.

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

class Test    {

public static void main (String[] args) throws java.lang.Exception
{
    // Primitive type
    System.out.println("Primitve:");
    int a = 5;
    primitiveFunc(a);
    System.out.println("Three: " + a);    //5

    //Object
    System.out.println("Object:");
    DummyObject dummyObject = new DummyObject();
    System.out.println("One: " + dummyObject.getObj());    //555
    objectFunc(dummyObject);
    System.out.println("Four: " + dummyObject.getObj());    //666 (555 if line in method uncommented.)

}

private static void primitiveFunc(int b)    {
    System.out.println("One: " + b);    //5
    b = 10;
    System.out.println("Two:" + b);    //10
}

private static void objectFunc(DummyObject b)   {
    System.out.println("Two: " + b.getObj());    //555
    //b = new DummyObject();
    b.setObj(666);
    System.out.println("Three:" + b.getObj());    //666
}

}

class DummyObject   {
    private int obj = 555;
    public int getObj() { return obj; }
    public void setObj(int num) { obj = num; }
}

Если строка b = new DummyObject() не закомментирована, изменения, сделанные после этого, будут сделаны для нового объекта, нового экземпляра. Следовательно, это не отражается в том месте, откуда вызывается метод. Однако в противном случае изменение отражается, поскольку изменения производятся только для «ссылки» на объект, т. Е. B указывает на тот же объект dummyObject.

Иллюстрации к одному из ответов в этой теме (https://stackoverflow.com/a/12429953/4233180) может помочь получить более глубокое понимание.

7 голосов
/ 07 июня 2016

Существует очень простой способ понять это. Давайте возьмем C ++ передать по ссылке.

#include <iostream>
using namespace std;

class Foo {
    private:
        int x;
    public:
        Foo(int val) {x = val;}
        void foo()
        {
            cout<<x<<endl;
        }
};

void bar(Foo& ref)
{
    ref.foo();
    ref = *(new Foo(99));
    ref.foo();
}

int main()
{
   Foo f = Foo(1);
   f.foo();
   bar(f);
   f.foo();

   return 0;
}

Каков результат?

1
1
99
99

Таким образом, после того, как bar () присвоил новое значение переданной «ссылке», он фактически изменил значение, переданное от самой main, объяснив последний вызов f.foo () из основной печати 99.

Теперь давайте посмотрим, что говорит Java.

public class Ref {

    private static class Foo {
        private int x;

        private Foo(int x) {
            this.x = x;
        }

        private void foo() {
            System.out.println(x);
        }
    }

    private static void bar(Foo f) {
        f.foo();
        f = new Foo(99);
        f.foo();
    }

    public static void main(String[] args) {
        Foo f = new Foo(1);
        System.out.println(f.x);
        bar(f);
        System.out.println(f.x);
    }

}

Там написано:

1
1
99
1

Voilà, ссылка Foo в main, которая была передана в bar, все еще остается неизменной!

Этот пример ясно показывает, что java отличается от C ++, когда мы говорим «передача по ссылке». По сути, java передает «ссылки» как «значения» в функции, то есть java передается по значению.

7 голосов
/ 12 января 2009

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

7 голосов
/ 17 марта 2018

PT 1: объектов недвижимости

Существует синий 120-футовый «Крошечный дом», в настоящее время припаркованный на главной улице 1234, с красиво ухоженным газоном и цветочной клумбой.

Нанимается риэлтор с местной фирмой и велит вести учет этого дома.

Давайте назовем этого риэлтора "Боб". Привет, Боб.

Боб постоянно обновляет свой Листинг, который он называет tinyHouseAt1234Main, веб-камерой, которая позволяет ему в реальном времени отмечать любые изменения в реальном доме. Он также ведет подсчет того, сколько людей спрашивали о листинге. Целое число Боба viewTally для дома сегодня составляет 42.

Всякий раз, когда кому-то нужна информация о синем Крошечном Доме на Мэйн-Стрит, 1234, они спрашивают Боба.

Боб просматривает свой список tinyHouseAt1234Main и рассказывает им об этом - цвет, красивый газон, кровать-чердак и туалет для компостирования и т. Д. Затем он добавляет запрос к своему viewTally. Однако он не сообщает им реальный физический адрес, потому что фирма Боба специализируется на крошечных домиках, которые можно переместить в любое время. Счет теперь 43.

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

Теперь, конечно, Боб физически не идет и не ставит фактический дом на грузовик, чтобы показать его клиентам напрямую - это было бы нецелесообразно и смешной тратой ресурсов. Передача полной копии его ведомости - это одно, но обходить весь дом все время дорого и смешно.

(Кроме того: фирма Боба также не печатает в 3D новые и уникальные копии перечисленных домов каждый раз, когда кто-то спрашивает об этом. Это то, что делают выскочки, одноименная веб-фирма и ее дочерние компании - это дорого и медленнее и люди часто путают две фирмы, но они все равно довольно популярны).

В некоторых других, более старых фирмах, расположенных ближе к морю, такой агент, как Боб, может даже не существовать для управления листингами. Вместо этого клиенты могут обратиться к Rolodex "Annie" (& для краткости) для прямого адреса дома. Вместо того, чтобы зачитывать ссылочные данные о домах из списка, как это делает Боб, клиенты вместо этого получают адрес дома от Энни (&) и переходят непосредственно к 1234 Main St, иногда даже не зная, что они там могут найти.

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

Хорошо, человек с этой информацией - Боб, поэтому клиент попросил Боба позвонить в службу и отправить ему копию листинга.

jobKillingAutomatedListingService(Listing tinyHouseAt1234Main, int viewTally) Боб отправляет с собой ...

Служба, в конце концов, называет этот Листинг houseToLookAt, но на самом деле она получает точную копию списка Боба с точно такими же ЗНАЧЕНИЯМИ в нем, которые относятся к дому по адресу: 1234 Main St.

Эта новая услуга также имеет собственный подсчет количества людей, просмотревших этот список. Служба принимает подсчет Боба из профессиональной вежливости, но ему все равно, и все равно полностью перезаписывает его собственной локальной копией. На сегодня это 1, а Бобу 43.

Фирмы по недвижимости называют это «передачей по стоимости», так как Боб передает текущее значение своего viewTally и его Листинга tinyHouseAt1234Main. На самом деле он не проходит по всему физическому дому, потому что это непрактично. И при этом он не передает реальный физический адрес, как Энни (&) сделал бы.

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

PT II: Где все запутанно и опасно ...

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

Получив объект Listing, он позволяет клиентам на самом деле перекрасить РЕАЛЬНЫЙ дом на 1234 Main St, используя удаленный парк роботов-дронов! Это позволяет клиентам управлять роботом-бульдозером, чтобы фактически выкопать клумбу! Это безумие!!!

Услуга также позволяет клиентам полностью перенаправить houseToLookAt в другой дом по другому адресу, не вовлекая Боба или его данные. Внезапно вместо этого они могли посмотреть на 4321 Elm St., которая не имеет никакого отношения к списку Боба (к счастью, они больше не могут наносить урон).

Боб смотрит все это на свою веб-камеру в реальном времени. Смирившись с тяжелой работой своей единоличной ответственности, он рассказывает клиентам о новой уродливой работе с краской и внезапном отсутствии привлекательности. В конце концов, его листинг составляет для 1234 Main St. Новый сервис houseToLookAt не может это изменить. Боб, как всегда, точно и покорно сообщает подробности своего tinyHouseAt1234Main, пока его не уволят или дом не будет полностью разрушен Ничем.

Действительно, единственное, что сервис НЕ МОЖЕТ сделать со своей houseToLookAt копией оригинального списка Боба, - это изменить адрес с 1234 Main St. на какой-либо другой адрес, или на пустоту, или на какой-то случайный тип. объекта, как утконос. Список Боба по-прежнему всегда указывает на 1234 Main St, чего бы он ни стоил. Как всегда, он передает текущее значение.

Этот странный побочный эффект передачи листинга новому автоматизированному сервису сбивает с толку людей, которые спрашивают о том, как он работает. В самом деле, в чем разница между способностью удаленно управлять роботами, которые изменяют состояние дома на 1234 Майне, против на самом деле , которые физически собираются туда и сеют хаос, потому что Энни дала вам адрес ??

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

Я имею в виду, если вы действительно собирали дома и физически перемещали их по другим адресам (не как мобильные или Tiny Homes, где это вроде как ожидаемая функция платформы), или вы обращались, переименовывали, и перетасовывая целые кварталы, как какой-то низкоуровневый сумасшедший, играющий Бога, ТОГДА, может быть, вам нужно больше передавать эти конкретные адресные ссылки, а не просто копии последних значений деталей дома ...

...