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

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

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

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

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

Ответы [ 79 ]

20 голосов
/ 25 сентября 2014

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

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

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

  • Передача копией: когда переменная передается методу / функции, создается копия (иногда мы слышим, что когда вы передаете примитивы, вы делаете копии).

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

Примеры передачи по копии / по значению

Пример из [ref 1]

void incrementValue(int inFunction){
  inFunction ++;
  System.out.println("In function: " + inFunction);
}

int original = 10;
System.out.print("Original before: " + original);
incrementValue(original);
System.out.println("Original after: " + original);

We see in the console:
 > Original before: 10
 > In Function: 11
 > Original after: 10 (NO CHANGE)

Пример из [ref 2]

хорошо показывает механизм смотреть максимум 5 минут

(Передача по ссылке) значение передачи-копии-переменной

Пример из [ref 1] (помните, что массив - это объект)

void incrementValu(int[] inFuncion){
  inFunction[0]++;
  System.out.println("In Function: " + inFunction[0]);
}

int[] arOriginal = {10, 20, 30};
System.out.println("Original before: " + arOriginal[0]);
incrementValue(arOriginal[]);
System.out.println("Original before: " + arOriginal[0]);

We see in the console:
  >Original before: 10
  >In Function: 11
  >Original before: 11 (CHANGE)

Сами сложные объекты копируются, но внутренние ссылки сохраняются.

Пример из [ref 3]

package com.pritesh.programs;

class Rectangle {
  int length;
  int width;

  Rectangle(int l, int b) {
    length = l;
    width = b;
  }

  void area(Rectangle r1) {
    int areaOfRectangle = r1.length * r1.width;
    System.out.println("Area of Rectangle : " 
                            + areaOfRectangle);
  }
}

class RectangleDemo {
  public static void main(String args[]) {
    Rectangle r1 = new Rectangle(10, 20);
    r1.area(r1);
  }
}

Площадь прямоугольника равна 200, а длина = 10 и ширина = 20

Последняя вещь Я хотел бы поделиться этим моментом лекции: Распределение памяти который я нашел очень полезным для понимания передачи Java по значению или, скорее, «передачи по копии-переменной-значения», как писал @Gevorg.

  1. REF 1 Lynda.com
  2. REF 2 Профессор Мехран Сахами
  3. REF 3 c4learn
    • * * +1081 прохождения объектно-а-параметра-к-методу * ** 1083 тысяча восемьдесят-два *
20 голосов
/ 03 февраля 2018

Это лучший способ ответить на вопрос, имо ...

Во-первых, мы должны понимать, что в Java поведение передачи параметров ...

public void foo(Object param)
{
  // some code in foo...
}

public void bar()
{
  Object obj = new Object();

  foo(obj);
}

точно так же, как ...

public void bar()
{
  Object obj = new Object();

  Object param = obj;

  // some code in foo...
}

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

Итак, на самом деле, что мы ищем в Java, так это то, как назначение переменных работает. Я нашел это в документах :

Одним из наиболее распространенных операторов, с которыми вы столкнетесь, является простой оператор присваивания "=" [...] , который присваивает значение справа от операнда слева от него:

int cadence = 0;
int speed = 0;
int gear = 1;

Этот оператор также можно использовать для объектов для назначения ссылок на объекты [...]

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

Правда в коде. Давайте попробуем это:

public class AssignmentEvaluation
{
  static public class MyInteger
  {
    public int value = 0;
  }

