Статья

Массивы Java

В этой статье мы глубоко погрузимся в основную концепцию языка Java – массивы.
Сначала мы посмотрим, что такое массив, а затем как их использовать; в целом, мы рассмотрим, как:
  • Начать работу с массивами
  • Читать и изменять элементы массивов
  • Проходить по элементам массивов
  • Преобразовывать массивы в другие объекты, такие как List или Stream
  • Сортировать, искать элементы и объединять массивы

Что такое массив?

Сначала нам нужно определить, что такое массив? Согласно документации Java, массив – это объект, содержащий фиксированное количество значений одного и того же типа. Элементы массива индексируются, что означает, что мы можем получить к ним доступ с помощью чисел (называемых индексами).
Мы можем рассматривать массив как нумерованный список ячеек, каждая ячейка которого является переменной, содержащей значение. В Java нумерация начинается с 0.
Существуют массивы примитивного типа и массивы ссылочного типа. Это означает, что мы можем использовать массивы int, float, boolean, … Но также и массивы String, Object и пользовательских типов.
Теперь, когда массивы четко определены, давайте углубимся в их использование.
Мы рассмотрим множество тем, которые научат нас, как использовать массивы. Мы изучим некоторые основы, например, как объявлять и инициализировать массив, но мы также рассмотрим более сложные темы, такие как сортировка и поиск в массивах.
Давайте сначала разберемся с объявлением и инициализацией.

Объявление массива

Существует два способа объявить массив в Java:
int[] array;
или
int otherArray[];
Чаще всего используется первый вариант объявления массива.

Инициализация массива

Теперь пришло время посмотреть, как инициализировать массивы. Опять же, существует несколько способов инициализации массива. Мы рассмотрим основные из них.
Давайте начнем с простого способа:
int[] array = new int[5];
Используя этот метод, мы инициализировали массив из десяти элементов типа int. Обратите внимание, что нам нужно указать размер массива.

При использовании этого метода мы инициализируем каждый элемент значением по умолчанию, в данном случае – нулем. При инициализации массива типа Object элементы по умолчанию равны null.

Теперь мы рассмотрим другой способ, дающий нам возможность задавать значения для массива непосредственно при его создании:
int[] array = new int[] {1, 2, 3, 4, 5, 6, 7, 8};
Здесь мы инициализировали массив из 8 элементов, содержащий числа от 1 до 8. При использовании этого метода нам не нужно указывать длину массива, т. к. количество элементов выводится на основании значений между фигурными скобками.

Доступ к элементам массива

Давайте теперь посмотрим, как получить доступ к элементам массива. Мы можем достичь этого, указав индекс ячейки массива.
Например, этот фрагмент кода выведет число 9 в консоль:
int[] array = new int[] {1, 2, 3, 4, 5, 6, 7, 8};
array[0] = 9;
System.out.println(array[0]);
Обратите внимание, как мы используем индексы для доступа к ячейкам массива. Число в скобках – это конкретная позиция массива, к которому мы хотим получить доступ.
При обращении к ячейке, если переданный индекс отрицательный или выходит за пределы последней ячейки, Java выдаст исключение ArrayIndexOutOfBoundException.
Мы должны быть осторожны, чтобы не использовать отрицательный индекс или индекс, больший или равный размеру массива.

Итерирование по элементам массива

Доступ к одиночному элементу может быть полезен, но мы могли бы захотеть выполнить итерацию по массиву. Давайте посмотрим, как мы можем этого добиться.
Первый способ – использовать цикл for:
int[] array = new int[] {1, 2, 3, 4, 5, 6};
for (int i = 0; i < array.length; i++) {
    System.out.println(array[i]);
}
Этот код выведет цифры от 1 до 6 на консоль. Как вы видите, мы использовали свойство length. Это общедоступное свойство, возвращающее нам размер массива.

Конечно, можно использовать другие виды циклов, такие как while или do while. Но, что касается коллекций Java, можно перебирать массивы с помощью цикла foreach:

