Перейти к содержимому
Теория · Массивы · урок 5 из 8

Массив — это ссылка

Присваивание int b = a кладёт в b копию числа, и дальше каждая переменная живёт сама по себе. Сделаем то же самое с массивом и посмотрим, что выйдет:

JAVA
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
    }
}

Давайте разберём по строкам:

  1. int[] a = {1, 2, 3}; создаёт массив из трёх элементов.
  2. int[] b = a; заводит переменную b и кладёт в неё значение a.
  3. b[0] = 9; меняет первый элемент через b.
  4. System.out.println(b[0]) печатает 9, как и ожидалось.
  5. 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. Выполняйте строки по одной и следите, к какому объекту ведёт каждая переменная. После последней строки виджет предложит три сравнения: прежде чем нажать, попробуйте угадать ответ.

ВживуюДве ссылки на один массивпеременных: 0 · объектов: 0
номера объектов условные

Программа ещё не начала работу. Нажмите кнопку — она выполнит первую строку.

Следите, к какому объекту ведёт каждая переменная

Здесь картинка с талоном неточна. Настоящий талон существует в одном экземпляре, и его не скопируешь, а ссылку Java копирует сколько угодно раз. И адрес на талоне вы не увидите: Java не показывает программе, где в куче лежит массив.

Массив в параметре метода

Ссылка копируется и при вызове метода. Параметр получает копию значения аргумента, а у массива значение — ссылка. Значит, у метода и у вызывающего кода талоны к одному ряду. Пусть параметр метода называется nums:

  • nums[0] = 0; меняет элемент того самого массива, и вызывающий код увидит этот ноль;
  • nums = new int[3]; переписывает только копию талона у метода, а переменная вызывающего кода по-прежнему ведёт к старому массиву.

На собеседовании это формулируют так: Java всегда передаёт аргументы по значению, а для массива значение — это ссылка.

Как сравнить два массива

Программа из виджета целиком печатает три сравнения:

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:

JAVA
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 ставит новый ряд шкафчиков и копирует в него элементы. Второй аргумент — длина нового массива:

  1. С длиной src.length получается точная копия. Запись в copy не трогает src.
  2. С длиной больше исходной лишние ячейки заполняются нулями — так «удлиняют» массив.
  3. С длиной меньше исходной копия обрезается: Arrays.copyOf(src, 2) даёт [1, 2].

Частая ошибка

Хотели получить отсортированную копию и сохранить исходный порядок:

JAVA
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).