1. Массив типа String
Ну и вкратце хотелось бы рассказать о массиве типа String.
Как мы уже говорили, массив может быть любого типа. А значит, можно создать массив типа String. Вот как бы выглядел код, если бы нам нужно было написать программу, которая «вводит с клавиатуры 10 строк и выводит их на экран в обратном порядке».
String[] array = new String[10]; // Создаем объект-массив на 10 элементов
for (int i = 0; i < 10; i++) // Цикл от 0 до 9
{
array[i] = console.nextLine(); // Читаем строку с клавиатуры и сохраняем ее в ячейку массива
}
for (int i = 9; i >= 0; i--) // Цикл от 9 до 0
{
System.out.println(array[i]); // Выводим на экран очередную ячейку массива
}
Код практически не изменился! Пришлось только при создании массива заменить тип int на String. В остальном всё абсолютно то же самое!
Знакомство с null
Вот вам загадка — что содержится в ячейках каждого из этих массивов сразу после создания?
int[] numbers = new int[10];
String[] strings = new String[10];
User[] users = new User[10];
Если подумать, то скорее всего ячейки numbers содержат 0. Правильно.
Тогда по такой же логике ячейки strings должны быть заполнены пустыми строками — "". Допустим.
А вот чем заполнены ячейки массива users? Пустыми User'ами?
В этом-то и подвох, что не для каждого типа данных есть «дефолтное значение». Поэтому создатели Java придумали специальную константу — null. null — это пустая ссылка. Когда создаётся переменная для типа-объекта, то её стартовое значение — null: ссылка ни на что, отсутствие ссылки.
String name; // name содержит null
name = "Alex"; // name содержит ссылку на объект/строку "Alex"
name = null; // name содержит null
Работа с null
Нельзя вызывать методы у объекта, если ссылка на него null — объекта-то нет! Вот в таком коде программа завершится с ошибкой:
String name; // name содержит null
String upperName = name.toUpperCase(); // Возникнет ошибка NullPointerException: name == null
Что же касательно нашей «загадки», то ответ будет таким:
int[] numbers = new int[10]; // {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
String[] strings = new String[10]; // {null, null, null, null, null, null, null, null, null, null}
User[] users = new User[10]; // {null, null, null, null, null, null, null, null, null, null}
Только примитивные типы имеют «дефолтное значение». Все остальные типы по умолчанию имеют значение null. Стартовое значение String — это не пустая строка. Такова жизнь.
2. Массив типа String в памяти
Картинка 1. Как объект String располагается в памяти:

Обращаем ваше внимание, что текст строки не хранится прямо в переменной: для него выделяется отдельный блок памяти. А в переменной типа String хранится адрес (ссылка) на объект с текстом.
Картинка 2. Как массив целых чисел располагается в памяти:

Эту картинку вы также видели.
Картинка 3. Как в памяти располагается массив строк:

