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

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

Работать будем с классом Cat:

public class Cat {
    private String name;

    public Cat(String name) {
        this.name = name;
    }
   //setter/getter
}

Пример №1

Для начала простой пример. Пусть у нас есть метод m(), который меняет имя кошки на Vasya:

 void m(Cat cat) {
     cat.setName("Vasya");
 }

Создадим кошку Petya, запомним ее в переменную oldCat и прогоним через метод:

Cat cat = new Cat("Petya");
Cat oldCat=cat;

this.pass.m(cat);

assertEquals("Vasya", cat.getName());
assertEquals(oldCat, cat);

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

Но это не значит, что передача параметра произошла по ссылке. Она произошла по значению. И этим значением является ссылка. 

То есть,  oldCat и cat как до вызова метода располагалась по адресу 10 (к примеру), так и после вызова будут располагаться по адресу 10. Даже если бы внутри метода этот адрес попробовали поменять. А давайте “попробуем”.

На следующем примере поясню, что такое настоящая передача по ссылке, и как это происходило бы к примеру в в языке C++ (в котором передача параметров и вправду происходит по ссылке).

Пример № 2

Теперь у нас метод m1(), в котором мы переменной cat присвоим новую кошку Fifa:

void m1(Cat cat) {
    cat.setName("Vasya"); //(1)
    cat=new Cat("Fifa"); //(2) здесь в С++ можно поменять адрес внешней cat, а у нас   меняется адрес внутренней cat
    cat.setName("Kitty"); //(3)
    System.out.println(cat.getName()); //Kitty
}

Повторим вышеприведенный тест, и результат будет таким же. Внешняя кошка снова Vasya, и ссылка осталась прежней:

Cat cat = new Cat("Petya");
Cat oldCat=cat;

this.pass.m1(cat);

assertEquals("Vasya", cat.getName());
assertEquals(oldCat, cat);

Рассмотрим по шагам, что происходит.

  • На строке (1) мы поменяли имя внешней кошки (поскольку cat здесь – все еще ссылка на внешнюю кошку) на имя Vasya, как и в первом примере.
  • Но на строке (2) cat становится уже ссылкой на новую кошку, и ее адрес уже не 10, а какой-нибудь 42. Но этот адрес относится к внутренней ссылке cat, а никак не ко внешней. Внешняя ссылка cat не поменялась. 
  • То есть на строке (3) и далее все, что мы делаем с кошкой уже касается новой кошки Fifi, расположенной по новому адресу. Внешнюю кошку это уже не касается, имя меняется не у нее. Она остается по старому адресу, который хранится во внешних переменных cat и oldCat.

Вот в C++ мы могли бы передать ссылку так, чтобы на строке (3) не внутренняя cat встала на новый адрес, а адрес внешней cat встал бы на новую кошку. Вот в чем разница.

У нас же после прогона метода m1() ссылка cat по прежнему равна oldCat, значение ее не поменялось.

Код примера тут.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *