Массив — это ссылка
Присваивание int b = a кладёт в b копию числа, и дальше каждая переменная живёт сама по себе. Сделаем то же самое с массивом и посмотрим, что выйдет:
public class Main {
public static void main(String[] args) {
int[] a = {1, 2, 3};
int[] b = a;
b[0] = 9;
System.out.println(b[0]); // 9
System.out.println(a[0]); // 9
}
}Давайте разберём по строкам:
int[] a = {1, 2, 3};создаёт массив из трёх элементов.int[] b = a;заводит переменнуюbи кладёт в неё значениеa.b[0] = 9;меняет первый элемент черезb.System.out.println(b[0])печатает9, как и ожидалось.System.out.println(a[0])тоже печатает9, хотя вaмы ничего не писали.
Чтобы понять, откуда взялась девятка, вернёмся к ряду шкафчиков. Сам ряд стоит в раздевалке. А в переменной a лежит не ряд, а талон с его адресом: по талону находят нужный ряд.
Строка int[] b = a сделала копию талона. Талонов стало два, а ряд остался один. Поэтому то, что положили в шкафчик по талону b, видит и владелец талона a.
Переменная хранит ссылку
Когда программа выполняет {1, 2, 3} или new int[3], JVM создаёт сам массив в отдельной области памяти — куче. В переменную a попадает не массив, а ссылка — значение, по которому JVM находит массив в куче. Это и есть талон. Всё, что JVM создаёт в куче, называют объектами, поэтому в виджете ниже массивы подписаны «объект 1» и «объект 2».
Правило = не изменилось: присваивание копирует значение переменной. Разница в том, что у int[] значение — ссылка, и int[] b = a копирует ссылку, а не элементы. Теперь a и b ведут к одному массиву.
Типы, переменные которых хранят ссылки, называют ссылочными. Массив — первый такой тип, который мы разбираем подробно. У восьми примитивных типов ссылок нет: в переменной int лежит само число. Поэтому элемент массива int[] — обычный int, и int x = a[1]; копирует число: запись x = 7 массив не тронет.
В виджете первые три строки те же, что в программе выше, а четвёртая создаёт массив c с теми же числами, что теперь лежат в a. Выполняйте строки по одной и следите, к какому объекту ведёт каждая переменная. После последней строки виджет предложит три сравнения: прежде чем нажать, попробуйте угадать ответ.
1int[] a = {1, 2, 3};2int[] b = a;3b[0] = 9;4int[] c = {9, 2, 3};
Программа ещё не начала работу. Нажмите кнопку — она выполнит первую строку.
Здесь картинка с талоном неточна. Настоящий талон существует в одном экземпляре, и его не скопируешь, а ссылку Java копирует сколько угодно раз. И адрес на талоне вы не увидите: Java не показывает программе, где в куче лежит массив.
Массив в параметре метода
Ссылка копируется и при вызове метода. Параметр получает копию значения аргумента, а у массива значение — ссылка. Значит, у метода и у вызывающего кода талоны к одному ряду. Пусть параметр метода называется nums:
nums[0] = 0;меняет элемент того самого массива, и вызывающий код увидит этот ноль;nums = new int[3];переписывает только копию талона у метода, а переменная вызывающего кода по-прежнему ведёт к старому массиву.
На собеседовании это формулируют так: Java всегда передаёт аргументы по значению, а для массива значение — это ссылка.
Как сравнить два массива
Программа из виджета целиком печатает три сравнения:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] a = {1, 2, 3};
int[] b = a;
b[0] = 9;
int[] c = {9, 2, 3};
System.out.println(a == b); // true
System.out.println(a == c); // false
System.out.println(Arrays.equals(a, c)); // true
}
}== сравнивает ссылки
Оператор == сравнивает значения двух переменных. У int это числа, а у массивов — ссылки. Поэтому для массивов == отвечает на вопрос «это один и тот же массив?»:
a == bдаётtrue: вbлежит копия того же талона;a == cдаётfalse: уcсвой массив, хотя элементы в нём те же.
Arrays.equals сравнивает элементы
Arrays.equals(a, c) сравнивает длины массивов и их элементы по порядку. Здесь длины равны, а элементы 9, 2, 3 совпадают, поэтому результат — true. Порядок важен: {1, 2} и {2, 1} для него разные массивы.
Этот вопрос часто задают на собеседовании: чем == отличается от сравнения содержимого? Для массивов ответ такой: == сравнивает ссылки, а Arrays.equals — длину и элементы.
Настоящая копия
Если нужен второй, независимый массив с теми же элементами, его создают методом Arrays.copyOf:
int[] src = {1, 2, 3};
int[] copy = Arrays.copyOf(src, src.length);
copy[0] = 9;
System.out.println(Arrays.toString(src)); // [1, 2, 3]
System.out.println(Arrays.toString(copy)); // [9, 2, 3]
int[] longer = Arrays.copyOf(src, 5);
System.out.println(Arrays.toString(longer)); // [1, 2, 3, 0, 0]Arrays.copyOf ставит новый ряд шкафчиков и копирует в него элементы. Второй аргумент — длина нового массива:
- С длиной
src.lengthполучается точная копия. Запись вcopyне трогаетsrc. - С длиной больше исходной лишние ячейки заполняются нулями — так «удлиняют» массив.
- С длиной меньше исходной копия обрезается:
Arrays.copyOf(src, 2)даёт[1, 2].
Частая ошибка
Хотели получить отсортированную копию и сохранить исходный порядок:
int[] scores = {30, 10, 20};
int[] sorted = scores; // ✗
Arrays.sort(sorted);
System.out.println(Arrays.toString(scores)); // [10, 20, 30]Строка sorted = scores скопировала только ссылку, и Arrays.sort отсортировал единственный массив. Исходный порядок 30, 10, 20 потерян. Нужна настоящая копия: int[] sorted = Arrays.copyOf(scores, scores.length);.
Главное
- Переменная массива хранит ссылку на массив в куче, а не сами элементы.
int[] b = aкопирует ссылку:aиbведут к одному массиву, и запись черезbвидна черезa.==сравнивает ссылки, аArrays.equals— длину и элементы по порядку.- Независимую копию создаёт
Arrays.copyOf(a, a.length).