Java, pass-by-value, ссылочные переменные

У меня проблема с пониманием действия "pass-by-value" Java в следующем примере:

public class Numbers {

    static int[] s_ccc = {7};
    static int[] t_ccc = {7};

    public static void calculate(int[] b, int[] c) {

        System.out.println("s_ccc[0] = " + s_ccc[0]); // 7
        System.out.println("t_ccc[0] = " + t_ccc[0]); // 7

        b[0] = b[0] + 9;  
        System.out.println("\nb[0] = " + b[0]); // 16

        c = b;
        System.out.println("c[0] = " + c[0] + "\n"); // 16
    }

    public static void main(String[] args) {

        calculate(s_ccc, t_ccc);

        System.out.println("s_ccc[0] = " + s_ccc[0]);  // 16
        System.out.println("t_ccc[0] = " + t_ccc[0]);  // 7 
    }
}

Я знаю, потому что s_ccc является ссылочной переменной, когда я передаю его методу calculate(), и я вношу некоторые изменения в его элементы в методе, изменения остаются даже после того, как я покинул метод. Я думаю, что то же самое должно быть с t_ccc. Это снова ссылочную переменную, я даю ее методу calculate(), и в методе я меняю рефери на t_ccc как на s_ccc. Теперь t_ccc должен быть ссылочной переменной, указывающей на массив, у которого один элемент типа int равен 16. Но когда метод calculate() оставлен, кажется, что t_ccc указывает на его старый объект. Почему это происходит? Разве это не должно измениться? В конце концов, это ссылочная переменная.

Привет

Ответ 1

В расширенном обсуждении того, как Java передает переменные по более раннему вопросу "Проходит ли Java по ссылке?". Java действительно передает ссылки на объекты по значению.

В вашем коде ссылки для массивов (которые являются объектами) передаются в calculate(). Эти ссылки передаются по значению, что означает, что любые изменения значений b и c видны только внутри метода (они на самом деле просто копия s_ccc и t_ccc). Вот почему t_ccc в main() никогда не затрагивался.

Чтобы укрепить эту концепцию, некоторые программисты объявляют параметры метода как переменные final:

public static void calculate(final int[] b, final int[] c) 

Теперь компилятор даже не позволит вам изменять значения b или c. Конечно, недостатком этого является то, что вы больше не сможете манипулировать ими удобно в своем методе.

Ответ 2

Вот простой способ понять это.

Java всегда передает копии аргументов. Если аргумент является примитивным типом (например, целочисленным), тогда вызываемый метод получает копию примитивного значения. Если аргумент является ссылочным типом, тогда вызываемый метод получает копию ссылки ( не копия упомянутой вещи).

Когда ваш метод main начинается, каждый из s_ccc и t_ccc относится к отдельному массиву. Это та ситуация, где в скобках указаны переменные, а квадратные скобки указывают на фактические структуры массивов:

(s_ccc) ---> [7]
(t_ccc) ---> [7]

Предполагая, что вы имели в виду calculate(s_ccc, t_ccc), то в начале метода calculate:

(s_ccc) ---> [7]  <---(b)
(t_ccc) ---> [7]  <---(c)

Локали b и c - это копии глобальных переменных s_ccc и t_ccc соответственно.

Все еще в calculcate, после завершения b[0] = b[0] + 9:

(s_ccc) ---> [16] <---(b)
(t_ccc) ---> [7]  <---(c)

Значение нуля (только) в массиве, на которое ссылается b, было изменено.

Назначение c = b внутри calculate создает следующую ситуацию:

(s_ccc) ---> [16] <---(b)
               ^------(c)
(t_ccc) ---> [7]

Локальная ссылочная переменная c теперь содержит ту же ссылку, что и b. Это не влияет на глобальную ссылочную переменную t_ccc, которая по-прежнему относится к тому же массиву, что и раньше.

Когда calculate завершается, его локальные переменные (в правой части диаграмм) исчезают. Глобальные переменные (с левой стороны) не использовались в calculate, поэтому они не подвержены влиянию. Конечная ситуация:

(s_ccc) ---> [16]
(t_ccc) ---> [7]

Ни c_ccc, ни t_ccc не были изменены; каждый из них по-прежнему относится к тому же массиву, что и раньше calculate. Вызов calculate изменил содержимое массива, на которое ссылается s_ccc, используя скопированную ссылку на этот массив (в b).

Локальная переменная c запущена как копия t_ccc и была обработана внутри calculate, но это не изменило ни самого t_ccc, ни ссылки на данные.

Ответ 3

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

Поэтому, когда вы изменяете значение b [0] внутри массива, это изменение можно увидеть вне метода. Однако линия

c = b;

изменит значение c внутри метода, но это изменение не будет видно за пределами метода, так как значение c было передано по значению.

Ответ 4

Строка calculate(s_ccc, s_ccc); указывает, что вы фактически ничего не делаете с t_ccc. Однако, если эта строка прочитала calculate(s_ccc, t_ccc);, чем эффект останется прежним.

Это связано с тем, что здесь присваивается новое значение: c = b;
При назначении нового значения опорного аргумента ссылка теряется.
В этом разница между изменением переменной a и ссылкой на новое значение.

Ответ 5

При входе в calculate(), b точки в s_ccc и c указывают на t_ccc. Таким образом, изменение b[0] приведет к изменению s_ccc, как вы видели.

Однако назначение

c = b;

указывают только точки c на том же объекте b, а именно s_ccc. Он не копирует содержимое того, что b указывает на то, на что указывает c. В результате t_ccc не изменяется. Если вы добавили следующую строку в конец calculate():

c[0] = c[0] + 5;

s_ccc[0] будет равно 21.