Это должно вывести цифры от 1 до 5 на консоль. Как мы можем видеть, мы использовали свойство length. Это общедоступное свойство, дающее нам размер массива.

Конечно, можно использовать другие механизмы цикла, такие как while или do while. Но, что касается коллекций Java, можно перебирать массивы с помощью цикла foreach:
int[] array = new int[] {1, 2, 3, 4, 5, 6};
for (int e : array) {
    System.out.println(e);
}
Этот пример эквивалентен предыдущему, но мы избавились от шаблонного кода с индексами. Цикл foreach – полезен, когда:
  • нам не нужно изменять массив (попытка записи другого значения не изменит элемент в массиве)
  • нам не нужны индексы, чтобы как-то их использовать в цикле

Varargs

Мы уже рассмотрели основы, когда дело доходит до создания массивов и манипулирования ими. Теперь углубимся в более сложные темы, начиная с varargs. Напомним, что varargs используются для передачи произвольного числа аргументов методу:
void method(int... varargs) {}
Этот метод может принимать от 0 до произвольного числа аргументов типа Int.

Что нам здесь нужно знать, так это то, что внутри тела метода параметр varargs превращается в массив. Но мы также можем передать массив непосредственно в качестве аргумента. Давайте посмотрим, как это сделать, повторно используя пример метода, объявленного выше:
int[] array = new int[] {1, 2, 3};
method(array);
Код выше будет вести себя так же, как и следующий код:
method(1, 2, 3);

Конвертация массива в List

Массивы – это здорово, но иногда бывает удобнее иметь дело со списком. Рассмотрим, как преобразовать массив в список.
Сначала мы сделаем это наивным способом, создав пустой список и выполнив итерацию по массиву, чтобы добавить его элементы в список:
Integer[] array = new Integer[] {10, 20, 30};
List<Integer> list = new ArrayList<>();
for (int e : array) {
    list.add(e);
}
Но есть другой способ, немного более лаконичный:
Integer[] array = new Integer[] {10, 20, 30};
List<Integer> list = Arrays.asList(array);
Статический метод Arrays.asList принимает аргумент varargs и создает список с переданными значениями. К сожалению, этот метод имеет некоторые недостатки:
  • Невозможно использовать массив примитивных типов
  • Мы не можем добавлять или удалять элементы из созданного списка, так как это вызовет исключение UnsupportedOperationException

Конвертация массива в Stream

Начиная с Java 8 у нас есть доступ к Stream API, и мы, возможно, захотим превратить наши массивы в Stream. Java предоставляет для этого метод Arrays.stream:
String[] array = new String[] {"Java", "Python", "C#"};
Stream<String> stream = Arrays.stream(array);
При передаче массива объектов методу он вернет поток соответствующего типа (например, Stream<Integer> для массива Integer). При передаче примитивного типа он вернет соответствующий примитивный поток.

Также возможно создать поток только для части массива:
String[] array = new String[] {"Java", "Python", "C#"};
Stream<String> otherStream = Arrays.stream(array, 1, 3);
Выше мы создали Stream<String> только со строками “Python” и “C#” (первый индекс является включающим, а второй – исключающим).

Сортировка массивов

Давайте посмотрим, как отсортировать массив, то есть переставить его элементы в определенном порядке. Класс Arrays предоставляет нам метод сортировки. Немного похожий на метод stream, sort имеет много перегрузок.
Перегрузки метода sort():
  • Для массивов примитивного типа: которые сортируются в порядке возрастания
  • Массивы Object (Object должен реализовывать Comparable интерфейс): которые сортируются в соответствии с естественным порядком (с использованием метода compareTo из Comparable)
  • Generic массивы: которые сортируются в соответствии с заданным компаратором
Кроме того, можно сортировать только определенную часть массива (передавая методу начальный и конечный индексы).
Алгоритмы, лежащие в основе метода сортировки, – это быстрая сортировка и сортировка слиянием для примитивных и других массивов соответственно.
Давайте посмотрим, как все это работает, на нескольких примерах:
int[] array = new int[] {8, 3, 1, 0, 9};
Arrays.sort(array); 
// array = {0, 1, 3, 8, 9}

