Путаница по поводу передачи по значению и неизменности Java

При подготовке к экзамену SCJP (или OCPJP, как его теперь называют) меня поймали на несколько фиктивных вопросов, касающихся значения pass-by-(reference) и неизменности.

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

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

Рассмотрим мой пример здесь:

public class PassByValueExperiment
{

    public static void main(String[] args)
    {
        StringBuilder sb = new StringBuilder();
        sb.append("hello");
        doSomething(sb);
        System.out.println(sb);


        Integer i = 0;
        System.out.println("i before method call : " + i);
        doSomethingAgain(i);
        System.out.println("i after method call: " + i);
    }

    private static void doSomethingAgain(Integer localI)
    {
        // Integer is immutable, so by incrementing it, localI refers to newly created object, not the existing one
        localI++;
    }

    private static void doSomething(StringBuilder localSb)
    {
        // localSb is a different reference variable, but points to the same object on heap
        localSb.append(" world");
    }
}

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


person Jimmy    schedule 02.12.2011    source источник
comment
private static void doSomething(StringBuilder localSb) { localSb = new StringBuilder(что происходит сейчас); }   -  person NimChimpsky    schedule 02.12.2011


Ответы (2)


На уровне языка нет разницы между изменяемыми и неизменяемыми объектами — неизменяемость является исключительно свойством API класса.

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

Таким образом, на самом деле разница заключается в том, что делает оператор ++, когда он используется в примитиве, и в оболочке, которая не имеет ничего общего с передачей параметров.

person Michael Borgwardt    schedule 02.12.2011
comment
+1 Если учесть, что localI = new Integer(localI.intValue()+1); - это фактически исполняемый код, все становится намного понятнее. - person Erick Robertson; 02.12.2011

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

Теперь, чтобы уточнить, этот код не меняет значение параметра:

localSb.append(" world");

Это изменяет данные внутри объекта, на который ссылается значение параметра, что сильно отличается. Обратите внимание, что вы не присваиваете новое значение localSb.

Принципиально нужно понимать, что:

  • Значение выражения (переменной, аргумента, параметра и т. д.) всегда является либо ссылкой, либо примитивным значением. Это никогда не объект.
  • Java всегда использует семантику передачи по значению. Значение аргумента становится начальным значением параметра.

Как только вы тщательно обдумаете эти вещи и разделите в уме понятия «переменная», «значение» и «объект», все станет яснее.

person Jon Skeet    schedule 02.12.2011