JavaRush /Курси /JAVA 25 SELF /Основні операції з одномірними масивами

Основні операції з одномірними масивами

JAVA 25 SELF
Рівень 7 , Лекція 2
Відкрита

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 у памʼяті

Зверніть увагу, що текст рядка не зберігається безпосередньо в змінній: для нього виділяється окремий блок памʼяті. А в змінній типу String зберігається адреса (посилання) на обʼєкт із текстом.

Картинка 2. Як масив цілих чисел розташовується в памʼяті:

Масив int у памʼяті

Цю картинку ви також уже бачили.

Картинка 3. Як у памʼяті розташовується масив рядків:

Як у памʼяті розташовується масив String

Ліворуч бачимо змінну-масив типу 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-each на наведений нижче код:

for (int i = 0; i < scores.length; i++)
{
    int score = scores[i];
    System.out.println("Бали: " + score);
}
Як працює for-each «під капотом»

Отже, ніякої магії. Зате тепер зрозуміло, чому ви не можете змінити елементи масиву через змінну score.

Напишімо ще приклад: знайдемо суму всіх балів.

int sum = 0;
for (int score : scores)
{
    sum += score;
}
System.out.println("Сума всіх балів: " + sum);
Підрахунок суми значень масиву через for-each

Коли використовувати який цикл?

  • Якщо потрібен індекс або треба змінювати елементи — цикл 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.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