  static public void main(String[] args)
  {
    System.out.println("Assignment operator evaluation using two MyInteger objects named height and width\n");

    MyInteger height = new MyInteger();
    MyInteger width  = new MyInteger();

    System.out.println("[1] Assign distinct integers to height and width values");

    height.value = 9;
    width.value  = 1;

    System.out.println("->  height is " + height.value + " and width is " + width.value + ", we are different things! \n");

    System.out.println("[2] Assign to height's value the width's value");

    height.value = width.value;

    System.out.println("->  height is " + height.value + " and width is " + width.value + ", are we the same thing now? \n");

    System.out.println("[3] Assign to height's value an integer other than width's value");

    height.value = 9;

    System.out.println("->  height is " + height.value + " and width is " + width.value + ", we are different things yet! \n");

    System.out.println("[4] Assign to height the width object");

    height = width;

    System.out.println("->  height is " + height.value + " and width is " + width.value + ", are we the same thing now? \n");

    System.out.println("[5] Assign to height's value an integer other than width's value");

    height.value = 9;

    System.out.println("->  height is " + height.value + " and width is " + width.value + ", we are the same thing now! \n");

    System.out.println("[6] Assign to height a new MyInteger and an integer other than width's value");

    height = new MyInteger();
    height.value = 1;

    System.out.println("->  height is " + height.value + " and width is " + width.value + ", we are different things again! \n");
  }
}

Это вывод моего прогона:

Assignment operator evaluation using two MyInteger objects named height and width

[1] Assign distinct integers to height and width values
->  height is 9 and width is 1, we are different things! 

[2] Assign to height's value the width's value
->  height is 1 and width is 1, are we the same thing now? 

[3] Assign to height's value an integer other than width's value
->  height is 9 and width is 1, we are different things yet! 

[4] Assign to height the width object
->  height is 1 and width is 1, are we the same thing now? 

[5] Assign to height's value an integer other than width's value
->  height is 9 and width is 9, we are the same thing now! 

[6] Assign to height a new MyInteger and an integer other than width's value
->  height is 1 and width is 9, we are different things again! 

В [2] у нас есть отдельные объекты и мы присваиваем значение одной переменной другой. Но после присвоения нового значения в [3] объекты имели разные значения, что означает, что в [2] назначенное значение было копией примитивной переменной, обычно называемой передача по значению , в противном случае значения, напечатанные в [3] , должны совпадать.

В [4] у нас все еще есть отдельные объекты и мы присваиваем один объект другому. И после присвоения нового значения в [5] объекты имели одинаковые значения, что означает, что в [4] назначенный объект не был копией другого, что должно быть вызывается передача по ссылке . Но если мы внимательно посмотрим на [6] , мы не можем быть настолько уверены, что ни одна копия не была сделана ... ?????

Мы не можем быть так уверены, потому что в [6] объекты были одинаковыми, затем мы присвоили новый объект одному из них, и после этого объекты имели разные значения! Как они могут различаться сейчас, если они были одинаковыми? Они должны быть одинаковыми и здесь! ?????

Нам нужно запомнить документы , чтобы понять, что происходит:

Этот оператор также может использоваться для объектов для назначения ссылок на объекты

Таким образом, наши две переменные хранили ссылки ... наши переменные имели одинаковую ссылку после [4] и разные ссылки после [6] ... если такая вещь возможно, это означает, что присвоение объектов выполняется копией ссылки на объект, в противном случае, если это не была копия ссылки, напечатанное значение переменных в [6] должно быть таким же. Таким образом, объекты (ссылки), как и примитивы, копируются в переменные посредством присваивания, что люди обычно называют передача по значению . Это единственный обход в Java.

18 голосов
/ 27 декабря 2009

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

18 голосов
/ 01 августа 2012

Это действительно довольно просто:

Для переменной типа примитива (например, int, boolean, char и т. Д.), Когда вы используете ее имя для аргумента метода, вы передаете содержащееся в нем значение (5, true или 'c'). Это значение копируется, а переменная сохраняет свое значение даже после вызова метода.

Для переменной ссылочного типа (например, String, Object и т. Д.), Когда вы используете ее имя для аргумента метода, вы передаете содержащееся в нем значение ( the ссылочное значение , которое "указывает" на объект ). Это эталонное значение копируется, и переменная сохраняет свое значение даже после вызова метода. Переменная-ссылка «указывает» на один и тот же объект.

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


Сравните это с C ++, где у вас может быть метод для взятия int&, или в C #, где вы могли бы взять ref int (хотя, в этом случае, вы также должны использовать модификатор ref при передаче имени переменной методу.)

18 голосов
/ 04 апреля 2018

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