Integer[] otherArray = new Integer[] {7, 2, 1, 4, 3};
Arrays.sort(otherArray); 
// otherArray = {1, 2, 3, 4, 7}

String[] sArray = new String[] {"A", "E", "D", "B", "C"};
Arrays.sort(sArray, 1, 3, 
  Comparator.comparing(String::toString).reversed()); 
// sArray = {"A", "D", "E", "B", "C"}

Поиск в массивах

Поиск в массиве довольно прост, мы можем перебирать массив и искать наш элемент среди всех значений:
int[] array = new int[] {7, 2, 3, 4, 8};
for (int i = 0; i < anArray.length; i++) {
    if (anArray[i] == 2) {
        System.out.println("index = " + i);
        break;
    }
}
Выше мы искали значение 2 и нашли его по индексу 1.

Однако, если у нас есть отсортированный массив, мы можем использовать другое, более производительное решение: двоичный поиск. Java предоставляет нам метод Arrays.BinarySearch. Мы должны предоставить ему массив и элемент для поиска.

В случае Generic массива мы также должны предоставить ему компаратор, который использовался для сортировки массива. Также можно вызвать метод для подмножества массива.

Давайте рассмотрим пример использования метода бинарного поиска:

В случае универсального массива мы также должны предоставить ему компаратор, который использовался для сортировки массива в первую очередь. Снова есть возможность вызвать метод для подмножества массива.

Давайте посмотрим пример использования метода бинарного поиска:
int[] array = new int[] {1, 2, 3, 4, 5, 6};
int index = Arrays.binarySearch(array, 5);
System.out.println("index = " + index);
Поскольку мы сохранили номер 5 в пятой ячейке, в результате будет возвращен индекс 4. Обратите внимание, что мы использовали уже отсортированный массив.

Как объединить массивы в Java?

Наконец, давайте посмотрим, как объединить два массива. Идея состоит в том, чтобы создать массив, длина которого равна сумме двух массивов для объединения. После этого мы должны добавить в новый массив элементы первого, а затем элементы второго массива:
int[] array = new int[] {5, 3, 7, 4, 8};
int[] otherArray = new int[] {10, 6, 9, 11, 3};

int[] result = new int[array.length + otherArray.length];
for (int i = 0; i < result.length; i++) {
    result[i] = (i < array.length ? array[i] : otherArray[i - array.length]);
}
Как мы видим, когда индекс меньше длины первого массива, мы добавляем элементы из этого массива. Затем мы добавляем элементы из второго. Мы можем использовать метод Arrays.setAll, чтобы исключить цикл и упростить код:
int[] array = new int[] {5, 3, 7, 4, 8};
int[] otherArray = new int[] {10, 6, 9, 11, 3};

int[] result = new int[array.length + otherArray.length];
Arrays.setAll(result, i -> (i < array.length ? array[i] : otherArray[i - array.length]));
Этот метод заполнит все элементы массива в соответствии с заданной функцией. Эта функция связывает индекс с результатом.

Вот третий вариант слияния с массивами: System.arraycopy. Этот метод принимает исходный массив, исходную позицию, целевой массив, конечную позицию и значение int, определяющее количество элементов для копирования:
System.arraycopy(array, 0, result, 0, array.length);
System.arraycopy(otherArray, 0, result, array.length, otherArray.length);
В коде выше, мы копируем первый массив, затем второй (после последнего элемента первого массива).

Заключение

В этой статье мы рассмотрели основные и некоторые расширенные способы использования массивов в Java.
Мы увидели, что Java предлагает множество методов для работы с массивами с помощью служебного класса Arrays. Существуют также служебные классы для работы с массивами в таких библиотеках, как Apache Commons или Guava, которые мы рассмотрим в последующих статьях.
java