Лабораторна робота 2
Робота з типами-посиланнями
1 Завдання на лабораторну роботу
1.1 Робота з масивами
Створити двовимірний масив цілих чисел з кількістю рядків і стовпців, вказаних в таблиці. Заповнити масив випадковими додатними значеннями відповідно до правила, наведеного в таблиці. Заповнити одновимірний масив рядків (String) довжини, вказаної в таблиці. Кожен елемент містить повторення певного символу кількістю, вказаною в таблиці. Здійснити сортування масиву рядків за правилом, вказаним в таблиці.
№ | Розміри двовимірного масиву | Правило заповнення двовимірного масиву |
Кількість елементів у масиву рядків | Довжина окремого рядка в масиві | Критерій сортування масиву рядків |
---|---|---|---|---|---|
1, 17 | 5 × 4 | випадкове парне число від 4 до 30 | кількість рядків масиву чисел | мінімальний елемент рядка | за збільшенням довжини |
2, 18 | 5 × 6 | випадкове парне число від 0 до 20 | кількість стовпців масиву чисел | максимальний елемент стовпця | за зменшенням довжини |
3, 19 | 4 × 3 | випадкове парне число від 20 до 30 | кількість рядків масиву чисел | максимальний елемент рядка | за алфавітом у зворотному порядку |
4, 20 | 4 × 3 | випадкове ціле число від 4 до 29 | кількість рядків масиву чисел | максимальний елемент рядка | за зменшенням довжини |
5, 21 | 4 × 6 | випадкове ціле число від 1 до 25 | кількість стовпців масиву чисел | максимальний елемент стовпця | за збільшенням довжини |
6, 22 | 6 × 4 | випадкове парне число від 10 до 20 | кількість рядків масиву чисел | мінімальний елемент рядка | за алфавітом у зворотному порядку |
7, 23 | 4 × 6 | випадкове парне число від 12 до 24 | кількість стовпців масиву чисел | мінімальний елемент стовпця | за зменшенням довжини |
8, 24 | 5 × 3 | випадкове непарне число від 3 до 31 | кількість рядків масиву чисел | мінімальний елемент рядка | за алфавітом у зворотному порядку |
9, 25 | 3 × 5 | випадкове ціле число від 6 до 29 | кількість стовпців масиву чисел | мінімальний елемент стовпця | за збільшенням довжини |
10, 26 | 4 × 5 | випадкове непарне число від 1 до 23 | кількість стовпців масиву чисел | максимальний елемент стовпця | за алфавітом у зворотному порядку |
11, 27 | 4 × 3 | випадкове парне число від 0 до 24 | кількість рядків масиву чисел | мінімальний елемент рядка | за зменшенням довжини |
12, 28 | 6 × 4 | випадкове парне число від 8 до 26 | кількість стовпців масиву чисел | мінімальний елемент стовпця | за збільшенням довжини |
13, 29 | 4 × 6 | випадкове непарне число від 5 до 21 | кількість стовпців масиву чисел | максимальний елемент стовпця | за зменшенням довжини |
14, 30 | 4 × 6 | випадкове ціле число від 4 до 33 | кількість рядків масиву чисел | максимальний елемент рядка | за збільшенням довжини |
15, 31 | 5 × 4 | випадкове непарне число від 7 до 37 | кількість рядків масиву чисел | максимальний елемент рядка | за алфавітом у зворотному порядку |
16, 32 | 4 × 6 | випадкове непарне число від 1 до 35 | кількість стовпців масиву чисел | мінімальний елемент стовпця | за зменшенням довжини |
Вивести отриманий масив рядків.
Наприклад, припустимо двовимірний масив цілих містить такі числа:
3 | 7 |
18 | 4 |
19 | 2 |
Якщо, припустимо, кількість елементів масиву рядків (String) відповідає кількості рядків масиву чисел, а мінімальні елементи масиву – це кількість повторення певного символу, і символ, який треба повторювати, це 'N', ми отримаємо такий масив рядків:
NNN |
NNNN |
NN |
Реалізувати два підходи: традиційний, побудований на циклах і роботі з окремими елементами й через функції класу
Arrays
(без циклів). Не використовувати роботу з потоками і метод Arrays.stream()
.
Додати до класу та окремих функцій коментарі Javadoc.
1.2 Ератосфенове решето
Заповнити масив із трьохсот цілих чисел послідовними додатними значеннями. Замінити всі значення, що не є простими числами, деяким від'ємним значенням. Для цього послідовно виключати всі числа – дільники інших чисел. Вивести на екран додатні значення, що залишилися, (прості числа).
У програмі не застосовувати ділення та знаходження залишку від ділення.
1.3 Знаходження чисел Фібоначчі
Реалізувати функцію обчислення чисел Фібоначчі (до 92-го числа включно) з використанням допоміжного масиву (статичного поля). Параметр функції – номер числа Фібоначчі. Пошук чисел Фібоначчі здійснюється за таким правилом:
Під час першого виклику функції масив заповнюється до необхідного числа (див. приклад 3.1). Під час наступних викликів
число або повертається з масиву, або обчислюється з використанням останніх двох чисел, що зберігаються у масиві
з подальшим заповненням масиву. Використовувати тип long
для представлення чисел. Не використовувати
рекурсію.
Здійснити тестування функції для різних значень номерів, що вводяться у довільному порядку.
1.4 Вирівнювання рядка
Прочитати аргумент командного рядка і додати в нього пропуски (space characters) так, щоб довжина рядка дорівнювала заданому числу. Пропуски додавати рівномірно між словами (за можливості).
1.5 Створення та використання класів для опису певної предметної області
Спроєктувати та реалізувати два класи відповідно до індивідуального завдання. У першому з класів повинен бути описаний масив елементів другого класу. Класи повинні мати конструктори, приватні поля та відкриті методи, зокрема методи доступу (сетери та гетери).
Для пошуку необхідних даних відповідно до завдання та форматованого виведення даних на консоль слід створити окремі
допоміжні класи. Функції пошуку повинні повертати масиви об'єктів (або null
, якщо пошук не дав результатів).
За можливості використовувати методи класу Arrays
. Не використовувати роботу з потоками і метод Arrays.stream()
.
Варіант завдання, який слід реалізувати у програмі, визначається залежно від номера студента у списку групи.
№№ | Перша сутність | Друга сутність | Основне завдання: знайти та вивести такі дані |
||
---|---|---|---|---|---|
Сутність | Обов'язкові поля | Сутність | Обов'язкові поля | ||
1, 17 | Погода | Сезон, коментар | День | Дата, температура, коментар | Середня температура, день з максимальною температурою, день з найдовшим коментарем |
2, 18 | Навчальний курс | Назва, наявність іспиту | Практичне заняття | Дата, тема, кількість студентів | Середня кількість студентів, заняття з максимальною кількістю студентів, список тем з певним словом у назві |
3, 19 | Трамвайна зупинка | Назва, список номерів маршрутів | Година | Кількість пасажирів, коментар | Загальна кількість пасажирів, година з найменшою кількістю пасажирів, найдовший коментар |
4, 20 | Навчальний курс | Назва, прізвище викладача | Лекція | Дата, тема, кількість студентів | Лекція з мінімальною кількістю студентів, список тем з певним словом у назві, остання літера у прізвищі викладача |
5, 21 | Погода | Рік, коментар | Вимір температури | Дата, температура, коментар | Виміри з мінімальною температурою, з найбільшою кількістю слів у коментарі до виміру, останнє слово коментаря до погоди |
6, 22 | Конференція | Назва, місце проведення | Засідання | Дата, тема, кількість учасників | Середня кількість учасників на засіданні, засідання з найбільшою кількістю учасників, довжина назви |
7, 23 | Виставка | Назва, прізвище художника | День | Кількість відвідувачів, коментар | Сумарна кількість відвідувачів, день з найменшою кількістю відвідувачів, список коментарів з певним словом |
8, 24 | Станція метрополітену | Назва, рік відкриття | Година | Кількість пасажирів, коментар | Сумарна кількість пасажирів, години з найменшою кількістю пасажирів та найбільшою кількістю слів у коментарі |
9, 25 | Лікар | Прізвище, фах | Прийом | День, зміна, кількість відвідувачів | Загальна кількість відвідувачів, прийом з мінімальною кількістю відвідувачів, довжина прізвища |
10, 26 | Музичний гурт | Назва, прізвище керівника | Гастрольна поїздка | Місто, рік, кількість концертів | Гастрольна поїздка з максимальною кількістю концертів, список гастрольних поїздок у певне місто, остання літера в прізвищі керівника |
11, 27 | Майстерня | Назва, адреса | Зміна | Кількість відремонтованих комп'ютерів | Сумарна кількість комп'ютерів, зміна з найбільшою кількістю відремонтованих комп'ютерів, довжина назви вулиці |
12, 28 | Лікар | Прізвище, стаж | Прийом | День, кількість відвідувачів, коментар | Середня кількість відвідувачів, прийом з мінімальною кількістю відвідувачів, найдовшим коментарем |
13, 29 | Трамвайний маршрут | Номер, середній інтервал руху | Зупинка | Назва, кількість пасажирів | Загальна кількість пасажирів, зупинки з найменшою кількістю пасажирів, найдовшою назвою |
14, 30 | Цілодобовий кіоск | Назва, адреса | Година | Кількість покупців, коментар | Загальна кількість покупців, година з найменшою кількістю покупців, коментарями з певними словами |
15, 31 | Виконавець | Прізвище, жанр | Концерт | Дата, кількість глядачів | Загальна кількість глядачів, концерт з максимальною кількістю глядачів, кількість слів у назві жанру |
16, 32 | Телефонний номер | Номер, оператор | Дзвінки | Дата, кількість хвилин розмов, кошти, що використано на розмови | Середня платня в день за період; кількість днів, коли вартість хвилини розмови перевищувала задане значення; Дні, коли кількість хвилин розмов була парна |
Слід додати коментарі Javadoc до сирцевого коду.
2 Методичні вказівки
2.1 Типи-посилання
У Java немає типів указівників. Імена змінних непримітивних типів по суті є іменами посилань на відповідні об'єкти.
Усі непримітивні типи мають назву типів-посилань (reference types). Розіменування не
потрібне: звернення до примітивних типів завжди здійснюється за значенням, а до непримітивних – за посиланням.
Типи-посилання ніколи не можуть бути приведені до примітивних і навпаки. У Java немає операцій розіменування (*
та ->)
.
Java також не підтримує адресної арифметики.
Спеціальне ключове слово null
використовують для того, щоб показати, що змінна типу-посилання
ні на що не посилається. Константа null
може бути присвоєна змінній будь-якого типу-посилання.
Об'єкти, на які вказують посилання, повинні бути розміщені в динамічній пам'яті за допомогою операції new
:
SomeReferenceType st =new SomeReferenceType();
Присвоювання значення одного посилання іншому не забезпечує копіювання об'єктів. Після присвоювання два посилання посилаються на один об'єкт.
SomeReferenceType a =new SomeReferenceType(); SomeReferenceType b =new SomeReferenceType(); a = b;
Об'єкт, на який раніше посилалося a
, загублений.
На відміну від С++, звільнення пам'яті від непотрібних об'єктів не потрібно. У Java немає операції delete
.
Для звільнення пам'яті використовується спеціальний механізм, який має назву збирання сміття. Цей механізм
базується на підрахунку посилань на об'єкти. Кожен об'єкт має свій лічильник посилань. Коли посилання копіюється
в нову змінну типу-посилання, лічильник збільшується на одиницю. Коли посилання виходить з області видимості, чи
перестає вказувати на об'єкт, лічильник зменшується на одиницю. Збирач сміття переглядає список об'єктів і видаляє
з пам'яті всі об'єкти, для яких кількість посилань дорівнює 0.
Операція ==
, застосована до змінних-посилань здійснює порівняння адрес, а не вмісту об'єктів.
Аргументи функцій типу-посилання передаються у функції за посиланням. Всередині функції створюється нове посилання на той самий об'єкт. Цей об'єкт можна змінити у функції та після повернення з функції використовувати його значення.
2.2 Масиви
2.2.1 Створення та використання масивів
Найпростішим типом-посиланням у Java є масив. Масив (array) – це набір комірок зберігання даних, кожна з яких має той же тип. Окрема комірка має назву елемента масиву (array item). На відміну від інших складених типів даних, масиви завжди розташовуються в цілісному блоці пам'яті. Оскільки всі елементи займають однакові за розміром комірки пам'яті, адреса конкретного елемента завжди може бути обчислена за його номером. Номери елементів називаються індексами. Звернення до конкретних елементів здійснюють через індекс.
Як і в С++, масиви в Java індексуються починаючи з нуля. Під час опису масиву квадратні дужки ставляться після імені типу, а не імені змінної. Розміщення квадратних дужок після імені змінної допускається, але не рекомендується.
int [] a;// посилання на масив int n = 10;// можна використовувати змінну a =new int [n];// створення масиву
У цьому прикладі описується посилання на масив, який створюється пізніше. Розмір масиву не є частиною типу, як це реалізовано в C++. Це дозволяє визначити масив необхідного розміру під час виконання
int [] numbers;// посилання на масив цілих чисел довільної довжини numbers =new int [10];// numbers складається з 10 елементів numbers =new int [20];// numbers складається з 20 елементів
У Java підтримуються одно- і багатовимірні масиви (масиви масивів). Наприклад, так можна визначити посилання на двовимірний масив:
byte [][] scores;
Наведені нижче приклади показують, як створити різні масиви:
int [] numbers =new int [5];// одновимірний масив double [][] matrix =new double [5][4];// прямокутний масив byte [][] scores =new byte [5][];// двовимірний масив з різною довжиною рядків for (int i = 0; i < 5; i++) { scores[i] =new byte [i + 4]; }
Останній приклад показує як можна створити двовимірний масив з різною довжиною рядків. Розмір масиву може бути визначений як за допомогою констант, так і за допомогою змінних і виразів, що дають цілий результат.
У Java масиви містять елементи разом з їх кількістю. Кількість елементів масиву завжди можна отримати за
допомогою спеціального поля length
. Це поле доступне тільки для читання:
int [] a =new int [10]; System.out.println(a.length);// 10
У Java передбачений простий спосіб ініціалізації масиву списком початкових значень:
int [] numbers =new int [] { 1, 2, 3, 4, 5 };
Можна опустити операцію new
:
int [] numbers = { 1, 2, 3, 4, 5 };
Аналогічно ініціалізуються багатовимірні масиви:
int [][] b = { { 1, 2, 3 }, { 0, 0, 1 }, { 1, 1, 11}, { 0, 0, 0 } };
У наведеному прикладі створюється двовимірний масив з чотирьох рядків і трьох стовпців.
Так виглядає типовий цикл для обходу масиву:
for (int i = 0; i < a.length; i++) { a[i] = 0; }
Аналогічно обхід усіх елементів двовимірного масиву зазвичай здійснюється так:
for (int i = 0; i < c.length; i++) {for (int j = 0; j < c[i].length; j++) { c[i][j] = 0; } }
Як видно з останнього прикладу, для отримання розміру i-го рядка двовимірного масиву використовують
конструкцію c[i].length
.
Альтернативна форма циклу for
(починаючи з JDK 1.5) дозволяє спростити повний обхід
масивів. Наприклад, замість
int [] nums = { 1, 2, 3, 4, 5, 6 };for (int i = 0; i < nums.length; i++) { System.out.println(nums[i]); }
можна написати
int [] nums = { 1, 2, 3, 4, 5, 6 };for (int n : nums) { System.out.println(n); }
Альтернативна форма може бути застосована. лише для читання значень елементів, а не для їхньої модифікації.
Масиви читають з клавіатури поелементно. Наведений нижче приклад демонструє читання кількості та значень елементів з клавіатури.
package ua.inf.iwanoff.java.second;public class ArrayTest {public static void main(String[] args) { System.out.println("Уведіть кількість елементів масиву:"); java.util.Scanner s =new java.util.Scanner(System.in);int size = s.nextInt();double [] a =new double [size]; System.out.println("Уведіть елементи масиву:");for (int i = 0; i < a.length; i++) { a[i] = s.nextDouble(); }// Робота з масивом // ... } }
Присвоювання імені масиву іншому імені масиву приводить тільки до копіювання посилання, але не самого масиву. Для копіювання елементів масиву необхідно організувати цикл або скористатися стандартними функціями.
2.2.2 Масиви як параметри та результат функцій
Масиви-параметри передаються у функції за посиланням. Після повернення з функції елементи можуть містити змінені значення:
package ua.inf.iwanoff.java.second;public class SwapElements {static void swap(int [] a) {int z = a[0]; a[0] = a[1]; a[1] = z; }public static void main(String[] args) {int [] b = { 1, 2 }; swap(b); System.out.println(b[0]);// 2 System.out.println(b[1]);// 1 } }
Якщо параметр описано як одновимірний масив, то можна фактичним параметром вказати рядок двовимірного масиву. Також можна використовувати параметри типу багатовимірних масивів. Наприклад:
package ua.inf.iwanoff.java.second;public class ArraysTest {static double sum(double [] arr1D) {double s = 0;for (double elem : arr1D) { s += elem; }return s; }static double sum(double [][] arr2D) {double s = 0;for (double [] line : arr2D) {for (double elem : line) { s += elem; } }return s; }public static void main(String[] args) {double [][] arr = { { 1, 2 }, { 3, 4 }, { 5, 6 } }; System.out.printf("Сума елементів рядка з індексом 1: %f%n", sum(arr[1])); System.out.printf("Сума всіх елементів: %f%n", sum(arr)); } }
У Java 5 з'явилася додаткова можливість створення функцій зі змінним числом параметрів визначеного типу (Variable Arguments List, varargs). Всередині функції такі параметри інтерпретуються як масив:
static void printIntegers(int ... a) {for (int i = 0; i < a.length; i++) { System.out.println(a[i]); } }
Викликати таку функцію можна у два способи: передаючи список аргументів типу елемента масиву, або передаючи масив цілком:
public static void main(String[] args) {int [] arr = {4, 5}; printIntegers(arr); printIntegers(1, 2, 3); }
Такий параметр може бути лише один і обов'язково розташований останнім в списку.
Функція може повертати посилання на масив. Найчастіше такий масив створюється всередині функції. Наприклад, створюємо масив цілих, заповнений одиницями:
static int [] arrayOfOnes(int n) {int [] arr =new int [n];for (int i = 0; i < arr.length; i++) { arr[i] = 1; }return arr; }
Тепер можна створити масив за допомогою нашої функції:
int [] a = arrayOfOnes(6);
Якщо потрібен тільки один елемент, для нього можна не створювати масив з ім'ям і поєднати створення масиву з його використанням:
System.out.println(arrayOfOnes(2)[0]);
Можна також повертати посилання на багатовимірні масиви.
Посилання на масиви передаються за значенням. Припустимо, ми хочемо створити функцію, яка змінює розмір наявного масиву:
static void resize(int [] arr,int newSize) {// збереження наявних елементів arr =new int [newSize];// копіювання }
Нове посилання слід повертати, інакше новий масив буде втрачено:
static int [] resize(int [] arr,int newSize) {// збереження наявних елементів arr =new int [newSize];// копіювання return arr; }
2.2.3 Стандартні функції для роботи з масивами
Клас System
надає найпростіший шлях копіювання одного масиву в іншій – використання
статичного методу arraycopy()
:
System.arraycopy(a, a_from, b, b_from, size);
Це еквівалентно такому циклу:
for (int i = a_from, j = b_from; i < size + a_from; i++, j++) { b[j] = a[i]; }
Масив, у який здійснюється копіювання, повинен мати необхідні розміри. Функція arraycopy()
не створює нового масиву. Весь масив a
можна скопіювати в b
таким викликом:
System.arraycopy(a, 0, b, 0, a.length);
Для роботи з масивами можна використовувати статичні методи класу Arrays
, реалізованого в
пакеті java.util
.
Статичний метод класу Arrays | Опис |
---|---|
void fill(тип[] a, тип val) |
заповнює масив вказаними значеннями |
void fill(тип[] a, int fromIndex, int toIndex, тип val) |
заповнює частину масиву вказаними значеннями |
String toString(тип[] a) |
повертає список елементів масиву у вигляді рядка |
String deepToString(тип[][] a) |
подає багатовимірний масив у вигляді рядка |
тип[] copyOf(тип[] a, int len) |
створює новий масив довжини len з копіями елементів |
тип[] copyOfRange(тип[] a, int from, int to)
|
створює новий масив з копіями елементів діапазону |
void setAll(тип[] array, Оператор generator) |
заповнює масив за формулою, визначеною генератором |
boolean equals(тип[] a, тип[] a2) |
перевіряє еквівалентність елементів двох масивів |
boolean deepEquals(тип[][] a, тип[][] a2) |
перевіряє еквівалентність елементів багатовимірних масивів |
void sort(тип[] a) |
здійснює сортування елементів за зростанням |
void sort(тип[] a, int fromIndex, int toIndex) |
здійснює сортування частини елементів за зростанням |
void sort(тип[] a, int fromIndex, int toIndex, Компаратор c) |
здійснює сортування елементів за критерієм, визначеним компаратором |
int binarySearch(тип[] a, тип key) |
здійснює пошук у відсортованому масиві |
У таблиці тип
означає один з фундаментальних (примітивних) типів або тип
Object
.
Перший варіант функції fill()
використовується для заповнення всього масиву, другий –
частини, при чому елемент з номером toIndex
не включається в послідовність. Наприклад:
package ua.inf.iwanoff.java.second;public class FillArray {public static void main(String[] args) {int [] a =new int [6]; java.util.Arrays.fill(a, 0, 4, 12);// Інші елементи дорівнюють 0 for (int x : a) { System.out.print(x + " "); } System.out.println(); java.util.Arrays.fill(a, 100);// Всі елементи дорівнюють 100 for (int x : a) { System.out.print(x + " "); } System.out.println(); } }
У попередньому прикладі для виведення елементів масиву на екран був використаний цикл. Альтернативний
спосіб – використання функції toString()
класу Arrays
. Ця функція
повертає представлення масиву у вигляді рядка, яке є зручним для більшості застосувань:
java.util.Arrays.fill(a, 100); System.out.println(Arrays.toString(a))// [100, 100, 100, 100, 100, 100];
Примітка: для виведення в рядок багатовимірних масивів слід використовувати функцію
deepToString()
.
Клас Arrays
надає альтернативний шлях копіювання масивів. Функція copyOf()
створює новий масив копій елементів. Перший параметр – вихідний масив, другий параметр –
довжина вислідного масиву. Елементи, які не помістилися, відкидаються, відсутні заповнюються нулями.
Функція copyOfRange(тип[] a, int from, int to)
копіює в новий масив
частину масиву, включаючи початок інтервалу і не включаючи кінця інтервалу:
package ua.inf.iwanoff.java.second;import java.util.Arrays;public class CopyOfTest {public static void main(String[] args) {int [] a = { 1, 2, 3, 4 };int [] b = Arrays.copyOf(a, 3); System.out.println(Arrays.toString(b));// [1, 2, 3] int [] c = Arrays.copyOf(a, 6); System.out.println(Arrays.toString(c));// [1, 2, 3, 4, 0, 0] int [] d = Arrays.copyOfRange(a, 1, 3); System.out.println(Arrays.toString(d));// [2, 3] } }
Порівняти два масиви чи частину їх можна за допомогою функцій групи equals()
. Масиви
порівнюються поелементно. Два масиви також вважаються еквівалентними, якщо обидва посилання –
null
. Наприклад:
package ua.inf.iwanoff.java.second;import java.util.Arrays;public class ArraysComparison {public static void main(String[] args) {double [] a =null , b =null ; System.out.println(Arrays.equals(a, b));// true a =new double [] { 1, 2, 3, 4 }; b =new double [4]; System.out.println(Arrays.equals(a, b));// false System.arraycopy(a, 0, b, 0, a.length); System.out.println(Arrays.equals(a, b));// true b[3] = 4.5; System.out.println(Arrays.equals(a, b));// false } }
Є також метод deepEquals()
, використання якого аналогічне. Різниця є істотною для
багатовимірних масивів. Здійснюється більш "глибока" перевірка:
int [][] a1 = { { 1, 2 } , { 3, 4 } };int [][] a2 = { { 1, 2 } , { 3, 4 } }; System.out.println(Arrays.equals(a1, a2));// false System.out.println(Arrays.deepEquals(a1, a2));// true
Функція setAll()
дозволяє здійснює заповнювати масив значеннями залежно від індексу. Для заповнення
застосовують механізм зворотного виклику (callback). Цей механізм у Java реалізовано за допомогою так званих функціональних
інтерфейсів. Починаючи з Java 8, найпростіший варіант реалізації callback-функції це застосування
так званих лямбда-виразів – альтернативної форми опису функцій. Докладно функціональні інтерфейси та лямбда-вирази
будуть розглянуті пізніше. У наведеному нижче прикладі масив цілих чисел заповнюється квадратами індексів:
int [] a =new int [6]; Arrays.setAll(a, k -> k * k); System.out.println(Arrays.toString(a));// [0, 1, 4, 9, 16, 25]
Лямбда-вираз у цьому прикладі слід розуміти так: функція приймає аргумент k
(індекс елемента) та повертає другий степінь
індексу, який записується у відповідний елемент.
За допомогою функції sort()
можна здійснити сортування масиву чисел за зростанням.
Наприклад:
package ua.inf.iwanoff.java.second;import java.util.Arrays;public class ArraySort {public static void main(String[] args) {int [] a =new int [] { 11, 2, 10, 1 }; Arrays.sort(a);// 1 2 10 11 for (int x : a) { System.out.print(x + " "); } System.out.println(); } }
Функція sort()
реалізована для масивів усіх примітивних типів та рядків. Рядки
впорядковуються за алфавітом. Можна також сортувати частину масиву. Як і для функції fill()
,
вказується початковий і кінцевий індекси послідовності, яку слід відсортувати. Кінцевий індекс не
включається в послідовність. Наприклад:
int [] a = { 7, 8, 3, 4, -10, 0 }; java.util.Arrays.sort(a, 1, 4);// 7 3 4 8 -10 0
У відсортованих масивах можна виконати пошук за допомогою методів класу Arrays
. Група
функцій binarySearch()
, реалізована для всіх примітивних типів і типу Object
,
повертає індекс знайденого елемента або від'ємне значення, якщо елемент відсутній.
2.3 Визначення класів. Інкапсуляція
2.3.1 Поля і методи
Клас – це структурований тип даних, набір елементів даних різних типів і функцій для роботи з цими
даними. Опис класу складається зі специфікаторів (наприклад, public
, final
),
імені, імені базового класу, списку інтерфейсів і тіла у фігурних дужках.
Тіло класу містить поля (їм відповідають елементи даних у C++) і методи (функції-елементи в C++). Поля і методи разом іменуються елементами (членами) класу (class members). Нижче наводиться приклад опису класу:
class Circle {double radius;double area() {return Math.PI * radius * radius; }double circumference() {return 2 * Math.PI * radius; } }
Після останньої фігурної дужки, що закривається, не слід ставити крапку з комою. Методи завжди реалізуються усередині визначення класу.
У наведеному вище прикладі клас містить одне поле й два методи, але реальні класи можуть мати довільну кількість полів і методів. Наприклад, можна додати поля – координати центру кола, а також методи обчислення довжини кола (периметру) та знаходження відстані від початку координат до центру кола:
class Circle {double radius;double x, y;// координати центру кола double area() {return Math.PI * radius * radius; }double circumference() {return 2 * Math.PI * radius; }double distance() {return Math.sqrt(x * x + y * y); } }
У подальших прикладах для спрощення ці додаткові поля й методи можна опустити.
Під час створення об'єкта класу поля ініціалізуються усталеними значеннями (нулями або null
для
посилань). Java допускає ініціалізацію полів початковими значеннями:
class Circle {double radius = 10;double area() {return Math.PI * radius * radius; }}
Можна створити спеціальний блок ініціалізації усередині тіла класу. Такий блок виконуватиметься щораз під час створення нового об'єкта:
class Circle {double radius; { radius = 10; }double area() {return Math.PI * radius * radius; } }
Для того, щоб працювати з полями й методами класу, необхідно створити об'єкт. Для цього спочатку створюють посилання
на об'єкт, а потім за допомогою операції new
створюють сам об'єкт шляхом виклику конструктора.
Ці дії можна поєднати. Після цього можна викликати методи та використовувати поля:
Circle circle =new Circle();// circle - ім'я посилання на об'єкт System.out.println(circle.radius);// 10.0 double a = circle.area(); System.out.println(a);// 314.1592653589793 circle.radius = 15;// зміна значення поля double b = circle.area(); System.out.println(b);// 706.8583470577034
Ключове слово this
використовується як посилання на об'єкт, для якого викликаний метод. Усі
нестатичні методи неявно отримують посилання на об'єкт для якого вони використані. Ключове слово this
використовувати
явно, наприклад, коли треба повернути з функції посилання на поточний об'єкт, або запобігти конфлікту імен. Але
синтаксично всередині тіла методу можна вживати перед будь-яким полем або методом цього ж класу, наприклад:
double area() {return Math.PI *this .radius *this .radius; }
2.3.2 Специфікатори доступу. Інкапсуляція
Java підтримує закритий (private
), пакетний, захищений (protected
)
і відкритий (public
) рівні доступу:
- Доступ до закритих (
private
) елементів класу обмежений методами усередині класу. У Java немає ключового словаfriend
, яке у С++ забезпечує доступ до закритих елементів ззовні класу. - Відкриті (
public
) елементи відкритого класу можуть бути доступні з будь-якої функції будь-якого пакета. - Елементи класу без атрибутів доступу мають пакетну видимість. Такий доступ ще називають "дружнім". Всі інші класи цього пакета мають доступ до таких елементів як до відкритих. Ззовні пакету такі елементи взагалі недоступні.
- Доступ до захищеного (
protected
) елемента класу, визначеного в деякому пакеті, обмежений методами цього класу і похідних класів будь-яких пакетів, а також класів цього пакету.
Сам клас може бути оголошений як public
.
Інкапсуляція (приховування даних) – одна з трьох парадигм об'єктно-орієнтованого програмування. Зміст
інкапсуляції полягає у приховуванні від зовнішнього користувача деталей реалізації об'єкта. Зокрема доступ до даних
(полів), які зазвичай описані з модифікатором private
, здійснюється через публічні функції доступу
(getters and setters, гетери й сетери). Якщо поле має ім'я name
, відповідні функції доступу мають імена setName
та getName
.
На відміну від C++, Java вимагає окремої специфікації доступу для кожного елемента, або групи полів одного типу:
public class Circle {private double radius;public double getRadius() {return radius; }public void setRadius(double radius) {this .radius = radius; }public double area() {return Math.PI * radius * radius; } }
Примітка: у тілі сетерів використання this
є цілком виправданим, оскільки з одного боку, добре,
коли ім'я параметра збігається з ім'ям поля, з іншого боку, без this
виникає конфлікт імен.
Автоматична генерація гетерів і сетерів здійснюється за допомогою функції
2.3.3 Конструктори
Екземпляр класу створюється шляхом застосування операції new
до конструктора.
Конструктор – це функція, яка здійснює ініціалізацію даних об'єкта. Ім'я конструктора збігається з ім'ям класу. Не можна вказувати типу результату конструктора. У класі може бути визначено кілька конструкторів. Якщо жоден конструктор явно не визначений, автоматично створюється усталений конструктор (без параметрів). Такий конструктор ініціалізує всі поля усталеними початковими значеннями.
У нашому прикладі можна створити конструктор без параметрів явно:
public class Circle {private double radius;public Circle() {// конструктор radius = 10; }public double getRadius() {return radius; }public void setRadius(double radius) {this .radius = radius; }public double area() {return Math.PI *this .radius *this .radius; } }//... Circle circle =new Circle();// виклик створеного вище конструктора
Якщо замість конструктора без параметрів створити конструктор з параметром (або декілька інших конструкторів з параметрами), під час виклику конструктора слід вказувати фактичний параметр (або параметри). Усталений конструктор автоматично не створюється і спроба його виклику призведе до синтаксичної помилки:
public class Circle {private double radius;public Circle(double radius) {this .radius = radius; }public double getRadius() {return radius; }public void setRadius(double radius) {this .radius = radius; }public double area() {return Math.PI *this .radius *this .radius; } }//... Circle circle1 =new Circle(20);// виклик створеного вище конструктора Circle circle2 =new Circle();// помилка компіляції
Тепер, якщо треба, конструктор без параметрів слід створювати окремо.
Один конструктор можна викликати з іншого з використанням слова this
,
після якого вказують необхідні аргументи:
public class Circle {private double radius;public Circle(double radius) {this .radius = radius; }public Circle() {this (10);// виклик іншого конструктора }// ... }
Спочатку здійснюється ініціалізація поля в місці опису, після якої значення може бути перевизначене в блоці ініціалізації, а потім перевизначене в конструкторі:
public class Circle {private double radius = 10; { radius = 20; }public Circle(double radius) {this .radius = radius; }public Circle() {this (30);// виклик іншого конструктора }// ... }//... Circle circle3 =new Circle();// radius = 30
Наведений приклад суто ілюстративний і не має практичного сенсу.
У Java немає конструкторів копіювання і деструкторів. Можна створити спеціальний метод finalize()
,
який викликається збирачем сміття перед ліквідацією об'єкта. У деяких випадках об'єкт може бути не вилучений збирачем
сміття ніколи (пам'яті вистачало до кінця програми), отже метод finalize()
може бути ніколи не викликаний.
Окрім фактичної некорисності, метод finalize()
може спричинити проблеми зі збиранням сміття. У сучасних
версіях Java метод finalize()
сприймається компілятором як небажаний і застарілий (deprecated).
2.3.4 Статичні елементи. Константи
Методи й поля можуть бути оголошені з ключовим словом static
. Звернення до таких полів
і методів може здійснюватися без створення екземпляра класу. Статичні поля є альтернативою відсутнім у Java глобальним
змінним. Статичні
поля можуть бути проініціалізовані під час створення.
Раніше розглядалися статичні методи, які є альтернативою відсутнім у Java глобальним функціям. Можна створювати класи, які містять тільки статичні функції. Такий клас можна розглядати як деяку бібліотеку функцій. Для того, щоб не створити випадково непотрібний об'єкт, можна додати приватний конструктор. Наведений нижче клас надає додаткові математичні функції:
public class MathExt {public static double square(double x) {return x * x; }public static double cube(double x) {return x * x * x; }private MathExt() {// приватний конструктор } }
До створених функцій можна звертатися через ім'я класу, але не можна створити об'єкт:
double x = 3; System.out.println(MathExt.square(x));// 9.0 System.out.println(MathExt.cube(x));// 27.0 MathExt ext =new MathExt();// помилка компіляції, конструктор приватний
Статичні поля та методи можуть бути доречними у звичайних класах. Розповсюджений приклад статичного поля – лічильник кількості створених об'єктів. Така інформація безумовно має відношення до класу, а не до конкретних екземплярів. Для того, щоб лічильник відображав достеменну інформацію про кількість, це поле слід зробити приватним з відповідним статичним гетером. Значення лічильника слід збільшувати в конструкторі:
public class Circle {private static int counter = 0;private double radius;public static int getCounter() {return counter; }public Circle(double radius) {this .radius = radius; counter++; }public Circle() {this (10);// виклик іншого конструктора }// ... }
Можна створити окремий блок статичної ініціалізації:
public class Circle {private static int counter;static { counter = 0; }// ... }
На відміну від нестатичної ініціалізації, створення й ініціалізація статичних полів здійснюється під час першого звернення до класу (створенні екземпляра класу чи зверненні до статичних елементів). Java не створює статичних полів для класів, які не використовуються.
Статичні методи не отримують посилання на об'єкт і не можуть використовувати посилання this
.
Конструктори не можуть бути статичними.
Звернення до статичних елементів може здійснюватися як через ім'я класу, так і через посилання на об'єкт:
System.out.println(Circle.getCounter());// 0 Circle circle =new Circle(); System.out.println(Circle.getCounter());// 1 System.out.println(circle.getCounter());// 1
Всередині класів можна визначати константи. Константи можуть бути двох видів – статичні й нестатичні. Статичну
константу створює компілятор. Така константа зазвичай визначається як public static final
.
Наприклад, замість константи Math.PI
можна визначити свою власну константу:
public class Circle {public static final double PI = 3.14159265;// константа, яку створює компілятор private double radius = 10;// ... public double area() {return PI * radius * radius; }double circumference() {return 2 * PI * radius; } }
За угодою ім'я статичної константи має містити лише великі літери.
Значення нестатичної константи слід визначити, причому один раз – у місці визначення, в блоці ініціалізації (тоді це значення буде однаковим для всіх екземплярів), або в конструкторі:
public class ConstDemo {public final int one = 1;public final int two; { two = 2; }public final int other;public ConstDemo(int other) {this .other = other; } }
Цілком безпечно визначати константи як public
, оскільки компілятор не дозволить змінити
їх значення.
2.3.5 Записи
Починаючи з JDK 14, до синтаксису Java додано нову конструкцію – так званий запис (record
).
Фактично це спрощений клас, призначений для зберігання даних тільки для читання. Для створення запису використовують
спеціальний синтаксис:
record SomeRec(список полів - формальних параметрів конструктора) { }
Запис надає конструктор з параметрами, функції доступу для читання, а також методи toString()
, equals()
і hashCode()
.
Запис дозволяє програмісту явно реалізовувати необхідні конструктори та інші методи, а також статичні функції. Можна
також додавати статичні поля.
Традиційний підхід до створення класу з даними тільки для читання передбачає створення необхідних полів, гетерів та конструктора:
public class CircleReadOnly {private double radius;public CircleReadOnly(double radius) {this .radius = radius; }public double getRadius() {return radius; }public double area() {return Math.PI * radius * radius; }public double perimeter() {return 2 * Math.PI * radius; } }
Створення запису дозволяє скоротити необхідний код:
public record CircleRecord(double radius) {// поле radius створено автоматично public double area() {return Math.PI * radius * radius; }public double perimeter() {return 2 * Math.PI * radius; } }
У наведеній нижче програмі здійснюється демонстрація обох реалізацій. Під час використання запису замість гетера
(getRadius()
) застосовано автоматично створений метод radius()
:
public class TestReadOnly {public static void main(String[] args) {// Робота з класом: CircleReadOnly circle1 =new CircleReadOnly(10); System.out.println(circle1.getRadius()); System.out.println(circle1.area()); System.out.println(circle1.perimeter());// Робота з записом: CircleRecord circle2 =new CircleRecord(10); System.out.println(circle2.radius()); System.out.println(circle2.area()); System.out.println(circle2.perimeter()); } }
Якщо необхідно, до запису можна додати конструктор без параметрів. Він повинен викликати автоматично створений конструктор:
public record CircleRecord(double radius) {public CircleRecord() {this (20); }// ... }
Якщо полів більше ніж один, можна додавати конструктори з меншою кількістю параметрів. Такі конструктори теж повинні викликати автоматично створений конструктор.
Автоматичне створення методів toString()
, equals()
і hashCode()
надає додаткові
переваги та істотно скорочує мінімально необхідний код. Цю можливість буде розглянуто пізніше.
Записи найчастіше застосовують для реалізації патерну проєктування DTO (Data Transfer Object), який використовують для передачі структурованих даних між різними рівнями програми. Об'єкти передачі даних (DTO) не містять логіки (лише дані). Об'єкти лише для читання є надійними з точки зору багатопотоковості. Записи часто застосовують у застосунках баз даних для відображення рядків реляційних таблиць в об'єкти Java.
2.4 Композиція класів
Під композицією класів розуміють створення нових класів з використанням об'єктів інших класів як полів. Java не дозволяє розміщення об'єктів усередині інших об'єктів, можна тільки описувати посилання. Композиція класів у цьому випадку припускає створення об'єктів безпосередньо (у тілі класу при оголошенні полів) чи в конструкторах.
class Cabin { }class Engine { }class Car { Cabin cabin =new Cabin(); Engine engine; Car() { engine =new Engine(); } }
Можна також створити внутрішній об'єкт безпосередньо перед його першим використанням.
Відношення, що моделюється композицією, часто називають відношенням "has-a".
Агрегування – це різновид композиції, який передбачає, що сутність (екземпляр) міститься в іншій сутності або не може бути створена та існувати без сутності, яка її охоплює. При цьому сутність, що охоплює, може існувати без внутрішньої, тобто час життя зовнішньої та внутрішньої сутностей може не збігатися. Більш строге трактування композиції (власне композиція) передбачає, що час життя зовнішньої та внутрішньої сутностей збігається. На рівні Java агрегування передбачає можливість створення внутрішнього об'єкта перед його використанням, тоді як строга композиція передбачає створення внутрішнього об'єкта в тілі класу, в блоці ініціалізації або в конструкторі.
2.5 Використання стандартних класів
2.5.1 Загальні відомості
Раніше вже використовувалися статичні засоби стандартних класів System
(поля-потоки in
і out
), Math
(стандартні
математичні функції, реалізовані у вигляді статичних методів) і Arrays
. Крім того, створювався об'єкт
класу java.util.Scanner
з викликом його методів.
Для тестування програмного забезпечення, статистичного моделювання, в задачах криптографії тощо використовують
випадкові та псевдовипадкові значення, які можна отримати за допомогою класу java.util.Random
.
Практично жодна програма на Java не може обійтися без об'єктів класу String
: функція main()
описується
з параметром типу масиву рядків, дані читаються з потоків у рядки й записуються з рядків у потоки, рядки використовуються
для представлення даних у візуальних компонентах графічного інтерфейсу користувача тощо. Для модифікації вмісту
рядків використовуються стандартні класи StringBuffer
і StringBuilder
. Для поділу рядка
на лексеми використовують клас StringTokenizer
.
Класи-обгортки Integer
, Double
, Boolean
, Character
, Float
, Byte
, Short
і Long
використовують
для зберігання даних примітивних типів у об'єктах, з якими можна працювати, як з посиланнями. Крім того, ці класи
надають ряд корисних методів для перетворення даних.
2.5.2 Робота з випадковими величинами
Іноді під час тестування виникає необхідність у заповненні масивів випадковими (псевдовипадковими) значеннями.
Це можна здійснити за допомогою функції random()
класу Math
і за допомогою спеціального
класу java.util.Random
. Перший варіант дає випадкове число у діапазоні від 0 до 1. Наприклад:
package ua.inf.iwanoff.java.second;import java.util.Arrays;public class MathRandomTest {public static void main(String[] args) {double [] a =new double [5];for (int i = 0; i < a.length; i++) { a[i] = Math.random() * 10;// випадкові значення від 0 до 10 } System.out.println(Arrays.toString(a)); } }
Використання класу Random
дозволяє отримати більш різноманітні результати. Конструктор класу Random
без
параметрів ініціалізує генератор псевдовипадкових чисел так, що послідовності випадкових значень практично не повторюються.
Якщо для зневадження нам необхідно кожного разу отримувати однакові випадкові значення, слід скористатися конструктором
з цілим параметром. Параметром може бути будь-яке ціле, яке ініціалізує генератор випадкових чисел.
У наведеній нижче таблиці представлені функції класу java.util.Random
, що дозволяють отримати різні
псевдовипадкові значення.
Функція | Опис |
---|---|
nextBoolean() |
повертає наступне рівномірно розподілене значення типу boolean |
nextDouble() |
повертає наступне значення типу double , рівномірно розподілене
на інтервалі від 0 до 1 |
nextFloat() |
повертає наступне значення типу float , рівномірно розподілене
на інтервалі від 0 до 1 |
nextInt() |
повертає наступне рівномірно розподілене значення типу int |
nextInt(int n) |
повертає наступне значення типу int ,
рівномірно розподілене від 0 до n (не включаючи n ) |
nextLong() |
повертає наступне рівномірно розподілене значення типу long |
nextBytes(byte[] bytes) |
заповнює масив цілих типу byte випадковими значеннями |
nextGaussian() |
повертає наступне значення типу double , розподілене на інтервалі
від 0 до 1 за нормальним законом |
У наведеному нижче прикладі ми отримуємо псевдовипадкові цілі значення в діапазоні від 0 (включно) до 10:
package ua.inf.iwanoff.java.second;import java.util.*;public class UtilRandomTest {public static void main(String[] args) {int [] a =new int [15]; Random rand =new Random(100);for (int i = 0; i < a.length; i++) { a[i] = rand.nextInt(10);// випадкові значення від 0 до 10 } System.out.println(Arrays.toString(a)); } }
Після кожного запуску програми ми будемо отримувати однакову послідовність псевдовипадкових значень. Якщо ми хочемо,
щоб значення були дійсно випадковими, треба скористатися конструктором Random
() без параметрів.
2.6 Рядки
2.6.1 Використання класу String
Рядки в Java – це екземпляри класу java.lang.String
. Об'єкти цього класу містять символи Unicode.
Об'єкт-рядок може бути створений під час опису посилання шляхом присвоювання йому рядкового літерала:
String s = "Перший рядок";
Рядок можна також створити за допомогою різних конструкторів. Клас String
у Java надає 15 конструкторів,
які дозволяють визначити початкове значення рядка. Наприклад, можна отримати рядок з масиву символів:
char [] chars = { 'Т', 'е', 'к', 'с', 'т' }; String s1 =new String(chars);// Текст
Можна створити масив байтів і отримати з нього рядок. Перетворення байтів у символи здійснюється згідно з усталеною таблицею кодування, прийнятою в системі.
byte [] bytes = { 49, 50, 51, 52 }; String s2 =new String(bytes); System.out.println(s2);// 1234
Можна створити рядок з іншого рядка. Слід відрізняти створення нового посилання від створення нового рядка:
String s = "текст"; String s1 = s;// s і s1 посилаються на один рядок String s2 =new String(s);// s2 посилається на новий рядок - копію s
Якщо один з операндів – рядок, а інший – ні, то цей операнд подається у вигляді рядка. Альтернативний
спосіб перетворення числових даних у рядки – застосування статичних функцій valueOf()
. Відповідні
функції реалізовані для аргументів числових типів, а також типів Object
, char
, boolean
і
масивів символів. Наприклад:
double d = 1.1; String sd = String.valueOf(d);// "1.1"
Нижче наведені методи класу String
, які використовують найбільш часто.
Метод | Аргументи | Повертає | Опис |
---|---|---|---|
length |
() |
int |
Повертає кількість символів у рядку. |
concat |
(String str) |
String |
Додає вказаний рядок у кінець поточного рядка |
charAt |
(int index) |
char |
Повертає символ, що відповідає певному індексу |
compareTo |
(String value) |
int |
Порівнює поточний рядок з аргументом-рядком. Результат від'ємний, якщо поточний рядок лексикографічно передує рядку-аргументу. Результат дорівнює 0, якщо рядки збігаються і додатний у протилежному випадку |
compareToIgnoreCase |
(String str) |
int |
Працює як compareTo() , але ігнорує регістр |
equals |
(String value) |
boolean |
Порівнює поточний рядок з аргументом-рядком. Повертає true , якщо рядки збігаються |
equalsIgnoreCase |
(String str) |
boolean |
Порівнює поточний рядок з аргументом-рядком з ігноруванням регістрів. Повертає true , якщо
рядки збігаються |
indexOf |
(String substring) |
int |
Повертає індекс, який відповідає першому входженню підрядка у рядок. Якщо підрядок не входить у рядок – повертає –1 |
indexOf |
(char ch) |
int |
Повертає індекс, який відповідає першому входженню символу в рядок. Якщо символ відсутній – повертає –1 |
lastIndexOf |
(String substring) |
int |
Повертає індекс, який відповідає останньому входженню підрядка у рядок. Якщо підрядок не входить у рядок – повертає –1 |
lastIndexOf |
(char ch) |
int |
Повертає індекс, який відповідає останньому входженню символу в рядок. Якщо символ відсутній – повертає –1 |
repeat |
(int count) |
String |
Повертає рядок, в якому вихідний рядок повторюється count разів |
substring |
(int beginindex, int endindex) |
String |
Повертає новий рядок, що є підрядком вихідного рядку |
toLowerCase |
() |
String |
Повертає рядок, усі символи якого переведені в нижній регістр |
toUpperCase |
() |
String |
Повертає рядок, усі символи якого переведені у верхній регістр |
regionMatches |
(int toffset, String other, int offset, int len) |
boolean |
Перевіряє, чи збігаються дві послідовності символів у двох рядках |
regionMatches |
(boolean ignoreCase, int toffset, String other, int ooffset, intlen) |
boolean |
Перевіряє, чи збігаються дві послідовності символів у двох рядках. Додатково можна встановлювати можливість ігнорування регістра під час перевірки |
toCharArray |
() |
char[] |
Перетворює поточний рядок у новий масив символів |
getChars |
(int srcBegin, int srcEnd, char[] dst, int dstBegin) |
void |
Копіює символи поточного рядка у вислідний масив символів |
getBytes |
() |
byte[] |
Повертає масив байтів, що містять коди символів з урахуванням таблиці кодів платформи (операційної системи) |
trim |
() |
String |
Повертає копію рядка, у якій початкові й кінцеві пропуски опущені |
startsWith |
(String prefix) |
boolean |
Перевіряє, чи починається рядок із зазначеного префікса |
endsWith |
(String suffix) |
boolean |
Перевіряє, чи закінчується рядок зазначеним суфіксом |
Наведені нижче приклади демонструють використання методів обробки рядків.
String s1 =new String("Hello World.");int i = s1.length();// i = 12 char c = s1.charAt(6);// c = 'W' i = s1.indexOf('e');// i = 1 (індекс 'e' у "Hello World.") String s2 = "abcdef".substring(2, 5);// s2 = "cde" int k = "AA".compareTo("AB");// k = -1 s2 = "abc".toUpperCase();// s2 = ABC
Одна з найбільш типових операцій з рядками – зшивання. Для зшивання двох рядків можна застосувати функцію concat()
:
String s3 = s1.concat(s2); s3 = s3.concat("додаємо текст");
Але найчастіше замість виклику функції concat()
застосовують операцію +
:
String s1 = "first"; String s2 = s1 + " and second";
Якщо один з операндів – рядок, а інший – ні, то цей операнд приводиться до рядкового представлення.
int n = 1; String sn = "n дорівнює " + n;// "n дорівнює 1" double d = 1.1; String sd = d + "";// "1.1"
Можна також використовувати операцію "+=
" для дошивання в кінець рядка.
Можна створювати масиви рядків. Як і для інших типів-посилань, масив зберігає не рядки безпосередньо, а посилання
на них. Функція sort()
класу java.util.Arrays
реалізована також для рядків. Рядки впорядковуються
за алфавітом:
String[] a = { "dd", "ab", "aaa", "aa" }; java.util.Arrays.sort(a);// aa aaa ab dd
Якщо ми хочемо перевизначити умову сортування, можна також скористатися лямбда-виразом. Відповідна функція повинна
повертати -1, якщо елементи розташовані в необхідному порядку (перший елемент менше, ніж другий), нуль, якщо вони
еквівалентні, та 1, якщо їхній порядок слід змінити на протилежний (перший елемент більше, ніж другий). Відповідний
метод compareTo()
класу String
надає можливість отримувати такі значення.
Наприклад, визначення такого-лямбда-виразу як умови сортування нічого не змінить: сортування здійснюватиметься в природному порядку, як і без визначення умови перевірки:
String[] a = { "dd", "ab", "aaa", "aa" }; java.util.Arrays.sort(a, (s1, s2) -> s1.compareTo(s2));// [aa, aaa, ab, dd] System.out.println(Arrays.toString(a));
А тепер сортування здійснюватиметься в протилежному порядку:
String[] a = { "dd", "ab", "aaa", "aa" }; java.util.Arrays.sort(a, (s1, s2) -> -s1.compareTo(s2));// [dd, ab, aaa, aa] System.out.println(Arrays.toString(a));
Якщо ми порівнюємо цілі значення (наприклад, довжину рядків), для отримання цих значень можна скористатися
статичними функціями compare()
,
яку надають класи Integer
, Double
та інші класи-обгортки.
Для читання рядка з потоку з використанням класу java.util.Scanner
рядок можна отримати за допомогою
методу next()
(до роздільника) або nextLine()
(до кінця рядка).
Екземпляр класу String
не може бути змінений після створення. Робота деяких методів та операцій ззовні
нагадує модифікацію об'єкта, однак насправді створюється новий рядок.
String s = "ab";// У пам'яті один рядок s = s += "c";// У пам'яті три рядки: "ab", "c" та "abc". На "abc" посилається s // Зайві рядки потім будуть видалені збирачем сміття
2.6.2 Використання класів StringBuffer і StringBuilder
Існує спеціальний клас StringBuffer
, що дозволяє модифікувати вміст рядкового об'єкта. Починаючи з
Java 5, замість StringBuffer
можна використовувати StringBuilder
. У програмах, які не
створюють окремих потоків виконання, функції цього класу виконуються більш ефективно.
Створити об'єкт типу StringBuilder
можна з наявного рядка. Після модифікації можна створити новий
об'єкт класу String
, використовуючи об'єкт класу StringBuilder
. Наприклад:
String s = "abc"; StringBuilder sb1 =new StringBuilder(s);// Виклик конструктора StringBuilder sb2 =new StringBuilder("cd");// Виклик конструктора // модифікація sb1 та sb2 // ... String s1 =new String(sb1);// Виклик конструктора String s2 = sb2 + "";// Перетворення типів
Окрім деяких типових для класу String
функцій, таких як length()
, charAt()
, indexOf()
, substring()
,
клас StringBuilder
надає низку методів для модифікації вмісту. Це такі методи, як append()
, delete()
, deleteCharAt()
, insert()
, replace()
, reverse()
,
та setCharAt()
. Розглянемо використання цих функцій на наведеному нижче прикладі:
public class StringBuilderTest {public static void main(String[] args) { String s = "abc"; StringBuilder sb =new StringBuilder(s); sb.append("d");// abcd sb.setCharAt(0, 'f');// fbcd sb.delete(1, 3);// fd sb.insert(1, "gh");// fghd sb.replace(2, 3, "mn");// fgmnd sb.reverse();// dnmgf System.out.println(sb); } }
Використання StringBuilder
може підвищити ефективність роботи програми у випадках, коли певний рядок
зазнає багаторазових модифікацій протягом роботи програми. Але важливо пам'ятати, що декілька посилань вказують
на один об'єкт типу StringBuilder
. Тому, коли ми його змінюємо, усі посилання вказуватимуть на змінений
рядок.
2.6.3 Поділ рядка на лексеми
Існує кілька способів поділу рядка на лексеми. Найпростіший спосіб – використання класу java.util.StringTokenizer
.
Об'єкт цього класу створюється за допомогою конструктора з параметром типу String
, який визначає рядок,
що підлягає поділу на лексеми:
StringTokenizer st =new StringTokenizer(someString);
Після створення об'єкта можна отримати загальну кількість лексем за допомогою методу countTokens()
.
Клас реалізує внутрішній "поточний вказівник", який вказує на наступне слово. Функція nextToken()
повертає
наступну лексему з рядка. Функції nextToken()
можна задати альтернативний роздільник лексем як параметр.
За допомогою функції hasMoreTokens()
можна перевірити, чи є ще лексеми. У наведеному нижче прикладі
всі слова рядка виводяться в окремих рядках:
package ua.inf.iwanoff.java.second;import java.util.*;public class AllWords {public static void main(String[] args) { String s =new Scanner(System.in).nextLine(); StringTokenizer st =new StringTokenizer(s);while (st.hasMoreTokens()) { System.out.println(st.nextToken()); } } }
Більш сучасний спосіб розбиття на лексеми – використання методу split()
класу String
.
Параметр цього методу – так званий регулярний вираз, який визначає роздільники. Регулярні вирази дозволяють
визначати шаблони для рядків. Наприклад, "\\s
" – це будь-який символ-роздільник. Однак
у найпростішому випадку можна використовувати безпосередньо розділовий символ – пропуск. Наприклад:
String s = "aa bb ccc"; String[] a = s.split(" "); System.out.println(Arrays.toString(a));// [aa, bb, ccc]
2.7 Класи-обгортки
Класи Integer
, Double
, Boolean
, Character
, Float
, Byte
, Short
і Long
дозволяють
представити числові та булеві значення в об'єктах. Тому ці класи також називають класами-обгортками. Додатково
ці класи надають набір методів для перетворення арифметичних значень у представлення рядком і навпаки й інші засоби
для зручної роботи з цілими й дійсними числами.
Статичний метод Double.parseDouble()
повертає дійсне число за представленням у вигляді рядка:
String s = "1.2";double d = Double.parseDouble(s);
Аналогічно працюють функції Integer.parseInt()
, Long.parseLong()
, Float.parseFloat()
, Byte.parseByte()
, Short.parseShort()
та Boolean.parseBoolean()
.
У версії Java 5 (JDK 1.5) об'єкти типу Integer
можна ініціалізувати виразами цілого типу, використовувати
у виразах для одержання значень (автоматичне упакування / розпакування). Цілі значення (константи) можна заносити
в списки й інші контейнери. Автоматично будуть створюватися і заноситися в контейнер об'єкти типу Integer
.
У попередній версії Java (JDK 1.4) необхідно було писати:
Integer m =new Integer(10);// ініціалізуємо об'єкт типу Integer int k = m.intValue() + 1;// використовуємо значення у виразі // створюємо масив: Integer[] a = {new Integer(1),new Integer(2),new Integer(3)}; a[2] =new Integer(4);// заносимо об'єкт з новим цілим значенням // отримаємо об'єкт типу Integer з масиву і використовуємо значення: int i = a[1].intValue() + 2;
Тепер усе простіше:
Integer m = 10;// ініціалізуємо об'єкт типу Integer int k = m + 1;// використовуємо значення у виразі // створюємо масив: Integer[] a = {1, 2, 3}; a[2] = 4;// заносимо об'єкт з новим цілим значенням // отримаємо об'єкт типу Integer з масиву і використовуємо значення: int i = a[1] + 2;
Примітка: автоматичне упакування і розпакування – це операція, яка вимагає додаткових ресурсів, зокрема,
неявного створення об'єктів; тому, наприклад операція m++
для змінної m
типу Integer
у
циклі виконуватиметься вкрай неефективно.
Те ж саме стосується інших типів-обгорток. Фактично об'єкти цих можуть бути використані замість змінних відповідних
примітивних типів. Недоліком використання типів-обгорток є зменшення ефективності внаслідок додавання операцій
розміщення у динамічній пам'яті. Але перевагою є можливість використання значення null
. Наприклад,
якщо значення функції не може бути обчислене, функція може повернути null
:
package ua.inf.iwanoff.java.second;import java.util.Scanner;public class Reciprocal {// Зворотна величина: static Double reciprocal(double x) {if (x == 0) {return null ; }return 1 / x; }public static void main(String[] args) { Scanner s =new Scanner(System.in);double x = s.nextDouble(); Double y = reciprocal(x);if (y ==null ) { System.out.println("Помилка"); }else { System.out.println(y); } } }
Клас Character
– це, в першу чергу, оболонка для зберігання значення примітивного типу char
(символ)
в об'єкті. Об'єкт типу Character
містить одне поле типу char
. Крім того, цей клас
надає кілька методів для визначення категорії символу (малі літери, цифри тощо) і для перетворення символів з
верхнього регістру в нижній і навпаки.
Є можливість переводити окремий символ у верхній (або нижній) регістр:
char c1 = 'a';char c2 = Character.toUpperCase(c1);// 'A' char c3 = Character.toLowerCase(c2);// 'a'
Є функції, що дозволяють перевіряти властивості символів. Наприклад, метод Character.isLetter()
повертає true
,
якщо символ є літерою в англійській, українській, російській, китайській, німецькій, арабській або іншій мові. Нижче
наведені деякі найбільш корисні методи порівняння символів:
isDigit()
повертаєtrue
, якщо символ є цифроюisLetter()
повертаєtrue
, якщо символ є літероюisLetterOrDigit()
повертаєtrue
, якщо символ є літерою або цифроюisLowerCase()
повертаєtrue
, якщо символ є літерою в нижньому регістріisUpperCase()
повертаєtrue
, якщо символ є літерою у верхньому регістріisSpaceChar()
повертаєtrue
, якщо символ є роздільником – символом пробілу, нового рядка або символом табуляції.
2.8 Використання аргументів командного рядка
У Java можна організувати читання аргументів з командного рядка (окремі слова, набрані в командному рядку після імені головного класу). Наприклад, наведена нижче програма виводить перший аргумент командного рядка на екран:
public static void main(String[] args) { System.out.println(args[0]); }
Аргументи командного рядка у програмі представлені у вигляді масиву рядків. Для того, щоб отримати числове значення,
яке представлено рядком, використовують функції класів Integer
та Double
. У наведеному
нижче прикладі програма здійснює читання з командного рядка цілого та дійсного значень та знаходить їхню суму.
public class TestArgs {public static void main(String[] args) {int n = Integer.parseInt(args[0]);double x = Double.parseDouble(args[1]);double y = n + x; System.out.println(y); } }
Кількість аргументів, які були уведені в командному рядку, можна отримати за допомогою виразу args.length
.
Для визначення аргументів командного рядка в середовищі IntelliJ IDEA спочатку слід додати конфігурацію часу виконання
(
3 Приклади програм
3.1 Обчислення факторіалів
Припустимо, необхідно розробити функцію обчислення факторіалів (від 0 до 20 включно) з використанням допоміжного масиву (статичного поля). Під час першого виклику функції масив заповнюється до необхідного числа. Під час наступних викликів число або повертається з масиву, або обчислюється з використанням останнього числа, що зберігається у масиві, з подальшим заповненням масиву.
Необхідно також здійснити тестування функції для різних чисел, що вводяться у довільному порядку. Програма матиме такий вигляд:
package ua.inf.iwanoff.java.second;import java.util.Arrays;public class Factorial {private static long [] f =new long [30];private static int last = 0;static { f[0] = 1; }public static long factorial(int n) {if (n > last) {for (int i = last + 1; i <= n; i++) { f[i] = i * f[i - 1]; } last = n; }return f[n]; }public static void main(String[] args) { System.out.println(factorial(5)); System.out.println(Arrays.toString(f)); System.out.println(factorial(1)); System.out.println(Arrays.toString(f)); System.out.println(factorial(3)); System.out.println(Arrays.toString(f)); System.out.println(factorial(6)); System.out.println(Arrays.toString(f)); System.out.println(factorial(20)); System.out.println(Arrays.toString(f)); } }
У функції main()
виводяться значення факторіалів у довільному порядку. Можна також простежити поступову
зміну вмісту масиву f
.
3.2 Сума цифр
Припустимо, необхідно обчислити суму цифр числа. Можна запропонувати два підходи. Перший підхід використовує знаходження залишку від ділення.
package ua.inf.iwanoff.java.first;import java.util.Scanner;public class SumOfDigits {public static void main(String[] args) { Scanner s =new Scanner(System.in);int n = s.nextInt();int sum = 0;while (n > 0) { sum += n % 10; n /= 10; } System.out.println(sum); } }
Для знаходження суми цифр цілого числа можна також скористатись його представленням у вигляді рядка:
package ua.inf.iwanoff.java.first;public class SumOfDigitsUsingStrings {public static void main(String[] args) { String n = args[0];int sum = 0;for (int i = 0; i < n.length(); i++) { sum += Integer.parseInt(n.charAt(i) + ""); } System.out.println(sum); } }
3.3 Видалення зайвих пропусків
У наведеному нижче прикладі з рядка, прочитаного з першого аргументу командного рядка, видаляються зайві пропуски (залишаємо по одному).
package ua.inf.iwanoff.java.second;public class SpaceRemover {public static void main(String[] args) { System.out.println(args[0]); String s = args[0];while (s.indexOf(" ") >= 0) { s = s.replaceAll(" ", " "); } System.out.println(s); } }
Перед виконанням програми необхідне значення слід встановити в налаштуваннях конфігурації часу виконання на закладці
"To be or not to be"
отримаємо рядок
To be or not to be
3.4 Робота з масивом рядків
Припустимо, необхідно створити програму, в якій одновимірний масив заповнюється випадковими додатними цілими числами, створюється масив рядків, довжина кожного з них визначена елементами першого масиву. В рядках відповідну кількість разів повторюється певний символ, наприклад, символ підкреслення. Потім повинно здійснюватися сортування масиву за збільшенням і за зменшенням довжини рядків.
Перша реалізація передбачає використання традиційних синтаксичних конструкцій та явних циклів:
package ua.inf.iwanoff.java.second;import java.util.Random;/** * Цей клас відповідає за демонстрацію роботи з одновимірними масивами * традиційними засобами */ public class WithoutArraysClass {public static void main(String[] args) {// Константи для зручнішого визначення критерію сортування: final boolean increaseLength =true ;final boolean decreaseLength =false ;// Створення та заповнення масиву цілих чисел: final int n = 10;int [] numbers =new int [n]; Random random =new Random();for (int i = 0; i < numbers.length; i++) { numbers[i] = random.nextInt() % n + 10; } printIntArray(numbers);// Створення та заповнення масиву рядків: String[] lines =new String[n];for (int i = 0; i < numbers.length; i++) { lines[i] = "_".repeat(numbers[i]); } printStringArray(lines); System.out.println();// Сортування рядків за збільшенням та зменшенням: sort(lines, increaseLength); printStringArray(lines); System.out.println(); sort(lines, decreaseLength); printStringArray(lines); }/** * Виводить елементи масиву цілих у стандартний вихідний потік * * @param arr масив цілих, який слід вивести */ public static void printIntArray(int [] arr) { System.out.printf("[");for (int i = 0; i < arr.length - 1; i++) { System.out.printf("%d, ", arr[i]); } System.out.printf("%d]\n", arr[arr.length - 1]); }/** * Виводить елементи масиву рядків у стандартний вихідний потік * * @param arr масив рядків, який слід вивести */ public static void printStringArray(String[] arr) {for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } }/** * Сортує масив рядків за довжиною. * Використовується сортування бульбашкою * * @param arr масив для сортування * @param increaseLength повинен бути true для сортування за збільшенням * і false у протилежному випадку */ public static void sort(String[] arr,boolean increaseLength) {boolean mustSort;// повторюємо доти, доки mustSort true do { mustSort =false ;for (int i = 0; i < arr.length - 1; i++) {if (increaseLength ? arr[i].length() > arr[i + 1].length() : arr[i].length() < arr[i + 1].length()) {// Міняємо елементи місцями: String temp = arr[i]; arr[i] = arr[i + 1]; arr[i + 1] = temp; mustSort =true ; } } }while (mustSort); } }
Друга реалізація побудована на використанні функцій класу Arrays
:
package ua.inf.iwanoff.java.second;import java.util.Arrays;import java.util.Random;/** * Цей клас відповідає за демонстрацію роботи з одновимірними масивами * за допомогою методів класу Arrays і лямбда-виразів */ public class WithArraysClass {public static void main(String[] args) {// Створення та заповнення масиву цілих чисел: final int n = 10;int [] numbers =new int [n]; Random random =new Random(); Arrays.setAll(numbers, i -> random.nextInt() % n + 10); System.out.println(Arrays.toString(numbers));// Створення та заповнення масиву рядків: String[] lines =new String[n]; Arrays.setAll(lines, i -> "\n" + "_".repeat(numbers[i])); System.out.println(Arrays.toString(lines));// Сортування рядків за збільшенням та зменшенням: Arrays.sort(lines);// рядки, що містять однакові символи, сортуються за довжиною System.out.println(Arrays.toString(lines)); Arrays.sort(lines, (s1, s2) -> -Integer.compare(s1.length(), s2.length())); System.out.println(Arrays.toString(lines)); } }
Як видно, друга реалізація значно більш прозора й компактна. Додаткові символи переведення рядків забезпечують більш наочне виведення в стандартний потік.
Недоліком реалізації з застосуванням можливостей класу Arrays
є неможливість керування формою і процесом
виведення елементів масиву в консольне вікно. Цей недолік можна подолати через використання так званих потоків
даних (Stream API), які будуть розглянуті пізніше.
3.5 Знаходження мінімальних значень у стовпцях двовимірного масиву
Припустимо, необхідно створити двовимірний масив цілих чисел, заповнити його випадковими значеннями у визначеному
діапазоні, а потім знайти й заповнити одновимірний масив мінімальними значеннями серед елементів стовпців. Бажано
використовувати функції класу Arrays
замість явних циклів. Програма може бути такою:
package ua.inf.iwanoff.java.second;import java.util.Arrays;import java.util.Random;/** * Клас демонструє можливість отримання мінімальних значень стовпців * у двовимірному масиві */ public class TwoDArrayDemo {public static void main(String[] args) {// Розміри масиву: final int m = 3;final int n = 4;// Діапазон можливих значень елементів: final int from = 3;final int to = 8;// Створення та заповнення масиву: int [][] arr =new int [m][]; Random random =new Random(); Arrays.setAll(arr, i -> fillRow(random, n, from, to)); System.out.println(Arrays.deepToString(arr));// Отримання масиву мінімальних значень int [] mins =new int [n]; Arrays.setAll(mins, j -> getMinInColumn(arr, j)); System.out.println(Arrays.toString(mins)); }/** * Створює масив цілих чисел і заповнює його значеннями із заданого діапазону * * @param random current Random type object used for filling row * @param size довжина масиву * @param from ліва межа діапазону * @param to права межа діапазону * @return масив цілих чисел із випадковими значеннями */ public static int [] fillRow(Random random,int size,int from,int to) {int [] result =new int [size]; Arrays.setAll(result, j -> Math.abs(random.nextInt() % (to - from)) + from);return result; }/** * Обчислює мінімальне значення елементів стовпця у двовимірному масиві * * @param arr вихідний масив * @param j індекс стовпця * @return мінімальне значення */ public static int getMinInColumn(int [][] arr,int j) {int [] column =new int [arr.length]; Arrays.setAll(column, i -> arr[i][j]); Arrays.sort(column);return column[0]; } }
Для знаходження мінімального елементу в одновимірному масиві (стовпці) здійснюється його сортування за збільшенням. Нульовий елемент такого масиву буде мінімальним.
3.6 Класи для представлення країни та перепису населення
Припустимо, необхідно спроєктувати програму, в якій описуються класи для представлення країни та перепису населення. Дані про країну – назва, територія, а також масив посилань на об'єкт типу "Перепис населення".
Своєю чергою, клас, який представляє перепис населення, повинен включати дані про рік перепису, кількість населення та коментарі.
Необхідно реалізувати такі функції для роботи з переписами:
- обчислення щільності населення згідно з переписом певного року;
- визначення перепису з найбільшою кількістю населення;
- перевірка наявності певного слова в коментарі;
- перевірка наявності певної послідовності літер у коментарі;
Окрім того, класи повинні містити конструктори та методи доступу.
Оскільки в класі для представлення країни повинен міститись масив посилань на переписи населення, доцільно почати
з класу Census
(перепис населення). Створюємо новий проєкт, наприклад з назвою Countries
.
Додаємо новий клас Census
у пакеті ua.inf.iwanoff.java.second
. Далі додаємо до його опису поля year
, population
(цілі)
та comments
(рядок):
package ua.inf.iwanoff.java.second;/** * Клас відповідає за представлення перепису населення. * Перепис населення представлено роком, кількістю населення та коментарем */ public class Census {private int year;private int population;private String comments;/** * Конструктор ініціалізує об'єкт усталеними значеннями */ public Census() { }/** * Конструктор ініціалізує об'єкт вказаними значеннями * * @param year рік перепису * @param population кількість населення * @param comments текст коментаря */ public Census(int year,int population, String comments) {this .year = year;this .population = population;this .comments = comments; }/** * Повертає рік перепису * @return рік перепису у вигляді цілого значення */ public int getYear() {return year; }/** * Встановлює значення року перепису * @param year рік перепису у вигляді цілого значення */ public void setYear(int year) {this .year = year; }/** * Повертає кількість населення * @return кількість населення у вигляді цілого значення */ public int getPopulation() {return population; }/** * Встановлює кількість населення * @param population кількість населення у вигляді цілого значення */ public void setPopulation(int population) {this .population = population; }/** * Повертає рядок коментаря до перепису * @return коментар перепису у вигляді рядка */ public String getComments() {return comments; }/** * Встановлює вміст рядка коментаря до перепису * @param comments коментар перепису у вигляді рядка */ public void setComments(String comments) {this .comments = comments; } }
Для роботи з класом необхідно також реалізувати функції перевірки наявності слова і послідовності літер. Ці функції
можна було б зробити методами класу Census
, але в цьому випадку клас
відповідатиме не тільки за представлення даних, але й за додаткові обчислення, пов'язані з об'єктом класу, що є
порушенням принципу єдиної відповідальності (Single Responsibility Principle, SRP) – одному з фундаментальних
принципів ООП. Такий клас фактично буде створено під конкретну задачу. Додавати нові обчислення доведеться, редагуючи
сирцевий код, що є порушенням принципів об'єктно-орієнтованого програмування, або через механізм успадкування, що
без додавання нових даних у більшості випадків не має сенсу.
Для того, щоб клас відповідав SRP, функції перевірки
доцільно винести в окремий клас. Ці функції можна визначити як статичні. Окремий клас CensusUtilities
може
бути таким:
package ua.inf.iwanoff.java.second;import java.util.Arrays;/** * Надає статичні методи для пошуку даних в коментарі */ public class CensusUtilities {/** * Перевіряє, чи міститься слово в тексті коментаря до перепису * @param census посилання на перепис * @param word слово, яке ми шукаємо в коментарі * @return {@code true}, якщо слово міститься в тексті коментаря * {@code false} в протилежному випадку */ public static boolean containsWord(Census census, String word) { String[] words = census.getComments().split("\\s"); Arrays.sort(words);return Arrays.binarySearch(words, word) >= 0; }/** * Перевіряє, чи міститься підрядок в тексті коментаря * @param census посилання на перепис * @param substring підрядок, який ми шукаємо в коментарі * @return{@code true}, якщо підрядок міститься в тексті коментаря * {@code false} в протилежному випадку */ public static boolean containsSubstring(Census census, String substring) {return census.getComments().toUpperCase().contains(substring.toUpperCase()); }/** * Допоміжна статична функція додавання посилання на перепис * до наданого масиву переписів * @param arr масив, до якого додається перепис * @param item посилання, яке додається * @return оновлений масив переписів */ public static Census[] addToArray(Census[] arr, Census item) { Census[] newArr;if (arr !=null ) { newArr =new Census[arr.length + 1]; System.arraycopy(arr, 0, newArr, 0, arr.length); }else { newArr =new Census[1]; } newArr[newArr.length - 1] = item;return newArr; } }
В окремий клас CensusDemo
виносимо функції для демонстрації можливостей роботи з об'єктом типу Census
:
package ua.inf.iwanoff.java.second;import static ua.inf.iwanoff.java.second.CensusUtilities.*;/** * Програма тестування можливості перевірки * наявності слова або підрядка */ public class CensusDemo {/** * Виводить на екран результати перевірки, чи міститься слово або підрядок * в тексті коментаря * @param census посилання на перепис * @param word слово або підрядок, для якого здійснюється перевірка */ private static void testWord(Census census, String word) {if (containsWord(census, word)) { System.out.println("Слово \"" + word + "\" міститься у коментарі"); }else { System.out.println("Слово \"" + word + "\" не міститься у коментарі"); }if (containsSubstring(census, word)) { System.out.println("Текст \"" + word + "\" міститься у коментарі"); }else { System.out.println("Текст \"" + word + "\" не міститься у коментарі"); } }/** * Здійснює тестування перевірки наявності слова або підрядка * в коментарі до перепису * @param census посилання на перепис */ public static void testCensus(Census census) { census.setYear(2001); census.setPopulation(48475100); census.setComments("Перший перепис у незалежній Україні"); testWord(census, "Україні"); testWord(census, "Країні"); testWord(census, "Україна"); }/** * Програма тестування можливості перевірки * наявності слова або підрядка * @param args аргументи командного рядка (не використовуються) */ public static void main(String[] args) { testCensus(new Census()); } }
Тепер ми можемо використати створені класи для реалізації основного завдання. Створюємо клас Country
:
package ua.inf.iwanoff.java.second;import java.util.Arrays;/** * Клас для представлення країни, в якій здійснюється перепис населення. * Країна характеризується назвою, площею та масивом переписів */ public class Country {private String name;private double area;private Census[] censuses;/** * Повертає назву країни * @return рядок - назва країни */ public String getName() {return name; }/** * Встановлює назву країни * @param name рядок - назва країни */ public void setName(String name) {this .name = name; }/** * Повертає територію країни * @return територія країни у вигляді числа з рухомою крапкою */ public double getArea() {return area; }/** * Встановлює територію країни * @param area територія країни у вигляді числа з рухомою крапкою */ public void setArea(double area) {this .area = area; }/** * Повертає посилання на перепис населення, * визачений його індексом у масиві * @param i номер (індекс) перепису * @return посилання на перепис за індексом */ public Census getCensus(int i) {return censuses[i]; }/** * Встановлює посилання на новий перепис у масиві за вказаним індексом. * @param i номер (індекс) позиції в масиві * @param census посилання на новий перепис */ public void setCensus(int i, Census census) { censuses[i] = census; }/** * Додає посилання на новий перепис в кінець масиву * @param census посилання на новий перепис */ public void addCensus(Census census) { setCensuses(CensusUtilities.addToArray(getCensuses(), census)); }/** * Створює новий перепис та додає посилання на нього в кінець масиву. * @param year рік перепису * @param population кількість населення * @param comments текст коментаря */ public void addCensus(int year,int population, String comments) { Census census =new Census(year, population, comments); addCensus(census); }/** * Повертає кількість переписів у масиві * @return кількість переписів */ public int censusesCount() {return censuses.length; }/** * Очищує масив переписів */ public void clearCensuses() { censuses =null ; }/** * Повертає масив переписів з внутрішнього масиву * @return масив посилань на переписи */ public Census[] getCensuses() {return censuses; }/** * Переписує дані з масиву переписів у внутрішній масив * @param censuses довільний масив переписів */ public void setCensuses(Census[] censuses) {this .censuses = censuses; } }
Окремо реалізуємо необхідну функціональність у класі CountryUtilities
:
package ua.inf.iwanoff.java.second;import static ua.inf.iwanoff.java.second.CensusUtilities.*;/** * Надає статичні методи для пошуку переписів */ public class CountryUtilities {/** * Повертає густоту населення для вказаного року * @param country посилання на країну * @param year рік (наприклад, 1959, 1979, 1989 тощо) * @return густота населення для вказаного року */ public static double density(Country country,int year) {for (int i = 0; i < country.censusesCount(); i++) {if (year == country.getCensus(i).getYear()) {return country.getCensus(i).getPopulation() / country.getArea(); } }return 0; }/** * Знаходить і повертає рік з максимальним населенням * @param country посилання на країну * @return рік з максимальним населенням */ public static int maxYear(Country country) { Census census = country.getCensus(0);for (int i = 1; i < country.censusesCount(); i++) {if (census.getPopulation() < country.getCensus(i).getPopulation()) { census = country.getCensus(i); } }return census.getYear(); }/** * Створює та повертає масив переписів зі вказаним словом в коментарях * @param country посилання на країну * @param word слово, яке відшукується * @return масив переписів зі вказаним словом в коментарях */ public static Census[] findWord(Country country, String word) { Census[] result =null ;for (Census census : country.getCensuses()) {if (containsWord(census, word)) { result = addToArray(result, census); } }return result; } }
Для демонстрації роботи нам також потрібні функції для представлення даних у вигляді рядка для подальшого виведення
цих даних на консоль. Розташування цих функцій в окремому класі дозволить, якщо треба, спільно керувати форматуванням
та мовою виведення результатів. Клас StringRepresentations
може бути таким:
package ua.inf.iwanoff.java.second;/** * Клас, який дозволяє отримувати представлення * у вигляді рядків різних об'єктів застосунку */ public class StringRepresentations {/** * Надає подання перепису у вигляді рядка * * @param census посилання на перепис * @return подання перепису у вигляді рядка */ public static String toString(Census census) {return "Перепис " + census.getYear() + " року. Населення: " + census.getPopulation() + ". Коментар: " + census.getComments(); }/** * Надає подання країни у вигляді рядка * * @param country посилання на країну * @return подання країни у вигляді рядка */ public static String toString(Country country) { StringBuilder result =new StringBuilder(country.getName() + ". Територія: " + country.getArea() + " кв. км.");for (int i = 0; i < country.censusesCount(); i++) { result.append("\n").append(toString(country.getCensus(i))); }return result + ""; } }
Клас CountryDemo
, який представляє основну програму, буде таким:
package ua.inf.iwanoff.java.second;import static ua.inf.iwanoff.java.second.CountryUtilities.*;/** * Програма тестування можливості роботи з країною */ public class CountryDemo {/** * Допоміжна функція створення нового об'єкта "Країна" * @return посилання на новий об'єкт "Країна" */ public static Country createCountry() { Country country =new Country(); country.setName("Україна"); country.setArea(603628);// Додавання переписів: country.addCensus(1959, 41869000, "Перший перепис після другої світової війни"); country.addCensus(1970, 47126500, "Нас побільшало"); country.addCensus(1979, 49754600, "Просто перепис"); country.addCensus(1989, 51706700, "Останній перепис радянських часів"); country.addCensus(2001, 48475100, "Перший перепис у незалежній Україні");return country; }/** * Виводить на екран дані про переписи, які містять певне слово в коментарях * @param country посилання на країну * @param word слово, яке відшукується */ public static void printWord(Country country, String word) { Census[] result = findWord(country, word);if (result ==null ) { System.out.println("Слово \"" + word + "\" не міститься в коментарях."); }else { System.out.println("Слово \"" + word + "\" міститься в коментарях:");for (Census census : result) { System.out.println(StringRepresentations.toString(census)); } } }/** * Здійснює тестування методів * @param country посилання на країну */ public static void testCountry(Country country) { System.out.println("Щільність населення у 1979 році: " + density(country, 1979)); System.out.println("Рік з найбільшим населенням: " + maxYear(country) + "\n"); printWord(country, "перепис"); printWord(country, "запис");} /** * Демонстрація роботи програми * @param args аргументи командного рядка (не використовуються) */ public static void main(String[] args) { testCountry(createCountry()); } }
4 Вправи для контролю
- Увести з клавіатури кількість елементів та елементи одновимірного масиву цілих чисел. Відсортувати масив за збільшенням елементів.
- Увести з клавіатури рядок (
String
), змінити порядок символів на зворотний та вивести рядок на екран. - Увести рядок, видалити літеру "а", вивести рядок на екран.
- Проініціалізувати одновимірний масив рядків. Відсортувати масив за алфавітом у зворотному порядку.
- Створити клас з конструктором для опису точки в тривимірному просторі.
- Створити клас з конструктором для опису товару (зберігаються назва та ціна).
- Створити клас з конструктором для опису користувача (зберігаються ім'я та пароль).
5 Контрольні запитання
- Чим відрізняється посилання Java від указівника C++?
- Чим типи-посилання відрізняються від типів-значень?
- Як у Java здійснюється розіменування?
- Що є результатом присвоєння одного посилання іншому?
- Як у Java видалити об'єкт, який було створено за допомогою
new
? - У чому полягає "збирання сміття"?
- Чи можна використовувати змінні для визначення довжини масиву?
- Чи можна змінити розміри масиву за допомогою поля
length
? - Як додати новий елемент у кінець масиву?
- Як визначити кількість стовпців двовимірного масиву?
- Чи можна створити двовимірний масив з різною довжиною рядків?
- Чим відрізняється застосування двох різних конструкцій
for
для обходу елементів масиву? - Чим визначається розмір масиву, який створюється функцією
arraycopy()
? - Чи можна за допомогою
arraycopy()
скопіювати частину масиву? - Як здійснити читання масиву з клавіатури?
- У які способи можна заповнити елементи масиву без циклу?
- Як без циклу встановити, що елементи масивів збігаються?
- Чи можна без циклу відсортувати частину масиву?
- Чи дозволяє функція
binarySearch()
здійснити пошук у невідсортованому масиві? - Чи можна змінити значення елементів масиву за допомогою функції?
- Як у Java створити функцію зі змінною кількістю аргументів?
- З яких основних елементів складається опис класу?
- Чи завжди необхідно явно ініціалізувати поля класу?
- Чи можна в Java поза класом реалізовувати методи, оголошені всередині класу?
- Чим відрізняються статичні та нестатичні елементи класу?
- Як здійснюється ініціалізація статичних даних?
- Де може бути розташована конструкція ініціалізації?
- Як у Java визначається дружній доступ до елементів класу?
- Як встановити рівень доступу для групи елементів?
- У чому полягає зміст інкапсуляції та як вона реалізована в Java?
- Як можна використовувати посилання
this
? - Як викликати конструктори з інших конструкторів?
- Скільки конструкторів без параметрів може бути створено в одному класі?
- Як створити клас, у якому немає жодного конструктора?
- Чому в Java немає деструкторів?
- Коли викликається метод
finalize()
? - У яких випадках доцільно використовувати композицію класів?
- Чи можна в Java цілком розмістити один об'єкт усередині іншого об'єкта?
- Чи можна змінити вміст раніше створеного рядка?
- Як змінити конкретний символ у рядку?
- У чому є недоліки й переваги класу
StringBuilder
у порівнянні з класомString
? - Як перевести число в його рядкове представлення і навпаки?
- Які є недоліки й переваги застосування об'єктів класу
Integer
замість змінних типуint
?