Когда метод изменяет параметр типа примитива, изменения параметра не влияют на исходное значение аргумента в вызывающем методе.

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

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

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

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

На изображении ниже вы можете видеть, что у нас есть две ссылочные переменные (они называются указателями в C / C ++, и я думаю, что этот термин облегчает понимание этой функции.) В основном методе. Примитивные и ссылочные переменные хранятся в памяти стека (левая сторона на изображениях ниже). ссылочные переменные array1 и array2 "точка" (как ее называют программисты C / C ++) или ссылки на массивы a и b соответственно, которые являются объектами (значения, которые эти ссылочные переменные содержат адреса объектов) в динамической памяти (справа на изображениях ниже) .

Pass by value example 1

Если мы передаем значение ссылочной переменной array1 в качестве аргумента методу reverseArray, в методе создается ссылочная переменная, и эта ссылочная переменная начинает указывать на тот же массив (a).

public class Test
{
    public static void main(String args)
    {
        int[] array1 = { 1, 10, -7 };

        reverseArray(array1);
    }

    public void reverseArray(Int[] array1)
    {
        // ...
    }
}

Pass by value example 2

Итак, если мы скажем

array1[0] = 5;

в методе reverseArray, он внесет изменение в массив a.

У нас есть другая ссылочная переменная в методе reverseArray (array2), которая указывает на массив c. Если бы мы сказали

array1 = array2;

в методе reverseArray, тогда ссылочная переменная array1 в методе reverseArray перестает указывать на массив a и начинает указывать на массив c (пунктирная линия на втором изображении).

Если мы вернем значение ссылочной переменной array2 в качестве возвращаемого значения метода reverseArray и присвоим это значение ссылочной переменной array1 в основном методе, array1 в main начнет указывать на массив c.

return array2; // This code is in reverseArray method.

Итак, давайте сейчас напишем все, что мы сделали сразу.

public class Test
{
    public static void main(String args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        array1 = reverseArray(array1); /* array1 of 
         main starts pointing to c instead of a */
    }

    public void reverseArray(Int[] array1)
    {
        int[] array2 = { -7, 0, -1 };

        array1[0] = 5; // array a becomes 5, 10, -7

        array1 = array2; /* array1 of reverseArray starts
          pointing to c instead of a (not shown in image below) */
        return array2;
    }
}

enter image description here

И теперь, когда метод reverseArray завершен, его ссылочные переменные (array1 и array2) исчезли. Это означает, что теперь у нас есть только две ссылочные переменные в основном методе array1 и array2, которые указывают на массивы c и b соответственно. Никакая ссылочная переменная не указывает на объект (массив) a. Так что он имеет право на сборку мусора.

Вы также можете назначить вазначение массива 2 в основном для массива1. array1 начнет указывать на b.

17 голосов
/ 31 декабря 2012

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

17 голосов
/ 06 сентября 2017

Одно из самых больших заблуждений в языке программирования Java заключается в том, является ли Java Pass by Value или Pass by Reference .

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

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

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

Допустим, у нас есть класс Balloon, как показано ниже.

public class Balloon {

    private String color;

    public Balloon(){}

