Создание и индексация массивов
Массивы — это основа для работы с наборами данных. Без них невозможно представить ни одну серьёзную программу. Разберёмся, как создавать массивы в Java, как обращаться к их элементам и какие подводные камни вас ждут.
🧱 Что такое массив?
Представьте, что вы стоите перед большим многоквартирным домом. У дома есть адрес (название массива), а внутри — множество квартир (ячеек), каждая со своим номером (индексом). В каждой квартире живёт какое-то значение (элемент массива). Массив — это фундаментальная структура данных, которая есть практически в каждом языке программирования. В Java массивы — это объекты, которые хранят фиксированное количество элементов одного типа. Когда вы объявляете массив, вы резервируете непрерывную область памяти, где все элементы будут расположены последовательно друг за другом. Это позволяет быстро обращаться к любому элементу по его индексу — время доступа O(1), то есть константное и не зависит от размера массива.
Массив в Java — это упорядоченный набор элементов одного типа. У каждого элемента есть номер (индекс), по которому к нему можно обратиться. Все элементы хранятся в памяти последовательно, друг за другом, как квартиры на этаже. Это обеспечивает очень быстрый доступ: чтобы получить элемент по индексу, Java просто вычисляет адрес в памяти как (базовый адрес + индекс × размер элемента). Никакого поиска не требуется — вы сразу попадаете в нужную ячейку. Именно поэтому массивы так популярны: они дают максимальную производительность для хранения и доступа к данным.
Зачем нужны массивы? Без них — если бы у нас было, скажем, 5 чисел — нам пришлось бы заводить 5 отдельных переменных: number1, number2, number3... А если чисел тысяча? Согласитесь, писать number1 ... number1000 — это безумие. Массив позволяет хранить любое количество элементов под одним именем и обращаться к ним по индексу. Кроме того, массивы позволяют обрабатывать данные в циклах — это одна из самых мощных возможностей программирования. Представьте, что вам нужно увеличить на 10% зарплату каждому из 1000 сотрудников. С массивом это делается в три строки кода. Без массива вам пришлось бы писать 1000 строк.
💡 Простыми словами: массив — это пронумерованный список элементов одного типа. Номер элемента называется индексом. Первый элемент всегда имеет индекс 0. Размер массива фиксирован и не может быть изменён после создания.
Массивы решают несколько важных задач в программировании:
- Группировка данных — объединить связанные значения под одним именем, например, все оценки студента в один массив grades
- Удобный доступ — обратиться к любому элементу по номеру за константное время O(1)
- Перебор — пройтись по всем элементам в цикле, не зная их точное количество на этапе написания кода
- Память эффективна — все элементы лежат рядом, процессор быстро их читает за счёт кэширования
- Основа для других структур — ArrayList, HashMap и другие коллекции внутри используют массивы
Важно понимать: массив — это объект. Как и любой объект в Java, он создаётся в куче (heap) и имеет методы, унаследованные от Object. Но у массивов есть особенность — они не являются экземплярами какого-то класса в привычном смысле. JVM имеет специальную поддержку для массивов на уровне байт-кода. Это делает их очень быстрыми, но накладывает ограничения — например, нельзя наследоваться от массива.
🏗️ Создание массива: три способа
В Java есть три основных способа создать массив. Рассмотрим каждый подробно — с примерами, пояснениями и типичными ошибками. Выбор способа зависит от того, знаете ли вы заранее значения элементов и где именно создаётся массив.
Способ 1: new с указанием длины
Самый явный способ — выделить память под массив с помощью ключевого слова new, указав тип и размер в квадратных скобках. Этот способ используется, когда вы знаете, сколько элементов вам нужно, но конкретные значения будут известны позже:
int[] numbers = new int[5]; // массив на 5 целых чисел
String[] names = new String[3]; // массив на 3 строки
double[] prices = new double[10]; // массив на 10 дробных чисел
boolean[] flags = new boolean[4]; // массив на 4 булевых значения
Что происходит в памяти? Java выделяет непрерывный блок памяти размером 5 × 4 байта (int занимает 4 байта) = 20 байт. Все элементы автоматически заполняются значениями по умолчанию: для int это 0, для double — 0.0, для boolean — false, для ссылочных типов (например, String) — null. Это очень важно — в Java нет «мусорных» значений в массивах, как в C/C++. Вы всегда можете быть уверены, что массив инициализирован.
int[] arr = new int[3];
System.out.println(arr[0]); // 0 — значение по умолчанию для int
System.out.println(arr[1]); // 0
System.out.println(arr[2]); // 0
String[] strArr = new String[2];
System.out.println(strArr[0]); // null — значение по умолчанию для ссылок
System.out.println(strArr[1]); // null
Размер массива может быть задан константой, переменной или даже результатом выражения:
int size = 10;
int[] arr = new int[size * 2]; // 20 элементов
int n = getUserInput(); // размер определяется пользователем
int[] data = new int[n];
⚠️ Важно: размер массива — это количество элементов. Он не может быть отрицательным. Если написать new int[-5], Java выбросит NegativeArraySizeException. Размер также не может быть изменён после создания массива. Если вам нужен массив переменного размера — используйте ArrayList.
Способ 2: Литерал массива (сразу с данными)
Если вы уже знаете, какие элементы должны быть в массиве, можно использовать фигурные скобки с перечислением значений. Java сама определит размер массива по количеству элементов и выделит нужную память:
int[] numbers = {10, 20, 30, 40, 50}; // массив из 5 элементов
String[] colors = {"красный", "зелёный", "синий"};
double[] rates = {1.5, 2.7, 3.14, 2.18};
char[] letters = {'A', 'B', 'C'};
Этот способ — самый короткий и наглядный. Java сама подсчитывает количество элементов и выделяет нужный размер. Но есть важное ограничение: так можно писать только при объявлении переменной. Нельзя написать:
int[] numbers;
numbers = {10, 20, 30}; // ❌ Ошибка компиляции — литерал только при объявлении
// Правильно:
int[] numbers;
numbers = new int[]{10, 20, 30}; // ✅ Анонимный массив
Анонимный массив (new int[]{...}) можно использовать в любом месте, не только при объявлении. Например, передать в метод сразу:
printArray(new int[]{1, 2, 3}); // ✅ анонимный массив в аргументе
// Без анонимного массива пришлось бы делать так:
int[] temp = {1, 2, 3};
printArray(temp);
Способ 3: new + инициализация по умолчанию + ручное заполнение
Иногда нужно создать пустой массив известного размера, а потом заполнить его в цикле или по одному элементу. Это комбинация первых двух подходов:
int[] squares = new int[10];
for (int i = 0; i < squares.length; i++) {
squares[i] = i * i;
}
// Теперь squares = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
Этот способ — самый гибкий. Вы сами решаете, какие значения в какой элемент положить и когда. Можно заполнять не по порядку, а, например, только чётные индексы:
int[] arr = new int[10];
for (int i = 0; i < arr.length; i += 2) {
arr[i] = i * 10; // только чётные позиции
}
// arr = {0, 0, 20, 0, 40, 0, 60, 0, 80, 0}
🔑 Совет: если знаете все элементы заранее — используйте литерал {...}. Если знаете только размер — new тип[размер], а затем заполняйте. Для передачи в метод без переменной — анонимный массив new тип[]{...}.
🔢 Индексация: от 0 до length-1
Одна из самых частых ошибок новичков — забыть, что индексация в Java (и почти во всех языках программирования) начинается с нуля. Первый элемент имеет индекс 0, второй — индекс 1, и так далее. Это кажется нелогичным — в обычной жизни мы считаем с единицы. Но в программировании почти везде счёт начинается с нуля, и к этому нужно привыкнуть.
Почему с нуля? Исторически так сложилось из-за того, как массивы устроены в памяти. Индекс — это смещение от начала массива. Чтобы добраться до первого элемента, нужно сместиться на 0 позиций. Чтобы добраться до второго — на 1 позицию, до третьего — на 2 и так далее. Ничего не пропускать — так быстрее для процессора, не нужно лишний раз вычитать единицу. Это решение было принято ещё в языке C (1972 год) и перешло во все современные языки, включая Java.
int[] arr = {10, 20, 30, 40, 50};
// Индексы: 0 1 2 3 4
System.out.println(arr[0]); // 10 — первый элемент
System.out.println(arr[1]); // 20 — второй элемент
System.out.println(arr[2]); // 30 — третий элемент
System.out.println(arr[3]); // 40 — четвёртый элемент
System.out.println(arr[4]); // 50 — пятый элемент
// Последний элемент:
System.out.println(arr[arr.length - 1]); // 50
Запомните формулу: индекс последнего элемента = длина массива − 1. Это следствие нумерации с нуля. Если длина массива 5, то допустимые индексы — 0, 1, 2, 3, 4. Индекс 5 — это уже выход за границу.
🚫 Ошибка: ArrayIndexOutOfBoundsException. Если вы попытаетесь обратиться к элементу по индексу, который меньше 0 или больше-равен length, Java выбросит это исключение, и программа упадёт. Это одна из самых частых ошибок при работе с массивами. Хорошая новость: в отличие от C/C++, Java всегда проверяет границы массива, и вы получите понятное сообщение об ошибке, а не чтение чужой памяти.
int[] arr = {1, 2, 3};
System.out.println(arr[3]); // ❌ ArrayIndexOutOfBoundsException!
// Индекс 3, а длина массива — 3. Допустимые индексы: 0, 1, 2.
// Индекс 3 — это четвёртый элемент, которого нет.
Как избежать этой ошибки? Всегда проверяйте, что индекс находится в допустимом диапазоне. Особенно будьте внимательны, когда индекс вычисляется или приходит от пользователя:
// Потенциально опасный код:
int index = computeIndex(); // может вернуть -1 или 100
System.out.println(arr[index]); // ❌ если index вне [0, arr.length-1]
// Безопасная версия:
int index = computeIndex();
if (index >= 0 && index < arr.length) {
System.out.println(arr[index]);
} else {
System.out.println("Индекс " + index + " недопустим");
}
📏 Свойство length
У каждого массива есть свойство length, которое хранит его размер — количество элементов. Это свойство можно читать, но нельзя менять (массивы в Java фиксированного размера). length — это не метод, а поле. Это важное отличие от String, где длина получается через метод length().
int[] arr = {10, 20, 30, 40, 50};
System.out.println(arr.length); // 5
String[] fruits = {"яблоко", "груша"};
System.out.println(fruits.length); // 2
int[] empty = new int[0];
System.out.println(empty.length); // 0 — пустой массив существует!
// Бесполезный, но возможный код:
int[][] matrix = new int[3][4];
System.out.println(matrix.length); // 3 — количество строк
System.out.println(matrix[0].length); // 4 — количество столбцов
⚠️ Частая путаница: Массив arr.length — поле, без скобок. Строка str.length() — метод, со скобками. Перепутаете — компилятор поругается.
Свойство length — ваша главная защита при работе с массивами. Его можно использовать в циклах, для проверок и вычислений. Запомните: length — это количество элементов, а не максимальный индекс. Максимальный индекс — length - 1.
🔄 Значения по умолчанию для элементов массива
Когда вы создаёте массив через new, Java автоматически заполняет все его элементы значениями по умолчанию. Это зависит от типа массива. Вы можете быть уверены: в массиве нет «мусора» — никаких случайных значений из памяти, как было бы в C. Java гарантирует безопасную инициализацию.
| Тип элемента | Значение по умолчанию | Пример |
|---|---|---|
| byte, short, int, long | 0 | new int[3] → {0, 0, 0} |
| float, double | 0.0 | new double[3] → {0.0, 0.0, 0.0} |
| char | '\u0000' | new char[3] → {0, 0, 0} |
| boolean | false | new boolean[3] → {false, false, false} |
| Ссылочные типы (String, Object и т.д.) | null | new String[3] → {null, null, null} |
🚫 NullPointerException: если в массиве хранятся ссылочные типы (строки, объекты), не забывайте, что по умолчанию они равны null. Попытка вызвать метод на null (например, arr[0].length()) приведёт к падению программы.
String[] names = new String[3];
System.out.println(names[0]); // null
System.out.println(names[0].length()); // ❌ NullPointerException!
// Перед использованием проверяем на null:
if (names[0] != null) {
System.out.println(names[0].length());
} else {
System.out.println("Элемент ещё не инициализирован");
}
✏️ Изменение элементов массива
Массивы в Java — изменяемые (mutable). Вы можете в любой момент записать новое значение в любую ячейку по её индексу. Операция присваивания элементу массива очень быстрая — это прямая запись в память:
int[] arr = new int[3]; // {0, 0, 0}
arr[0] = 42; // теперь {42, 0, 0}
arr[1] = 100; // теперь {42, 100, 0}
arr[2] = -7; // теперь {42, 100, -7}
System.out.println(arr[1]); // 100
Важно понимать разницу между изменением ссылки на массив и изменением содержимого. Когда вы присваиваете одну переменную-массив другой, обе начинают указывать на один и тот же объект:
int[] a = {1, 2, 3};
int[] b = a; // b ссылается на ТОТ ЖЕ массив, что и a
b[0] = 999; // меняем через b
System.out.println(a[0]); // 999 — и a «видит» изменение!
// Это не копия данных — это копия ссылки. Обе переменные
// указывают на один и тот же объект-массив в памяти.
Если нужно создать независимую копию — придётся скопировать элементы вручную или использовать Arrays.copyOf, о чём мы поговорим в следующих уроках. Это важный аспект работы с массивами, который часто приводит к неожиданным багам.
0️⃣ Пустые массивы
Массив может быть пустым — длины 0. Это полноценный объект, а не null. У него есть length == 0, и он не равен null:
int[] empty = new int[0];
System.out.println(empty.length); // 0 — это НЕ null!
// Пустой массив можно использовать, где нужен массив, но данных нет:
public static int[] getEvenNumbers(int[] arr) {
// ... если чётных нет, возвращаем пустой массив
return new int[0];
}
// Проверка: массив пуст или null?
public static boolean isEmpty(int[] arr) {
return arr == null || arr.length == 0;
}
Пустые массивы length == 0 и null — это разные вещи. Пустой массив — это объект, у которого нет элементов. null — это отсутствие объекта. Важно не путать их.
💪 Практические примеры
Пример 1: Сумма элементов массива
int[] numbers = {12, 7, 5, 19, 3, 8};
int sum = 0;
for (int i = 0; i < numbers.length; i++) {
sum = sum + numbers[i]; // или sum += numbers[i]
}
System.out.println("Сумма: " + sum); // Сумма: 54
Пример 2: Поиск максимального элемента
int[] numbers = {12, 7, 5, 19, 3, 8};
int max = numbers[0]; // предполагаем, что первый — максимальный
for (int i = 1; i < numbers.length; i++) {
if (numbers[i] > max) {
max = numbers[i]; // нашли больший — запоминаем
}
}
System.out.println("Максимум: " + max); // Максимум: 19
Пример 3: Переворот массива (reverse)
int[] arr = {1, 2, 3, 4, 5};
for (int i = 0; i < arr.length / 2; i++) {
int temp = arr[i];
arr[i] = arr[arr.length - 1 - i];
arr[arr.length - 1 - i] = temp;
}
// arr теперь = {5, 4, 3, 2, 1}
Разберём переворот: мы идём до половины массива. На каждом шаге меняем местами элемент с индексом i и симметричный ему с конца (arr.length - 1 - i). Для i=0 меняем первый и последний, для i=1 — второй и предпоследний и т.д. Важно идти только до половины — если пройти весь массив, элементы вернутся на исходные места (двойной обмен).
🚨 Типичные ошибки новичков
- Забыть про индексацию с нуля — первый элемент = 0, последний = length - 1
- Путать arr.length и str.length() — поле vs метод
- Выход за границы — ArrayIndexOutOfBoundsException
- NullPointerException на элементах-ссылках — проверяйте на null
- Путать копию ссылки с копией массива — b = a не копирует данные
- Создание массива с отрицательным размером — NegativeArraySizeException
✅ Итоги урока
- Массив — упорядоченный набор элементов одного типа, хранящихся в памяти последовательно
- Создание:
new тип[размер], литерал{...}, анонимныйnew тип[]{...} - Индексация с 0. Последний элемент:
arr[arr.length - 1] length— поле, а не метод. Не путать сString.length()- При создании через
new— значения по умолчанию (0, 0.0, false, null) - Выход за границы →
ArrayIndexOutOfBoundsException - Массивы mutable и фиксированного размера
b = aне копирует данные — обе переменные ссылаются на один массив
В следующем уроке разберём перебор массивов — классический for и for-each. 🚀
Тест: Создание и индексация массивов
7 вопросов