Слева мы видим переменную-массив типа String[] (хранит адрес объекта-массива).
В середине — объект-массив типа String.
А справа — объекты-строки, которые хранят какие-то тексты.
В ячейках объекта-массива типа String хранятся не сами строки (тексты), а их адреса (ссылки). Точно так же, как в переменных типа String хранятся адреса строк (текста).
3. Быстрая инициализация массива в Java
Массивы — очень полезная вещь, поэтому разработчики Java постарались сделать работу с ними максимально удобной. И первое, что они сделали — это упростили инициализацию массива, занесение в него стартовых значений.
Ведь очень часто, кроме данных, которые программа откуда-то считывает, ей для работы нужны ещё свои внутренние данные. Например, нам нужно хранить в массиве длины всех месяцев. Как может выглядеть этот код:
int[] months = new int[12];
months[0] = 31; // январь
months[1] = 28; // февраль
months[2] = 31; // март
months[3] = 30; // апрель
months[4] = 31; // май
months[5] = 30; // июнь
months[6] = 31; // июль
months[7] = 31; // август
months[8] = 30; // сентябрь
months[9] = 31; // октябрь
months[10] = 30; // ноябрь
months[11] = 31; // декабрь
Но есть способ записать его короче — спасибо создателям Java:
// длины месяцев года
int[] months = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
Можно просто перечислить через запятую все значения массива!
Оказывается компилятор может определить тип контейнера (объекта-массива) на основе типа переменной-массива. А для определения длины массива — банально подсчитать количество элементов, написанных в фигурных скобках.
Поэтому этот код можно записать ещё короче:
// длины месяцев года
int[] months = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
Разве не красота? 🙂 Такая запись называется «быстрая инициализация массива». Она, кстати, работает не только для типа int...
// названия месяцев года
String[] months = { "Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь" };
4. Цикл for-each
Проход по всем элементам массива настолько частая операция, что для неё придумали специальный цикл — for-each. Записывается он так:
for (int score : scores)
{
System.out.println("Баллы: " + score);
}
Здесь цикл сам перебирает элементы — никакого ручного индекса. Но нельзя менять элементы напрямую (в переменной цикла хранится только копия значения), зато очень удобно просто «пройтись» по массиву.
Компилятор превратит код цикла for-each в код ниже:
for (int i = 0; i < scores.length; i++)
{
int score = scores[i];
System.out.println("Баллы: " + score);
}
Так что никакой магии. Зато теперь становится понятно, почему вы не можете изменить элементы массива через переменную score.
Давайте напишем ещё пример: найдём сумму всех баллов.
int sum = 0;
for (int score : scores)
{
sum += score;
}
System.out.println("Сумма всех баллов: " + sum);
Когда использовать какой цикл?
- Если нужен индекс или требуется изменять элементы — цикл for.
- Если просто пройтись по значениям — for-each быстрее и чище.
5. Изменение элементов массива
А что, если нам нужно увеличить каждую оценку на 1 балл (например, за хорошее поведение)? Тут уже нужен классический for с индексом, ведь только так мы можем изменить значения в массиве.
Пример: увеличить каждый элемент на 1
for (int i = 0; i < grades.length; i++) {
grades[i] = grades[i] + 1;
}
После выполнения этого кода в массиве grades будут новые значения.
Почему нельзя так сделать через for-each?
for (int grade : grades) {
grade = grade + 1; // Не работает!
}
Этот код не изменит массив, потому что grade — это копия значения из массива, а не ссылка на сам элемент.
6. Вычисления на основе массива
Массивы часто используют для расчётов: сумма, среднее, максимум, минимум и так далее.
Пример: сумма всех элементов массива
int sum = 0;
for (int i = 0; i < grades.length; i++) {
sum += grades[i]; // то же самое, что sum = sum + grades[i];
}
System.out.println("Сумма оценок: " + sum);
Пример: поиск максимального значения
int max = grades[0]; // начнем с первого элемента
for (int i = 1; i < grades.length; i++) {
if (grades[i] > max) {
max = grades[i];
}
}
System.out.println("Максимальная оценка: " + max);
Пример: поиск минимального значения
int min = grades[0]; // начнем с первого элемента
for (int i = 1; i < grades.length; i++) {
if (grades[i] < min) {
min = grades[i];
}
}
System.out.println("Минимальная оценка: " + min);
Пример: вычисление среднего арифметического
int sum = 0;
for (int i = 0; i < grades.length; i++) {
sum += grades[i];
}
double average = (double) sum / grades.length; // обязательно привести к double!
System.out.println("Средняя оценка: " + average);
Обратите внимание: чтобы получить не целое, а дробное число, нужно явно привести сумму к типу double перед делением.
7. Практические задачи
Ввод массива с клавиатуры
Часто бывает нужно заполнить массив значениями, которые вводит пользователь. Для этого используем класс Scanner и цикл.
Scanner console = new Scanner(System.in);
int n = 5; // размер массива
int[] numbers = new int[n];
System.out.println("Введите " + n + " чисел:");
for (int i = 0; i < n; i++) {
numbers[i] = console.nextInt();
}
System.out.println("Вы ввели:");
for (int i = 0; i < n; i++) {
System.out.println(numbers[i]);
}
Вывод массива в обратном порядке
Иногда нужно вывести элементы массива с конца (например, чтобы узнать, кто последний вошёл в комнату).
for (int i = grades.length - 1; i >= 0; i--) {
System.out.println("Оценка №" + (i + 1) + ": " + grades[i]);
}
8. Типичные ошибки при работе с одномерными массивами
Ошибка №1: выход за границы массива
Самая частая проблема — попытка обратиться к несуществующему элементу. В Java это приводит к исключению ArrayIndexOutOfBoundsException. Помните: последний допустимый индекс — arr.length - 1.
int[] arr = new int[5];
System.out.println(arr[5]); // Ошибка! Индексы от 0 до 4.
Ошибка №2: забыли инициализировать массив
Объявили переменную, но не создали массив:
int[] arr;
arr[0] = 5; // Ошибка! Массив не создан.
Нужно обязательно создать массив с помощью new:
arr = new int[10];
Ошибка №3: попытка изменить элементы массива через for-each
В цикле for-each переменная — это копия значения, а не ссылка на элемент:
for (int x : arr) {
x = 100; // Не изменяет массив!
}
Чтобы изменить значения, используйте обычный for с индексом.
Ошибка №4: неправильное использование длины массива
Иногда путают длину массива с последним индексом:
for (int i = 0; i <= arr.length; i++) { // Ошибка! Должно быть i < arr.length
// ...
}
Такой код приведёт к выходу за границы.
Ошибка №5: неявное преобразование типов
Если массив типа int, нельзя напрямую присвоить ему значение типа double:
int[] arr = new int[3];
arr[0] = 3.14; // Ошибка! 3.14 — это double.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