    public Balloon(String c){
        this.color=c;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

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

public class Test {

    public static void main(String[] args) {

        Balloon red = new Balloon("Red"); //memory reference 50
        Balloon blue = new Balloon("Blue"); //memory reference 100

        swap(red, blue);
        System.out.println("red color="+red.getColor());
        System.out.println("blue color="+blue.getColor());

        foo(blue);
        System.out.println("blue color="+blue.getColor());

    }

    private static void foo(Balloon balloon) { //baloon=100
        balloon.setColor("Red"); //baloon=100
        balloon = new Balloon("Green"); //baloon=200
        balloon.setColor("Blue"); //baloon = 200
    }

    //Generic swap method
    public static void swap(Object o1, Object o2){
        Object temp = o1;
        o1=o2;
        o2=temp;
    }
}

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

red color=Red
blue color=Blue
blue color=Red

Если вы посмотрите на первые две строки вывода, ясно, что метод подкачки не сработал. Это связано с тем, что Java передается по значению, этот тест метода swap () можно использовать с любым языком программирования, чтобы проверить, передается ли он по значению или по ссылке.

Давайте шаг за шагом проанализируем выполнение программы.

Balloon red = new Balloon("Red");
Balloon blue = new Balloon("Blue");

Когда мы используем оператор new для создания экземпляра класса, экземпляр создается, и переменная содержит эталонное местоположение памяти, в которой сохранен объект. Для нашего примера, давайте предположим, что «красный» указывает на 50, а «синий» указывает на 100, и это место в памяти обоих объектов Balloon.

Теперь, когда мы вызываем метод swap (), создаются две новые переменные o1 и o2, указывающие на 50 и 100 соответственно.

Ниже приведен фрагмент кода, объясняющий, что произошло при выполнении метода swap ().

public static void swap(Object o1, Object o2){ //o1=50, o2=100
    Object temp = o1; //temp=50, o1=50, o2=100
    o1=o2; //temp=50, o1=100, o2=100
    o2=temp; //temp=50, o1=100, o2=50
} //method terminated

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

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

Теперь давайте проанализируем foo () выполнение метода.

private static void foo(Balloon balloon) { //baloon=100
    balloon.setColor("Red"); //baloon=100
    balloon = new Balloon("Green"); //baloon=200
    balloon.setColor("Blue"); //baloon = 200
}

Первая строка является важным, когда мы вызываем метод метод вызывается на объекте в опорном местоположении. В этот момент шар указывает на 100, и поэтому его цвет меняется на Красный.

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

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

16 голосов
/ 21 января 2016

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

Рассмотрим следующие 3 ситуации:

1) Попытка изменить примитивную переменную

public static void increment(int x) { x++; }

int a = 3;
increment(a);

x скопирует значение a и увеличит x, a останется прежним

2) Попытка изменить примитивное поле объекта

public static void increment(Person p) { p.age++; }

Person pers = new Person(20); // age = 20
increment(pers);

p скопирует ссылочное значение pers и увеличит поле возраста, переменные ссылаются на один и тот же объект, поэтому возраст изменяется

3) пытается изменить опорное значение эталонных переменных

public static void swap(Person p1, Person p2) {
    Person temp = p1;
    p1 = p2;
    p2 = temp;
}

Person pers1 = new Person(10);
Person pers2 = new Person(20);
swap(pers1, pers2);

после вызова swap p1, p2 копирует ссылочные значения из pers1 и pers2, меняются со значениями, поэтому pers1 и pers2 остаются неизменными

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

16 голосов
/ 30 декабря 2016

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

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

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

Но чтобы проверить, действительно ли он передается по значению или по ссылке, вы можете использовать примитивный тип и ссылки:

@Test
public void sampleTest(){
    int i = 5;
    incrementBy100(i);
    System.out.println("passed ==> "+ i);
    Integer j = new Integer(5);
    incrementBy100(j);
    System.out.println("passed ==> "+ j);
}
/**
 * @param i
 */
private void incrementBy100(int i) {
    i += 100;
    System.out.println("incremented = "+ i);
}

Вывод:

incremented = 105
passed ==> 5
incremented = 105
passed ==> 5

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

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

@Test
public void sampleTest2(){
    Person person = new Person(24, "John");
    System.out.println(person);
    alterPerson(person);
    System.out.println(person);
}

/**
 * @param person
 */
private void alterPerson(Person person) {
    person.setAge(45);
    Person altered = person;
    altered.setName("Tom");
}

private static class Person{
    private int age;
    private String name; 

    public Person(int age, String name) {
        this.age=age;
        this.name =name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Person [age=");
        builder.append(age);
        builder.append(", name=");
        builder.append(name);
        builder.append("]");
        return builder.toString();
    }

}

В этом случае вывод будет:

Person [age=24, name=John]
Person [age=45, name=Tom]
15 голосов
/ 16 марта 2016

Так много длинных ответов. Позвольте мне дать простой:

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

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

...