Передача параметров в 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 не поменялась. 
  • То есть на строке (2) и далее все, что мы делаем с кошкой уже касается новой кошки Fifi, расположенной по новому адресу. Внешнюю кошку это уже не касается, имя меняется не у нее. Она остается по старому адресу, который хранится во внешних переменных cat и oldCat.

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

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

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

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

  1. Уже который раз натыкаюсь на это утверждение. «в Java все передается по значению!»
    Почему такая путаница? Зачем людям вообще сранивать с С++?
    Ссылка в с++ может хранить адрес объекта в куче. Можно передать объект по ссылке в качестве параметра в метод и это будет аналогично тому что происходит в Java — две ссылки на один и тот же объект. Просто разница лишь в том как вы это называете, а работа будет аналогичная!
    В Java просто нету возможности создать объект на стэке или разыменовать указатель чтобы получить копию, зачем это придумали вообще?) Людей путать)

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

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