Перейти к содержанию

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

Двумя наиболее распространенными способами передачи аргументов методам являются «передача по значению» и «передача по ссылке». Различные языки программирования используют эти концепции по-разному. Что касается Java, все строго передается по значению.

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

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

Есть несколько механизмов передачи параметров функциям:

  • по значению;
  • по ссылке
  • по результату;
  • по значению-результату;
  • по имени.

Двумя наиболее распространенными механизмами в современных языках программирования являются «передача по значению» и «передача по ссылке».

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

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

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

Передача по ссылке

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

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

Передача параметров в Java

Фундаментальными понятиями любого языка программирования являются «значения» и «ссылки». В Java примитивные переменные хранят фактические значения, тогда как непримитивные хранят ссылочные переменные, которые указывают на адреса объектов, на которые они ссылаются. И значения, и ссылки хранятся в памяти стека.

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

В случае примитивов значение просто копируется в стек, которое затем передается вызываемому методу; в случае непримитивов ссылка в стеке указывает на фактические данные, которые находятся в куче. Когда передаем объект, ссылка в стеке копируется, и новая ссылка передается методу.

Посмотрим на это в действии с помощью нескольких примеров кода.

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

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

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

Попробуем разобраться на примере кода:

public class PrimitivesUnitTest {
 
    @Test
    public void whenModifyingPrimitives_thenOriginalValuesNotModified() {
        
        int x = 1;
        int y = 2;
       
        // до изменения
        assertEquals(x, 1);
        assertEquals(y, 2);
        
        modify(x, y);
        
        // после изменения
        assertEquals(x, 1);
        assertEquals(y, 2);
    }
    
    public static void modify(int x1, int y1) {
        x1 = 5;
        y1 = 10;
    }
}

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

  1. Переменные x и y в основном методе являются примитивными типами, и их значения хранятся непосредственно в стеке.
  2. Когда вызываем метод modify(), создается точная копия каждой из этих переменных и сохраняется в другом месте стека.
  3. Любое изменение этих копий влияет только на них и оставляет исходные переменные без изменений.

Передача ссылок на объекты

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

Объект Java, в отличие от примитивов, сохраняется в два этапа. Ссылочные переменные хранятся в стеке, а объект, на который они ссылаются, хранится в куче.

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

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

Попробуем разобраться в этом на примере кода:

public class NonPrimitivesUnitTest {
 
    @Test
    public void whenModifyingObjects_thenOriginalObjectChanged() {
        Foo a = new Foo(1);
        Foo b = new Foo(1);

        // до изменения
        assertEquals(a.num, 1);
        assertEquals(b.num, 1);
        
        modify(a, b);
        
        // после изменения
        assertEquals(a.num, 2);
        assertEquals(b.num, 1);
    }
 
    public static void modify(Foo a1, Foo b1) {
        a1.num++;
       
        b1 = new Foo(1);
        b1.num++;
    }
}
 
class Foo {
    public int num;
   
    public Foo(int num) {
        this.num = num;
    }
}

Когда ссылки a и b передаются в метод modify(), он создает зеркальные копии этих ссылок a1 и b1, которые указывают на одни и те же старые объекты:

В методе modify(), когда изменяем ссылку a1, она изменяет исходный объект. Однако для ссылки b1 назначили новый объект. Теперь она указывает на новый объект в куче.

Любое изменение, внесенное в b1, ничего не изменит в исходном объекте:

Заключение

В этой статье рассмотрели, как обрабатывается передача параметров в случае примитивов и не-примитивов.

Мы узнали, что передача параметров в Java всегда осуществляется по значению. Однако контекст меняется в зависимости от того, имеем ли мы дело с примитивами или объектами:

  • для примитивных типов параметры передаются по значению;
  • для объектов ссылка на объект передается по значению.

Фрагменты кода, использованные в этой статье, можно найти на GitHub.

Оригинал