Передача параметров в 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: по значению или по ссылке?: 2 комментария

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

  2. чуть проще. схематично
    Obj obj1 = new Obj(«Вася»);

    m(obj1); // передается значение ссылки, т.е. адрес на объект

    assertEquals(«Вася», obj1.name); // оригинал ссылки obj1, объект не изменился

    void m(Obj obj2){ // копия ссылки obj1
    obj2 = new Obj(«Петя»); // переписали копию ссылки, но не объект по ней
    }

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

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