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

Автор оригинала: Ananya
  • Перевод

Простое объяснение принципов передачи параметров в Java.

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

Начнем с основ.

Данные передаются между методами через параметры. Есть два способа передачи параметров:

  1. Передача по значению (by value). Значения фактических параметров копируются. Вызываемый метод создает свою копию значений аргументов и затем ее использует. Поскольку работа ведется с копией, на исходный параметр это никак не влияет.

  2. Передача по ссылке (by reference). Параметры передаются как ссылка (адрес) на исходную переменную. Вызываемый метод не создает свою копию, а ссылается на исходное значение. Следовательно, изменения, сделанные в вызываемом методе, также будут отражены в исходном значении.

В Java переменные хранятся следующим образом:

  1. Локальные переменные, такие как примитивы и ссылки на объекты, создаются в стеке.

  2. Объекты — в куче (heap).

Теперь вернемся к основному вопросу: переменные передаются по значению или по ссылке?

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

Чтобы разобраться с этим, давайте посмотрим на пример.

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

Поскольку Java передает параметры по значению, метод processData работает с копией данных. Следовательно, в исходных данных (в методе main) не произошло никаких изменений.

Теперь рассмотрим другой пример:

Передача объекта
Передача объекта

Что здесь происходит? Если Java передает параметры по значению, то почему был изменен исходный список? Похоже, что Java все-таки передает параметры не по значению? Нет, неправильно. Повторяйте за мной: "Java всегда передает параметры по значению".

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

Память стека (stack) и кучи (heap)
Память стека (stack) и кучи (heap)

В программе, приведенной выше, список fruits передается методу processData. Переменная fruitRef — это копия параметра fruit. И fruits и fruitsRef размещаются в стеке. Это две разные ссылки. Но самое интересное заключается в том, что они указывают на один и тот же объект в куче. То есть, любое изменение, которое вы вносите с помощью любой из этих ссылок, влияет на объект.

Давайте посмотрим на еще один пример:

Передача объекта по ссылке
Передача объекта по ссылке
Память стека (stack) и кучи (heap)
Память стека (stack) и кучи (heap)

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

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

Вышеприведенная концепция очень важна для правильного решения ваших задач.

Например, рассмотрим удаление узла в односвязном списке.

Удаление узла в связанном списке
Удаление узла в связанном списке

Решение:

class Node {
   int data;
   Node next;
   Node(int d){
       data = d;
       next = null;
   }
}
class LinkedList {
   public static Node push(Node head, int data) {
       Node newNode = new Node(data);
       newNode.next = head;
       head = newNode;
       return head;
   }
   public static void deleteNode(Node head, int position) {
  
       // List is empty
       if (head == null){
           return;
       }

      // If position is 1st, removing head node
      if (position == 1) {
          head = head.next;
          return;
      }
      Node prevNode = head;
      int i = 2;
      while (prevNode != null && i != position) {
          prevNode = prevNode.next;
          i++;
      }
     // When position is more than number of node
     if (prevNode == null || prevNode.next == null) {
         return;
     }
     prevNode.next = prevNode.next.next;
   }
   public static void printList(Node head) {
       Node currNode = head;
       while (currNode != null) {
           System.out.print(currNode.data + " ");
           currNode = currNode.next;
       }
   }
   public static void main(String[] args) {
       Node head = null;
       head = push(head, 5);
       head = push(head, 4);
       head = push(head, 3);
       head = push(head, 2);
       head = push(head, 1);
       System.out.println("Created Linked list is: ");
       printList(head);

       // Delete node at position 2
       deleteNode(head, 2);

       System.out.println("\nLinked List after Deletion at position 2: ");
       printList(head);
   }
}

Это решение работает во всех случаях, кроме одного — когда вы удаляете первый узел (Position = 1). Основываясь на ранее описанной концепции, видите ли вы в чем здесь проблема? Возможно, поможет следующая диаграмма.

Удаление первого узла односвязного списка
Удаление первого узла односвязного списка

Для исправления алгоритма необходимо сделать следующее:

public static Node deleteNode(Node head, int position) {
   // List is empty
   if (head == null){
      return head;
   }

   // If position is 1st, removing head node
   if (position == 1) {
       head = head.next;
       return head;
   }
   Node prevNode = head;
   int i = 2;
   while (prevNode != null && i != position) {
       prevNode = prevNode.next;
       i++;
   }
   // When position is more than number of node
   if (prevNode == null || prevNode.next == null) {
       return head;
   }
   prevNode.next = prevNode.next.next;
   return head;
}
public static void main(String[] args) {
   Node head = null;
   head = push(head, 5);
   head = push(head, 4);
   head = push(head, 3);
   head = push(head, 2);
   head = push(head, 1);
   System.out.println("Created Linked list is: ");
   printList(head);

   // Delete node at position 2
   head = deleteNode(head, 2);

   System.out.println("\nLinked List after Deletion at position 2: ");
   printList(head);
}
//Rest of the code remains same

В этой статье мы обсудили одну небольшую, но важную концепцию Java: передачу параметров. 


Перевод статьи подготовлен в преддверии старта курса "Подготовка к сертификации Oracle Java Programmer (OCAJP)".

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

ЗАПИСАТЬСЯ НА ВЕБИНАР

OTUS
Цифровые навыки от ведущих экспертов

Комментарии 12

    +4
    Очень длинно для того чтобы всего лишь сказать, что даже «указатель» передается по значению.
      +3

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

        –2
        Очень жаль, что Вы так считаете
      0
      Я вот только не понял, к чему это? Что это знание дает?
        +1
        вопрос на интервью
        0
        А как же тогда «передача по ссылке» выглядит? Чем она отличается от «передачи по значению» указателя на объект?
          0
          Псевдокод
          doSomething(int value) {
          value = 321;
          }

          value = 123;
          println(value);

          doSomething(value);
          println(value);

          Если значение в функцию doSomething() будет передаваться по ссылке, то вывод будет следующим
          123
          321

          0
          А глобальных переменных, что в яве нет?
            0
            Увы, но это не пэхапэ
              +4
              Не «увы», а «к счастью нет». Глобальные переменные не нужны.
                0
                Это жеж сарказм)) А любители глобальных переменных всё равно смогут в статические поля накодить)
                  0
                  Ну статическая память же есть и никуда не делась. И скорей всего ява в ней много чего, кроме глобальных переменных размешает. Зачем куча, если новые объекты в рантайме не создаются.

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

            Самое читаемое