You are on page 1of 258

Департамент освіти, науки та молодіжної політики

Івано-Франківської обласної державної адміністрації

Івано-Франківський обласний інститут післядипломної педагогічної


освіти

Авторська творча майстерня вчителів інформатики

ОБ’ЄКТНО-ОРІЄНТОВАНЕ ПРОГРАМУВАННЯ МОВОЮ JAVA


НАВЧАЛЬНИЙ ПОСІБНИК У 2 ЧАСТИНАХ
Частина 2. 11 клас

Івано-Франківськ
2017
Автори:
Г. Двояк, О. Мішагіна, С. Дерюга, О. Рибак, Л. Борисевич, М. Чижик, І. Новак, О.
Новосельський, А. Поліщук, С. Зітинюк, В. Москалик, Т. Багрій, І. Матлюк, Т.
Лужний
Координатор АТМ:
Стрільчик Г. Д., завідувач лабораторії педагогічного досвіду, старший викладач
кафедри менеджменту та освітніх інновацій Івано-Франківського обласного
інституту післядипломної педагогічної освіти
Консультанти:
Іщеряков С. М., кандидат технічних наук, доцент, директор Центру підготовки
сертифікованих програмістів;
Малий П. М., методист лабораторії природничо-математичних дисциплін Івано-
Франківського обласного інституту післядипломної педагогічної освіти
Рецензенти:
Мельничук С. І., доктор технічних наук, доцент, завідувач кафедри
інформаційних технологій Прикарпатського національного університету імені
Василя Стефаника
Кузь М. В., доктор технічних наук, доцент, завідувач кафедри інформаційних
технологій та програмної інженерії Івано-Франківського Університету Короля
Данила
Воробей О. В., учитель інформатики загальноосвітньої школи І-ІІІ ступенів №21
Івано-Франківської міської ради, спеціаліст вищої категорії, учитель-методист

Навчальний посібник орієнтований на реалізацію змістової лінії


«Алгоритмізація та програмування» навчальної програми для вивчення
інформатики в класах інформаційно-технологічного профілю (автор С. М.
Іщеряков). Посібник складається з двох частин: «10 клас» (частина 1) та «11 клас»
(частина 2).
У частині 2 розглядаються розширені Java-технології: створення графічного
інтерфейсу, використання регулярних виразів, фреймворк колекцій, технологія
Generics, розробка Android-проектів, проектування баз даних, основи багато
потоковості та JavaScript.
Автори посібника пропонують розгорнутий теоретичний матеріал, який є
компіляцією інформації з різних джерел, апробований учителями шкіл області у
класах інформаційно-технологічного профілю. Цей матеріал доповнює велика
кількість прикладів – кодів працюючих програм. В кінці більшості тем містяться
тести для перевірки знань теоретичних основ мови Java, зміст тестів орієнтований
на підготовку учнів до сертифікаційного екзамену Oracle. Також автори
пропонують достатньо практичних завдань для самостійного виконання.
Посібник призначений для учнів, що навчаються в класах інформаційно-
технологічного профілю. Він стане корисним і для тих, хто вирішив самостійно
оволодіти основами об‘єктно-орієнтованого програмування мовою Java та бажає
пов‘язати своє майбутнє з ІТ-галуззю.
ПЕРЕДМОВА

Багато років поспіль Java є лідером серед сучасних об‘єктних мов


програмування завдяки своїй надійності, незалежності від платформи та наявності
засобів захисту. Ця мова надає засоби, що дозволяють вирішувати задачі
підвищеної складності – забезпечувати взаємодію в мережі, управління базами
даних та підтримувати виконання і взаємодію декількох потоків.
Навчальний посібник «Об‘єктно-орієнтоване програмування мовою Java»
допоможе вам ознайомитись з основами, перевагами та тонкощами цієї мови. В
частині 2 посібника розглядаються розширені Java-технології. Вона містить
чотири розділи.
В першому розділі описуються принципи роботи з файлами, створення
графічного інтерфейсу засобами JavaFX, технологія регулярних виразів,
використання enum.
В другому розділі ви ознайомитесь з широким вибором колекцій в Java та
специфікою їх використання, а також цікавою технологією Generics.
Розділ третій насичений матеріалом, що допомагає розробляти Android-
додатки, проектувати бази даних та розбиратись в багатопотоковості.
Четвертий розділ містить опис основ паралельного API, шаблонів
проектування, використання lambda-виразів. Тут вас також очікує знайомство з
основами JavaScript.
Кожна з тем розділу містить приклади, які ілюструють теоретичні викладки і
містять пояснення для кращого розуміння коду. Також в словничок винесена
англійська термінологія, що стосується теми.
Автори даного посібника – вчителі інформатики, які постійно працюють з
учнями різного віку, знають потреби та можливості сучасних молодих людей. Весь
матеріал написаний доступною для учнів мовою, містить багато пояснень і
коментарів.
Посібник призначений для учнів 10 – 11 класів інформаційно-технологічного
профілю і є навчальним супроводом для тих, хто вчиться за програмою С. М.
Іщерякова. Потреба у Java-програмістах не зменшується протягом багатьох років і
ті з вас, для кого вибір майбутньої професії пов'язаний з ІТ-технологіями,
отримають необхідні базові знання.
Посібник також стане у нагоді вчителям інформатики загальноосвітніх шкіл
та викладачам вищих навчальних закладів, які викладають курс програмування
Java.
РОЗДІЛ 1. РОЗШИРЕНІ JAVA-ТЕХНОЛОГІЇ
ТЕМА 1. Розширені засоби введення-виведення
даних та графічного інтерфейсу
1.1. Байтові потоки. Ієрархія
Словничок
stream – потік, абстракція, яка створює або приймає інформацію
сокет – з’єднувач – назва програмного інтерфейсу для забезпечення обміну даними
між процесами. У Internet-домені сокет – це комбінація ІР-адреси і номера порту,
яка одночасно визначає окремий мережевий процес у глобальній мережі. Два
сокети, – один для хоста-одержувача, інший для хоста-відправника, – визначають
з’єднання для протоколів, орієнтованих на встановлення зв’язку, такого як TCP
created – створюється
concerning – стосовно
instantiate – створювати, реалізовувати

Байтові потоки. Ієрархія


Більшість java-програм не є текст-орієнтованими консольними програмами,
– вони є графічно-орієнтованим для кращої взаємодії з користувачем. Однак
система вводу-виводу виконує важливу роль при роботі з файлами і мережами.
Java-програми створюють потоки вводу-виводу. Потік пов‘язаний з
фізичним пристроєм за допомогою системи вводу-виводу java. Всі потоки ведуть
себе однаково, не зважаючи на те, що фізичні пристрої, до яких вони підключені,
відрізняються один від одного. Таким чином, всі класи і методи вводу-виводу
застосовуються для пристроїв різного типу (диску, клавіатури, мережевого сокета).
Потоки – це явний спосіб працювати з вводом-виводом без необхідності
вашому коду розбиратися в різниці між клавіатурою та мережею.

Вхідний потік (input stream)


Джерело даних Програма

(файл, пристрій, інша


Вихідний потік (output
програма і т.д.)
stream)
Рис. 1.1

Отже, для зчитування даних із чогось створюється один потік – вхідний, для
виводу даних кудись створюється інший потік – вихідний. Це є два різних об‘єкти,
які уособлюють ці потоки, і в кожному з них вказано, з чим вони можуть
зв‘язатися (рис 1.1).
Java визначає два типи потоків: байтові та символьні.

4
Байтові потоки надають зручні послуги для керування вводом-виводом
байтів і використовуються для зчитування та запису бінарних даних (записаних
символами 0 і 1).
Символьні потоки надають можливість керувати вводом та виводом
символів. Вони використовують кодування Unicode і, таким чином, можуть бути
інтернаціоналізовані. В деяких випадках символьні потоки більш ефективні ніж
байтові.
На найнижчому рівні увесь ввід-вивід все одно байт-орієнтований.
Символьні потоки надають більш зручні і ефективні засоби керування символами.
Для використання класів потоків необхідно імпортувати java.io.
Java-програми автоматично імпортують пакет java.lang, в якому визначено
клас System. Використовуючи деякі його методи, можна одержати поточну годину
чи певні відомості про систему. System також містить три перевизначені потокові
змінні in, out, err. Ці змінні оголошені як public, static, final в класі System. Це
означає, що вони можуть бути використані в будь-якій частині програми без
звертання до об‘єкта System.
System.out посилається на стандартний вихідний потік, по замовчуванню це
консоль.
Systеm.in використовується для зчитування з консолі.

Байтовий потік Символьний потік


Ієрархія класів байтових потоків Ієрархія класів символьних потоків
починається з двох абстрактних класів починається з двох абстрактних класів
InputStream та OutputStream. В цих Reader та Writer. В цих класах визначені
класах визначені методи read() та методи read() та write().
write(). Таблиця 1.1

Клас байтового Пояснення Клас символьного Пояснення


потоку потоку
InputStream Абстрактний клас, що Reader Абстрактний клас, що
описує потік вводу описує потік вводу
OutputStream Абстрактний клас, що Writer Абстрактний клас, що
описує потік виводу описує потік виводу
FilterInputStream Клас, що реалізує InputStreamReader Клас потоку вводу,
абстрактний клас що переводить байти
InputStream в символи
FilterOutputStream Клас, що реалізує OutputStreamWriter Клас потоку виводу,
абстрактний клас що переводить
OutputStream символи в байти
BufferedInputStream Клас буферизованого FileReader Клас потоку вводу для
потоку вводу зчитування з файлу
BufferedOutputStream Клас буферизованого FileWriter Клас потоку виводу
потоку виводу для запису в файл
FileInputStream Клас потоку вводу для BufferedReader Клас буферизованого
зчитування з файлу потоку вводу

5
FileOutputStream Клас потоку виводу BufferedWriter Клас буферизованого
для запису в файл потоку виводу
PrintStream Клас потоку виводу, PrintWriter Клас потоку виводу,
що підтримує методи що підтримує методи
print() та println() print() та println()

Консольний ввід
В Java консольний ввід виконується зчитуванням Systеm.in. Щоб одержати
символьний потік, приєднаний до консолі, потрібно помістити Systеm.in в
оболонку об‘єкта BufferedReader:
InputStreamReader br = new InputStreamReader(System.in);
Для зчитування символів використовується метод read().
Сигнатура методу:
int read()throws IOException
Приклад 1. Створити проект, в якому символ зчитується з клавіатури і виводиться
на екран.
public static void main(String[] args) throws IOException {
InputStreamReader br = new InputStreamReader(System.in);
System.out.println("Введіть символ");
inttemp = br.read();
System.out.println("Ви ввели - " + temp);
}
Результат роботи:
Введіть символ
4
Ви ввели - 52
Програма зчитує тільки один символ. Для того, щоб можна було зчитувати
декілька символів, використовують цикл:
System.out.println("Вводьте символи, для завершення вводу введіть Q");
char temp;
do {
temp = (char)br.read();
System.out.println(temp);
} while (temp != ‘Q’);

Але і це не дає того ефекту, на який сподівалися, тому що System.in є


рядково-буферизованим по замовчуванню. А це значить, що ввід відбудеться,
тільки при натисканні на клавішу Enter. І тільки тоді розпочнеться посимвольне
зчитування елементів введенного тексту. Причому, якщо після Q ввести ще текст,
то він проігнорується програмою при обробці.

Зчитування з консолі текстових рядків


Для зчитування текстових рядків використовується метод readLine() класу
BufferedReader.
Сигнатура методу
String readLine()throws IOException
Клас InputStreamReader переводить байти в символи, але дозволяє зчитати
лише один символ. Тому він вкладається в клас BufferedReader, який дозволяє

6
зчитувати рядок символів. Для цього символи вхідного потоку заносяться в буфер,
звідки зчитуються.
Приклад 2. Змінити програму так, щоб можна було зчитувати рядки:
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
System.out.println("Введіть символи");
String temp = br.readLine();
System.out.println("Ви ввели - " + temp);
Консольний ввід в JDK 5.0. Клас Scanner
Ввід можна реалізовувати за допомогою класу Scanner. На основі
стандартного потоку вводу System.in створюється об‘єкт класу Scanner, через який
реалізується введення даних з консолі.
Форма запису:
Scanner sc = new Scanner(System.in);
Деякі методи класу Scanner:
 nextLine() – зчитування рядка;
 next()– зчитування слова;
 nextInt()– зчитування цілого числа;
 nextDouble()– зчитування дійсного числа.
Робота з файлами
Для роботи з файлами використовують класи файлових потоків
FileInputStream, FileOutputStream.
Конструктори класів:
FileInputStream(String) throws FileNotFoundException
FileOutputStream throws FileNotFoundException
Виняток FileNotFoundException означає, що файл не знайдено.
Після завершення роботи з файлом його треба закрити, – для цього
використовують метод close().

Приклад 3. Фрагмент коду, в якому переписуються дані з файлу in.txt в файл


out.txt. Файл in.txt має вже існувати, файл out.txt створиться самостійно, якщо його
немає.
// об’єктні змінні, які посилатимуться на потоки
FileInputStream in = null;
FileOutputStream out = null;
/* При помилках читання/запису можуть генеруватися винятки, тож
потрібно перехопити їх. Наприклад, помилка може виникнути при
відсутності файлу first.txt у вказаному місці */
try {
/* створюється вхідний і вихідний потік, файл in.txt повинен вже
існувати, якщо out.txt не буде існувати,то буде створений при
спробі запису */
in = new FileInputStream("D:/workspace/in.txt");
out = new FileOutputStream("D:/workspace/out.txt");
int c;

/*Доки з файлу in.txt не буде прочитано всі байти,читається байт


з файлу in.txt і записується в out.txt, якщо потік не повертає -1
(не досягнено кінець файлу) */
while ((c = in.read()) != -1) {

7
out.write(c);
}
} finally { //дії,коли не знайдено файли
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
При роботі з файлами можна використовувати клас File, який працює не з
потоками, а безпосередньо з файлами. Даний клас має методи, які дозволяють
одержати інформацію про файл (права доступу, дату створення, шлях до каталогу),
а також здійснювати навігацію по ієрархії підкаталогів.

Тести
1. Якщо порівнювати класи java.io.BufferedWriter та java.io.FileWriter, яка
можливість існує тільки в одному з них?
a) закриття потоку
b) запис в потік
c) фіксація місцеположення в потоці
d) запис розділювача рядків в потоці
2. Чи створюється новий файл, якщо ви запишете команду File f = new File
(―tem.txt‖);
a) так
b) ні
3. Виберіть вірне твердження:
a) при описі файла необхідно обов‘язково вказувати повний шлях до файла
b) при створенні екземпляра типу Filе відповідний файл буде створено в
локальній файловій системі, якщо він не був до того раніше створений
c) коли об‘єкт типу File буде видалений garbage collected, то відповідний файл
буде видалено у файловій системі.
d) нічого з переліченого
4. Скільки байт інформації буде записано у текстовий файл внаслідок роботи
такого фрагменту програми:
FileOutputStream outStream = new FileOutputStream("t.txt");
String s = "мама";
for (int i=0;i<s.length();++i)
outStream.write(s.charAt(i));
outStream.close();
a) 2 bytes
b) 4 bytes
c) 8 bytes
d) 16 bytes
5. Розглянемо код:
4. File inFile = new File("infile.txt");
5. File outFile = new File("outfile.txt");
6. BufferedReader in = new BufferedReader(inFile);
7. BufferedWriter out = new BufferedWriter(new FileWriter(outFile));

8
8. String temp;
9. while ((temp = in.readLine()) != null){
10. out.write(temp);
11. }
13. in.close();
14. out.close();
Яким буде результат виконання цього коду?
a) помилка компіляції в рядку 3.
b) помилка компіляції в рядку 6.
c) код скомплюється, але в рядку 6 згенерується виняток під час виконання.
d) помилка компіляції в рядку 7.
e) код скомпілюється і виконається без помилок.

Завдання для самостійного виконання


1. Скласти програму-діалог, в якій вводиться три цілі числа і знаходиться їхня
сума.
Приклад виводу на консоль:
Введіть перше число
3
Введіть друге число
2
Введіть третє число
8
3+2+8=13
2. Скласти програму-діалог по складанню замовлення:
Доброго дня,.Що бажаєте замовити?
…Відповідь
Ще щось?
…Відповідь
Діалог продовжується до того часу, поки замовник не скаже «Ні»
Ваше замовлення: …, …, …. Дякуємо, ваше замовлення прийнято.
3. Зчитати інформацію по рядках з одного файла, записати її в масив ArrayList, а
потім з массива інформацію записати в інший файл.
4. З файла читаються рядки, в кожному з яких записано два числа, розділені
пропуском. Комп‘ютер запитує дію, яку необхідно виконати (+, *, -, /), і виводить
результат виконання даної дії.
5. Користувач з клавіатури задає номер по порядку числа з послідовності чисел у
файлі, а комп‘ютер визначає, що це за число.

1.2. Серіалізація
Словничок
serialization – серіалізація – це процес збереження об’єктів в послідовність байт;
deserialization – десеріалізація – це процес відновлення об’єкта з послідовності
байтів
created – створюється
instantiate – створювати, реалізовувати

Серіалізація та десеріалізація

9
Більшість java-програм не є текст-орієнтованими консольними програмами.
Серіалізація – це процес збереження об‘єктів в послідовність байт, а десеріалізація
– процес відновлення об‘єкта з цих байтів.
Для чого необхідна серіалізація?
1) Відомо, що Java дозволяє створювати в пам‘яті об‘єкти для
багаторазового використання. Однак всі об‘єкти існують лиш до тих пір, поки
виконується віртуальна машина, що їх створила. Було б непогано, щоб створені
об‘єкти могли існувати і поза життєвим циклом віртуальної машини.
Використовуючи серіалізацію, можна розкласти об‘єкт на байти, зберегти його і
всі його дані і потім єфективно використовувати.
2) В сучасному світі типова промислова програма буде мати багато
компонентів і буде поширена в різних системах та мережах. В Java все
представлене у вигляді об‘єктів. Якщо двом компонентам Java необхідно
спілкуватися між собою, то їм необхідно мати механізм для обміну даними. Є
декілька способів реалізувати цей механізм. Перший спосіб – розробити свій
протокол і передати об‘єкт. Але це означає, що одержувач також повинен знати
цей протокол, що ускладнює розробку компонент. Отже, має бути універсальний і
ефективний протокол передачі об‘єктів між компонентами. Саме серіалізація і
створена для цього. Компоненти Java використовують цей протокол для передачі
об‘єктів.
До появи серіалізації при зберіганні і реконструюванні об‘єктів із файлів
необхідно було слідкувати за порядком оголошення та ініціалізації полів, бо в
іншому випадку, наприклад, вага предмета могла перетворитися на висоту, що
звісно порушувало би структуру об‘єкта.
Серіалізація просто каже «зберегти цей об‘єкт та всі його змінні, крім тих
змінних, які позначені модифікатором transient»
Серіалізація насправді здійснюється двома методами: один серіалізує
об‘єкти та записує їх до потоку, другий читає потік та десеріалізує об‘єкти:
ObjectOutputStream.writeObject() // серіалізує та записує
ObjectInputStream.readObject() // читає та десеріалізує
Для того, щоб об‘єкти класу можна було серіалізувати, клас має
імплементувати інтерфейс Serializable. Це інтерфейс-маркер, в ньому не
задекларовано жодного методу, але він повідомляє серіалізуючому механізму, що
клас може бути серіалізованим.
Приклад 1. Створити об‘єкт Cat (без параметрів), серіалізувати та десеріалізувати
його.
public class serializaciaTemp {
public static void main(String[] args) throws Exception{
Cat c = new Cat ();
//створення потоку для серіалізації
//створення об’єкту класу потоку виводу для запису у файл
FileOutputStream fos = new FileOutputStream ("probaCat.txt");

/* створення об’єкту класу ObjectOutputStream, який є фільтруючим


потоком, він огортає низькорівневий потік байтів і надає потік
серіалізації, бо має відповідний метод */
ObjectOutputStream oos = new ObjectOutputStream(fos);

10
//виклик методу переведення об’єкта в байти (серіалізуємо об’єкт)
//він також записує його до файлу
oos.writeObject(c);
oos.close();

//створення потоку для десеріалізації


FileInputStream fis = new FileInputStream("probaCat.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
//кастинг необхідний, бо ми зчитуємо об’єкти, а не Cat
c = (Cat)ois.readObject();
ois.close();
}
}
сlass Cat implements Serializable{}

Збереження об’єктів, що містять поля-посилання на інші об’єкти


Що насправді означає збереження об‘єкта? Якщо змінні екземпляра є
виключно примітивами, це здійснюється доволі просто. А якщо змінні екземпляра
є посиланнями на об‘єкти? Що дасть збереження? В Java немає змісту зберігати
дійсні значення змінної посилання, тому що значення java-посилання означає
тільки внутрішній зміст єдиного екземпляра JVM. Iншими словами, посилання є
зайвим при намаганні зберегти об‘єкт у іншому екземплярі JVM, навіть якщо
виконання здійснюється на тому самому комп‘ютері, де відбувалась серіалізація.
А якщо об‘єкт посилається на інший об‘єкт?
Якщо клас, на який здійснюється посилання, також є серіалізованим, то
ніяких проблем не виникає.
При серіалізації об‘єкта Java турбується про збереження всіх об‘єктів у
цілому «графі об‘єкта». Це означає глибоке копіювання всіх об‘єктів, необхідних
для збереження. Наприклад, якщо клас Girl має поле типу Flower, то при
серіалізації об‘єкта Girl об‘єкт Flower буде серіалізовано автоматично. A якщо клас
Flower містить посилання на інші об‘єкти, то цей об‘єкт також буде серіалізовано
тощо. Єдиним об‘єктом, про який слід потурбуватись для збереження та
відновлення ,є Girl. Інші об‘єкти, необхідні для відновлення Girl, зберігаються та
відновлюються автоматично при серіалізації.
Створення серіалізованого об‘єкта реалізацією інтерфейсу Serializable є
усвідомленим вибором.
Приклад 2. Для того, щоб об‘єкти вкладених класів серіалізувались, потрібно, щоб
вони імплементували Serializable. Нижченаведений код демонструє успішну
серіалізацію.
public class Girl_with_flower {
public static void main(String[] args) throws Exception{
Flower f1 = new Flower(20);
Girl g1 = new Girl(80, f1);

FileOutputStream fos = new FileOutputStream("fGirlFlower.txt");


ObjectOutputStream oos = new ObjectOutputStream (fos);
oos.writeObject(g1);
oos.close();

FileInputStream fis = new FileInputStream("fGirlFlower.txt");

11
ObjectInputStream ois = new ObjectInputStream (fis);
Girl readGirl1 = (Girl)ois.readObject();
oos.close();

System.out.println(readGirl1);
}
}

class Girl implements Serializable{


private int height;
private Flower flower;
public Girl(int height, Flower flower) {
this. height= height;
this.flower = flower;
}

public String toString (){


return "height Girl " + height+ " Flower Girl has size " +
flower.getSizeFlower();
}
}

сlass Flower implements Serializable{


private int sizeFlower;
public Flower(int size) {
this.sizeFlower = size;
}
public int getSizeFlower(){
return sizeFlower;
}
}

Збереження об’єктів, що містять поля-посилання на несеріалізований клас


Якщо Flower не є серіалізованим класом, то це може бути проблемою.
Якщо, наприклад, немає доступу до класу Flower, то не можна імплементувати для
нього Serializable. Що можна зробити? Можна створити нащадок класу Flower і
позначити його як серіалізований. Але це не завжди може бути реалізовано, бо:
1) клас Flower може бути final, тобто не допускає створення підкласів;
2) клас Flower може сам посилатися на інші несеріалізовані об‘єкти, і без
знання внутрішньої структури класу Flower не можна внести всі зміни;
3) Створення підкласів є неприпустимим через інші причини, пов‘язані з
структурою проекту.
В такому разі використовують модифікатор transient. Якщо позначити
змінну як transient, то серіалізація просто її пропустить. При десеріалізації Girl
утворить Flower із значенням null.
Як гарантувати, що при десеріалізації об‘єкта Girl його новий Flower буде
співпадати з попереднім?
Серіалізація Java має для цього спеціальний механізм – набір private
методів, які можна реалізувати у класі і які автоматично викликаються під час
серіалізації та десеріалізації. Це майже так, ніби методи є частиною інтерфейсу
Serializable, проте їх там немає. Вони є частиною спеціального контракту

12
зворотнього виклику, який система серіалізації пропонує програмісту приблизно
так: «якщо ви маєте пару методів, що абсолютно співпадають з нижченаведеною
сигнатурою, то ці методи гарантовано будуть викликані під час процесу
серіалізації та десеріалізації».
Таким чином вони є ідеальним вирішенням нашої проблеми. В цьому
випадку додається стан змінної Flower (як int) до потоку під час серіалізації.
Звісно, тоді вручну прийдеться відновити Flower при десеріалізації,
використовуючи метод додаткового зчитування int-вої величини. і використати
його для створення нового Flower для Girl.
Ці методи повинні мати сигнатуру такого вигляду:
private void writeObject(ObjectOutputStream os) {
//код для збереження змінних Flower
}
private void readObject(ObjectInputStream os) {
/* код для читання стану Flower, створення нового Flower,
призначення його до Girl
*/
}
Є деякі речі, які не можуть бути серіалізовані, тому що вони мають
особливості під час виконаня (runtime). Не можуть бути серіалізовані потоки
(streams, threads) і деякі класи, підпорядковані операційним системам.
Приклад 3. Програма, що демонструє серіалізацію та десеріалізацію об‘єкту Girl,
при умові, що об‘єкт Flower не імплементує Serializable.
class Girl implements Serializable{
private int size;
private transient Flower Flower; //не серіалізується
… … …
/*Як більшість методів введення-виведення, writeObject() може
викликати виключення.Можна або оголошувати їх, або обробляти їх,
проте краще обробляти.*/
private void writeObject (ObjectOutputStream os){
//throws IOException
try{
os.defaultWriteObject();
/*виклик цього методу із середини методу writeObject
спонукає JVM зробити нормальну саріалізацію.
Записуємо додаткову змінну цілого типу - розмір квітки
Можемо записувати всі додаткові елементи до або після
виклику методу defaultWreObject(), але при десеріалізації
їх зчитують в такому ж порядку */
os.writeInt(Flower.getSizeFlower());
}catch (Exception e){
e.printStackTrace();
}
}
private void readObject (ObjectInputStream is) throws IOException,
ClassNotFoundException{
is.defaultReadObject(); // керує нормальною десеріалізацією
Flower = new Flower(is.readInt()); //будуємо новий об’єкт,
/*зчитуючи int-ову величину, яка містить значення розміру квітки,
всі величини зчитуємо в тому ж порядку, в якому записували*/
}

13
Тести
6. Який з нижченаведених операторів, якщо його записати замість закоментованого
рядка, дозволить цьому класу бути правильно серіалізованим та десеріалізованим?
import java.io.*;
public class Foo implements Serializable {
public int x, y;
public Foo( int x, int y ) {
this.x =x; this.y = y; }
private void writeObject (ObjectOutputStream s ) throws IOException {
s.writeInt(x); s.writeInt(y) ;
}
private void readObject (ObjectInputStream s )
throws IOException,ClassNotFoundException {
// insert code here
}
}
a) s.defaultReadObject();
b) this = s.defaultReadObject();
c) y = s.readInt(); x = s.readInt();
d) x = s.readInt(); y = s.readInt();

7. Які три твердження про використання інтерфейсу java.io.Serializable є вірними?


a) об‘єкти класів, що використовують агрегацію, не можуть бути
серіалізованими.
b) об‘єкт, що серіалізований на одній JVM може бути успішно
десеріалізований на іншій JVM.
c) значення полів з модифікатором volatile можуть бути неправильно
серіалізовані та десеріалізовані.
d) значення полів з модифікатором transient втратять свої значення при
серіалізації та десеріалізації.
e) можливо серіалізувати об‘єкт, у якого супертип не реалізує
java.io.Serializable.
8. Розглянемо код:
import java.io.*;
public class Forest implements Serializable {
private Tree tree = new Tree();
public static void main(String [] args) {
Forest f = new Forest();
try{
FileOutputStream fs = new FileOutputStream("Forest.ser");
ObjectOutputStream os = new ObjectOutputStream(fs);
os.writeObject(f);
os.close();
} catch (Exception ex) { ex.printStackTrace();}
}
}
class Tree {}

Яким буде результат виконання?

14
a) помилка компіляції.
b) виняток під час виконання.
c) екземпляр Forest буде серіалізовано.
d) екземпляри Forest та Tree обидва будуть серіалізовані.
9. Розглянемо код. Яким буде результат його виконання?
import java.io.*;
public class Hotel implements Serializable {
private transient Room room = new Room();
public static void main(String[] args) {
Hotel h = new Hotel();
try {
FileOutputStream fos = new FileOutputStream("Hotel.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(h);
oos.close();
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
class Room { }
a) помилка компіляції
b) згенерується виняток під час виконання
c) екземпляр Hotel серіалізується
d) екземпляри Hotel та Room обидва серіалізуються
10. Розглянемо наступний код, вважаючи, що методи serializeBanana() та
deserializeBanana() коректно використовують серіалізацію Java:
import java io.*;
class Food implements Serializable {int good = 3;}
class Fruit extends Food {int juice = 5;}
public class Banana extends Fruit {
int yellow = 4;
public static void main(String [] args) {
Banana b = new Banana(); Banana b2 = new Banana();
b.serializeBanana(b); //assume correct serialization
b2 = b.deserializeBanana(); // assume correct
System.out.println("restore "+b2.yellow+ b2.juice+b2.good);
}
// more Banana methods go here
}
Яким буде його результат?
a) restore 400
b) restore 403
c) restore 453
d) помилка компіляції.
e) помилка під час виконання програми.

15
Завдання для самостійного виконання
1. Створити клас Computer з полями: operational_memory, типу int, winchester, типу
Winchester (клас серіалізований, має поле volumes) рrocessor, типу Processor (клас
несеріалізований, має поля clock_frequency (тактова частота) та number_of_cores
(кількість ядер)). Створити два об‘єкта класу Computer, записати їх у файл,
зчитати, присвоївши зчитані об‘єкти двом іншим змінним типу Computer, змінити
у цих об‘єктах значення поля operational_memory. Вивести інформацію про
посортовані по полю operational_memory об‘єкти на екран.

1.3. Нестандартна серіалізація


Java-серіалізація з успадкуванням (IS-A відносини)
Якщо клас є серіалізованим, то всі його підкласи будуть також
серіалізованими.
Тепер можна серіалізувати об‘єкт класу Student, який розширює клас
Person, що імплементує Serializable. Властивості батьківського класу
успадковуються підкласами, отже, якщо батьківський клас серіалізований, то
підклас також буде серіалізований.
Приклад 1. Розробити проект, в якому клас Student (int id, String name,
String course, int fee) наслідує клас Person(int id, String name), який є
серіалізованим. Створити двох студентів та записати їх у файл, зчитати з файлу
інформацію і записати її для двох нових об‘єктів типу Student.
public class СlassS {
public static void main(String[] args) throws Exception {
Student st1 = new Student(12, "Ivan", "kiber", 8000);
Student st2 = new Student(13, "Oleg", "it", 7000);

FileOutputStream fos;
ObjectOutputStream oos = null ;

} catch (IOException e) {
e.printStackTrace();
} finally{
oos.flush();
oos.close();
}

FileInputStream fis = new FileInputStream("st_ser_nas.txt ");


ObjectInputStream ois = new ObjectInputStream(fis);
Student st3 = (Student)ois.readObject();
System.out.println(st3);
Student st4 = (Student)ois.readObject();
System.out.println(st4);
}
}

class Person implements Serializable{


int id;
String name;
Person(int id, String name) {
this.id = id;

16
this.name = name;
}
}

class Student extends Person{


String course;
int fee;
public Student (int id, String name, String course, int fee) {
super(id, name);
this.course = course;
this.fee = fee;
}
public String toString(){
return"" + id + " " + name + " " + course + " " + fee;
}
}
Java серіалізація з Aggregation (HAS-A відносини)
Якщо клас має посилання іншого класу, всі посилання повинні бути
серіалізовані, інакше процес серіалізації не виконуватиметься. В такому випадку
під час виконання буде NotSerializableException.
Приклад 2.
import java.io.Serializable;
public class Student implements Serializable{
intid;
String name;
Address address;//HAS-A
public Student(intid, String name) {
this.id = id;
this.name = name;
}
}
class Address{
String addressLine,city,state;
public Address(String addressLine, String city, String state) {
this.addressLine=addressLine;
this.city=city;
this.state=state;
}
}
Оскільки Address не Serializable, то не можна серіалізувати екземпляр класу
Student. Всі об‘єкти всередині об‘єкта повинні бути Serializable.
Java-серіалізація зі статичним елементом даних
Якщо в класі є статичний елемент, то він не буде серіалізований, оскільки
static є частиною класу і не є об‘єктом.
class Employee implements Serializable{
int id;
String name;
static String company="SSS IT Pvt Ltd";//не буде серіалізовано
public Student(int id, String name) {
this.id = id;
this.name = name;
}

17
}
Java серіалізація масиву або колекції
У разі масиву або колекції, всі об‘єкти масиву або колекції повинні бути
серіалізовані. Якщо який-небудь об‘єкт не serialiizable, серіалізація буде
провалена.

Тести
11. Припустимо, що метод серіалізації Banana2() та десеріалізації Banana2() будуть
коректно використовувати серіалізацію Java:
import java.io.*;
class Food {Food() { System.out.print(‚1‛); } }
class Fruit extends Food implements Serializable {
Fruit() { System.out.print("2"); }}
public class Banana2 extends Fruit { int size = 42;
public static void main(String [ ] args) {
Banana2 b = new Banana2();
b.serializeBanana2(b); // assume correct serialization
b = b.deserializeBanana2(b); // assume correct
System.out.println(" restored " + b.size + " "); }
// more Banana2 methods
}
Яким буде результат вищенаведеного коду?
a) помилка компіляції.
b) 1 restored 42
c) 12 restored 42
d) 121 restored 42
e) 1212 restored 42
f) помилка під час виконання.

1.4. Інтерфейс Externalizable


Словничок
provide – забезпечує
custom – користувацький
access – доступ
bypass – проходить
avoid – уникнути

Іноді буває потрібно управляти процесом серіалізації. Ось деякі з причин:


1. Об‘єкт не готовий до серіалізації: його теперішній внутрішній стан в
процесі зміни.
2. Об‘єкт містить несереалізовані об‘єкти, але може перевести їх в зручну
для серіалізації форму (зберегти в масив байт або ще щось).
3. Об‘єкт хоче десеріалізувати всі свої дані, як одне ціле і/або зашифрувати
їх перед серіалізацією.
Причин виконати серіалізацію в ручному режимі може бути безліч. Але не
хотілося б втрачати всіх тих переваг, які дає стандартна серіалізація. Адже об‘єкти

18
можуть використовувати інші об‘єкти. І вони теж не зможуть бути серіалізовані,
якщо об‘єкт відмовиться від підтримки серіалізації.
На цей випадок теж є рішення – інтерфейс Externalizable. Досить замінити
інтерфейс Serializable на інтерфейс Externalizable, і клас зможе управляти
процесом серіалізації в ручному режимі.
Інтерфейс Externalizable, на відміну від Serializable, містить два методи
writeExternal та readExternal, які викликаються Java-машиною при серіалізації
об‘єкта.
Приклад.
class Cat implements Externalizable {
public String name;
public int age;
public int weight;

public void writeExternal(ObjectOutput out){


out.writeObject(name);
out.writeInt(age);
out.writeInt(weight);
}
public void readExternal(ObjectInput in){
name = (String) in.readObject();
age = in.readInt();
weight = in.readInt();
}
}

Якщо влаштовує стандартна серіалізація – просто успадковуємо клас від


інтерфейсу Serializable. Якщо не влаштовує – успадковуємо від Externalizable і
пишемо код для збереження/завантаження об‘єкта нашого класу.
Якщо клас реалізує інтерфейс Serializable або Externalizable, він вважається
серіалізованим.
Відмінності між інтерфейсами
Serializable хоч і має можливість керувати процесом серіалізації об‘єктів, які
не є серіалізовані (readObject і writeObject), але ці методи не є визначеними в
даному інтерфейсі. Програміст має самостійно не забути додати ці методи до
класів, які необхідно серіалізувати, якщо вони містять поля, які є посиланнями на
інші класи.
В класі Externalizable є два методи
writeExternal(ObjectOutput)
readExternal(ObjectInput),
які описані в інтерфейсі і програміст зобов‘заний їх роботу описати.
При записі класу до файла за допомогою інтерфейсу Serializable всі його
поля окремо записуються в потік, що не є раціональним. При десеріалізації під
об‘єкт виділяється пам‘ять, після чого його поля заповнюються значеннями з
потоку, конструктор при цьому не викликається. Це означає, що якщо в
батьківського класу (несеріалізованого) відсутній конструктор без параметрів, то
при десеріалізації дочірного класу виникне помилка, бо його конструктор навіть не
викликається.

19
При Externalizable спочатку викликається конструктор без параметрів, а
потім уже на створеному об‘єкті викликається метод readExternal, який зчитує свої
дані. Тому будь-який клас, що реалізує Externalizable, зобов‘язаний мати
конструктор без параметрів.

Тести
12. Припустимо, у вас є серіалізований клас, який імплементує java.io.Serializable
(не java.io.Externalizable). Які модифікатори доступу мають бути у методу
writeObject()?
a) public
b) protected
c) default
d) private

13. Припустимо, клас Аlpha розширює Object; клас Beta розширює Alpha; і клас
Gamma розширює Beta. З них тільки клас Gamma реалізує java.io.Serializable. Які з
наведених нижче тверджень мають бути правдивими, щоб уникнути винятку при
десеріалізації екземпляра Gamma?
a) Аlpha повинен мати конструктор без аргументів.
b) Beta повинен мати конструктор без аргументів.
c) Gamma повинен мати конструктор без аргументів.
d) не існує жодних обмежень щодо конструкторів без аргументів.

Завдання для самостійного виконання


Розробити проект, в якому створити два об‘єкти серіалізованого класу Avto
(marka, rik, cina, еngine – поле класу Еngine, яке описує потужність (power)
двигуна, клас є серіалізованим).
Клас Avto наслідує несеріалізований клас MechanicalMovingDevice з полями
color_body, type_body (колір та тип кузова).
Інформацію про них записати до файла.
Інформацію зчитати з файла, присвоїти її двом іншим об‘єктам і змінити
ціну і рік.
Посортувати одержані об‘єкти у порядку зростання ціни.
Інформацію про об‘єкти вивести на екран.

1.5. Розробка графічного інтерфейсу користувача (ГІК) за


допомогою Java FX. Основи та властивості Java FX
Словничок
stage – підмостки
node – вузол

Розробка JavaFX проходила в два етапи (І – розробка на основі мови


сценаріїв JavaFXScript, ІІ – безпосередньо на Java), внаслідок чого появився
прикладний інтерфейс API. В JavaFX підтримується мова FXML, на якій можна

20
робити розмітку користувацького інтерфейсу. Остання версія JavaFX8 входить в
комплект JDK 8.
Основні поняття
Каркас JavaFX має всі можливості Swing, але надає більш раціональний,
простий в користуванні і удосконалений,підхід до побудови ГІК.
Компоненти JavaFX містяться в пакетах, імена яких починаються з префікса
java.fx.
Центральним поняттям JavaFX є stage (підмостки), що визначає простір,
який містить сцену (те, що знаходиться в даному просторі). Підмостки є
контейнером для сцени, а сцена – контейнером для елементів, що її створюють. Ці
елементи інкапсульовані в класах Stage і Scene. Щоб створити JavaFX-додаток,
необхідно ввести хоча б один об‘єкт типу Scene в контейнер Stage.
Щоб створити сцену, необхідно всі елементи (прапорці, кнопки, перемикачі
та ін.) ввести в екземпляр класу Scene.
Окремі елементи сцени називаються вузлами (наприклад, елемент керування
екранною кнопкою). Але самі вузли можуть складатися з груп інших вузлів. У
кожного вузла може бути наслідник або породжений вузол, тоді його називають
батьківським вузлом або вузлом розгалуження. Ті вузли, що є кінцевими,
називають листками. Сукупність всіх вузлів в сцені називають графом, що
утворює дерево. В графі вузла є спеціальний вузол, який називають кореневим
(верхній і єдиний, що не має батьків).
Базовим для всіх вузлів служить клас Node.
Клас додатку і методи його життєвого циклу
JavaFX-додаток має бути підкласом класу Application. Тут визначено три
методи циклу життя, які можуть бути перевизначені в класі додатку:
 void init() викликається в той момент, коли додаток починає виконуватися,
він служить для виконання різних ініціалізацій. Але за допомогою нього не
можна будувати підмосток чи сцену. Якщо ініціалізація не потрібна, то і
перевизначати метод немає потреби;
 abstract void start(Stage головний_підмосток) викликається після методу
init(). Саме з нього починається додаток, оскільки він дозволяє побудувати
сцену. Метод абстрактний, тому повинен бути перевизначений в класі;
 void stop() викликається при закритті додатку і в ньому мають бути виконані
всі операції по очистці та закриттю. Якщо такі операції не потрібні, то
задається порожній варіант методу.
Запуск JavaFX-додатка
Для запуску автономного додатка JavaFX на виконання необхідно
викликати метод launch()
public static void launch(String ... аргументи)
Аргументи можуть бути відсутні або зчитані з командного рядка у вигляді
величини типу String.
Виклик даного методу будує сцену і викликає методи init() та start(). Вихід з
методу відбудеться тільки тоді, коли завершиться робота додатка.
Скелет JavaFX-програми
Створення скелета програми та створення підмостків і сцени

21
import javafx.application.*;
import javafx.scene.*;
import javafx.scene.layout.FlowPane;
import javafx.stage.*;
public class JavaFX_SkeletProgramu extends Application{
public static void main(String[] args) {
System.out.println("Запуск JavaFX-додатку");
launch (args);
}
public void init(){
System.out.println("В тілі методу init()");
}
public void start(Stage myStage){
/*параметр типу Stage одержує посилання на головний підмосток
додатка, де встановлюється сцена*/
System.out.println("В тілі методу start");
myStage.setTitle ("JavaFXskeletProgramu");
//задаємо заголовок помосту
/*створюємо кореневий вузол – панель компонування, в якій елементи
розташовуються в рядок, з автоматичним переходом в наступний*/
FlowPanerootNode = new FlowPane();
//створюємо сцену з кореневим вузлом і розмірами
ScenemyScene = new Scene(rootNode, 200, 300);
/*встановлюємо сцену на підмостки, та показуємо підмостки і сцену
на ньому*/
myStage.setScene(myScene);
myStage.show();
}
// перевизначаємо метод stop()
public void stop(){
System.out.println("В методі stop()");
}
}
Вивід на консоль при запуску програми на виконання:
Запуск JavaFX-додатку
В тілі методу init()
В тілі методу start
При закритті вікна додатка появиться в консолі:
В методі stop()
Зазвичай, в реальних додатках ніхто не виводить на консоль. Тут це
використано для того, щоб показати процес роботи методів.

Основні елементи керування в JavaFX


Мітка Label – відображає повідомлення.
Приклад використання:
Label myLabel = new Label(); //створюємо мітку
myLabel.setText("Мітка"); //задаємо текст
rootNode.getChildren().addAll(myLabel);//додаємо мітку на сцену
Кнопка Button – основний елемент керування.
Прапорець CheckBox – графічний компонент, який може перебувати в двох
станах – true або false і дає можливість створювати множинний вибір.

22
Перемикач RadioButton – графічний компонент, який може перебувати в
двох станах – true або false і дає можливість зробити одиночний вибір у групі.
Поле для введення тексту TextField – текстовий компонент, який дозволяє
вводити один рядок тексту.
Для кожного з цих та багатьох інших елементів визначено певні властивості,
деякі з них розглянемо в прикладах.

Компонувальники
Існує ряд панелей для компонування (розміщення елементів на сцені).
HBox – всі вузли розташовуються в один
горизонтальний рядок (рис. 1.2)
VBox – всі вузли розташовуються в один вертикальний
стовпчик. Наприклад:
VBox root = new VBox();
HBox hBox = new HBox();
hBox.getChildren().addAll(new
Button("Button#1"), new Button("Button#2")); Рис. 1.2
Slider slider = new Slider(1.0, 10.0, 4.0);
BorderPane – впорядковує вузли в верхній частині,
зліва, справа, знизу і в центрі ( рис. 1.3).
BorderPane root = new BorderPane();
root.setLeft(new Button("Left"));
root.setTop(new Button("Top"));
root.setRight(new Button("Right"));
root.setBottom(new Button("Bottom"));
root.setCenter(new Button("Center")); Рис. 1.3

FlowPane представляє потокове компонування: всі вузли розташовуються по


висоті на всю ширину вікна, якщо ж вікно зменшити, то елементи змістяться в
новий рядок (рис. 1.4).
FlowPane root = new FlowPane();
root.getChildren().add(new Button("Button #1"));
root.getChildren().add(new Button("Button #2"));
root.getChildren().add(new Button("Button #3"));
root.getChildren().add(new Button("Button #4"));
root.getChildren().add(new Button("Button #5"));
root.getChildren().add(new Button("Button
Рис. 1.4
#6"));.

GridPane – сіткове компонування у вигляді стовпчиків та рядків таблиці (рис. 1.5).


GridPane root = new GridPane();
// Для відображення сітки
root.setGridLinesVisible(true);
root.add(new Label("0x0"), 0, 0);
root.add(new Label("0x1"), 0, 1);
root.add(new Label("1x1"), 1, 1);
root.add(new Label("1x2"), 1, 2);
root.add(new Label("5x5"), 5, 5);
Рис. 1.5
Вони знаходяться в пакеті javafx.scene.layout.
Створення файлу .exe

23
Для створення ехе-файлу виконуються наступні дії:
 контекстне меню проекту / Refresh;
 контекстне меню проекту / Export / Runable JAR file / Next;
 у launch configuration вибрати необхідний файл з методом main();
 у Export destination вибрати цільову папку для збереження;
 у Library handling вибрати перший перемикач Extract… / Finish.

Застосування кнопок і подій


Більшість елементів керування генерують події, які опрацьовуються в
програмах-додатках (наприклад: кнопки, прапорці та ін.)
Базовим для подій є клас Event. Використовується його підклас
ActionEvent.
Щоб опрацювати подію, необхідно:
 зареєструвати обробника подій, який буде діяти як приймач даної події.
Коли настане подія, здійсниться виклик приймача, який реагує на подію і
виконує повернення;
 для обробки необхідно реалізувати інтерфейс EventHandler. В інтерфейсі
визначено метод handle(), який приймає в якості параметра об‘єкт події.
public class Demo extends Application{
Label response;
public static void main(String[] args) {
launch (args);
}
public void start (Stage myStage) throws Exception{
myStage.setTitle ("Демонстрація роботи кнопки");
FlowPane rootNode = new FlowPane(10, 10);
// (проміжки між елементами керування
//по горизонталі та вертикалі 10)
rootNode.setAlignment(Pos.CENTER);
myStage.setTitle ("Демонстрація роботи кнопки");
//вирівнювання елемента керування по центру
rootNode.setAlignment(Pos.CENTER);
Scene myScene = new Scene(rootNode, 300, 100);
myStage.setScene(myScene);
response = new Label("Натисніть кнопку");
Button btA = new Button("A"); //створюємо кнопки
Button btB = new Button("B");
//опрацьовуємо події від натиску кнопки А
btA.setOnAction(new EventHandler<ActionEvent>() {
publicvoid handle(ActionEvent arg0) {
btA.setText("кнопку А натиснуто");
}
});
btB.setOnAction(new EventHandler<ActionEvent>() {
publicvoid handle(ActionEvent arg0) {
btB.setText("кнопку В натиснуто");
}
});
rootNode.getChildren().addAll(response, btA, btB);
myStage.show();
}

24
public void stop(){}
}

Кнопка та деякі її властивості


public void start(StageprimaryStage) throws Exception {
primaryStage.setTitle("Властивості кнопки");
Group root = new Group();
Scene sc = new Scene(root, 300, 300, Color.LIGHTGREEN);
primaryStage.setScene(sc);
Button btn1 = new Button();
btn1.setLayoutX(20);
btn1.setLayoutY(20);
btn1.setText("Кнопка 1");
root.getChildren().add(btn1);
Button btn2 = new Button();
btn2.setLayoutX(20);
btn2.setLayoutY(120);
btn2.setText("Кнопка 2");
/*задання властивостей кнопки – похилий, 12, Arial, колір тексту,
ширина межі, заокругленість кутів. Детальніше форматування буде розгрянуто в
наступних розділах*/
btn2.setStyle("-fx-font: bolditalic 12pt Arial; "
+ "-fx-text-fill: #660000;"
+ "-fx-border-width: 3px; -fx-border-radius: 30;"
+ "-fx-background-radius: 30;");
btn2.setPrefSize(200, 30);
root.getChildren().add(btn2);
btn2.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent arg0) {
//ефект накладання для кнопки
btn1.setBlendMode (BlendMode.HARD_LIGHT);
//курсор мишки змінюється при наведенні
btn1.setCursor(Cursor.CLOSED_HAND);
}
});
primaryStage.show();
}

Прапорець (CheckBox) та його властивості


Створити проект, в якому за вибраними за допомогою CheckBox діями при натиску
на кнопку додають або множать два числа і результат виводиться в мітки поряд
public void start(StageprimaryStage) throws Exception {
primaryStage.setTitle("ur1");
Group root = new Group();
Scene sc = new Scene(root, 300, 300, Color.LIGHTGREEN);
primaryStage.setScene(sc);
Button btn1 = new Button();
btn1.setLayoutX(20);
btn1.setLayoutY(20);
btn1.setText("Обчислити");
root.getChildren().add(btn1);
CheckBoxchb1 = new CheckBox("Додати");

25
chb1.setLayoutX(80);
chb1.setLayoutY(50);
chb1.setStyle("-fx-font: bolditalic 12pt Arial;");
root.getChildren().add(chb1);
CheckBoxchb2 = new CheckBox("Помножити");
chb2.setLayoutX(80);
chb2.setLayoutY(80);
chb2.setStyle("-fx-font: bolditalic 12pt Arial;");
root.getChildren().add(chb2);
Labellb1 = new Label();
lb1.setLayoutX(220);
lb1.setLayoutY(50);
root.getChildren().add(lb1);
Labellb2 = new Label();
lb2.setLayoutX(220);
lb2.setLayoutY(80);
root.getChildren().add(lb2);
btn1.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEventarg0) {
if (chb1.isSelected()) lb1.setText("2+6="+(2+6));
if (chb2.isSelected()) lb2.setText("2*6="+(2*6));
}
});

Перемикач (RadioButton) та його властивості


Для роботи з перемикачами слід створювати групу, яка їх об‘єднує, щоб вони
працювали, взаємовиключаючи один одного.
ToggleGroup tg1 = new ToggleGroup(); //створення групи перемикачів
RadioButton rb1 = new RadioButton("1");
RadioButton rb2 = new RadioButton("2");
rb1.setToggleGroup(tg1); //додавання перемикача до групи
rb2.setToggleGroup(tg1);
root.getChildren().addAll(rb1, rb2);

Завдання для самостійної роботи


1. Змінити проект «Демонстрація роботи
кнопки» так, щоб при натиску на кнопку інша кнопка
відновлювала свій початковий напис (тобто при
натиску на кнопку В на кнопці А появлявся напис «А» і
навпаки).
2. Створити проект, у якому користувач може
вибрати собі обід (рис. 1.6):
 декілька салатів з переліку;
 один із видів гарніру;
 одну другу страву з переліку.
При натиску на кнопку обчислюється ціна
вибраного обіду. Рис. 1.6
3. Розробити програму «Тест на знання таблички
множення».

26
Розробити проект, у якому користувач по натиску на кнопку «Старт»
починає проходити тест на знання таблички множення (рис.1.7). Приклади
генеруються з випадкових чисел в межах від 1 до 9. Після введення вірної відповіді
в поле для введення (TextField) (рис. 1.8) при натиску на кнопку Enter у вікні
з‘являється повідомлення: «Ваша відповідь…., вірна відповідь ….», поле для
введення зникає, кнопка «Старт» змінює напис на «Ще раз» (рис. 1.9).
При натиску на кнопку «Оцінка» тестування припиняється, користувачу
виводиться його оцінка (оцінювання придумати самостійно), кнопка «Ще раз»
(рис. 1.10) змінює напис на «Старт» і користувач може почати тестування
спочатку.

Початкове вікно: Після натиску на кнопку «Старт»

Рис. 1.7 Рис. 1.8

Після введення відповіді Після натиску на кнопки «Ще раз»

Рис. 1.9 Рис. 1.10

3. Розробити програму, яка кодує текст, введений з клавіатури або з файла.


У шифрі Цезаря кожна i-та буква алфавіту в початковому тексті замінюється на (i-
k)-ту, де k - зміщення. Коди літер алфавіту йдуть по колу, після літери "я" йде
буква "а". Шифрований файл легко декодується при відомому k.
Шифр Цезаря
Принцип дії полягає в тому, щоб циклічно зсунути алфавіт, а ключ — це
кількість літер, на які робиться зсув.
27
Шифр Цезаря — симетричний алгоритм шифрування підстановками.
Використовувався римським імператором Юлієм Цезарем для приватного
листування (рис. 1.11)

Математична модель
Якщо присвоїти кожному символу алфавіту його порядковий номер
(нумеруючи з 0), то шифрування і
дешифрування можна виразити
формулами:
y = (x + k) mod n
x = (y - k) mod n
де y – символ відкритого тексту,
x– символ шифрованого тексту,
n – потужність алфавіту,
k – ключ. Рис. 1.11
Приклад:
Припустимо, що, використовуючи шифр Цезаря, з ключем, який дорівнює 3,
необхідно зашифрувати словосполучення «ШИФР ЦЕЗАРЯ».
Для цього зрушимо алфавіт так, щоб він починався з четвертої букви (Г).
Отже, беручи вихідний алфавіт
АБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ
і зміщуючи всі літери вліво на 3, отримуємо:
ГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯАБВ,
де Г=А, Ґ=Б, Д=В, і т. д.
Використовуючи цю схему, відкритий текст «ШИФР ЦЕЗАРЯ» перетворюємо на
«ЮЙЧУ ЩЗЇГУВ». Для того, щоб одержувач повідомлення міг відновити вихідний
текст, необхідно повідомити йому, що ключ – 3.

1.6. Мова розмітки FXML


Словничок
scriptablе – сценарій

FXML є scriptablе, заснований на мові XML (мові розмітки) для того, щоб
створювати графи об‘єктів Java. Це є зручною альтернативою побудови
користувацького інтерфейсу додатків JavaFX.
Можна використовувати FXML для створення цілої сцени чи частини сцени.
FXML дозволяє розробникам програм відділяти логіку побудови інтерфейсу від
бізнес-логіки. Якщо змінюється частина інтерфейсу програми, не потрібно
перекомпільовувати код JavaFX. Замість цього можна змінити FXML за
допомогою текстового редактора та перезапустити програму. При цьому як і
раніше використовується JavaFX для написання бізнес-логіки за допомогою мови
Java. Документ FXML є документом XML.
Графи сцени JavaFX – це ієрархічна структура об‘єктів Java. Формат XML
добре підходить для зберігання інформації, яка представляє собою певну ієрархію.
Тому використання FXML для зберігання сценарію дуже інтуїтивне.

28
Загальноприйнятим є використання FXML для побудови графіки сцени у програмі
JavaFX.
В FXML елемент XML є одним з нижчепереліченого:
 екземпляр class;
 властивість екземпляру class;
 «статична» властивість;
 «визначений» блок;
 блок коду сценарію.
Екземпляри класу можуть бути створені в FXML декількома способами.
Найбільш поширеним є спосіб через елементи оголошення екземпляру, які
створють новий екземпляр class по імені.
Якщо ім‘я тегу починається з великої букви (і це не статичний метод set), то
це є оголошенням екземпляру. Коли завантажувач FXML зустрічається з таким
елементом, то створює екземпляр класу class.
Оскільки в Java імена класів можуть бути повністю визначеними, то вони
можуть бути імпортовані з використанням інструкції обробки «імпорту» (РІ).
Наприклад:
<?import javafx.scene.control.*?>
Тут імпортуються всі класи з javafx.scene.control пакету в поточний простір
імен документа FXML.
Приклад створення мітки:
<?import javafx.scene.control.Label?>
<Label text="Hello, World!"/>
Властивість text встановлюється використанням атрибуту XML.
Атрибут fx:value використовується для ініціалізації екземплярів типів, які не
мають конструкторів по замовчуванню, наприклад, примітивних типів:
<String fx:value="Hello, World!"/>
<Double fx:value="1.0"/>
<Boolean fx:value="false"/>
Також об‘єкти можна створювати, використовуючи розробник Builder.
Інтерфейс розробника має єдиний метод build(), який відповідає за створення
об‘єкта:
public interface Builder<T> {
public T build();
}
Якщо в деякому файлі створено об‘єкт засобами FXML, то за допомогою
тегу <fx:include> можна створити цей об‘єкт. Наприклад:
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns:fx="http://javafx.com/fxml">
<children>
<fx:include source="my_button.fxml"/>
</children>
</VBox>
Якщо my_button.fxml містить:
<?import javafx.scene.control.*?>
<Button text="My Button"/>,
то граф сцени буде містити VBox – кореневий об‘єкт з однією кнопкою, як
дочірний елемент.

29
Задання властивостей екземпляру:
<Rectangle fx:id="rectangle" x="10" y="10" width="320" height="240"
fill="#ff0000"/>
Коли дана розмітка буде опрацьована, то х, у, width будуть присвоєні
відповідні значення, а значення fill буде перетворено у відповідний колір.
Приклад. Використання FXML для створення інтерфейсу користувача
Розглянемо процес створення такого вікна програми. (рис. 1.12)
1. Налаштовування проекту:
 File / New Project / FXMLApplication
 File /New / Other / JavaFX / New FXML Document /
Next /
Name файлу FXMLExample1 / Finish.
2. Текст файла
<?xmlversion="1.0"encoding="UTF-8"?>
<?importjavafx.scene.layout.AnchorPane?>
<AnchorPanexmlns:fx="http://javafx.com/fxml/1">
<?languageJavaScript?>
Рис. 1.12
<?importjavafx.scene.control.*?>
<?importjavafx.scene.layout.*?>
<VBoxfx:id="vbox"layoutX="10.0"layoutY="10.0"prefHeight="250.0"prefWidth="3
00.0"spacing="10"xmlns:fx="http://javafx.com/fxml/1"xmlns="http://javafx.co
m/javafx/2.2">
<style>
-fx-padding: 10;
-fx-border-style: solid inside;
-fx-border-width: 2;
-fx-border-insets: 5;
-fx-border-radius: 5;
-fx-border-color: blue;
</style>
<children>
<Labelfx:id="inputLbl"alignment="CENTER_LEFT"cache="true"cacheHint="SCALE"p
refHeight="30.0"prefWidth="200.0"text="Please insert Your Input
here:"textAlignment="LEFT"/>
<TextFieldfx:id="inputText"prefWidth="100.0"/>
<Buttonfx:id="okBtn"alignment="CENTER_RIGHT"contentDisplay="CENTER"mnemonic
Parsing="false"text="OK"textAlignment="CENTER"/>
<Labelfx:id="outputLbl"alignment="CENTER_LEFT"cache="true"cacheHint="SCALE"
prefHeight="30.0"prefWidth="200.0"text="Your Input:"textAlignment="LEFT"/>
<TextAreafx:id="outputText"prefHeight="100.0"prefWidth="200.0"wrapText="tru
e"/>
</children>
</VBox>
</AnchorPane>

Пояснення коду програми:


 Додавання елементів інтерфейсу користувача.
Кореневим елементом документа FXML є об‘єкт верхнього рівня в
об‘єктному графі. Об‘єктом вищого рівня наведеного вище прикладу є VBox.
Отже, кореневий елемент FXML буде:
<VBox>
</VBox>

30
Відомо, що для представлення VBox в об‘єктному графі необхідно
використовувати тег у FXML. В FXML є кілька правил, що пояснюють, що таке
ім‘я тегу. Наприклад, якщо ім‘я тегу є назвою класу,то тег створить об‘єкт цього
класу. Вищевказаний елемент створить об‘єкт класу VBox. Наведений вище код
FXML можна переписати, використовуючи повне ім‘я класу:
<javafx.scene.layout.VBox>
</javafx.scene.layout.VBox>
У JavaFX панелі компонування мають нащадків. У FXML панелі
компонування мають свої дочірні елементи. Можна додати мітку, кнопку та інші
елементи до VBox таким чином:
<children>
<Label/>
<TextField/>
<Button/>
<Label/>
<TextArea/>
</children>
Це визначає основну структуру об‘єкт-графу для нашої програми. Він
створить VBox з двома мітками (Label), текстовим полем (TextField),
багаторядковим текстовим полем (TextArea) та кнопкою (Button).
 Імпорт типів Java у FXML.
Щоб використовувати прості назви класів Java у FXML, необхідно
імпортувати класи, як і у програмах Java. Існує одне виключення: у програмах Java
не потрібно імпортувати класи з пакету java.lang, однак у FXML потрібно
імпортувати класи з усіх пакетів, включаючи пакет java.lang. Інструкція обробки
імпорту використовується для імпорту класу або всіх класів з пакету. Дані
інструкції з обробки імпортують класи VBox, Label і Button:
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
Наступні інструкції з обробки імпорту імпортують всі класи з пакетів
javafx.scene.control та java.lang:
<?import javafx.scene.control.*?>
<?import java.lang.*?>
 Налаштування властивостей у FXML.
Ви можете встановити властивості об‘єктів Java у FXML. Властивість
об‘єкта може бути встановлена у FXML, якщо оголошення властивості відповідає
конвенціям JavaBean. Назва атрибуту або ім‘я елементу властивості збігається з
іменем встановленого ресурсу. Наступний FXML створює TextField і встановлює
властивість prefWidth за допомогою атрибута:
<TextField fx:id="inputText" prefWidth="100.0"/>
 Визначення простору імен FXML.
FXML не має схеми XML. Він використовує простір імен, який потрібно
вказати, використовуючи префікс простору імен "fx". Здебільшого, аналізатор
FXML визначить імена тегів, які є класами, властивостями класів і т. д. FXML
використовує спеціальні елементи та імена атрибутів, які повинні бути позначені
префіксом простору імен "fx". Необов‘язково, але можна додати версію FXML в
URI UI імен. Аналізатор FXML перевірить, чи може він проаналізувати зазначене.

31
Наступний FXML оголошує префікс простору імен "fx".
<VBox xmlns:fx=http://javafx.com/fxml/1 xmlns="http://javafx.com/javafx/2.2">
... </VBox>
 Призначення ідентифікатора об‘єкту.
Об‘єкт, створений в FXML, може бути переданий деінде в одному
документі. Зазвичай отримуються посилання на об‘єкти інтерфейсу, створені в
FXML всередині коду JavaFX. Це можна зробити, спочатку ідентифікуючи об‘єкти
у FXML з атрибутом fx: id. Значення атрибуту fx: id є ідентифікатором для об‘єкта.
Якщо тип об‘єкта має властивість id, то відповідне значення буде встановлено для
властивості. Важливо, що кожен вузол у JavaFX має властивість id, яку можна
використовувати для позначення їх у CSS. Нижче наведено приклад визначення
атрибута fx: id для мітки.
<Label fx:id="inputLbl"/>
 Відповідний клас Java FxFXMLExample1.java
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class FxFXMLExample1 extends Application{
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws IOException{
/*створюємо FXMLLoader, за допомогою якого завантажуємо
необхідний View з ресурсів*/
FXMLLoader loader = new FXMLLoader();
/* створюємо кореневий вузол Parent (нащадок класу Node),
в нього можна додавати дочірні вузли */
Parent root;
try {
root = oader.load(getClass().getResource("FxFXMLExample1.fxml"));
} catch (IOException e) {
e.printStackTrace();
return;
}
// створюємо сцену
Scene scene = new Scene(root);
// додаємо Scene на підмостки Stage
stage.setScene(scene);
stage.setTitle("A simple FXML Example");
stage.show();
}
}
Якщо запустити програму на виконання, то з‘явиться необхідне вікно
програми.
Якщо встановлено SceneBuilder, то можна відкорегувати елементи вікна.
Для цього викликаємо контекстне меню fxml-файл / Open with SceneBuilder.

32
Додавання обробника подій
Можна встановити обробники подій для вузлів у
FXML. Встановлення обробника події схоже на
налаштування будь-яких інших властивостей. У FXML
допустимо вказувати два типи обробників подій:
 Script Event Handlers (Обробники сценаріїв
подій)
 Controller Event Handlers (Контролер обробки
подій)
Створюється обробник подій сценарію.
Значенням атрибуту є сам скрипт, наприклад: виклик
функції, одне або декілька тверджень. Рис. 1.13
Додаємо до опису кнопки у файлі fxml-обробник подій:
onAction="printOutput();"
Описуємо функцію printOutput() після опису всіх компонент:
<fx:script>
function printOutput() {
outputText.setText(inputText.getText());
}
</fx:script>
Таким чином, після натиску на кнопку «OK»
(рис.1.13), текст, введений у верхнє поле, відображається
у нижньому полі.

Завдання для самостійного виконання


Зчитати з поля для введення вираз (рис. 1.14), що
містить два цілих числа, розділених знаками «+» або «–».
Обчислити вираз. Результат вивести натисканням на
кнопку у вікно програми.
Рис. 1.14.
Для обробки події обчислення виразу в даному
випадку корисно написати новий клас:
public class MyCont implements Initializable {
//змінні, які відповідаютьid елементів у файлі fxml
@FXML
private Button myB;
@FXML
private Label myL;
@FXML
private TextField myTF;
@Override
public void initialize(URL location, ResourceBundle resources) { }
/*Коли користувач натисне на кнопку, спрацює даний обробник подій, його
назва така ж, яку дали при описі атрибуту onAction кнопки*/
public void obcVurazy (ActionEvent event) {
/*зчитає значення з текстового поля*/
String t = myTF.getText();
/*напишіть метод*/
}
}

33
У файлі fxml додати до опису кореневого вузла атрибут
fx:controller="MyCont" для зв‘язку методу обробки, який написаний у файлі
MyCont, з кнопкою.

ТЕМА 2. Розкладання рядків на лексеми.


Форматоване виведення даних
2.1. Основи рarsing, клас StringTokenizer
Словничок
parsing – поділ тексту на дискретні частини
token – лексеми, дискретні частини, які мають якесь значення
delimiters – розділювачі, символи, що розділяють лексеми
regular expression (regex, regexp) – це рядок, що описує або збігається з множиною
рядків, відповідно до набору спеціальних синтаксичних правил

Обробка тексту – це дуже часто розбір форматованого вхідного рядка.


Розбір (parsing) – це поділ тексту на набір дискретних складових, чи лексем
(token), що являють собою послідовності, які можуть мати певне семантичне
значення. Клас StringTokenizer – це лексичний аналізатор чи сканер, який є
першим кроком в процесі розбору.
Щоб використовувати даний клас, потрібно задати вхідний рядок і рядок,
що містить розділювачі. Розділювачі (delimiters) – це символи, що розділяють
лексеми. Кожний символ в рядку-розділювачі розглядається як допустимий
розділювач, наприклад, рядок «,;:» встановлює в якості розділювачів кому, крапку
з комою та двокрапку. Набір розділювачів по замовчуванню складається зі знаку
пропуску, знаку табуляції, переводу рядка, повернення каретки.
Конструктори StringTokenizer, де str – це рядок, який буде розбитий на
частини:
 StringTokenizer(String str) – розділювачем буде використовуватися
розділювач по замовчуванню;
 StringTokenizer(String str, String delimiters) – delimiters містить розділювачі;
 StringTokenizer(String str, String delimiters, boolean delimAsToken) – якщо
delimAsToken рівне true, то самі розділювачі при розборі рядка
повертаються в якості окремих лексем.
В усіх випадках, окрім третього, розділювачі не повертаються.
Створивши об‘єкт StringTokenizer, можна використовувати метод
nextToken() для виділення послідовних лексем. Метод hasMoreTokens() повертає
true до тих пір, поки існують лексеми для виділення.
Приклад. Дано текст, що складається з пар «ключ=значення», а їх послідовність
розділена «;».
title=Java;autor=Shildt;publisher=Osborn;
Вивести у консоль текст без розділювачів.
public class Demo {

34
static String in = "title=Java;autor=Shildt;" +
"publisher=Osborn;";
public static void main(String[] args) {
//розділювачами буде і «=» і «;»
StringTokenizer st = new StringTokenizer(in, "=;");
while (st.hasMoreTokens()){ //поки є лексеми
String key = st.nextToken(); //відділяємо ключ
String val = st.nextToken(); //відділяємо значення
System.out.println(key + " " + val);
}
}
Тести
14. Поділ тексту на дискретні складові це:
a) token
b) parsing
c) delimiters
15. Дано рядок коду програми:
String in = "Клас Object містить три final методи: метод wait(),
notify(), notifyall()";
StringTokenizer st = new StringTokenizer(in, ":,", true);
На які лексеми поділиться даний текстовий рядок?
a) в тексті є 7 лексем
b) кожне слово окремо
c) текст до «:», «метод wait()», «notify()», «notifyall()"
d) лексемами будуть і розділювачі

Завдання для самостійного виконання


1. Зчитати з файлу текст – речення, що складається з слів, розділених проміжками,
комами та двокрапками. Вивести на екран кожне слово окремо.

2.2. Основи технології регулярних виразів. Класи Pattern,


Matcher, Scanner
В програмуванні регулярний вираз (від англ. regular expression, скорочено
regex або regexp, а іноді ще й називають rational expression) – це рядок, що описує
множину рядків, або збігається з нею, відповідно до набору спеціальних
синтаксичних правил. Вони використовуються в багатьох текстових редакторах та
допоміжних інструментах для пошуку та зміни тексту на основі заданих шаблонів.
Багато мов програмування підтримують регулярні вирази для роботи з рядками.
Правило пошуку називається шаблоном і може використовуватися для
пошуку співпадінь в інших послідовностях.
Існують два класи, які підтримують обробку регулярних виразів: Pattern
(задає регулярний вираз) та Matcher (співставляє шаблон з послідовністю
символів). Ці класи працюють разом.
Клас Pattern створює шаблон шляхом виклику методу compile():
static Pattern compile (String pattern)

35
Метод перетворює рядок у шаблон, який можна використовувати за
допомогою класу Matcher. Метод повертає об‘єкт Pattern, який містить даний
шаблон.
Наприклад: Pattern p = Pattern.compile("a"); //що шукаємо
Після створення об‘єкта Pattern, викликається метод matcher() класу Pattern
для створення Matcher:
Matcher matcher (CharSequence str)
str – це послідовність символів, яка буде порівнюватися з шаблоном (вхідна
послідовність).
Наприклад, Matcher m = p.matcher("abaaaba"); //де шукаємо
CharSequence – інтерфейс, який визначає набір символів тільки для читання
(реалізується класом String). Таким чином ви передаєте рядок методу matcher().
Клас Matcher не має конструкторів, він створюється шляхом виклику
метода matcher(), класу Pattern. Після створення Matcher ми можемо
використовувати його методи для виконання різноманітних операцій по
співставленню шаблонів.
Методи
boolean matches() – визначає, чи співпадає послідовність символів з
шаблоном, і повертає true, якщо ж не співпадає, то повертає false. Слід пам‘ятати,
що з шаблоном має співпадати вся послідовність, а не її частина.
boolean find() – визначає, чи співпадає підпослідовність з вхідною
послідовністю і повертає true, якщо є співпадіння. Метод можна викликати
неодноразово, тому можна знайти всі послідовності, що співпадають. При кожному
виклику методу пошук починається з того місця, де закінчився попередній пошук.
String group() – повертає останню послідовність, що співпала, якщо ж
співпадіння не було – повертає IllegalStateException.
int start() – повертає індекс, починаючи з якого є співпадіння даної
підпослідовності в послідовності, якщо ж співпадіння не було – повертає
IllegalStateException.
int end() – повертає індекс, що слідує за закінченням поточної
підпослідовності, якщо співпадіння не було – повертає IllegalStateException.
String replaceAll(String newStr) – замінює кожну послідовність іншою
послідовністю. Нова обновлена послідовність повертається у вигляді рядка.
Всі методи, крім останнього, не працюють без попереднього використання
методу find().

Деякі правила створення регулярних виразів


Набір символів «як є».
Приклад 1. Дослідження методів пошуку тексту-шаблону ―ba‖ в тексті ―abaaaba‖
Pattern p = Pattern.compile("ba"); //задали шаблон пошуку
/*задаємо текст, в якому будемо здійснювати пошук*/
Matcher m = p.matcher("abaaaba");
/*виведе false, бо не повністю виконується співпадіння*/
System.out.println(m.matches());
/*виведе true, бо містить шаблон як частину тексту*/
System.out.println(m.find());

36
/*наступні методи без попереднього використання методу find()
викликають винятки*/
/*виведе 3, наступний номер символа після першого знайденого, нумерація
починається з 0*/
System.out.println(m.end());
/* виведе 1, номер позиції першого входження*/
System.out.println(m.start());
/* виведе ba, бо повертає останню послідовність, що співпала*/
System.out.println(m.group());
/*метод заміни працює без попереднього використання методу find()*/
System.out.println(m.replaceAll("11"));
/* виведе a11aa11, бо замінює всі послідовності ba на 11*/
Клас символів – набір символів в квадратних дужках []. Наприклад, [abc] –
це співпадіння будь-якого із символів або a, або b, або c.
Щоб задати інвертований набір, тобто «всі, тільки не…», використовують
символ «^» в дужках перед набором символів. Наприклад, [^abc] – всі, крім або а,
або b, або c.
Щоб задати діапазон,використовують дефіс. Наприклад [a-с], букви a, b, c
Приклад 2. Дослідження методів пошуку тексту-шаблону ―[ba]‖ та [^ba] в тексті
―abaaaba‖ та в зміненому тексті ―abaaabaсс‖
Pattern p = Pattern.compile("[ba]");
Matcher m = p.matcher("abaaaba");
System.out.println(m.matches()); //false
System.out.println(m.find()); //true
System.out.println(m.end()); //1
System.out.println(m.start()); //0
System.out.println(m.group()); //a
System.out.println(m.replaceAll("11")); //11111111111111
Символ групи – крапка «.» – співпадіння з будь-яким символом.
Використання квантифікатора. Квантифікатор вказує на кількість співпадінь:
 + співпадає з одним або більше (хоча б один або більше);
 * співпадє з нулем або більше (або немає, або є довільна кількість) ;
 ? співпадає з нулем або одним (або один, або немає взагалі).
Якщо визначити невірний вираз, то буде згенеровано виняток
PatternSyntaxException.
Пошук з використанням метасимволів (при записі в шаблон символи
екрануються ще одним бекслешем \\d)
\d - цифра,
\s - пробіл,
\w - символ слова (букви, цифри, або "_" )
Основне правило – пошук здійснюється зліва направо і одноразово використаний
символ джерела вже не може бути використаний ще раз.
Для того, щоб можна було використовувати символ джерела декілька разів,
організовують цикл, змінюючи початкову позицію для пошуку.
Практична робота
1. В тексті «ababababa» знайти кількість буквосполучень «aba».
Pattern p = Pattern.compile("aba"); // вираз
Matcher m = p.matcher("abababababa"); // джерело
boolean b = false;
int count = 0;

37
int start = 0;
while(m.find(start)==true) {
System.out.print(m.start() + " ");
count++;
start = m.start()+1;
}
System.out.println("кількість входжень: " + count);
2. Підрахувати кількість одноцифрових шістнадцяткових чисел в тексті та вивести
їх на екран.
Pattern p = Pattern.compile("0[xX][0-9a-fA-F]"); // вираз
Matcher m = p.matcher("a10x22c30Xe456f"); // джерело
boolean b = false;
int count = 0;
while(m.find()==true) {
System.out.print(m.start() + " число " + m.group());
count++;
}
System.out.println("кількість входжень: " + count);

Тести
16. Яким буде результат виконання цього фрагменту коду?
String st = "sdfsd321df848";
System.out.println(st);
Pattern p = Pattern.compile ("(\\d)+");
Matcher m = p.matcher (st);
int start;
int end;
int chuslo;
int suma = 0;
while (m.find()==true){
start = m.start();
end = m.end();
chuslo = Integer.parseInt(sb.substring(start, end));
suma=suma+chuslo;
}
System.out.println(suma);
a) 321848
b) 1169
c) 26
d) 11

17. Із запропонованого переліку виберіть вірні шаблони для пошуку в тексті дати
народження людей, записаних за правилом dd.mm.rr
a) Pattern p = Pattern.compile("\\[01][0-9].[0-9][0-9] .[0-9][0-9]");
b) Pattern p = Pattern.compile("\\.[1][0-9]\\.[0-9][0-9].[0-9][0-9]");
c) Pattern p = Pattern.compile("\\.[01][0-9]\\.[0-9][0-9]\\.[0-9][0-9]");
d) Pattern p = Pattern.compile("\\.[01][09]\\.[0-9][0-9]\\.[0-9][0-9]");
18. Що з переліченого задає пошук слова в тексті:
a) Pattern p = Pattern.compile("(\\w)+");
b) Pattern p = Pattern.compile("\\s(\\w)*");

38
c) Pattern p = Pattern.compile("\s(\w)*");
d) Pattern p = Pattern.compile("\\s(\\w)");
e) Pattern p = Pattern.compile("\\s(\\w)+");
f) Pattern p = Pattern.compile("\s(\w)");

19. Яким буде результат виконання цього фрагменту коду?


String text = "Користувач може надати класам такі імена: myProject, Project,
MyProject ";
Pattern p = Pattern.compile("\\s[A-Z]+([a-z])*");
Matcher m = p.matcher(text);
int count = 0;
while (m.find()){
int start = m.start();
String temp = m.group();
System.out.print(start + " " + temp +" ");
count++;
}
System.out.println(count);
a) 52 Project 61 My 2
b) 52 Project 61 My 2
c) 52Project61My2
d) 52 Project 61 MyProject 2
Завдання для самостійного виконання
1. Дослідити методи пошуку тексту-шаблону ―.ba‖ в тексті ―abaaaba‖
2. Дослідити методи пошуку тексту-шаблону ―.?ba‖, ―.*ba‖, ―.+ba‖в тексті
―abaaaba‖
3. Визначити, скільки разів і починаючи з яких позицій входить текст ―ab‖ в
послідовність
4. Підрахувати кількість входжень цифр і номери їх позицій в тексті
«a12c3e456f»
5. Пошук та виведення слів (без повторів) заданої довжини у запитальних
реченнях.
6. Визначення кількості розділових знаків у тексті.
7. Підрахувати кількість входжень перших трьох літер англійського
алфавіту (великих або маленьких) в тексті "cafeBABE".
8. Підрахувати кількість шістнадцяткових чисел в тексті (тобто можуть бути
одна або більше кількість цифр та букв після х. Для цього використовують
квантифікатор +, а набір можливих символів для повтору беруть у ()).
9. Припустимо, що текстовий файл містить розділений комами список всіх
назв файлів в директорії, в якій розміщено декілька важливих проектів. Необхідно
створити список всіх файлів, що починаються на proj1. При цьому, можуть бути
виявленими файли із розширеннями .txt, .java, .pdf, тощо. Який вираз regex
необхідний для знаходження цих різних proj1 файлів? Наприклад, таких:
..."proj3.txt,proj1sched.pdf,proj1,proj2,proj1.java"...

39
2.3. Клас Formatter. Форматоване виведення чисел та
грошових даних
Клас Formatter
Часто потрібно утворювати рядки, які складаються зі змінних (наприклад,
цілі числа) і тексту. String.Format ідеально підходить для цього. Правила виводу
регламентує мова форматування.
Клас Formatter – клас загального призначення для форматування. Даний
клас використовується методом print().
Formatter перетворює двійкову форму даних, що використовуються
програмою, в відформатований, читабельний текст. Він виводить відформатований
текст в цільовий об‘єкт – буфер потоку. Вміст буфера може використовуватися
програмою за необхідністю. Formatter створює буфер автоматично, але можна
створити буфер при явному створенні об‘єкта Formatter.
Клас має багато конструкторів. Найбільш широко використовується
конструктор по замовчуванню: Formatter().
Він автоматично використовує локалізацію по замовчуванню і виділяє
об‘єкт StringBuffer в якості буфера для збереження відформатованого виводу.
Методи, визначені в Formatter
void close () – закриває Formatter. Після закриття Formatter він не може
повторно використатись.
void flush () – виштовхує буфер і записується по призначенню.
Formatter format (Localeloc, StringfmtStr, Object … args) – форматує
аргументи, передані через args, відповідно до специфікаторів, які містяться в
fmtStr. Локалізація вказується в loc.
Sting toString () – повертає рядок, що одержується викликом toString(), до
цільового об‘єкта.
Специфікатори форматування
%b або %B булеве значення
%c символ
%d десяткове ціле
%h або %H хеш-код аргумента
%f десяткове з плаваючою крапкою
%o вісімкове ціле
%n символ нового рядка
%s або %S рядок
%t або %T година і дата
%x або %X шістнадцяткове ціле
%% додає знак %
Загальний вид інструкції форматування:
%[argument_index$] [flags] [width] [.precision]
conversion (рис. 1.15)
 % – спеціальний символ, що символізує
початок інструкції форматування.

40 Рис. 1.15
 [argument_index$] – ціле десяткове число, що вказує на позицію
аргумента в списку аргументів (не обов‘язкове, якщо елементи беруться
попорядку).
 [flags] – спеціальний символ для форматування. Наприклад, flags «+»
означає, що числове значення має включати знак +, flags «–» означає
вирівнювання по лівому краю, flags «,» встановлює розділювач тисяч у
цілих числах (не є обов‘язковим).
 [width] – додатнє ціле число, яке визначає мінімальну кількість символів,
які будуть виведені (не є обов‘язковим).
 [.precision] – невід‘ємне ціле десяткове число з точкою перед ним.
Використовується для обмеження кількості знаків (не є обов‘язковим).
 Conversion – символ, що вказує на те,як аргумент має бути
відформатований. Наприклад, d для цілих чисел. Є обов‘язковим
елементом.

Практичне завдання
1. Вивести на екран число, результат його ділення на 5 та остачу від ділення
(для чисел від 7 до 300 із кроком 33).
//без використання можливостей форматування
for (intx = 7; x< 300; x+=33){
System.out.println(x + " " + x/5 + " " + x%5);
}
// з використанням форматування
Formatterf = newFormatter();
for (intx = 7; x< 300; x+=33){
f.format(" %5d | %5d | %5d %n", x, x/5, x%5);
}
System.out.println(f);
2. Вивести назву товару і ціну в доларах, відповідно до зразка
Автомобіль $31 007,2

String pattern = "$###,###.###";


DecimalFormat myFormatter = new DecimalFormat(pattern);
Formatter f = new Formatter();
String t = myFormatter.format(31007.2);
f.format("%-15s"+t , "Автомобіль");
System.out.print(f);

Форматований вивід дати та часу


Специфікатор формату Перетворення
%tH Година (00 – 23)
%tI Година (1 – 12)
%tM Хвилина як десяткове ціле (00 – 59)
%tS Секунда як десяткове ціле (00 – 59)
%tL Мілісекунда (000 – 999)
%tY Рік в чотирьохзначному форматі
%ty Рік в двохзначному форматі (00 – 99)
%tB Повна назва місяця (Січень)
%tb или %th Коротка назва місяця (січ)

41
%tm Місяць в двохзначному форматі (1 – 12)
%tA Повна назва дня тижня (П‘ятниця)
%ta Коротка назва дня тижня (Пт)
%td День в двохзначному форматі (1 – 31)
%tR Таке ж як "%tH:%tM"
%tT Таке ж як "%tH:%tM:%tS"
%tr Таке ж як "%tI:%tM:%tS %Tp" (%Tp це AM або PM)
%tD Таке ж як "%tm/%td/%ty"
%tF Таке ж як "%tY–%tm–%td"
%tc Таке ж як "%ta %tb %td %tT %tZ %tY"

Практичне завдання
Вивести сьогоднішню дату і годину в різних форматах
Наприклад:
11:45:20 PM
Нд жовт. 09 23:45:20 EEST 2016
11:45
жовтня жовт. 10

Formatter f = newFormatter();
Calendar cal = Calendar.getInstance();
f.format("%tr", cal); // вивід в 12-годинному часовому форматі
System.out.println(f);

f = newFormatter();
f.format("%tc", cal); //вивід повної дати
System.out.println(f);

f = newFormatter();
f.format("%tl:%tM", cal, cal); //вивід поточної години і хвилини
System.out.println(f);

// різні способи виводу місяця


f = newFormatter();
f.format("%tB %tb %tm", cal, cal, cal);
System.out.println(f);

Завдання для самостійного виконання


1. Дано файл із списком товарів та їх цінами (ціни визначено як цілими так і
дробовими числами). Зчитати з файла інформацію і вивести її у відформатованому
вигляді у два стовпчики.
2. Вивести табличку множення
3. Вивести число  на 20-ти позиціях з 5 знаками після коми
4. Вивести до файла дані про чисельність персоналу (ос.), продуктивність (тонн),
прибуток (грн.) декількох сільськогосподарських підприємств

42
ТЕМА 3. Розширені Java-технології
3.1. Використання еnum
Словничок
enum – перерахування
definitions – визначення
Програмуючи, часто необхідно обмежити безліч допустимих значень для
деякого типу даних. Так, наприклад, день тижня може мати 7 різних значень,
місяць в році – 12, а пора року – 4. Для вирішення подібних завдань в багатьох
мовах програмування зі статичною типізацією передбачений спеціальний тип
даних – перерахування (enum). В Java перерахування з'явилося не відразу.
Спеціалізована мовна конструкція enum була введена починаючи з версії 1.5. До
цього моменту програмісти використовували інші методи для реалізації
перерахувань.
Конструкція enum
Тип enum – це тип, поля якого складаються з набору деяких констант.
Простий приклад – сторони світу; такий об'єкт буде містити 4 константи: NORTH,
SOUTH, EAST і WEST. Змінні оголошені великими буквами, тому що це
константи.
Для оголошення використовується ключове слово enum. Наприклад, нам
необхідно перерахувати дні тижня:
enum Day {SUNDAY, MONDAY, TUESDAY, WEDNESDAY,THURSDAY, FRIDAY,
SATURDAY}
Ну і простий приклад його використання:
Day weekday = Day.SUNDAY;
if (weekday == Day.SUNDAY) weekday = Day.MONDAY;
System.out.println(weekday);
В результаті виконання на консоль буде виведено MONDAY.
Перерахування – це клас
Оголошуючи enum, неявно створюється клас, похідний від java.lang.Enum.
Умовно конструкція
enum Day {...} еквівалентна
class Day extends java.lang.Enum {...}
І хоча явно успадковуватися від java.lang.Enum не дозволяє компілятор, все
ж в тому, що enum успадковується, легко переконатися за допомогою reflection:
System.out.println(Day.class.getSuperclass());
На консоль буде виведено:
class java.lang.Enum
Власне,спадкування за нас автоматично виконує компілятор Java. Надалі
клас, створений компілятором для реалізації перерахування буде називатись enum-
класом, а можливі значення перерахування – елементами enum-у.

Елементи перерахування - екземпляри enum-класу, доступні статично


Елементи enum Day {SUNDAY, MONDAY і т.д.) – це статично доступні
екземпляри enum-класу Day. Їх статична доступність дозволяє виконувати
порівняння за допомогою оператора порівняння посилань ==.

43
Приклад:
Day weekday = Day.SUNDAY;
if (weekday == Day.SUNDAY) weekday = Day.MONDAY;

Назва та порядковий номер елемента enum


Як вже було сказано раніше, будь-який enum-клас успадковує
java.lang.Enum, який містить ряд методів, корисних для всіх перерахувань, які
компілятор створює автоматично.
ordinal() – отримати значення, яке вказує позицію константи в списку
констант перерахування (порядковий номер). Порядкові значення починаються з
нуля.
System.out.println(weekday.ordinal());
name() – повертає незмінне ім‘я константи, яка викликала цей метод.
System.out.println(weekday.name());
toString() – повертає рядок String, який відображає назву об‘єкта, що
викликав його.
System.out.println(weekday.toString());
valueOf (String name) – повертає елемент enum-а з назвою name.
String name = "SUNDAY";
weekday = Day.valueOf(name);
System.out.println(weekday);
Якщо в метод valueOf передати значення, якого нема в переліку констант,
то виникне виняток часу виконання IllegalArgumentException.
String name2 = "SUN";
weekday = Day.valueOf(name);
System.out.println(weekday);
values () – повертає масив з елементів перерахування.
З допомогою циклу можна вивести на консоль увесь список enum-а
Day [] array = Day.values();
for (Day s : array) {
System.out.println(s);
}
Додавання власних методів в enum-клас
Перерахування мають багато чого спільного зі звичайними класами: вони
можуть оголошувати всередині тіла конструктори, методи і поля (в цьому випадку
після оголошення останньої константи потрібно поставити «;»).
Синтаксис мови вимагає, щоб константи оголошувалися до інших полів і
методів.
Конструктор enum-у при створенні об‘єкта явно викликати не можна, його
викликають при оголошенні константи. Крім конструкторів, можна в enum-і
створювати свої методи, можна перевизначати методи для кожного елемента. Для
цього потрібно створити тіло елемента.
Приклад.
package enum_Week;
enum Week{
MONDAY(2,0),
TUESDAY(0,3),
WEDNESDAY(2){
void occupation(){

44
super.occupation();
System.out.print(" - Sport");
}
},
THURSDAY(1,1),
FRIDAY(2,2),
SATURDAY(3){
void occupation(){
super.occupation();
System.out.print(" - Sport");
}
},
SUNDAY{
void occupation(){
super.occupation();
System.out.print(" - Rest");
}
}; /* оголошуються сім констант перерахування Week, для середи,
суботи і неділі метод occupation() перевизначений */
int mathematics;
int physics;
int sport;
Week(){
}
Week(int s){
this.sport = s;
}
Week(int m, int ph){
this.mathematics = m;
this.physics = ph;
}
void occupation(){
System.out.printf("%n%-9s Occupation - %dh", name(),
mathematics+physics+sport);
}
}
public class Enum_Week {
public static void main(String[] args) {
Week []array = Week.values();
for (Week temp: array){
temp.occupation();;
}
}
}
Вивід програми:
MONDAY Occupation - 2h
TUESDAY Occupation - 3h
WEDNESDAY Occupation - 2h - Sport
THURSDAY Occupation - 2h
FRIDAY Occupation - 4h
SATURDAY Occupation - 3h - Sport
SUNDAY Occupation - 0h - Rest
Однак на перерахування накладається цілий ряд обмежень.

45
Їм заборонено:
 бути суперкласами;
 бути підкласами;
 бути абстрактними;
 створювати екземпляри, використовуючи ключове слово new.
Тести
20. Що з нижче приведеного є неправильним оголошенням enum?
a) enum Day {SUNDAY, MONDAY, TUESDAY}
b) enum Day {
SUNDAY, MONDAY, TUESDAY,
private String holiday;
}
c) enum Day {
SUNDAY, MONDAY, TUESDAY;
private String holiday;
}
d) enum Day {
private String holiday;
SUNDAY, MONDAY, TUESDAY;
}
e) enum Day {
SUNDAY, MONDAY, TUESDAY;
private String holiday;
Day(){
System.out.println("Hello");
}
}
21. Розглянемо оголошення enum:
public enum Mountain {
GOVERLA, HOMYAK, PIP_IVAN
}
Що буде результатом наступної операції ?
System.out.println(Mountain.HOMYAK.ordinal());
a) 0
b) 1
c) HOMYAK
d) 9
e) Код не буде скомпільовано
22. Дано наступне визначення enum:
public enum Mountain {
GOVERLA, HOMYAK, PIP_IVAN
}
Яким буде результат виконання даного коду?
Mountain f = Mountain. HOMYAK;
switch (f) {
case 0:
System.out.println("goverla");
case 1:

46
System.out.println("homyak");
case 2:
System.out.println("pip_ivan");
break;
default:
System.out.println("missing mountain");
}

a) goverla
b) homyak
c) pip_ivan
d) missing mountain
e) код не буде скомпільовано
23. Дано наступне оголошення enum:
enum Dean {
MMF("Олійник"), FPMI("Бондаренко"), GEO("Ковальчук");
String name;

Dean(String arg) {
name = arg;
}

String getName() {
return name;
}
}
Яким буде результат виконання цього коду?
Dean dn = Dean.valueOf("FPMI");
System.out.print(dn.ordinal());
System.out.println(" : " + dn + " : " + dn.getName());

a) 1 : FPMI : Бондаренко
b) 1 : GEO : Ковальчук
c) 1 : MMF : Олійник
d) 2 : FPMI : Бондаренко
e) 2 : GEO : Ковальчук
f) 2 : MMF : Олійник
g) 3 : FPMI : Бондаренко
h) 3 : GEO : Ковальчук
i) 3 : MMF : Олійник
24. Дано наступне оголошення enum:
enum cafe{
BIG (10),
SMALL (1),
MED(5)
int mySize = 0;
cafe(int size){
mySize = size;
}
}
Що відбудеться, коли дане оголошення винести за межі класу?

47
a) помилка компіляції
b) компіляція успішна
c) якщо доступна змінна mySize, то виникне помилка під час виконання.
Завдання для самостійного виконання
1. Створити проект area_figure (використавши enum), результатом роботи якого
буде виведення площ різних геометричних фігур.
Наприклад, вивід програми може виглядати так:
RECTANGLE = 6,00
TRIANGLE = 3,00
CIRCLE = 12,57
2. Створити проект exchange (використавши enum), результатом роботи якого
сума, задана в гривнях, буде переводитись, по наперед заданому курсу, у різні
валюти.
Наприклад, вивід програми може виглядати так:
450.0 UAN
16.898235073225685 USD
16.100178890876567 EUR
70.97791798107255 PLN
3. Створити проект tutoring з enum, в якому буде перелік днів тижня з кількістю
годин репетиторства з трьох предметів (наприклад, SUNDAY(2,0,1), де 2 –
кількість годин в репетитора з мови, 0 – з математики і 1 – з фізики). В основній
програмі вивести день з найбільшою зайнятістю, з найменшою зайнятістю та за
введеним предметом назву днів та кількість годин репетиторства з цього предмету
4. Створити перелічення місяців пори року весна, з вказанням кількості днів у
них. Вивести на екран назву місяця, кількість днів у ньому.
5. Відомо що в класі перелічення Apple є такі константи Jonathan, GoldenDel,
RedDel, Winsap, Cortland, використовуючи команду switch виведіть інформацію
про їх колір у форматі «Winsap is red», якщо відомо, що всі яблука крім GoldenDel
є червоні, а GoldenDel є жовтим. Змінити програму так, щоб кожний з сортів
характеризувався кольором та ціною. Користувач вводить в діалогове вікно яку він
хоче ціну і колір яблука. Програма видає можливі варіанти у формі «You can byu
Jonathan it price 8 grn.»

3.2. Клас java.lang.Class. Основи рефлексії


Словничок
reflection – рефлексія
Рефлексія коду, reflection
Рефлексія (від reflexio – звернення назад) – це механізм дослідження
програми під час її виконання, тобто дозволяє отримати інформацію про поля,
методи і конструктори класів. Можна також виконувати операції над полями і
методами, які досліджуються. Рефлексія в Java здійснюється за допомогою Java
Reflection API. Цей інтерфейс API складається з класів пакетів java.lang і
java.lang.reflect.
В інформатиці рефлексія означає процес, під час якого програма може
відстежувати і модифікувати власну структуру і поведінку під час виконання.
За допомогою інтерфейсу Java Reflection API можна робити наступне:

48
 Визначити клас об'єкта java.lang.Class.
 Отримати інформацію про модифікатори класу, поля, методи, конструктори
і суперклас.
 З‘ясувати, які константи і методи належать інтерфейсу.
 Створити екземпляр класу, ім‘я якого невідоме до моменту виконання
програми.
 Отримати і встановити значення властивостей об‘єкта.
 Викликати метод об‘єкта.
 Створити новий масив, розмір і тип компонентів якого невідомі до моменту
виконання програми.
Клас Object, що стоїть на чолі ієрархії класів Java, представляє всі об‘єкти,
що діють в системі, є їхньою спільною оболонкою. Кожен об‘єкт можна вважати
екземпляром класу Object.
Клас з ім‘ям Сlass являє собою характеристики класу, екземпляром якого є
об‘єкт. Він зберігає інформацію про те, чи не є об‘єкт насправді інтерфейсом,
масивом або примітивним типом, який суперклас об‘єкта, яке ім‘я класу, які в
ньому конструктори, поля, методи і вкладені класи.
У класі Сlass немає конструкторів, екземпляр цього класу створюється
виконуючою системою Java під час завантаження класу і надається методом
getClass() классу Object.
Отримання об’єкта типу Class, getClass()
Найпростіше, що зазвичай необхідне в динамічному програмуванні,– це
отримання об'єкта типу java.lang.Class. Якщо є екземпляр об'єкта TestClass, то
можна отримати всю інформацію про цей клас і здійснювати операції над ним.
TestClass clazz = new TestClass();
Class aclass = clazz.getClass();
Вищенаведений метод getClass() корисний тоді, коли є екземпляр об‘єкта,
але невідомо, якого класу цей екземпляр. Якщо є клас з відомим типом на момент
компіляції, то отримати екземпляр класу ще простіше.
Class aclass = TestClass.class;
Class iclass = Integer.class;
Якщо ім‘я класу на момент компіляції невідоме, але стає відомим під час
виконання програми, то можна використовувати метод forName(), щоб отримати
об'єкт Class.
Class clazz = Class.forName ("com.mysql.jdbc.Driver");
Наступний код ілюструє застосування рефлексії на прикладі створення
екземпляру foo класу Foo і виклику методу hello (або Hello). Наведено два
приклади: перший не використовує рефлексію, а другий використовує.
// Без рефлексії
new Foo().hello();
// З рефлексією
Class foo = Class.forName("Foo");
foo.getMethod("hello", null).invoke(foo.newInstance(), null);

Отримання імені класу, getName()

49
Об‘єкт типу String, що повертається методом getName(), містить повне ім‘я
класу, тобто, якщо типом об‘єкта myObject буде Integer, то результат буде виду
java.lang.Integer.
Class aclass = myObject.getClass ();
String s = aclass.getName();

Дослідження модифікаторів класу, getModifiers()


Щоб дізнатися, які модифікатори були застосовані до заданого класу,
спочатку потрібно за допомогою методу getClass() отримати об‘єкт типу Class,
який представляє даний клас. Потім потрібно викликати метод getModifiers() для
об‘єкта типу Class, щоб визначити значення типу int, біти якого представляють
модифікатори класу. Після цього можна використовувати статичні методи класу
java.lang.reflect.Modifier, щоб визначити, які саме модифікатори були застосовані
до класу.
Class aclass = obj.getClass();
int mods = aclass.getModifiers();
if (Modifier.isPublic(mods)){ System.out.println("public");}
if (Modifier.isAbstract(mods)){ System.out.println("abstract");}
if (Modifier.isFinal(mods)){ System.out.println("final");}

Визначення суперкласу, getSuperclass ()


Можна використовувати метод getSuperclass() для об‘єкта Class, щоб
отримати об‘єкт типу Class, який представляє суперклас рефлексованого класу.
Class aclass = myObj.getClass();
Class superclass = aclass.getSuperclass();

Необхідно враховувати, що в Java відсутнє множинне спадкування, і клас


java.lang.Object є базовим класом для всіх класів, внаслідок чого, якщо у класу
немає батька, метод getSuperclass() поверне class java.lang.Object. Для того, щоб
отримати всіх батьків суперкласу, потрібно рекурсивно викликати метод
getSuperclass().
Визначення інтерфейсів класу, getInterfaces ()
За допомогою рефлексії можна визначити інтерфейси, реалізовані в класі.
Метод getInterfaces() поверне масив об‘єктів типу Class. Кожен об‘єкт в масиві
представляє один інтерфейс, реалізований в поточному класі.
Class aclass = LinkedList.class;
Class[] interfaces = aclass.getInterfaces();
for(Class interface: interfaces) {
System.out.println( interface.getName());

Визначення полів класу, getFields()


Щоб дослідити поля, які належать класу, можна скористатися методом
getFields() для об‘єкта типу Class. Метод getFields() повертає масив об‘єктів типу
java.lang.reflect.Field, які відповідають усім відкритим полям об‘єкта. Ці відкриті
поля необов‘язково повинні міститися безпосередньо всередині класу, з яким
працюємо, вони також можуть міститися в суперкласі, інтерфейсі або інтерфейсі,
що представляє собою розширення інтерфейсу, реалізованого класом. За

50
допомогою классу Field можна отримати ім‘я поля, тип і модифікатори. Якщо
відоме ім‘я поля, то можна отримати про нього інформацію за допомогою методу
getField()
Class aclass = obj.getClass();
Field[] publicFields = aclass.getFields();
for (Field field : publicFields) {
Class fieldType = field.getType();
System.out.println("Имя: " + field.getName());
System.out.println("Тип: " + fieldType.getName());
}
Методи getField() і getFields() повертають тільки відкриті члени даних
класу. Якщо потрібно отримати всі поля деякого класу, необхідно використовувати
методи getDeclaredField() і getDeclaredFields(). Ці методи працюють точно так, як і
їх аналоги getField() і getFields(), за винятком того, що вони повертають всі поля,
включаючи закриті і захищені. Щоб отримати значення поля, потрібно спочатку
отримати для цього поля об‘єкт типу Field, потім використовувати метод get().
Метод приймає вхідним параметром посилання на об‘єкт класу.
Class aclass = obj.getClass();
Field nameField = aclass.getField("name");
Клас Field має спеціалізовані методи для отримання значень примітивних
типів: getInt(), getFloat(), getByte() і ін. Для установки значення поля
використовується метод set().
Class aclass = obj.getClass();
Field field = aclass.getField("name");
String nameValue = (String) field.get(obj);
field.set(obj, "New name");
Для примітивних типів є методи setInt(), setFloat(), setByte() і ін.

Отримання інформації про конструктори класу, getConstructors()


Class aclass = obj.getClass();
Constructor[] constructors = aclass.getConstructors();
for (Constructor constructor : constructors) {
Class[] paramTypes = constructor.getParameterTypes();
for (Class paramType : paramTypes) {
System.out.print(paramType.getName() + " ");
}
System.out.println();
}
Щоб отримати інформацію про відкриті конструктори класу, потрібно
викликати метод getConstructors() для об‘єкта Class. Цей метод повертає масив
об‘єктів типу java.lang.reflect.Constructor. За допомогою об‘єкта Constructor
можна отримати ім‘я конструктора, модифікатори, типи параметрів і можливі
винятки. Можна також отримати окремий відкритий конструктор, якщо відомі
типи його параметрів.
Class[] paramTypes = new Class[] { String.class, int.class };
Class aclass = obj.getClass();
Constructor aConstruct = aclass.getConstructor(paramTypes);
Методи getConstructor() і getConstructors() повертають тільки відкриті
конструктори. Якщо потрібно отримати всі конструктори класу, включаючи
закриті, можна використовувати методи getDeclaredConstructor() і

51
getDeclaredConstructors(). Ці методи працюють таким же чином,як і їхні аналоги
getConstructor() і getConstructors()

Інформація про метод, paramTypes


Class aclass = obj.getClass();
Method[] methods = aclass.getMethods();
for (Method method : methods) {
System.out.println("Ім’я: " + method.getName());
System.out.println("Тип повернення: " +
method.getReturnType().getName());
Class[] paramTypes = method.getParameterTypes();
System.out.print("Типи параметрів: ");
for (Class paramType : paramTypes) {
System.out.print(" " + paramType.getName());
}
System.out.println();
}
Щоб отримати інформацію про відкриті методи класу, потрібно викликати
метод getMethods() для об‘єкта Class. Цей метод повертає масив об‘єктів типу
java.lang.reflect.Method. Потім за допомогою об‘єкта Method можна отримати ім‘я
методу, тип повернення, типи параметрів, модифікатори і винятки, які
генеруються. Також можна отримати інформацію по окремому методу, якщо
відомі ім‘я методу і типи параметрів.
Class aclass = obj.getClass();
Class[] paramTypes = new Class[] { int.class, String.class};
Method m = aclass.getMethod("methodA", paramTypes);
Методи getMethod() і getMethods() повертають тільки відкриті методи, для
того щоб отримати всі методи класу незалежно від модифікатора доступу, потрібно
скористатись методами getDeclaredMethod() і getDeclaredMethods(), які працюють
таким же чино як і їх аналоги getMethod() і getMethods().
Виклик методу invoke()
Інтерфейс Java Reflection API дозволяє динамічно викликати метод, навіть
якщо під час компіляції ім‘я цього методу невідоме. У наступному прикладі
розглянуто виклик методу, якщо відоме його ім‘я. Наприклад, метод
getCalculateRating():
Class aclass = obj.getClass();
Class[] paramTypes = new Class[] { String.class, int.class };
Method method = aclass.getMethod("getCalculateRating", paramTypes);
Object[] args = new Object[] {new String("First Calculate"), new
Integer(10) };
Double d = (Double) method.invoke(obj, args);
В даному прикладі спочатку отримаємо об‘єкт Method по імені методу
getCalculateRating(), потім викликаємо метод invoke() об‘єкта Method, і вкінці
отримуємо результат роботи методу. Метод invoke() приймає два параметри:
перший – це об‘єкт, клас якого оголошує або успадковує даний метод, а другий –
масив значень параметрів, які передаються методу, що викликається. Якщо метод
має модифікатор доступу private, тоді вищенаведений код потрібно модифікувати:

52
для об‘єкта Method замість методу getMethod() викликаємо getDeclaredMethod(),
потім для отримання доступу викликаємо setAccessible(true).
Class aclass = obj.getClass();
Method method = aclass.getDeclaredMethod("getCalculateRating",
paramTypes);
method.setAccessible(true);

Завантаження та динамічне створення екземпляра класу


Class aclass = Class.forName("Test");
Object obj = aclass.newInstance();
Test test = (Test) obj;
За допомогою методів Class.forName() і newInstance() об‘єкта Class можна
динамічно завантажувати і створювати екземпляри класу в тому випадку, коли ім‘я
класу невідоме до моменту виконання програми. У наведеному коді
завантажується клас за допомогою методу Class.forName(), передаючи ім‘я цього
класу. В результаті повертається об‘єкт типу Class. Потім викликається метод
newInstance() для об‘єкта типу Class, щоб створити екземпляри об‘єкта вихідного
класу.
Метод newInstance() повертає об‘єкт узагальненого типу Object, тому в
останньому рядку відбувається приведення об‘єкту до того типу, який потрібен.
Приклад модифікації private полів
import java.lang.reflect.Field;
class WithPrivateFinalField
{
private int i = 1;
private final String s = "String S";
private String s2 = "String S2";
public String toString() {
return "i = " + i + ", " + s + ", " + s2;
}
}
public class ModifyngPrivateFields
{
public static void main(String[] args) throws Exception
{
WithPrivateFinalField pf = new WithPrivateFinalField();
Field f = pf.getClass().getDeclaredField("i");
f.setAccessible(true);
f.setInt(pf, 47);
System.out.println(pf);
f = pf.getClass().getDeclaredField("s");
f.setAccessible(true);
f.set(pf, "MODIFY S");
System.out.println(pf);
f = pf.getClass().getDeclaredField("s2");
f.setAccessible(true);
f.set(pf, "MODIFY S2");
System.out.println(pf);
}
}

53
З наведеного коду видно, що private поля можна змінювати. Для цього
потрібно отримати об‘єкт типу java.lang.reflect.Field за допомогою методу
getDeclaredField(), викликати метод setAccessible(true) і за допомогою методу set()
встановити значення поля. Необхідно мати на увазі, що поле final при виконанні
даної процедури не видає попереджень, а значення поля залишається колишнім,
тобто final поля залишаються незмінні.

3.3. Використання аssertions


Словничок
аssertion – припущення, твердження
Припущення (Assertion) в програмуванні – конструкція в мові
програмування, яка описує певне припущення щодо властивостей змінних або
стану програми. Це припущення зазвичай повинне бути істинним. Більшість мов
програмування використовують ці припущення для перевірки коректності даних і
виконання, а деякі використовують їх для документування. Якщо припущення не
виконується, це може вказувати на наявність помилки в програмі.
Перевірка тверджень (assertions) і умовна компіляція
Починаючи з JDK 1.4, в мові Java з‘явилася вбудована підтримка перевірки
тверджень (assertions). Твердження – це код, який використовується, як правило,
тільки під час розробки, за допомогою якого програма перевіряє правильність
свого виконання. Використання тверджень є ефективним і дешевим способом
виявлення помилок в логіці програми.
На рівні мови перевірка тверджень реалізована за допомогою введення
нового оператора assert, який має дві форми. Перша форма має таку структуру:
assert умова;
де умова означає вираз, в результаті обчислення якого має бути отримано
логічне значення. Якщо це логічне значення true, то твердження істинне і ніяких
дій більше не виконується. Якщо ж обчислення умови дає логічне значення false,
то твердження не підтверджується і за замовчуванням генерується об‘єкт
виключення типу AssertionError.
Нижче наведена друга форма оператора assert:
assert умова : вираз;
Тут вираз позначає значення, яке передається конструктору класу винятку
AssertionError. Це значення перетворюється в рядок і виводиться, якщо
твердження не підтверджується. Як правило, в якості виразу задається символьний
рядок, але, в загальному, дозволяється будь-який вираз, крім типу void.
Перевірка тверджень під час виконання програми припускає виконання
довільних обчислень, які можуть серйозно вплинути на продуктивність програми.
Таким чином, твердження – це простий і зручний механізм для пошуку
помилок під час розробки, який не впливає на роботу готового продукту (звичайно,
це справедливо тільки у разі коректного використання тверджень).
Нижче наведено приклад програми, що демонструє застосування оператора
аssert. Приклад демонструє, що метод getnum() повертає позитивне значення.
Приклад 1.
// застосування оператора assert
class AssertDemo {

54
static int val = З ;
// повернути цілочисельне значення
static int getnum(){
return val--;
public static void main(String args[]){
int n;
for (int i = O; i < 10; i++){
n = getnum();
assert n > О ; /* не nідтвердиться, якщо n == О */
System.out.println("n дорівнює " + n);
}}}
Щоб дозволити перевірку тверджень під час виконання, слід вказати
параметр -еа в командному рядку. Наприклад, для перевірки тверджень в класі
АssertDemo потрібно ввести наступну команду:
java -еа AssertDemo
Після компіляції і запуску, ця програма виводить наступний результат:
n дорівнює З
n дорівнює 2
n дорівнює 1
Exception in thread "main" java.lang.AssertionError at
AssertDemo.main(AssertDemo.java:17)
<Виключення в потоці "main" java.lang.AssertionError при виклику
AssertDemo.main(AssertDemo.java:17)>

У методі main() виконуються повторювані виклики методу getnum(), який


повертає цілочисельне значення. Це значення присвоюється змінній n, а потім
перевіряється в операторі assert, як показано нижче:
assert n> 0; /* не nідтвердиться, якщо n == О */
Результат обчислення цього оператора виявиться невдалим, тобто,
твердження не підтвердиться, якщо значення змінної n дорівнюватиме нулю. Це
відбудеться після четвертого виклику методу getnum(). І, як наслідок, буде
згенеровано виняток.
Як пояснювалося вище, є можливість задати повідомлення, яке виводиться,
якщо твердження не підтверджується. Так, якщо підставити наступне твердження в
вихідний код з попереднього прикладу програми:
assert n> 0: "n від’ємне!",
то буде виведено такий результат:
n дорівнює З
n дорівнює 2
n дорівнює 1
Exception in thread "main" java.lang.AssertionError : n від’ємне! at
AssertDemo.main(AssertDemo.java:17)
Для правильного розуміння тверджень важливо мати на увазі наступне – на
них не можна покладатися для виконання будь-яких конкретних дій в програмі.
Справа в тому, що налагоджений код остаточної версії програми буде
виконуватися з відключеним режимом перевірки тверджень.
Розглянемо, як приклад, наступний варіант попередньої програми:

// Невдале застосування оператора assert !!!


class AssertDemo {
// отримати генератор випадкових чисел

55
static int val = 3 ;
// повернути цілочисельне значення
static int getnum(){
return val--;
public static void main(String args []){
int n = 0 ;
for (int i = 0; i < 10; i++){
assert (n = getnum()) > 0 ; // Невдала ідея!
System.out.println("n is " + n ) ;
} } }

У цій версії програми виклик методу getnum() перенесений в оператор


аssert. І хоча такий прийом, коли активізовано режим перевірки тверджень, є
цілком працездатним, відключення цього режиму призведе до неправильної роботи
програми, тому що так і не відбудеться виклик методу getnum()! По суті, значення
змінної n повинно бути тепер ініціалізоване, оскільки компілятор виявить, що це
значення може бути не присвоєно в операторі assert.
Твердження є дуже корисними, тому що вони спрощують пошук помилок,
на стадії розробки.
В той же час, є ситуації, коли слід уникати використання тверджень, –
згідно зі специфікацією Sun, твердження не слід використовувати для перевірки
аргументів в публічних методах, оскільки це повинно згенерувати відповідне
виключення під час виконання, наприклад IllegalArgumentException,
NullPointerException і т.д.
Компілювання коду з твердженнями-попередженнями
Компілятор Java6 буде за замовчуванням використовувати assert як
ключове слово. Якщо не буде вказівок діяти інакше, компілятор згенерує
повідомлення про помилку, якщо побачить assert в якості ідентифікатора. Але
можна повідомити компілятору про наявність фрагмента коду попередніх версій, і
він виконає функції попередніх компіляторів. Припустимо, що потрібно
компілювати код версії 1.3, де assert використовується в якості ідентифікатора. В
такому випадку у командному рядку необхідно ввести:
javac -source 1.3 OldCode.java
Компілятор видасть попередження при використанні слова assert в якості
ідентифікатора, але код скомпілюється і виконається. При повідомленні
компілятору про версію коду Java 1.4, наприклад:
javac -source 1.4 NotQuiteSoOldCode.java
компілятор видасть помилку при використанні assert в якості
ідентифікатора.
Щоб задати компілятору правила, Java6 необхідно виконати щось одне:
 видалити опцію -source, що використовується за замовчуванням;
 додати одну з двох опцій джерела: -source 1.6 або source 6.
При використанні assert в якості ідентифікатора необхідно використати при
компіляції опцію – source 1.3.
Наступна таблиця підсумовує реакцію Java6 на assert в якості
ідентифікатора або ключового слова.
Таблиця 1.2

56
assert – ключове
Командний рядок аssert – ідентифікатор
слово
javac -source 1.3 Скомпілюється з
Помилка компіляції
TestAsserts.java попередженням
javac -source 1.4
Помилка компіляції Код скомпілюється
TestAsserts.java
javac -source 1.5
Помилка компіляції Код скомпілюється
TestAsserts.java
javac -source 5
Помилка компіляції Код скомпілюється
TestAsserts.java
javac -source 1.6
Помилка компіляції Код скомпілюється
TestAsserts.java
javac -source 6
Помилка компіляції Код скомпілюється
TestAsserts.java
javac TestAsserts.java Помилка компіляції Код скомпілюється

Виконання коду із твердженнями


В написаному коді із твердженнями-попередженнями (iншими словами, в
коді, що використовує assert в якості ключового слова для виконання тверджень
під час виконання) можна обирати – дозволити чи заборонити твердження під час
виконання!
!!!Пам'ятайте – твердження за замовчуванням є забороненими.
Ввімкнення твердження під час виконання
Дозвіл тверджень під час виконання здійснюється наступним чином:
java -ea com.geeksanonymous.TestClass
або
java -enableassertions com.geeksanonymous.TestClass
Попередні перемикачі у командному рядку повідомляють JVM про
виконання коду із дозволеними твердженнями.
Заборона тверджень під час виконання
Необхідно також знати перемикачі командного рядку для заборони
тверджень під час виконання:
java -da com.geeksanonymous.TestClass
або
java -disableassertions com.geeksanonymous.TestClass
Через те, що твердження є забороненими за замовчуванням, використання
перемикачів заборони може здатися непотрібним. Дійсно, використання
перемикачів у попередньому прикладі викликає поведінку програми за
замовчуванням (іншими словами, отримано однаковий результат незалежно від
використання перемикачів заборони). Проте, можна вибірково дозволяти та
забороняти твердження таким чином, що вони будуть дозволеними для деяких
класів і/або пакетів, та заборонені для інших під час виконання програми.

57
Вибіркове вмикання і вимикання
Перемикачі командного рядку для тверджень можуть використовуватись
кількома способами:
 без аргументів (як в попередньому прикладі) – дозволяють або
забороняють твердження в усіх класах, крім системних.
 з ім‘ям пакету – дозволяють або забороняють твердження у вибраному
пакеті та в усіх пакетах, нижчих цього пакету в одній ієрархічній директорії.
 з ім‘ям класу – дозволяють або забороняють твердження у вибраному
класі.
Можна комбінувати перемикачі, наприклад, для заборони тверджень в
одному класі, але залишити їх дозволеними для всіх інших, наприклад:
java -ea -da:com.geeksanonymous.Foo
Попередній командний рядок наказує JVM дозволити твердження в цілому,
але заборонити їх для класу com.geeksanonymous.Foo.
Те саме можна робити і з пакетами:
java -ea -da:com.geeksanonymous...
Попередній командний рядок наказує JVM дозволити твердження в цілому,
але заборонити їх для com.geeksanonymous та всіх його субпакетів! (Субпакетом є
кожний пакет в субдиректорії названого пакету).
Наступна таблиця містить приклади перемикачів командного рядка для
дозволу та заборони тверджень.
Таблиця 1.3
Приклад командного
Що означає
рядка
java –ea
Дозволяє твердження
java -enableassertions
java –da Забороняє твердження (поведінка Java 6 за
java -disableassertions замовчуванням)
java -ea:com.foo.Bar Дозволяє твердження в класі com.foo.Bar.
Дозволяє твердження в пакеті com.foo і його
java -ea:com.foo...
субпакетах
Дозволяє твердження в цілому, але забороняє їх в
java -ea –dsa
системних класах
Дозволяє твердження в цілому, але забороняє їх в
java -ea -da:com.foo...
пакеті com.foo і його субпакетах.

Відповідне використання тверджень


Не всі коректні використання тверджень вважаються відповідними. Можна
свідомо зловживати використанням тверджень, не звертаючи уваги на
застереження інженерів Sun.
Наприклад, не є передбаченою обробка відмови твердження, тобто,
перехоплення винятку та намагання відновлення зворотного виклику. Проте,
AssertionError є підкласом Throwable, тобто, може бути перехопленим. Але цього
дійсно не потрібно робити! Спроба відновити виклик після відмови тверджень
спричинить виняток. Щоб запобігти спробам замінити твердження на виняток

58
AssertionError, заборонено доступ до об'єктів, які його генерують. Вони
представлені у String повідомленні.

Не використовуйте тверджень для перевірки аргументів в public методах


Наступний код є невідповідним використанням тверджень:
public void doStuff(int x) {
assert (x > 0); //невідповідно!
// оброблення x
}
Public метод може бути викликаний кодом, що є поза контролем. Оскільки
public методи є частиною інтерфейсу до зовнішнього світу, передбачено гарантії,
що будь-які комбінації аргументів будуть враховані методом. Проте, оскільки
твердження не гарантують дійсного виконання (в нормальному режимі вони є
забороненими), ці врахування не відбудуться, якщо твердження не є дозволеними.
Не потрібно робити доступ public до коду, який виконуватиметься умовно, в
залежності від дозволу тверджень.
При необхідності перевірки аргументів public методу, можливо використати
виклик винятків, наприклад, IllegalArgumentException, якщо значення, що
передані до public методу є некоректними.

Використовуйте твердження для перевірки аргументів private методу


При написанні private методу, як правило, пишуть (або контролюють) будь-
який код, що його викликає. Припущення щодо коректності логіки коду виклику
private методу можна протестувати наступним твердженням:
private void doMore(int x) {
assert (x > 0); // оброблення x
}
Єдиною різницею між двома попередніми прикладами є модифікатор
доступу. Тому потрібно перевіряти обмеження аргументів private методів, але не
потрібно перевіряти обмеження аргументів public методів.
Можна, звичайно, відкомпілювати код із невідповідним використанням
тверджень для перевірки public аргументів, але потрібно знати, що цього не можна
робити.

Не використовуйте тверджень для перевірки аргументів командного рядка


Це є дійсно специфічним випадком правила «Не використовуйте тверджень
для перевірки аргументів public методу». Якщо програма вимагає аргументів
командного рядка, можна використати механізм винятків для дотримання правил.
Використовуйте твердження, навіть в public методах, для виділення випадків,
які ніколи не повинні відбутись
Це може містити блоки коду, які ніколи не будуть виконаними, включно із
виразами default в операторі switch:
switch(x) {
case 1: y = 3; break;
case 2: y = 9; break;
case 3: y = 27; break;
default: assert false; //Сюди неможливо дістатись!
}

59
Якщо можна припустити, що певний блок коду ніколи не буде виконаним,
як у попередньому прикладі, де припущено, що x повинен приймати значення 2, 3
або 4, тоді можна використати assert false для миттєвого виклику AssertionError,
якщо до цього коду все ж таки дістатись. Тому у прикладі із switch тест boolean не
виконається — вже припустили, що ніколи тут не опинимось, тобто дійсне
знаходження в цьому місці є автоматичною відмовою твердження/припущення.
Не використовуйте виразів аssert, що можуть спричинити побічні ефекти!
Наступний код є надзвичайно неправильним:
public void doStuff() {
assert (modifyThings()); // продовження
}
public boolean modifyThings() {
y = x++;
return true;
}
Це є правилом: вираз assert після свого виходу повинен залишити програму
у тому ж стані, в якому вона була до цього виразу! Вирази assert не завжди
виконуються, тому є небажаною різна поведінка коду в залежності від дозволу
тверджень. Твердження не повинні спричиняти жодних побічних ефектів. Якщо
твердження є дозволеними, єдиною зміною шляху виконання програми може бути
AssertionError, що викличеться у випадку хибності одного з тверджень.

3.4. Пошук файлів за допомогою classpath


Classpath є параметром в віртуальній машині Java, який визначає місце
розташування заданих користувачем класів і пакетів. Параметр може бути
встановлений або в командному рядку, або через змінну оточення.
Як було сказано раніше, в ОС пакети відображаються каталогами. Ця
обставина породжує важливе питання: звідки системі часу виконання Java відомо,
де слід шукати створювані пакети? Відповідь на нього складається з наступних
частин: по-перше, за замовчуванням в якості відправної точки система часу
виконання Java використовує поточний робочий каталог. Отже, якщо пакет
знаходиться в підкаталозі поточного каталогу, він буде знайдений. По-друге, шлях
або шляхи до каталогу можна вказати, встановлюючи значення змінної середовища
CLASSPATH. По-третє, java і javac можна використовувати з параметром –
classpath, що вказує шлях до класів. Наприклад, розглянемо наступну
специфікацію пакета:
package MyPack;

Щоб програма могла знайти пакет MyPack, має виконуватися одна з


наступних умов:
 програма повинна виконуватися з каталогу, розташованого безпосередньо
над каталогом MyPack;
 змінна середовища CLASSPATH повинна містити шлях до каталогу
MyPack;
 параметр -classpath повинен вказувати шлях до каталогу MyPack під час
виконання програми за допомогою java.
60
При використанні двох останніх способів шлях класу не повинен містити
сам пакет MyPack. Він повинен просто вказувати шлях до цього каталогу.
Наприклад, в середовищі Windows, якщо шлях до каталогу MyPack має вигляд:
С:\MyPrograms\Java\MyPack,
то шлях класу до MyPack буде виглядати так:
С:\MyPrograms\Java
Змінна CLASSPATH використовується тільки Java (JDK), а не всією
системою. Можливо, її прийдеться створити самостійно.
CLASSPATH дозволяє дізнатись, де потрібно шукати класи (байт-коди),
створені користувачами. Класи можуть бути збережені в окремих *.class файлах,
jar- та zip-архівах.
Щоб перевірити, чи існує змінна CLASSPATH, потрібно в командному
рядку Windows набрати команду:
echo %CLASSPATH%
Якщо змінна ще не визначена, або її взагалі не існує, то відповідь буде:
%CLASSPATH%
Значення CLASSPATH можна поміняти:
 назавжди (тоді воно збережеться після перезавантаження комп'ютера);
 тимчасово (до наступного перезавантаження, або до наступного виклику
команд, які змінять поточне значення).
Щоб змінити значення змінної CLASSPATH назавжди, потрібно спочатку
створити її, якщо цього не було зроблено раніше, і присвоїти потрібні значення,
скориставшись діалоговим вікном
Властивості системи → Змінити налаштування → Додатково → Змінні
оточення → Створити:
По замовчуванню, значення CLASSPATH рівне «поточній папці», яка
позначається через символ крапки (.). Якщо присвоїти змінній інше значення, то
воно перепише значення по замовчуванню. Тож, якщо потрібно додати поточну
папку до CLASSPATH, потрібно додати крапку, як один із шляхів пошуку класів:
.;D:\myApps;C:\myCLasses\projects;
Після цього потрібно перезавантажити комп‘ютер.
Але в документації по Java радять змінювати значення CLASSPATH
тимчасово, залежно від проекту. Таким чином, можна компілювати проекти
незалежно один від одного. Тимчасово (максимум до наступного
перезавантаження системи) змінити змінну CLASSPATH можна або засобами
Windows, використовуючи команду set у командному рядку, або під час виклику
однієї з програм JDK-пакету, яка може використовувати ключ -classpath або його
скорочений варіант -cp.
set CLASSPATH=path1;path2
Настпуні інструменти JDK-пакету можуть працювати з ключем -classpath,
який замінить поточне значення системної змінної CLASSPATH на час роботи
інтсрументу: java, jdb, javac, javah, jdeps.
java -classpath C:\projects\MyClasses utility.myapp.Cool
де
C:\projects\MyClasses – значення CLASSPATH;
utility.myapp.Cool – ім‘я класу, включаючи ім‘я його пакету.

61
Як і у випадку з set, можна вказати декілька шляхів для пошуку одночасно,
розмежованих крапкою з комою:
java -classpath C:\projects\MyClasses;C:\projects\OtherClasses
utility.myapp.Cool
Порядок, у якому вказані шляхи пошуку, є важливим. Інтерпретатор Java
починає шукати класи зліва направо і як тільки він знайде відповідний клас у
першому шляху,то далі пошук не продовжується, навіть якщо в інших шляхах
існує клас із таким самим іменем.
Щоб видалити всі значення CLASSPATH для поточної сесії роботи
командного рядка, потрібно виконати:
set CLASSPATH=
Значення шляху може містити символ зірочки (*), використання якої
еквівалентне переліку всіх jar-пакетів у вказаній папці. Зірочка не стосується .class
та .zip файлів.
Пошук відбувається тільки у вказаних папках, або jar-пакетах, але не у
вкладених папках.
Остання папка в значенні CLASSPATH повинна бути кореневою для дерева
папок, визначеного ім‘ям пакету програми. В цьому прикладі ієрархія папок є
наступною:
java -classpath C:\projects\MyClasses utility.myapp.Cool

+ projects
+ MyClasses
+ utility
+ myapp
+ Cool.class
Якщо ж вказувати шлях до архіву, то значення повинне містити повний
шлях до архіву, включаючи його ім‘я, або повний шлях і зірочку в кінці:
C:\projects\MyClasses\*
C:\projects\MyClasses\allclasses.jar
Якщо інструменти JDK не використовують ключі –sourcepath та\або
-processorpath, то значення CLASSPATH буде також служити для пошуку не
тільки класів, а і вихідного коду класів та обробників анотацій.
Помилки
Із змінною CLASSPATH пов‘язані дві найпоширеніші помилки:
ClassNotFoundException та NoClassDefFoundError.
Помилка ClassNotFoundException може виникнути під час виконання
програми (runtime). Вона виникає тоді, коли JRE не може знайти класи, які ми
намагаємося завантажити. Імена цих класів виступають рядковими параметрами
одного з методів:
 forName() метод в класі Class;
 findSystemClass( ) метод в класі ClassLoader;
 loadClass( ) метод в класі ClassLoader.
Наявність класів не перевіряється під час компіляції (compilation), тому і
виникає ця помилка тільки під час запуску програми.

62
Помилка NoClassDefFoundError може виникнути під час виконання
програми (runtime). Вона виникає тоді, коли класи були знайдені під час
компіляції, але не можуть бути знайдені під час виконання. Наприклад, їх було
переміщено до іншої папки.

3.5. Використання jar-файлів


Дуже часто користувачам комп‘ютерів потрібно обмінюватися файлами.
Вони можуть скопіювати файли на USB flash drive, CD, скористатися електронною
поштою або просто відправити дані через мережу. Існує спеціальна програма, яка
стискає кілька файлів в один файл-архів. Розмір такого архіву зазвичай менший,
ніж загальний розмір всіх файлів окремо, його швидше копіювати і він займає
менше місця на жорсткому диску.
JAR-файл (скорочення від Java Archive) є механізмом, що забезпечує Java
стискання даних та архівування. Java встановлюється з програмою під назвою jar,
яка архівує кілька Java класів в один файл з розширенням .jar. Коли цей файл буде
створено, його можна буде переміщувати з місця на місце, з комп‘ютера на
комп‘ютер, всі класи в файлі будуть доступні через classpath і можуть
використовуватись через java та javac без розпаковування JAR-файлу.
Базовий формат команди в командному рядку для створення JAR-файлу
такий: jar cf jar-файл вхідний файл (и)
Опції і аргументи, використані в цій команді:
 опція c показує, що ви хочете створити (create) JAR-файл;
 опція f показує, що ви хочете направити вивід в файл, а не в
стандартний потік виведення;
 jar-файл – це ім‘я, яке ви хочете дати результуючому JAR-файлу. Ви
можете використовувати будь-яке ім‘я для JAR-файлу. За угодою, до імені JAR-
файлу дається розширення .jar, хоча це і не обов‘язково;
 аргумент вхідний_файл(и) – список з роздільником-пропуском з
одного або більше файлів, які треба додати в JAR-файл. Аргумент вхідний_файл(и)
може містити також символ «*». Якщо будь-які з вхідних-файлів є папками, вміст
цих папок рекурсивно додаються в архів JAR.
Опції c та f вживаються в будь-якому порядку, але між ними не повинно
бути пропусків.
Ця команда згенерує стиснений JAR-файл і помістить його в поточну папку,
а також згенерує за замовчуванням файл маніфесту для архіву JAR.
Маніфест – це спеціальний файл, який може містити інформацію про файли,
зібрані в JAR-файл.
В архіві є тільки один файл маніфесту, він завжди має ім'я META-INF /
MANIFEST.MF
Тепер можна скопіювати цей файл на інший диск або відправити по
електронній пошті.
Для того, щоб розпакувати файли з архіву, використовують команду:
jar xvf jar-файл
Всі файли будуть розпаковані в поточну папку.
Якщо потрібно подивитися вміст jar-архіву без розархівування, потрібна
наступна команда:

63
jar tvf jar-файл
Для того, щоб переглянути вміст архіву, можна використовувати й інші
програми-архіватори. В більшості випадків Java-додатки складаються з безлічі
класів, які знаходяться в jar-архівах. Хоча й існує багато інших опцій, які можна
використовувати з командою jar, ці команди – це головне, що потрібно знати для
створення майбутніх проектів.
Опції для застосування в базовій команді:
Таблиця 1.4
Опція Пояснення
c створити (create) JAR-файл
f направити вивід в файл, а не в стандартний потік виведення
v вивід в стандартний потік повідомлень про помилки під час створення
JAR-файлу. Докладний вивід повідомляє ім'я кожного файла, коли він
додається в JAR-файл.
0(нуль) показує, що непотрібно, щоб JAR-файл стискувався.
M показує, що файл маніфесту за замовчуванням не повинен створюватись.
m використовується для включення інформації маніфесту з існуючого
файлу. Формат використання цієї опції такий:
jar cmf існуючий-маніфест jar-файл вхідний_файл(и)
-C для зміни каталогу під час виконання команди
x для видобування файлів з архіва в поточну папку
t зміст архіву
Тести
25. Дано повні імена класів:
com.foo.bar.Dog
com.foo.bar.blath.Book
com.bar.Car
com.bar.blath.Sun
Який граф являє собою правильну структуру каталогу для JAR-файлу, з якого
ці класи можуть використовуватися компілятором і JVM (рис. 1.16)?
a) Jar A
b) Jar B
c) Jar C
d) Jar D
e) Jar E

Рис. 1. 16

64
Завдання для самостійного виконання
1. Створіть об‘єкти с, а, d класів Cat, Animal, Dog (Cat і Dog наслідують
Animal). Визначити, які конструктори є в класі Сat (в класі задати хоча б два
конструктори)
2. Дослідити клас String.
3.6. Aнотації
Java-анотації з‘явилися починаючи з JDK 5. Це засіб, що дозволяє
вбудувати інформацію підтримки в вихідні файли, тобто анотації можна
використовувати для відслідковування помилок, усунення попереджень, генерації
коду, XML файлів.
Анотація не змінює дії програми, вона зберігає семантику прогpам
незмінною. Однак ця інформація може бути використана різними
інструментальними засобами, як під час розробки, так і в період розгортання.
Виглядає як @Ім’яАнотації і передує визначенню змінної, параметра, методу,
класу, пакета.
Анотація виконує наступні функції:
 дає необхідну інформацію для компілятора;
 дає інформацію різних інструментів для генерації іншого коду,
конфігурацій і т. д .;
 може використовуватися під час виконання для отримання даних через
рефлексію (reflection);

Створення власних анотацій


Анотації створюються з використання механізму, заснованого на
інтерфейсі. Нижче приклад оголошення анотації:
@interface МуAnnotation{
String str();
int val();
}
Символ @ вказує компілятору, що оголошена анотація. Методи, оголошені в
анотації, поводяться скоріше як поля. Всі анотації складаються тільки з оголошень
методів, однак не програміст визначає тіло цих методів, їх реалізує java.
//Анотування методу
@МуAnnotation (str = "Приклад анотації", val = 10)
public static void myMethod() { // ...
}
Для анотування методу треба за ім‘ям анотації записати в круглих дужках
список проініціалізованих членів.
Анотація не може включати слова extends, однак всі анотації автоматично
розширюють інтерфейс Annotation. Тобто Annotation є суперінтерфейсом для всіх
анотацій. Він оголошений в пакеті java.lang.annotation. У Annotation
перевизначені методи hashCode(), equals() і toString(), які визначені в класі Object.
У ньому також специфікований метод annotationType(), який повертає
об'єкт Class, який представляє анотацію, яка його викликає.
Оголосивши анотацію, можна використовувати її для анотування
оголошення. Будь-який тип оголошення може мати анотацію, асоційовану з ним.

65
Наприклад, анотуватись можуть класи, методи, поля, параметри і константи enum.
Анотованою може бути навіть сама анотація. У всіх випадках анотація передує
оголошенню.

Політики утримання анотації


Існує тр політики утримання анотації. Політика утримання визначає, в якій
точці анотація скидається. Такі політики вміщені в перерахування
java.lang.annotation.RetentionPolicy, це SOURSE, CLASS, RUNTIME.
Анотації з політикою SOURCE містяться тільки в вихідному файлі та
відкидаються при компіляції.
Анотації з політикою CLASS зберігаються в файлі .c1ass при компіляції,
однак вони недоступні JVM під час виконання.
Анотації з політикою утримання RUNTIME зберігаються в файлі .c1ass під
час компіляції і залишаються доступними JVM під час виконання.
Загальна форма оголошення політики утримання показана нижче:
@Retention(політика_утримання)
Якщо для анотації не було вказано ніякої політики утримання, то по
замовчуванню використовується CLASS, тобто буде зберігатися в файлі .c1ass під
час компіляції, але буде відкидатися під час виконання програми.
Нижче наведено приклад використання політики RUNTIМE для анотації
@MyAnnotation, тобто анотація @MyAnnotation буде доступна під час виконання
програми.
@Retention(RetentionPolicy.RUNTIME)
@interface МуAnnotation{
String str();
int val();
}

Значення по замовчуванню
Членам анотації можна надати значення по замовчуванню, які будуть
використовуватись при застосуванні анотації. Значення по замовчуванню
задаються додаванням слова default до оголошення члена анотації:
type member() default value;
Значення value має мати тип сумісний з type.
Так буде виглядати @MyAnnotation з використанням значень по
замовчуванню:
/* Оголошення типу анотації, яка містить значення по замовчуванню */
@Retention(RetentionPolicy.RUNTIME)
@interface МуAnnotation{
String str() default "Значення";
int val() default 100;
}
Анотація-маркер
Анотація-маркер – це спеціальний вид анотацій, який не містить членів, її
єдине призначення помітити (маркувати) оголошення.
Оскільки така анотація не має членів, немає необхідності після
@MyAnnotationMarker вказувати дужки. Тобто @MyAnnotationMarker
застосовується просто використанням її імені:
//Анотацiя_-маркер

66
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotationMarker {
}
class MyMarker {
// Анотування метода з допомогою анотації-маркера.
@MyAnnotationMarker
public static void myMethod() {
// ...
}
}
Одночленні анотації
Одночленна анотація містить, як зрозуміло, тільки один член. Вона працює
так як звичайна анотація, але допускає скорочену форму: якщо присутній тільки
один член, можна просто оголосити його. Коли анотація застосовується, не
потрібно вказувати ім‘я члена. Однак для того, щоб використовувати це
скорочення, член повинен мати ім‘я value.
Нижче показаний приклад створення і використання одночленної анотації:
//Одночленна анотацiя
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotationOne {
int value; // Іменем члена має бути value
}
class MyClass {
// Анотування метода одночленною анотацією
@MyAnnotationOne(1000)
public static void myMethod() {
// ...
}
}
Вбудовані анотації
В Java визначено багато корисних вбудованих анотацій, більшість з них є
спеціалізованими.
Анотації, що мають загальне призначення:
 @Retention – ця інструкція призначена для застосування тільки в якості
анотації до інших анотацій. Визначає, як зазначена анотація може зберігатися – в
коді, в скомпільованому класі або під час виконання програми.
 @Documented – це маркер-інтерфейс, який повідомляє інструменту, що
анотація повинна бути документально підтверджена.
 @Target – ця інструкція задає тип оголошення, до яких може бути
застосована анотація. Приймає один аргумент, який повинен бути константою з
перерахування ElementType. Наприклад, щоб вказати, що анотація може бути
застосована тільки до полів і локальних змінних: @Targer ({ElementType.FIELD,
ElementTyle.LOCAL_VARIABLE})
 @Inherited – це анотація-маркер, яка може застосовуватися в іншому
оголошенні анотації, вона стосується тільки тих анотацій, що будуть використані в
оголошеннях класів. Ця інструкція дозволяє анотації супер класу бути
успадкованою в підкласі.

67
 @Override – анотація-маркер, яка може застосовуватися тільки до
методів. Метод, анотований як @Override, повинен перевизначати метод
суперкласу.
 @Deprecated – вказує, що оголошення застаріло і має бути замінено
більш новою формою.
 @SafeVarargs – анотація-маркер, застосовується до методів і
конструкторів. Вона вказує, що ніякі небезпечні дії, пов‘язані з параметром змінної
кількості аргументів, неприпустимі. Застосовується тільки до методів і
конструкторів з перемінною кількістю аргументів, які оголошені як static або final.
 @SuppressWarnings – ця інструкція вказує, що одне або більше
попереджень, які можуть бути видані компілятором, слід придушити.

public class Animal{


public void speak(){
}
}

public class Cat extends Animal{


@Override /* Анотація, що говорить про те, що цей метод перевизначає
одноіменний метод батьківського класу */
public void speak(){
System.out.println("Meow");
}
@Deprecated /* Анотація, що говорить про те, що цей метод застарів і
буде видалений найближчим часом */
public boolean soundsGood(){
Return true;
}
}

Обмеження
Існує кілька обмежень, що стосуються оголошення анотацій:
 одна анотація не може наслідувати другу;
 всі методи, оголошені в анотації, не повинні приймати ніяких
параметрів. Більш тoгo, вони повинні повертати один з наступних типів:
 примітивний тип, такий як int або double;
 об'єкт типу String або Class;
 тип enum;
 тип іншої анотації;
 масив одного з попередніх типів.
 анотації не можуть бути узагальненими, тобто вони не можуть
приймати параметризовані типи.
 в методах aнотації не може бути вказана конструкція throws.

68
ВІДПОВІДІ ДО ТЕСТІВ
РОЗДІЛ І.
РОЗШИРЕНІ JAVA-ТЕХНОЛОГІЇ
Відповіді до тестів (Тема 1.1)
1. e (тільки BufferedWriter виконує запис до файлу з використанням
system.separator. Не всі платформи використовують новий символ ('\ n'), щоб
закінчити рядок)
2. b (Створення екземпляра класу Файл не створює файл у системі. Тим не менш,
ви можете викликати відповідний метод екземпляра файла для створення файла,
якщо ви хочете)
3. d. (Решта помилкові. Конструктор File не перевіряє семантику назв файлів, не
створює файлів, а тільки вказує на файл, з яким буде здійснено зв’язок,
Створенння файлів та робота garbage collection не впливають на локальну
файлову систему).
4. b (запишеться 4 байти)
5. b (Клас BufferedReader не може бути безпосередньо пов’язаний з пристроями
введення / виводу, такими як файли)

Відповіді до тестів (Тема 1.2)


6. d (послідовність зчитування відповідає послідовності запису)
7. b, d, e (Об’єкт, сереіалізований на одній JVM, буде успішно десеріалізований на
іншій; поля, позначені як transient ,не відновлюються при десеріалізації; можна
серіалізувати об’єкти, суперклас яких є несеріалізованим).
8. b (клас Forest містить поле типу Tree, а цей клас є несеріалізований, поле не
позначено модифікатором transient)
9. c (клас Hotel імплементує Seralizable, поле room класу має модифікатор
transient)
10. c (клас, що наслідує серіалізований клас і сам серіалізований, визначені методи
запису і зчитування об’єктів)

Відповіді до тестів (Тема 1.3)


11. с

Відповіді до тестів (Тема 1.4)


12. d (cтандартна серіалізація проходить лише ,якщо метод writeObject () має
доступ private)
13. b (cуперкласс найнижчого рівня об’єкта, який має бути десеріалізованим,
повинен мати конструктор no-args)

Відповіді до тестів (Тема 2.1)


14. b
15. a, d (це конструктор з трьома параметрами, третій параметр вказує на те,
що розділювачі будуть враховуватися як окремі лексеми)

69
Відповіді до тестів (Тема 2.2)
16. b (виокремлюються числа і знаходиться їхня сума)
17. c (знак «.» екранується)
18. e (знак «\» екранується, «+» вказує на те, що буква буде зустрічатися хоча б
один раз)
19. b (на виводі буде два пропуски між номером позиції і словом, бо в шаблоні є
пропуск; відповідь D не підходить, бо по шаблону слово починається з великої
літери, а потім малі, тому великі не можуть бути)

Відповіді до тестів (Тема 3.1)


20. b (якщо в enum-, крім констант, оголошено конструктори, методи, поля, то
після оголошення останньої константи потрібно поставити «;»); d – (синтаксис
мови вимагає, щоб константи оголошувалися до інших полів і методів)
21. b (метод ordinal() дозволяє отримати значення, яке вказує позицію константи
в списку констант перерахування (порядковий номер). Нумерація починаються з
нуля)
22. е (код не скомпілюється, тому що параметр case не відповідає по типу
параметру switch)
23. а (метод ordinal() дозволяє отримати значення, яке вказує позицію константи
в списку констант перерахування (порядковий номер). Порядкові значення
починаються з нуля; valueOf (String name) – повертає елемент enum-а з назвою
name; getName() – визначено в enum-класі, повертає значення поля name)
24. a (пропущена крапка з комою після MED(5). Слідкуйте за крапкою з комою,
коли перерахування має змінні та функції)

Відповіді до тестів (Тема 3.5)


25. а

70
РОЗДІЛ ІІ. ФРЕЙМВОРК КОЛЕКЦІЙ
ТЕМА 4. Фреймворк колекцій

4.1. Створення, побудова та ініціалізація одно- та


багатовимірних масивів примітивних та об’єктних
змінних
Словничок
фреймворк – (каркас, структура) – інфраструктура програмних рішень, що
полегшує розробку складних систем. В даному випадку ієрархія інтерфейсів і їх
реалізацій для колекцій (динамічних масивів, списків та ін.)
multidimensional – багатовимірний
define – визначення
array – масив
execution – виконання
declaration – опис
В Java існує багато різних способів зберігання об‘єктів, проте масиви
займають особливе місце. Це пов‘язано з ефективністю (швидкий прямий доступ
по індексу), типізацією (об‘єднує елементи одного типу) та можливістю зберігання
сукупності примітивів. Але варто зазначити і мінус такої організації даних: розмір
масиву фіксується і не може бути зміненим на протязі його життєвого циклу.
Ініціалізація масивів об’єктів
Варто згадати, що ідентифікатор масиву насправді є посиланням на об‘єкт,
створений в динамічній пам‘яті. Цей об‘єкт може містити посилання на інші
об‘єкти і створюється, або неявно (при ініціалізації масиву), або явно,
конструкцією new. Наступний приклад допоможе згадати способи ініціалізації
масиву:
Student[] groupA;
/*локальна неініціалізована змінна, з якою компілятор не дасть
нічого зробити
System.out.println("groupA.length "+groupA.length); - викличе
помилку компіляції*/
Student[] groupB = new Student[5];
//посилання в масиві автоматично ініціалізуються як null
Student[] groupC = new Student[4];
for (int i=0; i<groupC.length; i++){
if (groupC[i]==null){
//перевірка, чи зв’язане посилання з об’єктом
groupC[i]=new Student();
}
}
//агрегатна ініціалізація
Student[] groupD = {new Student(), new Student(), new
Student()};
//динамічна агрегатна ініціалізація
groupA = new Student[]{new Student(), new Student()};

71
System.out.println("groupA.length "+groupA.length);
System.out.println("groupB.length "+groupB.length);
System.out.println("groupC.length "+groupC.length);
System.out.println("groupD.length "+groupD.length);
groupA=groupD;
System.out.println("groupA.length "+groupA.length);
При виведенні побачимо наступне
groupA.length 2
groupB.length 5
groupC.length 4
groupD.length 3
groupA.length 3

Ініціалізація масивів примітивів


Масиви примітивів та масиви об‘єктів практично ідентичні. Єдина
відмінність між ними полягає в тому, що масиви об‘єктів містять посилання на
об‘єкт, а масиви примітивів – примітивні значення. Це демонструє приклад
ініціалізації:
int[] a;// посилання null
int[] b= new int[5];// автоматична ініціалізація нулями
int[] c = new int[4];
for(int i=0; i<c.length; i++){
c[i]=i*i;
}
int[] d={45, 89,-12};
System.out.println("b.length = "+b.length);
System.out.println("c.length = "+c.length);
System.out.println("d.length = "+d.length);
a=d;
System.out.println("a.length = "+a.length);
a = new int[]{1,2};
System.out.println("a.length = "+a.length);
При виведенні одержимо
b.length = 5
c.length = 4
d.length = 3
a.length = 3
a.length = 2
Багатовимірні масиви є просто масивами масивів. Тож двовимірний масив
типу int є масивом-об‘єктом типу int, в якому кожен елемент містить посилання на
інший масив int. Другий вимір дійсно містить примітиви int. Наступний код
оголошує і будує двовимірний масив типу int:
int[][] scores = new int[3][]; /*Оголошення та побудова
масиву з трьох посилань на масиви int*/
Ініціалізація масиву може виглядати так:
scores[0] = new int[4]; /*перший елемент масиву scores
є масивом з чотирьох елементів int*/
scores[1] = new int[6]; /*другий елемент масиву scores
є масивом з чотирьох елементів int*/
scores[2] = new int[1]; /*третій елемент масиву scores
є масивом з чотирьох елементів int*/
Або ось так:

72
int[][] scores = {{5,2,4,7}, {9,2}, {3,4}};
Інші види ініціалізації, які розглянуто в попередніх прикладах ініціалізації
одновимірних масивів, працюють аналогічно.
Тести
1. Що з наведеного нижче є правильним фрагментом коду для визначення
багатовимірного масиву?
a) int[][] array1 = {{1, 2, 3}, {}, {1, 2,3, 4, 5}};
b) int[][] array2 = new array() {{1, 2, 3}, {}, {1, 2, 3, 4, 5}};
c) int[][] array3 = {1, 2, 3}, {0}, {1, 2,3, 4, 5};
d) int[][] array5 = new int[2][];
2. Розглянемо фрагмент коду:
int[] x = new int[25];
Які твердження є істинними? (Виберіть всі можливі вірні відповіді.)
a) x[24] is 0
b) x[24] is undefined
c) x[25] is 0
d) x[0] is null
e) x.length is 25
3. Які з наведених оголошень масивів не є вірними? Виберіть дві правильні
відповіді.
a) int[] i[] = { { 1, 2 }, { 1 }, {}, { 1, 2, 3 } };
b) int i[] = new int[2] {1, 2};
c) int i[][] = new int[][] { {1, 2, 3}, {4, 5, 6} };
d) int i[][] = { { 1, 2 }, new int[ 2 ] };
e) int i[4] = { 1, 2, 3, 4 };
Завдання для самостійного виконання
1. Оголосіть одновимірний масив об‘єктів з полями name та weight і зробіть
динамічну агрегатну ініціалізацію. Виведіть імена тих, у кого вага більша
90.
2. Оголосіть двовимірний масив з 5-х рядочків чисел. Ініціалізуйте кожен
рядок такою кількістю елементів, яким є номер рядка + 1 та задайте
значення елементів в кожному рядку як номер рядка + 1. Виведіть
створений масив (примітка: має вийти як на зразку)
1
22
333
4444
55555

4.2. Клас Arrays


Словничок
search – пошук
fill – заповнити
list – список

73
Клас Arrays надає різноманітні зручні методи для роботи з масивами. Ці
методи допомагають розширити можливості роботи з масивами.
Короткий опис методів класу:
static int binarySearch (E arr, E el) – метод бінарного пошуку значення (el) в
відсортованому масиві (arr). Повертає позицію (номер) першого входження;
static void fill (E arr, E value) – метод для заповнення масивів (arr)
значеннями (value) різних типів та примітивами;
static void sort (E arr) – метод сортування масиву в порядку зростання
значень його елементів;
static void sort (E arr, Comparator c) – метод сортування, що використовує
компаратор для впорядкування елементів;
static void sort (E er, int begin, int end) – метод сортування, що впорядковує
частину масиву, яка знаходиться між begin та end-1;
static String toString(E arr) – представляє масив у вигляді рядка;
static String deepToString(E arr) – дає рядкове представлення
багатовимірних масивів.
static boolean equals (E arr1, E arr2) – порівнює два масиви (щоб вони були
рівними, необхідно, щоб у них була однакова кількість елементів і щоб значення
цих елементів були рівними).
Наступний приклад демонструє використання методів:
String[] catsName = {
"Матильда",
"Кузя",
"Барсік",
"Мурзик",
"Леопольд",
"Бегемот",
"Рижик",
"Пушок" };
Arrays.sort(catsName);
// відсортовано в порядку зростання
for(int i=0; i<catsName.length; i++)
System.out.print(catsName[i]+" ");
System.out.println("\nЛеопольд знаходиться на позиції"
+Arrays.binarySearch(catsName,"Леопольд")+
" у впорядкованому масиві");
int[] numbers = new int[] { 1, 7, 3, 5, 2, 6, 4 };
Arrays.sort(numbers,3,6);
/* відсортовано в порядку зростання в діапазоні від елемента з
номером 3 до елемента з номером 5 включно */
System.out.println();
for(int i=0;i<numbers.length;i++)
System.out.print(numbers[i]+" ");
int[] figures = Arrays.copyOf(numbers,numbers.length+2);
System.out.println();
for (int i:figures){
System.out.print(i+" ");
}
String[][] s = {
{"a(1,1)", "a(1,2)", "a(1,3)"},
{"a(2,1)", "a(2,2)", "a(2,3)"},

74
{"a(3,1)", "a(3,2)", "a(3,3)"}
};
char[][] ch = new char[][] {
{'A','B','C'},
{'D','E','F'},
{'G','H','I'}
};
System.out.println("\n Масив рядків ");
System.out.println(Arrays.deepToString(s));
System.out.println();
System.out.println("Масив символів");
System.out.println(Arrays.deepToString(ch));
System.out.println();
int[] a1 = new int[10];
int[] a2 = new int[10];
Arrays.fill(a1, 9);
Arrays.fill(a2, 9);
System.out.println("Порівняли: " +Arrays.equals(a1, a2));
При виведенні отримаємо:
Барсік Бегемот Кузя Леопольд Матильда Мурзик Пушок Рижик
Леополд знаходиться на позиції 3 у впорядкованому масиві

1 7 3 2 5 6 4
1 7 3 2 5 6 4 0 0
Масив рядків
[[a(1,1), a(1,2), a(1,3)], [a(2,1), a(2,2), a(2,3)], [a(3,1), a(3,2), a(3,3)]]
Масив символів
[[A, B, C], [D, E, F], [G, H, I]]
Порівняли: true

Тести
4. Розглянемо фрагмент коду:
int [ ] arr = {1, 2, 3, 4, 5};
int [ ] arr2 = new int [4];
arr2 = arr;
System.out.println(arr2[4]);
Яким є результат його виконання?
a) помилка виконання, згенерує виняток ArrayOutOfBoundsException.
b) помилка компіляції
c) виведе 4
d) компіляція з попередженням
e) виведе 5
5. Що з наведеного нижче є вірним записом циклу for, що використовується для
перегляду масиву цілих чисел j?
a) foreach (int i : j) {}
b) for (int i : [ ] j) {}
c) for (int i : j) {}
d) for (int i : j [ ] ) {}
e) нічого з переліченого вище

6. Розглянемо код:

75
boolean [ ] arr = { true , false , true };
Arrays.sort(arr);
System.out.println( Arrays.binarySearch (arr, true) ) ;
Яким є результат його роботи?
a) не скомпілюється
b) скомпілюється, але видасть помилку при виконанні
c) виведе 0
d) виведе 2
e) виведе 1
f) нічого з вищенаведеного
7. Розглянемо код:
int [ ]arr = new int[]{1,2,3,4} ;
String [ ]arrstr = Arrays.toString(arr);
for ( String s : arrstr ) {
System.out.println(s);
}
Яким буде результат його роботи?
a) виведеться 1 2 3 4
b) нічого не виведеться
c) помилка компіляції
d) помилка при виконанні
e) жодне з вищепереліченого
8. Розглянемо код:
System.out.println (Arrays.equals(new int []{1,2,3,4},new Integer []{1,2,3,4}));
Яким буде результат його виконання?
a) виведеться true
b) виведеться false
c) помилка під час виконання
d) помилка компіляції
9. Розглянемо код:
int [] arr = new int []{1,2,3,4};
int [] arr2 = new int []{new Integer(1),2,3,4};
System.out.println( Arrays.equals(arr, arr2));
Яким буде результат його роботи?
a) помилка при виконанні
b) виведе false
c) нічого не виведеться
d) помилка компіляції
e) виведеться true

76
Завдання для самостійного виконання
1. Створити масив цілих чисел, заповнивши його випадковими числами,
відсортувати його в порядку зростання та підрахувати кількість різних
елементів.

4.3. Ієрархія колекцій. Інтерфейс Collections


Словничок
Collections Framework – каркас колекцій
Каркас колекцій
Колекції – це сховища, що підтримують різні способи накопичення та
впорядкування елементів для забезпечення можливості ефективного доступу до
них. Вони є реалізацією абстрактних типів (структур) даних, що підтримують три
основні операції: додавання нового елемента в колекцію, видалення елемента з
колекції, зміну елементу в колекції.
Каркас колекцій Collections Framework – дуже зручний і необхідний
інструмент для Java-розробника. Він входить до пакету java.util і стандартизує
способи управління групами об‘єктів. Колекції дозволяють створювати масиви
змінної довжини, реалізовувати черги, працювати з наборами даних з унікальними
елементами тощо. Реалізація основних колекцій (динамічних масивів, зв‘язних
списків, дерев та хеш-таблиць) характеризується високою ефективністю, вона
дозволяє різним типам колекцій легко взаємодіяти між собою.Також додано
механізм інтеграції стандартних масивів в систему колекцій.
Каркас колекцій являє собою складну ієрархію інтерфейсів і класів (рис.2.1),
що реалізують сучасну технологію управління групами об‘єктів.

Рис. 2.1

Інтерфейс Collection
Інтерфейс – це клас, який не містить реалізації методів, а лише їхні
оголошення. Можна написати різні реалізації інтерфейсу. Програмісту, який

77
використовуватиме ці реалізації, достатньо знати методи, які передбачає даний
інтерфейс. З вище наведеної діаграми видно, що на вершині ієрархії знаходиться
інтерфейс Collection. Його не реалізовує жоден клас, а лише наслідують інші
інтерфейси. За допомогою такого рішення всі колекції мають єдину спільну
логічну базу, з якою зручно працювати.
Розглянемо нащадків:
 Set – розширює інтерфейс Collection для управління множинами, які
повинні містити однозначні елементи;
 SortedSet – розширює інтерфейс Set для управління відсортованими
множинами;
 NavigableSet – розширює інтерфейс SortedSet для вилучення елементів за
результатами пошуку найближчого збігу;
 Queue – розширює інтерфейс Collection для управління спеціальними
типами списків, де елементи видаляються тільки з початку списку (черга);
 Deque – розширює інтерфейс Queue для організації двосторонніх черг;
 List – розширює інтерфейси Collection для управління послідовностями
(списками об‘єктів).
Інтерфейс Collection розширює інтерфейс Iterable. Це означає, що всі
колекції можна перебирати, організувавши цикл for в стилі foreach.
В інтерфейсі Collection визначаються основні методи, які повинні мати всі
колекції.
Розглянемо найбільш необхідні й часто використовувані:
 boolean add(Е obj) – додає об‘єкт в колекцію та повертає true, якщо
об‘єкт успішно введений в колекцію; якщо ж об‘єкт вже присутній в колекції, яка
не допускає дублювання об‘єктів, то повертає false;
 boolean addAll(Collection с) – додає всі елементи заданої колекції в
колекцію та повертає true, якщо колекція змінена (тобто всі елементи введені), в
протилежному випадку – false;
 void clear() – видаляє всі елементи з колекції, що викликає метод;
 boolean remove(Object obj) – видаляє один екземпляр об‘єкта з колекції,
що викликає метод, і повертає true; якщо елемент вилучений – false;
 boolean removeAll(Collection с) – видаляє всі елементи заданої колекції з
колекції, що викликає метод та повертає true, якщо в кінцевому підсумку колекція
змінюється (тобто елементи з неї вилучені), а інакше – false;
 boolean retainAll(Collection с) – видаляє з колекції, що викликає, всі
елементи, крім елементів заданої колекції c. Повертає true, якщо колекція
змінюється (тобто елементи з неї вилучені), а інакше – false;
 boolean contains(Object obj) – повертає true, якщо заданий об‘єкт є
елементом колекції, яка викликає метод, інакше – false;
 boolean containsAll(Collection c) – повертає true, якщо колекція, що
викликає метод містить всі елементи заданої колекції с, інакше – false;
 boolean isEmpty() – повертає true, якщо колекція порожня, а інакше –
false;
 int size() – повертає кількість елементів, що містяться в колекції;
 int hashCode() – повертає хеш-код колекції;

78
 Object [] toArray() – повертає масив, що містить всі елементи колекції.
Елементи масиву є копіями елементів колекції;
 boolean equals(Object obj) – повертає true, якщо колекції еквівалентні, а
інакше – false.
Інтерфейси Iterator, ListIterator
Часто необхідно перебирати всі елементи колекції. Один зі способів це
зробити – використати об‘єкт одного з двох інтерфейсів: Iterator або Listlterator.
Iterator дозволяє організувати цикл для перегляду колекції, отримуючи або
видаляючи елементи. ListIterator розширює Iterator для забезпечення
двонаправленого проходу по списку і модифікації елементів.
Методи інтерфейсу Iterator:
 boolean hasNext() – перевіряє наявність наступного елементу, а у випадку
його відсутності (закінчення колекції) повертає false. Ітератор при цьому
залишається незмінним;
 E next() – повертає об‘єкт, на який вказує ітератор, і пересуває поточний
вказівник на наступний елемент, надаючи доступ до нього. Якщо ж
наступний елемент відсутній, то генерує виняток NoSuchElementException;
 void remove() – видаляє об‘єкт, який повернув останній виклик методу
next().
Методи інтерфейсу ListIterator:
 void add(Е obj) – додає obj перед елементом, який повинен бути
повернутий наступним викликом next ();
 boolean hasPrevious() – повертає true, якщо є попередній елемент, інакше
– false
 int nextIndex() – повертає індекс наступного елемента. Якщо його немає,
повертає розмір списку;
 Е previous() – повертає попередній елемент. Якщо його нема, породжує
виняток NoSuchElementException;
 int previousIndex() – повертає індекс попереднього елемента. Якщо його
нема, то повертає -1;
 void remove() – видаляє поточний елемент списку. Якщо викликати метод
без попереднього виклику методів next() або previous(), то виникає виняток
IllegalStateException;
 void set (Е obj) – присвоює obj поточному елементу.
Приклад використання методів:
ArrayList<Double>c = new ArrayList<Double>(7);
for(int i = 0 ;i< 10; i++) {
double z = new Random().nextGaussian();
c.add(z);//заповнення списку випадковими числами
}
//вивід списку
System.out.println(" Створена колекція");
for (Double d: c) {
System.out.println(d);
}
int positiveNum = 0;
int size = c.size();//визначення розміру колекції
//ітератор

79
Iterator<Double>it = c.iterator();
//перевірка існування наступного елемента
while(it.hasNext()) {
//витяг поточного елемента та перехід до наступного
if (it.next() > 0) positiveNum++;
else it.remove();//видалення додатнього елемента }
System.out.println();
System.out.println("Кількість додатніх "+ positiveNum);
System.out.println("Кількість недодатніх "+( size - positiveNum));
System.out.println("Додатня колекція");
for (Double d : c) {
System.out.println(" "+d);
}
Тести
10. Що є результатом виконання коду програми?
1. List list = new ArrayList();
2. list.add("one");
3. list.add("two");
4. list.add(7);
5. for(String s :list) {
6. System.out.print(s);
7. }

a) onetwo
b) onetwo7
c) onetwo і потім помилка виконання
d) помилка компіляції в рядку 4
e) помилка компіляції в рядку 5

11. Розглянемо код:


1. import java.util.*;
2. class Test {
3. public static void main ( String [ ] args ) {
4. List < Integer > list = new ArrayList < Integer > ();
5. for ( int i = 1 ; i<10 ; i++ ) {
6. list.add(i);
7. }
8. list.remove( new Integer(4) ); // 1
9. list.remove( 1 ); // 2
10. }
11. }

Рядки, позначені в коментарях 1 та 2, викликають методи видалення.


Яким буде результат виконання цього коду?
a) в рядку 1 видалиться елемент, номер якого 4 , а в рядку 2 елемент зі
значенням 1
b) в рядку 1 видалиться елемент зі значенням 4, а в рядку 2 – елемент з
індексом 1

80
c) обидва методи видалять елементи із індексами 4 та 1
d) обидва методи видаляють із значеннями 4 та 1

12. Яким буде результат виконання цього фрагменту коду?


10. List < String > one = new ArrayList < String > ();
11. one.add(―abc‖);
12. List < String > two =new ArrayList < String > ();
13. two.add(―abc‖);
14. if(one == two) System.out.println("A");
16. else if(one.equals(two))
System.out.println("B");
else System.out.println("C");

a) A
b) B
c) C
d) помилка компіляції в рядку 14
e) помилка компіляції в рядку 16
Завдання для самостійного виконання
1. Створити ArrayList із шести елементів типу String. Вивести список,
використовуючи літератор. Модифікувати поточний елемент ітерації,
використовуючи ListIterator, додавши до кожного елемент сьогоднішню
дату. Вивести вміст списку на екран. Виведіть вміст списку у зворотному
порядку.

4.4. Інтерфейси List, Set, Queue. Класи, що реалізують


ці інтерфейси
Інтерфейс List
Цей інтерфейс розширює інтерфейс Collection і визначає таку поведінку
колекцій, яка зберігає послідовність елементів. Елементи можуть бути введені або
отримані по індексу їх позиції в списку, починаючи з нуля. Список може містити
елементи, що повторюються.
Крім методів, оголошених в інтерфейсі Collection, в інтерфейсі List
визначається ряд своїх методів, перерахованих нижче. Однак деякі з цих методів
генерують виключення типу UnsuрроrtеdОреrаtiоnЕхсерtiоn, якщо колекція не
може бути видозмінена, а виключення типу ClassCastException генерується, якщо
об‘єкти несумісні, наприклад, при спробі ввести в список елемент несумісного
типу. Крім того, деякі методи генерують виняток типу IndexOutOfBounsException,
якщо вказано невірний індекс. А виключення типу NullPointerException
генерується при спробі ввести в список порожній об‘єкт зі значенням null, коли
порожні елементи в даному списку не допускаються. І нарешті, виключення типу
IllegalArgumentException генерується при вказуванні невірного аргументу.
 boolean add(int ind, Е obj) – додає заданий об‘єкт до списку за вказаним
індексом. Будь-які введені раніше елементи зміщуються праворуч (їхні індекси

81
збільшуються на одиницю). Повертає логічне значення true, якщо колекція змінена
(тобто елемент введений), а інакше – логічне значення false
 boolean addAll(int ind, Collection с) – вводить всі елементи з колекції с в
список, що викликає метод, починаючи з позиції за вказаним індексом. Введені
раніше елементи зміщуються праворуч. Повертає логічне значення true, якщо
колекція змінена (тобто всі елементи введені), а інакше – логічне значення false.
 Е set(int ind, Е obj) – присвоює заданий об‘єкт елементу, який
знаходиться в списку на позиції за вказаним індексом.
 Е get(int ind) – повертає об‘єкт, що зберігається у викликаючому списку
за вказаним індексом.
 int lastIndexOf(Object obj) – повертає індекс останнього примірника
заданого об‘єкта у списку. Якщо заданий об‘єкт відсутній в списку, повертається
значення -1.
 Е remove(int ind) – видаляє елемент з списку на позиції за вказаним
індексом і повертає видалений елемент. Результуючий список ущільнюється, тобто
елементи, які йдуть за видаленим, зміщуються на одну позицію назад.
 void replaceAll(UnaryOperator<E> opToApply) – оновлює кожен елемент
списку значенням, отриманим з функції, яка визначається параметром opToApply
(додано в версії JDК 8).
 void sort(Comparator c) – сортує список, використовуючи заданий
компаратор (додано в версії JDК 8).
 List<E> subList(int begin, int end) – повертає список, що включає
елементи з викликаючого списку від позиції begin до позиції end-1.
Класи, що реалізують інтерфейс List
У класі ArrayList підтримуються динамічні масиви, які можуть
збільшуватись по мірі потреби. Стандартні масиви в Java мають фіксовану
довжину. Після того, як масив створений, він не може збільшуватися або
зменшуватися, а отже, важливо заздалегідь знати, скільки елементів потрібно в
ньому зберігати. Але іноді ще до стадії виконання невідомо, наскільки великий
масив буде потрібний. Для цього в каркасі колекцій визначається клас ArrayList.
В класі ArrayList визначені наступні конструктори:
ArrayList () – створює порожній списковий масив;
ArrayList (Collection с) – списковий масив ініціалізується елементами із заданої
колекції с;
ArrayList (int capacity) – утворює порожній масив заданої ємності (ємність
збільшується автоматично під час введення елемента в список).
Розглянемо роботу методів інтерфейсу List на прикладі роботи з об‘єктами
класу ArrayList:
Приклад 1.
class Student{
String name;
int averageBall;
boolean scholarship;
Student(String name,int averageBall,boolean scholarship){
this.name=name;
this.averageBall=averageBall;
this.scholarship=scholarship;

82
}
public String toString(){
return name+" "+averageBall+" "+scholarship;
}
}
public class ATMP {
public static void main(String[] args) {
// TODO Auto-generated method stub
//Створюємо список об’єктів класу Student
ArrayList<Student>group=new ArrayList<Student>();
System.out.println("Розмір порожнього списку "
+ group.size());
//Додаємо елемент в список
group.add(new Student("Olga",85,true));
//Вивід елементів списку
for(Student s:group){
System.out.println(s);
}
// Створюємо підгрупу
ArrayList<Student>subGroup=new ArrayList<Student>(5);
subGroup.add(new Student("Pavlo",56,false));
subGroup.add(new Student("Nina",75,false));
subGroup.add(new Student("Ostap",68,false));
//Добавляємо підгрупу в групу
group.addAll(subGroup);
// Оновлений список
for(int i=0;i<group.size();i++){
System.out.println(group.get(i));
}
// Витягаємо зі списку обєкт з індексом 2
Student nina=group.get(2);
// Виводимо позицію обєктів в списку
System.out.println(group.lastIndexOf(new
Student("Nina",75,false))+
" "+group.lastIndexOf(nina));
System.out.println(subGroup.size());
// Очищуємо список
subGroup.clear();
System.out.println(subGroup.size());
group.set(2, new Student("Svitlana",90,true));
/* утворюємо новий список як підпослідовнісь
елементів списку group*/
List<Student>subGroup1 =group.subList(1,3);
for(int i=0;i<subGroup1.size();i++){
System.out.println(subGroup1.get(i));
}

}
}
При виведенні побачимо:
Розмір порожнього списку 0
Olga 85 true
Olga 85 true
Pavlo 56 false

83
Nina 75 false
Ostap 68 false
-1 2
3
0
Pavlo 56 false
Svitlana 90 true

LinkedList – реалізація інтерфейсу List на основі двостороннього зв‘язного


списку, коли кожен елемент структури містить вказівник на попередній та
наступний елементи. Ітератор підтримує обхід в обидві сторони. Потребує, як
правило, більше пам‘яті і, якщо необхідно додати елемент не на початок чи в
кінець списку, то і більше часу.
Приклад 2. Фрагмент коду, що демонструє роботу зі списком:
LinkedList<String>lst1=new LinkedList<String>();
lst1.add("F");
lst1.add("B.") ;
lst1.add("D") ;
lst1.add("E") ;
lst1.add("C") ;
lst1.addLast("Z");
lst1.addFirst("A");
lst1.add(1, "А2");
System.out.println("Список: " + lst1);
// Видаляємо елементи за значенням та індексом
lst1.remove ("F") ;
lst1.remove (2) ;
System.out.println("Список після видалення: " + lst1);
// Видаляємо перший та останній елементи
lst1.removeFirst();
lst1.removeLast();
System.out.println("Список після видалення першого і " +
+ " останнього елементів: " + lst1);
// Отримуємо значення списку
String val=lst1.get(2);
lst1.set(2,val + " Змінений");
System.out.println("Список після зміни: "+ lst1);
Виведення:
Список: [A, А2, F, B., D, E, C, Z]
Список після видалення: [A, А2, D, E, C, Z]
Список після видалення першого і останнього елементів: [А2, D, E, C]
Список після зміни: [А2, D, E Змінений, C]

Приклад 3.
LinkedList<Number>a = new LinkedList<Number>();
for(int i =10;i<=15;i++)
a.add(i);
for(int i=16;i<=20;i++)
a.add(new Float(i));
ListIterator<Number>list = a.listIterator(10);
System.out.println("\n"+ list.nextIndex()
+ "-й індекс");
list.next(); // важливо!

84
System.out.println(list.nextIndex()
+ "-й індекс");
list.remove(); // видалення елемента з поточним індексом
while(list.hasPrevious())
System.out.print(list.previous()+" "); /*вивід в
зворотньому порядку, демонстрація роботи методів*/
a.removeFirst();
a.offer(71); // додавання елемента в кінець списку
a.poll(); // видалення нульового елемента зі списку
a.remove(); // видалення нульового елемента зі списку
a.remove(1); // видалення першого елемента зі списку
System.out.println("\n" + a);
Queue<Number>q = a; // перетворення списку на чергу
for (Number i : q) // вивід елементів
System.out.print(i + " ");
System.out.println(" :size= " + q.size());
// видалення п’яти елементів
for (int i = 0; i< 5; i++) {
Number res = q.poll();
}
System.out.print("size= " + q.size());
Виведення:
10-й індекс
11-й індекс
19.0 18.0 17.0 16.0 15 14 13 12 11 10
[13, 15, 16.0, 17.0, 18.0, 19.0, 71]
13 15 16.0 17.0 18.0 19.0 71 :size= 7
size= 2

Інтерфейс Set
В інтерфейсі Set визначається множина. Він розширює інтерфейс Collection
і визначає поведінку колекцій, що не допускають дублювання елементів. Таким
чином, метод add() повертає логічне значення false при спробі ввести в множину
дублюючий елемент. У цьому інтерфейсі не визначається ніяких додаткових
методів.
Інтерфейс SortedSet
Інтерфейс SortedSet розширює інтерфейс Set і визначає поведінку множин,
відсортованих в порядку зростання.
Крім методів, що надаються інтерфейсом Sеt, в інтерфейсі SortedSet
оголошуються свої методи. Деякі із них генерують виняток типу
NoSuchElementException, якщо у множини, що викликається, відсутні якісь
елементи. Виняток типу СlаssCastЕхсерtion генерується, якщо заданий об‘єкт не
сумісний з елементами множини. Виняток типу NullPointerException генерується
при спробі використовувати порожній об‘єкт, коли порожнє значення null в
множині неприпустимо. А при вказуванні невірного аргументу генерується
виключення типу IllegalArgumentException.
Основні методи SortedSet:
 Comparator <E> comparator() – повертає компаратор відсортованої
множини. Якщо для множини вибирається природний порядок сортування, то
повертається порожнє значення null;

85
 Е first () – повертає перший елемент відсортованої множини;
 SortedSet <E> headSet(Е end) – повертає об‘єкт типу SortedSet, що
містить елементи з відсортованої множини, які передують елементу, що
визначається параметром end;
 Е last() – повертається останній елемент відсортованої множини;
 SortedSet <E> subSet(Е begin, Е end) – повертає об‘єкт типу SortedSet,
котрий включає в себе елементи, починаючи з позиції begin і до позиції end-1;
 SortedSet <E> tailSet(Е begin) – повертає об‘єкт типу SortedSet, що
містить елементи з множини, які слідують після елемента на заданій позиції begin.
Класи, що реалізують інтерфейс Set
Клас HashSet розширює AbstractSet та реалізує інтерфейс Set. Він створює
колекцію, що використовує для зберігання елементів хеш-таблицю. Хеш-таблиця
для зберігання інформації задіює механізм хешування, в якому вміст ключа
використовується для визначення унікального значення, яке називається хеш-
кодом. Перетворення ключа в хеш-код відбувається автоматично і побачити сам
хеш-код неможливо.
В класі визначені наступні конструктори:
HashSet () – створення хеш-набору за замовчуванням;
HashSet(Collection<? extends Е> с) – ініціалізація хеш-набору значеннями колекції
с;
HashSet(int capacity) – створення хеш-набору певної ємності;
HashSet(int capacity, float fillRatio) – визначається, при якому наповненні хеш-
набору буде змінена його ємність.
HashSet не визначає ніяких додаткових методів окрім методів, наданих
інтерфейсами та суперкласами. Ця колекція не гарантує впорядкованості
елементів, оскільки процес хешування, зазвичай, не утворює відсортованих
наборів.
Наступний код демонструє застосування HashSet:
HashSet<String>hs = new HashSet<String>();
hs.add("B") ;
hs.add("A") ;
hs.add("C") ;
hs.add("E") ;
hs.add("C") ;
hs.add("F") ;
hs.add("A");
System.out.println(hs);
Виведення:
[A, B, C, E, F]

Клас LinkedHashSet підтримує зв‘язний список елементів набору в тому


порядку, в якому вони додавались. Це дозволяє організувати впорядковану
ітерацію вставки в набір, тобто, коли використовується цикл з застосуванням
ітератора, то елементи будуть витягатись в тому порядку, в якому вони були
поміщені в набір. Це також той порядок, в якому вони будуть повернуті методом
toString() об‘єкта LinkedHashSet:
LinkedHashSet<String>hs = new LinkedHashSet<String>();
hs.add("B") ;

86
hs.add("A") ;
hs.add("C") ;
hs.add("E") ;
hs.add("C") ;
hs.add("F") ;
hs.add("A");
System.out.println(hs);

Отримаємо:
[B, A, C, E, F]

Клас TreeSet розширює клас AbstractSet і реалізує інтерфейс NavigableSet.


Він створює колекцію, де для зберігання елементів застосовується деревоподібна
структура. Об‘єкти зберігаються у порядку, відсортованому по зростанню. Час
доступу і вилучення елементів досить малий, завдяки чому клас TreeSet є
відмінним вибором для зберігання великих обсягів відсортованих даних, які
повинні бути швидко знайдені. У класі TreeSet визначені конструктори:
TreeSet () – конструює порожній набір, який буде сортувати елементи в порядку
зростання;
TreeSet (Collection с) – будує набір-дерево, який містить елементи колекції с;
TreeSet (Comparator<? super Е>comp) – створює порожній набір, в якому елементи
будуть відсортовані вказаним компаратором;
TreeSet (SortedSet <E> ss) – створюється набір-дерево, елементами якого будуть
елементи відсортованої множини ss.
Приклад 4. Використання TreeSet:
TreeSet<Integer>ts = new TreeSet<Integer>();
ts.add(23);
ts.add(45) ;
ts.add(2) ;
ts.add(15) ;
ts.add(67) ;
ts.add(8) ;
System.out.println("Найбільший елемент "+ts.last());
System.out.println("Найменший елемент "+ts.first());
System.out.println(ts);
Виведення:
Найбільший елемент 67
Найменший елемент 2
[2, 8, 15, 23, 45, 67]

Інтерфейс Queue
Цей інтерфейс розширює інтерфейси Collection і визначає поведінку черги,
яка діє як cписок за принципом «перший увійшов – перший опрацьований». Проте
є різні види черг, порядок організації в котрих ґрунтується на деякому критерії.
Методи, визначені в інтерфейсі Queue (Е вказує на тип об‘єктів колекції):
 Е element() – повертає елемент з голови черги. Елемент, що повертається,
не видаляється. Якщо черга порожня, генерується виключення типу
NoSuchElementException;
 boolean offer(Е obj) – намагається ввести заданий об‘єкт в чергу. Повертає
true, якщо об‘єкт введений, а інакше – false;

87
 Е peek() – повертає елемент з голови черги. Якщо черга порожня, повертає
null;
 Е poll() – повертає елемент з голови черги і видаляє його. Якщо черга
порожня, повертає null;
 Е remove() – видаляє елемент з голови черги, повертаючи його. Генерує
виключення типу NoSuchElementException, якщо черга порожня.
Деякі методи з даного інтерфейсу генерують виключення типу
ClassCastException, якщо заданий об‘єкт несумісний з елементами черги.
Виключення типу NullPointerException генерується, коли робиться спроба
зберегти порожній об‘єкт, а порожні елементи в черзі не дозволені. Виключення
типу IllegalArgumentException генерується при вказівці невірного аргументу.
Виключення типу IllegalStateException генерується при спробі ввести об‘єкт в
заповнену чергу фіксованої довжини. Виключення типу NoSuchElementException
генерується при спробі видалити елемент з порожньої черги.
Слід мати на увазі, що метод offer() тільки намагається ввести елемент в
чергу. А оскільки деякі черги мають фіксовану довжину і можуть бути заповнені,
то виклик методу offer() може завершитися невдало.
Класи, що реалізують інтерфейс Queue
Клас PriorityQueue розширює AbstractQueue та реалізує інтерфейс Queue.
Він створює чергу з пріоритетами на базі компаратора черги. PriorityQueue є
динамічною колекцією і при необхідності може збільшуватись. В цьому класі
визначені наступні конструктори (Е вказує на тип елементів):
PriorityQueue () – будує порожню чергу;
PriorityQueue(int capacity) – будує чергу заданої ємності;
PriorityQueue(int capacity, Comparator<? super Е> сотр) – будує чергу заданої
ємності з заданим компаратором.
У всіх випадках при додаванні елемента до черги ємність автоматично
збільшується. Якщо при створенні черги не вказується компаратор, то по
замовчуванню застосовується компаратор того типу елементів, які зберігаються в
черзі. Компаратор за замовчуванням розміщує елементи в зростаючому порядку.
Отримати посилання на компаратор, який використовує PriorityQueue, можна
викликом методу comparator(). Якщо в черзі застосовується компаратор за
замовчуванням, то він поверне null.
Черга дозволяє отримувати елементи в поряду сортування, хоча вони могли
додаватись туди в довільному порядку. Метод remove() завжди дозволить отримати
найменший з елементів, які є в черзі. Якщо ж проглядати колекцію за допомогою
ітератора, то дані можуть бути не відсортованими. При роботі з цією чергою
використовується структура даних «купа» (heap) – двійкове дерево, в якому методи
add() та remove() заставляють найменший елемент підніматись в корінь дерева.
Таким чином сортування в явному вигляді не відбувається.
Приклад 5. Створення та робота з об‘єктами PriorityQueue демонструє цю
особливість:
PriorityQueue<GregorianCalendar>pq=
new PriorityQueue<GregorianCalendar>();
pq.add(new GregorianCalendar(1906,Calendar.DECEMBER,9));
// G. Hopper

88
pq.add(new GregorianCalendar(1815,Calendar.DECEMBER,10));
// A. Lovelace
pq.add(new GregorianCalendar(1903,Calendar.DECEMBER,3));
// J. vonNeumann
pq.add(new GregorianCalendar(1910,Calendar.JUNE,22));
//K. Zuse
System.out.println(" Iterating over elements...");
for(GregorianCalendar date:pq){
System.out.println(date.get(Calendar.YEAR));
}
System.out.println("Comparator "+pq.comparator());
System.out.println("Removing elements...");
while(!pq.isEmpty()){
System.out.println(pq.remove().get(Calendar.YEAR));
}
Буде виведено:
Iterating over elements...
1815
1906
1903
1910
Removing elements...
1815
1903
1906
1910

В Java SE 6 появився клас ArrayDeque. Він не додає власні методи і створює


динамічний масив, що не має обмеження по ємності (інтерфейс Deque підтримує
реалізацію з обмеженою ємністю, але не вимагає цього). ArrayDeque має наступні
конструктори:
ArrayDeque() – порожня двонаправлена черга, початкова ємність якої 16 елементів;
ArrayDeque(int size) – двонаправлена черга із вказаною ємністю;
ArrayDeque(Collection<? extends Е> с) – двонаправлена черга, ініційована
елементами колекції с.
Приклад 6. Демонстрація роботи методів:
public static void printDeque(Deque<?>d){
for (Object de : d)
System.out.println(de + "; ");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Deque<String>deque = new ArrayDeque<String>();
deque.add(new String("5"));
deque.addFirst("A");
//deque.addLast(new Integer(5));//помилка компіляції
System.out.println(deque.peek());
System.out.println("Before:");
printDeque(deque);
deque.pollFirst();
System.out.println(deque.remove(5));
System.out.println("After:");

89
printDeque(deque);
}
Виведення:
Before:
A;
5;
false
After:
5;
Використання двонаправленої черги як стеку:
// Створити чергу
ArrayDeque<String>adq=new ArrayDeque<String>();
//Використати ArrayDeque як стек.
adq.push("A") ;
adq.push("В");
adq.push("D");
adq.push("Е");
adq.push("F");
System.out.print ("Виштовхуємо зі стеку: ");
while(adq.peek()!=null)
System.out.print(adq.pop() + " ");
System.out.println();

Виведення:
Виштовхуємо зі стеку: F Е D В A

Тести
13. Розглянемо фрагмент коду:
11. public static void append(List list) {list.add("0042");}
12. public static void main(String[] args) {
13. List<Integer> intList = new ArrayList<Integer>();
14. append(intList);
15. System.out.println(intList.get(0));
16.}

Яким буде результат його виконання?


a) 42
b) 0042
c) генерує виняток при виконанні
d) помилка при компіляції, бо помилка в 13 рядку
e) помилка при компіляції, бо помилка в 14 рядку
14. Розглянемо код. Яким є результат його виконання?
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class HashTest {
private String str;
public HashTest(String str) {
this.str = str;
}

90
public String toString() {
return this.str;
}
public static void main(String args[]) {
HashTest h1 = new HashTest("2");
String s1 = new String("1");
List<Object> list = new LinkedList<Object>();
list.add(h1);
list.add(s1);
Collections.sort(list);
for (Object o : list) {
System.out.print(o + " ");
} } }

a) Виведеться"2 1".
b) Виведеться "1 2".
c) Помилка компіляції.
d) При виконанні генерується виняток.
15. Розглянемо код. Яким буде результат його виконання?
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class TryMe {
public static void main(String args[]) {
List list = new LinkedList<String>();
list.add("one");
list.add("two");
list.add("three");
Collections.reverse(list);
Iterator iter = list.iterator();
for (Object o : iter) {
System.out.print(o + " ");
}
}
}
a) виведеться "three two one "
b) виведеться " one two three"
c) нічого не виведеться
d) помилка компіляції
e) помилка при виконанні
16. Розглянемо код. Яким буде результат його виконання ?
import java.util.HashSet;
public class HashTest {
private String str;
public HashTest(String str) {

91
this.str = str;
}
public int hashCode() {
return this.str.hashCode();
}
public boolean equals(Object obj) {
return this.str.equals(obj);
}
public static void main(String args[]) {
HashTest h1 = new HashTest("1");
HashTest h2 = new HashTest("1");
String s1 = new String("2");
String s2 = new String("2");
HashSet<Object> hs = new HashSet<Object>();
hs.add(h1);
hs.add(h2);
hs.add(s1);
hs.add(s2);
System.out.print(hs.size());
}
}

a) виведеться "4"
b) виведеться "3"
c) виведеться "2"
d) помилка компіляції
e) помилка при вивконанні
17. Розглянемо код. Яким буде результат його виконання?
import java.util.*;
public class TryMe {
public static void main(String args[]) {
Queue<String> q = new PriorityQueue<String>();
q.add("3");
q.add("1");
q.add("2");
System.out.print(q.poll() + " ");
System.out.print(q.peek() + " ");
System.out.print(q.peek());
} }
a) 1 2 3
b) 3 2 1
c) 1 2 2
d) 3 1 1
e) 2 3 3
f) 1 1 2
18. Розглянемо код:
92
List<String> list =newArrayList<String>; // 1
list.add ("hello"); // 2
list.add ("My dear"); // 3
for(Object o : list ){ // 4
System.out.println (o); // 5
}
Яким буде результат його виконання?
a) runtime error
b) compile error
c) prints my dear hello
d) prints hello my dear
e) cannot predict the sequence in which the strings are printed.

Завдання для самостійного виконання


1. Створити Set із шести елементів (One two Two TWo twO Three).
Використовуючи метод iterator(), створити ітератор для проходження по
елементах, вивести всі елементи на екран. Вивести тільки ті елементи, що
знаходяться після слова ―two‖, ігноруючи регістр букв.
2. Створити клас Student з полями name (ім‘я студента) та allExamScores
(сумарний екзаменаційний бал). Створити колекцію ArrayList об‘єктів Student.
Вивести імена тих студентів, які отримуватимуть стипендію, якщо відомо
прохідний бал (наприклад, 75). Вивести імена всіх студентів, які отримуватимуть
стипендію, якщо відомо відсоток від кількості всієї групи тих, хто може її
отримувати.
3. Створити ArrayList з імен, перетворити його в колекцію HashSet для
підрахунку кількості унікальних імен.
4. Створити чергу з пріоритетом, куди занесіть дати народження своїх друзів.
Вивести чергу в порядку зростання.
5. Список типу FIFO. Створит клас LinkedList<String> patients. Пацієнта
вводити з клавіатури і, якщо його нема в списку, то додавати на початок списку.
Якщо ж пацієнт з таким іменем вже є, то вважати що новоприбулий є його родичем
і додавати до списку поряд з ним (справа або зліва), але при цьому виводити
повідомлення на кшталт «До Василя приєднався Василь-молодший». Таким чином
сформувати список з 7 пацієнтів. Вивести список у вигляді «№ пацієнта ім‘я»,
використовуючи ListIterator. Далі змоделювати обслуговування пацієнтів, яке буде
відбуватись з кінця списку: видаляти пацієнта, виводячи повідомлення на кшталт
«Василь заходить до лікаря»
6. Список типу LIFO. Змоделювати аналіз арифметичного виразу на
правильність дужок. Вираз у вигляді послідовності дужок задається у String.
Алгоритм: кожну відкриваючу дужку додаєте в кінець списку, але як тільки
зустрічаєте закриваючу – видаляєте відкриваючу з кінця. Якщо вираз правильний,
то розмір списку має бути 0.
7. «Гра в міста». Усім відома гра, в якій один гравець повинен назвати місто,
яке починається з літери, на яку закінчується місто, назване попереднім гравцем.
Коли гра триває довго, важко відслідкувати, чи не було повторів назв міст.
Напишіть програм, яка зчитує з клавіатури назву міста і, якщо воно ще не було

93
назване, додає його до списку, інакше каже гравцеві, що таке місто вже було.
Вивести список названих міст.

4.5. Інтерфейси Map та MapEntry. Hash-таблиці


Карта – це об‘єкт, який зберігає асоціації між ключами та значеннями або
пари «ключ-значення». По ключу знаходиться значення. І ключі, і значення є
об‘єктами. Ключі можуть бути унікальними, але значення можуть дублюватись.
Деякі карти допускають null-ключі і null-значення.
Карти не реалізують інтерфейс Iterable, тобто для проходження колекції не
можна використовувати ні ітератор, ні цикл "foreach", але можна перетворити
карту на іншу колекцію, яка дозволить це зробити.
Інтерфейс Map
Інтерфейс Мар відображає унікальні ключі на значення. Задаючи ключ і
значення, можна помістити значення в об‘єкт Мар. Після того, як значення
збережеться, його можна отримати по ключу. Інтерфейс оголошується так:
interface Мар<К, V>, де К – тип ключа, V– тип значення;
Методи інтерфейсу:
 void clear() – видаляє всі пари «ключ-значення» з колекції;
 boolean containsKey(Object k) – повертає true, якщо карта містить ключ k,
інакше – false;
 boolean containsValue(Object v) – повертає true, якщо карта містить
значення v, інакше – false;
 Set<Map.Entry<K,V>> entrySet()– повертає Set, що містить всі значення
карти (тобто перетворює карту в набір);
 boolean equals(Object obj) – повертає true, якщо obj є Map, що містить
однакові значення;
 v get(Object k) – повертає значення, що асоціюється з ключем k, якщо ж
ключ не знайдено, то поверне null;
 int hashCode() – повертає хеш-код карти;
 boolean isEmpty() – визначає, чи карта порожня;
 Set<K> keySet() – перетворює ключі карти в набір;
 v put(К k, V v) – поміщає елемент в карту, перезаписуючи будь-яке
попереднє значення, що асоціюється з ключем;
 V remove(Object k) – видаляє елемент з ключем k;
 int size() – повертає кількість пар «ключ-значення» в карті;
 Collection<V> values() – перетворює карту в колекцію.
Методи можуть викидати виключення C1assCastException, коли об‘єкт
несумісний з елементами карти, NullРоintеrЕхсерtiоn – якщо відбувається доступ
до null-об‘єкта. UnsupportedOperationException генерується при спробі змінити
карту, яка не модифікується.
Інтерфейс MapEntry
Інтерфейс Мар.Entry дозволяє працювати з елементом карти. Метод
entrySet(), що оголошений в інтерфейсі Мар, повертає Set, який містить елементи
карти. Кожен з елементів цього набору є об‘єктом типу Мар.Entry.
Методи інтерфейсу:

94
 boolean eqиals(Object obj) – повертає true, якщо obj є Map.Entry, ключ та
значення якого еквівалентні йому;
 K getKey() – повертає ключ даного елемента карти;
 V getValue() – повертає значення даного елемента карти;
 int hashCode() – повертає хеш-код даного елемента карти;
 V setValue(V v) – встановлює значення даного елемента карти рівним v.
Hash-таблиці
Хеш-таблиця (hash-table) – структура даних, яка реалізує інтерфейс
асоціативного масиву. На відміну від дерева пошуку, забезпечує менший час для
пошуку та вставки елементів. Є дуже ефективною для реалізації словників. В Java
класами, які реалізують хеш-таблиці, є HashMap, LinkedHashMap, Hashtable.
Клас Hashtable<K,V> реалізує інтерфейс Map, але має свої цікаві методи:
 Enumeration<V> elements() – повертає перечислення (аналог літератора)
для значення карти;
 Enumeration<K> keys() – повертає перечислення для ключів карти.
Приклад 7. Використання Hashtable (створення карти та пошук елементів по
ключу):
Hashtable<Integer, Double>ht =
new Hashtable<Integer, Double>();
for (int i = 0; i< 5; i++)
ht.put(i, Math.atan(i));
Enumeration<Integer>ek = ht.keys();
int key;
while (ek.hasMoreElements()) {
key = ek.nextElement();
System.out.printf("%4d ", key);
}
System.out.println("");
Enumeration<Double>ev = ht.elements();
double value;
while (ev.hasMoreElements()) {
value = ev.nextElement();
System.out.printf("%.2f ", value);
}
Результат роботи:
4 3 2 1 0
1.33 1.25 1.11 0.79 0.00
1.34

Клас HashMap
Класс HashMap використовує хеш-таблицю для зберігання і забезпечує
швидкість виконання запитів get() та put() при на великих наборах даних. Клас
реалізує інтерфейс Map (зберігання даних в вигляді пари ключ/значення). Ключі та
значення можуть бути будь-яких типів, в тому числі і null. При цьому всі ключі
обов‘язково мають бути унікальними, а значення можуть повторюватись. Дана
реалізація не гарантує впорядкованості елементів.
Для створення HashMap використовуються конструктори:
Map<String, Integer> hashMap = new HashMap<String, Integer>();
Map<String, String> hashMap = new HashMap<String, String>();

95
По замовчуванню при використанні порожнього конструктора створюється
картотека ємністю з 16 комірок. Можна вказати свою ємність та коефіцієнт
загрузки, використовуючи конструктори HashMap(capacity) та HashMap(capacity,
loadFactor). Максимальна ємність, яку можна встановити, дорівнює половині
максимального значення int(1073741824).
Приклад 8.
Map<String, String>hashMap = new HashMap<String, String>();
hashMap.put("0", "Василько);// додавання елемента
System.out.println(hashMap.size()); // отримання розміру
//перевіряємо ключі та значення на наявність
System.out.println(hashMap.containsKey("0"));
hashMap.containsValue("Василько");
for (String key : hashMap.keySet()) {
//вибираємо всі ключі
System.out.println("Key: " + key);
}
for (String value : hashMap.values()) {
System.out.println("Value: " + value);
}
//вибираємо всі ключі та значення
for (Map.Entry entry : hashMap.entrySet()) {
System.out.println("Key: " + entry.getKey() + " Value: "
+ entry.getValue());
}
При виведенні побачимо:
1
true
Key: 0
Value: Василько
Key: 0 Value: Василько
Розглянемо задачу: генерується набір випадкових чисел і підраховується
кількість повторів. Оскільки ключі є унікальними значеннями, то вирішення цієї
задачі за допомогою карти буде нескладним: в якості ключа буде використано
число, а значення – кількість повторів.
Random random = new Random(36);
Map<Integer, Integer> hashMap = new HashMap<>();
for (int i = 0; i < 100; i++){
// Створюємо число від 0 до 10
int number = random.nextInt(10);
Integer frequency = hashMap.get(number);
hashMap.put(number, frequency == null ? 1 : frequency + 1);
}
System.out.println(hashMap);
При виведенні можна побачити таке:
{0=8, 1=12, 2=14, 3=10, 4=6, 5=16, 6=6, 7=8, 8=9, 9=11}

4.6. Взаємні перетворення колекцій та карт


Можна перетворювати карти в колекції та навпаки. Приклад, який
демонструє перетворення карти (імена клієнтів та їх поточних балансів) в
колекцію-набір:
Приклад 9.

96
Hashtable<String, Double>balance=new Hashtable<String,Double>();
Enumeration<String>names;
Entry<String, Double>str;
String str1;
double bal;
balance.put("Джон Доу",3434.34);
balance.put("Toм Сміт",123.22);
balance.put("Джейн Бейкер",1378.00);
balance.put("Тод Холл",99.22);
balance.put("Ральф Сміт", 19.08);
// Показати всі рахунки в хеш-таблиці
names=balance.keys();
while(names.hasMoreElements()) {
str1=names.nextElement();
System.out.println(str1 + ": " + balance.get(str1));
}
// Перетворити таблицю на набір
Set<Entry<String,Double>>set=balance.entrySet();
System.out.println();
// Додати 1 000 на рахунок Джона Доу.
bal = balance.get("Джон Доу") ;
balance.put("Джон Доу", bal+1000);
System.out.println ("Новий баланс Джона Доу: " +
balance.get ("Джон Доу"));
// Отримати ітератор
System.out.println("Вивід перетвореного набору");
Iterator<Entry<String, Double>>itr=set.iterator();
while(itr.hasNext()){
str=itr.next();
System.out.println(str);}

Виведення буде таким:


Тод Холл: 99.22
Toм Сміт: 123.22
Ральф Сміт: 19.08
Джон Доу: 3434.34
Джейн Бейкер: 1378.0
Новий баланс Джона Доу: 4434.34
Вивід перетвореного набору
Тод Холл=99.22
Toм Сміт=123.22
Ральф Сміт=19.08
Джон Доу=4434.34
Джейн Бейкер=1378.0

4.7. Hash-контракт для ключів HashMaps


Всі об‘єкти в Java наслідують стандартну реалізацію методу hachCode(),
описаного в класі Object. Цей метод повертає хеш-код, отриманий шляхом
конвертації внутрішньої адреси об‘єкта в число, що приводить до створення
унікального коду для кожного конкретного об‘єкту.
Хеш-функція повинна повертати однаковий хеш-код кожного разу, коли
вона застосовується до однакових або рівних об‘єктів. Іншими словами, два
однакових об‘єкти повинні повертати однакові хеш-коди.

97
Тести
19. Розглянемо фрагмент коду:
34. HashMap props = new HashMap();
35. props.put("key45", "some value");
36. props.put("key 12", "some other value");
37. props.put("key39", "yet another value");
38. Set s = props.keySet();
39. //insert code here
Що необхідно поставити в 39 рядок, щоб відбувалося сортування ключів в наборі
HashMap?
a) Arrays.sort(s);
b) s = new TreeSet(s);
c) Collections.sort(s);
d) s = new SortedSet(s);
20. Розглянемо код:
import java.util.*;
classTest {
publicstaticvoid main (String[] args ){
Map<Integer,String> map =newLinkedHashMap<Integer,String>();
Map<Integer,String> sap =newHashMap<Integer,String>();
populate( map );
populate( sap );
System.out.println( map.get(1)+ sap.get(1));
}
staticvoid populate (Map m ){
for(int i = 0 ; i < 10 ; i++){
m.put(i,i);
}} }
Яким буде результат його виконання?
a) виведеться 11
b) виведеться 2
c) помилка під час виконання
d) помилка компіляції
e) nullnull
Завдання для самостійного виконання
1. Створити карту з 2 студентів, інформація про кожного з них також буде
картою і міститиме інформацію – прізвище, оцінка з фізики, математики,
української мови, англійської мови. Вивести інформацію про студентів на
екран. Знайти середнє значення оцінок з фізики. Видалити з карти студента,
що має оцінку нижчу 4.

ТЕМА 5. Технологія GENERICS


5.1. Призначення Generics.
98
Оголошення Generic-класів і методів
Словничок
generics – узагальнення
wildcard Parameters(wildcard) – маска
bounded wildcards – обмеження маски
diamond syntax – діамантовий синтаксис
raw types – сирі типи
Generics – це можливість узагальненого програмування, що була додана у
мову програмування Java в 2004 році як частина стандартної платформи J2SE 5.0.
Узагальнення дають можливість створювати типи або методи таким чином, щоб
вони могли оперувати різноманітними типами даних, при цьому на етапі
компіляції для типів забезпечується відповідний механізм безпеки.
Так, наприклад, метод може приймати параметри типу String або Integer і
повертати різноманітні типи без реалізації програмістом кількох різних методів.
Після появи узагальнень, ряд класів платформи Java були перероблені під їх
використання. Так, узагальнення реалізовані в колекціях класів Java (Java
Collections Framework), які можуть одночасно зберігати та оперувати
різноманітними типами даних. До їх появи для досягнення згаданих цілей
програміст використовував надкласи тих типів, з якими необхідно працювати
(наприклад Object), та, коли це було необхідно, здійснював перетворення типів,
проте при такому підході легко допуститися цілого ряду помилок, які виявляються
лише під час виконання програми. Це вимагало постійного використання перевірок
instanceOf і явного приведення типу, які, як відомо, не є безпечними і можуть
викидати ClassCastException.
Наступний блок Java-коду демонструє проблему, що виникає, коли
узагальнення не застосовуються. Спочатку створюється список: оголошується
ArrayList, методи якого працюють з даними типу Object, який є суперкласом для
усіх класів Java. Тобто ArrayList може працювати з будь-яким типом даних. Далі
додаємо рядок тексту типу String до списку. І зрештою, пробуємо одержати
доданий рядок, привівши його до типу Integer.
List v = new ArrayList();
v.add("test");
Integer i = (Integer)v.get(0); // Помилка часу виконання програми
Помилки під час компіляції ми не отримуємо. Проте ми отримуємо
повідомлення про виняткову ситуацію під час виконання програми
(java.lang.ClassCastException) у третьому рядку. Даний тип проблеми можна
усунути, застосувавши узагальнення, наперед передбачивши, які типи можна
поміщати в ArrayList.
З використанням узагальнень, вищенаведений код може бути переписаний
таким чином:
List<String>v = new ArrayList<String>();
v.add("test");
Integer i = v.get(0);// (type error) Помилка під час компіляції
Параметр типу String в кутових дужках говорить, що ArrayList призначений
для String. З узагальненнями не потрібне перетворення типів в третьому рядку до
будь-якого типу. Результатом v.get(0) буде String і компілятор тепер знає, який тип
повинен бути на виході, і краще прослідкує за правильністю використання

99
результату методу. Оскільки в програмі ми намагаємось записати результат у
змінну типу Integer, то отримаємо помилку під час компіляції програми. Звичайно,
що це лише спрощений приклад із застосування узагальнень.
Таким чином, класи мають бути спроектовані відповідно. Ось невеликий
витяг з опису інтерфейсів List та Iterator в пакеті java.util:
public interfaceList<E> {
void add(E x);
Iterator<E>iterator();
}
public interface Iterator<E> {
E next();
boolean hasNext();
}
Оголосити зі змінними generic можна:
 клас і інтерфейс (після імені класу чи інтерфейсу в кутових дужках
вказуються перераховані через кому generic)
public class MyMap<K, V>
 метод (перед типом повертання в кутових дужках вказуються generic)
public <T> void fill (List<T>list, T val)
При створенні об‘єкта generic-класу слід вказати, які типи даних
використовувати. При виклику параметризованого методу типи даних можна
вказати наступним чином:
<Integer>swap (ints, 1, 3);
Приклад опису узагальнюючого класу.
public class Entry<K, V> {
private final K key;
private final V value;
public Entry(K k,V v) {
key = k;
value = v;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public StringtoString() {
return "(" + key + ", " + value + ")";
}
}
Даний узагальнений клас може бути використаний наступним чином:
Entry<String, String> grade10 = new Entry<String, String>("mike", "A");
Entry<String, Integer> grade11 = new Entry<String, Integer>("mike", 100);
System.out.println("grade10: " + grade10);
System.out.println("grade11: " + grade11);

Щоб спростити життя програмістам, в Java7 був введений алмазний


синтаксис (diamond syntax), в якому можна опустити параметри типу. Тобто, можна
надати компілятору визначення типів при створенні об‘єкта. Вид спрощеного
оголошення:
Entry<String, String> grade10 = new Entry<>("mike", "A");

100
Слід зазначити, що, з метою сумісності з раннім версіями Java, при
використанні generic допустимо не вказувати тип даних (використовувати так звані
raw types), однак це не рекомендується, оскільки повертає всі ті проблеми, які
покликаний вирішити цей механізм.
За аналогією з універсальними класами (generic-класами), можна
створювати універсальні методи (generic-методи), тобто методи, які приймають
загальні типи параметрів. Універсальні методи не треба плутати з методами в
generic-класі. Універсальні методи зручні, коли одна і та ж функціональність
повинна застосовуватися до різних типів. (Наприклад, є численні загальні методи в
класі java.util.Collections.)
Розглянемо реалізацію такого методу:
class Utilities {
public static <T> void fill(List<T>list, T val) {
for (int i = 0; i <list.size(); i++)
list.set(i, val);
}
}
classTest {
public static void main(String[] args) {
List<Integer>intList = new ArrayList<Integer>();
intList.add(1);
intList.add(2);
System.out.println("Список до обробки методом: " +
intList);
Utilities.fill(intList, 0);
System.out.println("Список після обробки: " +
intList);
}
}
Нам в першу чергу цікаво це:
public static <T> void fill(List<T>list, T val)
"<T>" розміщено після ключових слів "public" і "static", а потім слідують
тип повернення значення, ім‘я методу і його параметри. Таке оголошення
відрізняється від оголошення універсальних класів, де універсальний параметр
вказується після імені класу. Тіло методу цілком звичайне – в циклі всі елементи
списку встановлюються в одне значення (val). Ну і в main() відбувається виклик
нашого універсального методу:
Utilities.fill(intList, 0);
Варто звернути увагу на те, що тут не заданий явно тип параметра. Для
IntList - це Integer і 100 теж упаковується в Integer. Компілятор ставить у
відповідність типу Т - Integer.
Можливі помилки, пов‘язані з імпортом List з java.awt замість java.util.
Важливо пам‘ятати, що список з java.util є універсальним типом, а список з
java.awt - ні.
5.2. Обмеження при оголошенні Generic-класів
Підтримка generic реалізована засобами компілятора. Під час компіляції
generic-параметри приводяться до Object і там, де це потрібно, замість них робимо
приведення типів. Відповідно, під час виконання інформація про підставні generic
недоступна. Ця технологія породжує ряд обмежень:

101
 відсутня можливість визначити generic примітивним типом;
 такі прості типи, як, наприклад, long або double, передавати не можна. Але
дане обмеження не становить серйозної проблеми, оскільки в Java для
кожного примітивного типу є відповідний клас-оболонка.
 неможливо створити об‘єкт типу generic (неприпустимі конструкції виду
E e = new E ()), це очевидно, оскільки наперед невідомо, чи буде це
повноцінний клас, чи абстрактний, чи інтерфейс. Те ж саме стосується
створення масивів;
 неможливо створити масив параметризованого типу;
 у параметризованому класі заборонено використовувати generic в
статичному контексті;
 неможливо використовувати параметризовані класи як правий операнд
оператора instanceof ;
 неможливо вказати generic в секціях catch і throw (проте допустимо в секції
throws );
 неможливо перевантажувати методи таким чином, щоб після приведення
типів сигнатури обох методів співпадали.
Java надає можливість обмежити допустимі generic-типи. При оголошенні
параметризованого класу або методу можна використовувати конструкції виду <T
extends Phone>. У цьому випадку як T може фігурувати тільки Phone і його
спадкоємці. Можна вказати один клас і кілька інтерфейсів, розділяючи їх
оператором &.
5.3. Шаблони в Generic-аргументах методів
Механізм Generic надає можливість оперувати з масками (wildcard) –
замість типу використовувати знак питання. Вони використовуються тоді, коли
потрібно абстрагуватися від конкретних аргументів типу, і дозволяють
використовувати його там, де не потрібно знати конкретні типи параметрів.
Використовувати маски можна без обмежень (наприклад, Map<key, ?> , а також з
обмеженнями List<? extends MyClass>, List<? super MyClass>).
Приклад коду з використанням маски:
static void printList(List<?>list) {
for (Objectl : list)
System.out.println("{" + l + "}");
}
public static void main(String[] args) {
List<Integer>list = new ArrayList<>();
list.add(10);
list.add(100);
printList(list);
List<String>strList = new ArrayList<>();
strList.add("10");
strList.add("100");
printList(strList);
}
Метод printList приймає список, для якого в сигнатурі використана маска:
static void printList(List<?>list)
І цей метод працює для списків з різними типами даних (в прикладі Integer і
String).

102
Однак наступний код не скомпілюється:
List<?>intList = new ArrayList<Integer>();
intList.add(new Integer(10));
// intList.add(newFloat(10.0f));
Причина в тому, що при використанні маски ми повідомляємо компілятору,
щоб він ігнорував інформацію про тип, тобто <?> – невідомий тип. При кожній
спробі передачі аргументів generic-типу компілятор Java намагається визначити
тип переданого аргументу. Однак тепер ми використовуємо метод add() для
вставки елемента в список. При використанні маски ми не знаємо, якого типу
аргумент може бути переданий. Тут знову є можливість помилки, бо якби
додавання було можливе, то ми могли б спробувати додати в наш список,
призначений для чисел, рядкове значення. Щоб уникнути цієї проблеми,
компілятор не дозволяє викликати методи, які можуть додати невалідний тип –
наприклад, додати значення типу Float, з яким ми потім спробуємо працювати як з
Integer.
І ще один приклад:
List<?>numList = new ArrayList<Integer>();
numList = new ArrayList<String>();
Тут не виникне проблем компіляції. Однак недобре, що змінна numList
зберігає список з рядками. Припустимо нам потрібно так оголосити цю змінну,
щоб вона зберігала тільки списки чисел. Рішення є:
List<? extends Number>numList = new ArrayList<Integer>();
numList = new ArrayList<String>();
Даний код не скомпілюється. Причиною буде те, що за допомогою маски ми
задали обмеження. Мінлива numList може зберігати посилання тільки на список,
що містить елементи, успадковані від Number, а все через оголошення: List<?
extends Number>numList. Тут ми бачимо, як масці задається обмеження – тепер
numList призначений для списку з обмеженою кількістю типів. Double як і Integer
успадковується від Number, тому код, наведений нижче, скомпілюється.
List<? extends Number>numList = new ArrayList<Integer>();
numList = new ArrayList<Double>();
Конструкції, описані вище, називаються обмеженими масками (Bounded
wildcards). Застосування їх може бути вельми красивим і корисним. Припустимо,
нам необхідно порахувати суму чисел різного типу, які зберігаються в одному
списку:
public static Doublesum(List<? extends Number>numList) {
double result = 0.0;
for (Numbernum : numList) {
result += num.doubleValue();
}
return result;
}
Double-тип був використаний для змінної result, тому він без проблем
взаємодіє з іншими числовими типами (тобто не буде проблем з приведенням
типів).
Аналогічно ключовому слову extends, в подібного роду виразах може
використовуватися ключове слово super.

103
Опис типу <? super Number> має на увазі «будь-який тип, який є супер-
класом для Number», включаючи сам Number. Отже, можна передати в метод,
наприклад, List<Object>.
Оскільки тип елементів в цьому списку є «щось, що є суперкласом для
Number», то можна додавати до списку елементи, тип яких є дочірнім для Number.
Наприклад, Integer:
public void add(List<? super Number>list) {
list.add(1);
}
Це дозволено, бо в список можна додавати елементи, тип яких є дочірнім
для типу елементів списку, як у випадку зі звичайним List<Number>:
List<Number>list = new ArrayList<>();
list.add(1);
Однак не можна додати в список newObject(), тому що немає ніяких
гарантій того, що в списку можна зберігати елементи типу Object: під «?» може
«ховатися» не тільки Object, а й, наприклад, сам Number. У разі інших класів (?
super X), що мають проміжні класи між Object і самим X, – ще й будь-який
проміжний клас.
Оскільки істинний тип елементів списку невідомий (?), але будь-який клас
має в якості суперкласу Object, то з отриманим зі списку елементом можна
працювати тільки як з Object. ,
У разі <? extends Number> мається на увазі «будь-який тип, який є дочірнім
класом для Number», а також сам Number; в цьому випадку можна передати,
наприклад, List<Іnteger> в метод. Так як тип елементів є «щось, що є дочірнім
класом для Number», то неможливо нічого додати в список (крім, хіба що, null), бо
невідомо, що саме там може зберігатися.
А ось з отриманим зі списку елементом можна працювати як з Number:
public static void add2(List<? extends Number>list){
int value = list.get(0).intValue();
}
Бо які б там не були елементи в списку, але вони точно є з дочірніх для
Number класів, а, значить, можуть працювати як Number.

5.4. Алгоритми Generic-колекцій класу Collection


У каркасі колекцій визначається ряд алгоритмів, які можна застосовувати до
колекцій і відображень. Ці алгоритми визначені у вигляді статичних методів з
класу Collections, деякі з них перераховані в наступній таблиці (таблиця 2.1). У
версїї JDK 5 всі алгоритми були перероблені з врахуванням узагальнень. Тепер
вони стали більш безпечні у відношенні типів.
Таблиця 2.1
static<Т>bоо1еаn addAl1(Col1ection Додає елементи, які передаються в
<? super Т> с, Т ... elements) elements, в колекцію, вказану в с.
Повертає true, якщо елементи були
додані, і false – в інакшому випадку.

104
static<Т>int binarySearch (List<? extends Виконує пошук в list значення value у
Т>list, Тvalue, Comparator<? super Т> с) відповідності з с. Повертає позицію
value в list, або від‘ємне число, якщо
value не знайдено.
static int indexOfSubList(List<?>list, Шукає в list перше входження subList.
List<?> subList) Повертає індекс першого співпадіння,
або -1, якщо входження не знайдено.
static <Т> void сору (List<? super Т> Копіює елементи list2 в list1
listl, List<? Extends Т> list2)
static boolean disjoint(Collection<?> а, Порівнює елементи в а з елементами в
Collection<?>b) b. Повертає true, якщо ці колекції не
містять спільних елементів, false – в
інакшому випадку.
static<Т>void fil1 (List<? super Т>list, Т Присвоює obj кожному елементу list
оbj)
static int frequency(Collection<?> Підраховує кількість входжень obj в c
с,Objectobj) та повертає результат.
static<Т> Т mах (Collection<? extends Т> Повертає максимальний елемент з с,
с, Comparator<? super Т>соmр) визначений за допомогою comp.
static<Т extends Object&Comparable<? Повертає максимальний елемент з с.
super Т>> Тmах (Collection<? extends Т> Колекція має бути відсортована.
с)
static<Т>boolean replaceAll (List<T> Замінює всі вхождения old на new в
list, Т old, Т new) list.
static void reverse (List<T>list) Змінює послідовність елементів в list
на зворотню
static void swap (List<T> list, intidxl, int Міняє місцями елементи list, що
idx2) знаходяться на позиціях idxlіidx2.
Більше методів можна побачити в API до класу Collection
Тести
21. Які з наведених нижче рядків відкомпілюються без проблем?
a) List <Integer> list = new List<Integer>();
b) List <Integer> list = new ArrayList<Integer>();
c) List <Number> list = new ArrayList<Integer>();
d) List <Integer> list = new ArrayList<Number>();
Перед відповіддю на це питання слід врахувати, що List – інтерфейс,
ArrayList успадковується від List; Number – абстрактний клас і Integer
успадковується від Number.
22. Дано наступний код:
11. // Додайте сюди рядок
12. private N min, max;
13. public N getMin() { return min; }
14. public N getMax() { return max; }
15. public void add( N added ) {
16. if (min == null || added.doubleValue() <min.doubleValue())

105
17. min = added;
18. if (max == null || added.doubleValue() >max.doubleValue())
19. max = added;
20. }
21. }
Які два з наступних варіантів можна підставити в рядок 11 для того, щоб
код вище скомпілювався коректно?
a) public class MinMax<?> {
b) public class MinMax<? extends Number> {
c) public class MinMax<N extends Object> {
d) public class MinMax<N extends Number> {
e) public class MinMax<? extends Object> {
f) public class MinMax<N extends Integer>{
23. Дано наступний код:
public static void main(Stringargs[ ] ){
List<? extends Number>type = new ArrayList<Integer>(); // 1
for (Integer n : type ) { // 2
System.out.println(n); // 3
}
public <T> void seth (List<?>type) { // 4
type.add("hi"); // 5
} }

a) рядки 2, 5 мають помилки компіляції


b) рядки 1, 5 мають помилки компіляції
c) рядки 2, 5 і 1 мають помилки компіляції
d) рядки 4, 5 мають помилки компіляції
e) нічого з вищепереліченого
24. Дано наступний код:
import java.util.*;
class Empty { }
class Extende dextends Empty {}
public class TryMe {
public static void doStuff1(List<Empty>list) {
// деякий код
}
public static void doStuff2(Listlist) {
// деякий код
}
public static void doStuff3(List<? extends Empty>list) {
// деякий код
}
public static void main(Stringargs[]) {
List<Empty>list1 = new LinkedList<Empty>();
List<Extended>list2 = new LinkedList<Extended>();
// Тут викликається метод
}
}
Вкажіть всі коректні виклики методів
a) doStuff1(list1);
b) doStuff2(list2);
c) doStuff2(list1);

106
d) doStuff3(list1);
e) doStuff3(list2);
f) doStuff1(list2);
25. Дано наступний код. Який результат його виконання?
public class TrickyNum<X extends Object> {
private X x;
public TrickyNum(X x) {
this.x = x;
}
private double getDouble() {
return x.doubleValue();
}
public static void main(String args[]) {
TrickyNum<Integer>a = new
TrickyNum<Integer>(new Integer(1));
System.out.print(a.getDouble());
}
}
a) помилка компіляції
b) помилка часу виконання
c) надрукується "1.0"
d) надрукується "1"
26. Дано наступний код. Який результат його виконання?
public class TrickyNum <X extends Object> {
private X x;
publicTrickyNum(X x) {
this.x = x;
}
private double getDouble() {
return ((Double) x).doubleValue();
}
public static void main(String args[]) {
TrickyNum<Integer>a = new
TrickyNum<Integer>(new Integer(1));
System.out.print(a.getDouble());
}
}

a) помилка компіляції
b) помилка часу виконання
c) надрукується "1.0"
d) надрукується "1"

107
ВІДПОВІДІ ДО ТЕСТІВ
РОЗДІЛ ІІ.
ФРЕЙМВОРК КОЛЕКЦІЙ
Відповіді до тестів (Тема 4.1)
1. a, d (в варіанті b в описі вжито не ті службові слова, в варіанті c пропущені
дужки {})
2. a, e (варіант b невірний, бо при описі всі елементи числового типу ініціюються
0; варіант c невірний, бо елемента з номером 25 не існує; варіант d невірний, бо
елементи числового типу і не можуть бути null)
3. b, e (варіант B неправильний, тому що не можна вказувати розмір масиву при
його динамічній ініціалізації; варіант E неправильний, бо розмірність масиву
вказується при створенні масиву, а не при оголошенні)

Відповіді до тестів (Тема 4.2)


4. e (хоч arr2 оголошений як масив з 4 елементів, але при присвоєнні посилань він
буде зв’язаний з масивом arr, в якому є 5 елементів. Це можна перевірити, вивівши
arr.length)
5. c (в варіанті а невірно записано цикл foreach; в варіанті b неправильний запис
масиву в умові циклу; в варіанті d цикл for записано невірно)
6. a (метод sort не застосовується до аргументів типу boolean)
7. c (метод toString повертає результат типу String, а не масив String)
8. d (метод equals не може порівняти масив примітивів з масивом об’єктів)
9. e (вони будуть рівними, бо розпакування – unboxing перетворює об’єкт до
примітива)

Відповіді до тестів (Тема 4.3)


10. e (цей рядок викличе помилку компіляції, бо тип об’єктів ListObject є вужчим,
ніж тип об’єктів ArrayList)
11. b
12. b (тому що колекції еквівалентні, бо співпадає кількість та значення
елементів)

Відповіді до тестів (Тема 4.4)


13. g
14. b
15. d (можна переходити тільки по масиву або екземпляру класу java.lang.Iterable)
16. b
17. c
18. b (в конструкторі при створенні об’єкту ArrayList опущені круглі дужки)

Відповіді до тестів (Тема 4.7)

108
19. b (варіант а невірний, бо метод sort класу Arrays приймає аргументи типу int;
варіант c невірний, бо метод sort з Collection приймає аргументи типу
List<T>;варіант d невірний, бо конструктор не приймає аргументи такого типу)
20. с (рядок з викликом методу get викличе виняток ClassCastException через
неможливість зробити кастинг аргументу до String)

Відповіді до тестів (Тема 5.4)


21. b (варіант a неправильний, тому що не можна створювати об’єкт
інтерфейсу. У варіанті b ми створюємо об’єкт типу ArrayList і посилання на
нього базового для ArrayList класу. І там, і там generic-тип однаковий - все
правильно. У c і d випадку буде помилка компіляції, тому що generic-типи повинні
бути однаковими (зв’язок успадкування тут ніяк не враховується).
22. d, f
23. a (Ви не можете додати що-небудь до List, що має <?> (якщо він не має super,
за яким слідує назва класу), і тип main() має бути посиланням на Number, а не на
Integer.
24. a, b, c, d, e
25. a
26. b (java.lang.Integer не може бути приведеним до java.lang.Double)

109
РОЗДІЛ ІІІ. ПРОГРАМУВАННЯ ANDROID,
БАЗ ДАНИХ, БАГАТОПОТОКОВЕ
ПРОГРАМУВАННЯ
ТЕМА 6. Розробка ANDROID-проектів
6.1. Структура Android-проекту
Словничок
Linux – загальна назва UNIX-подібних операційних систем на основі однойменного
ядра. Це один із найвидатніших прикладів розробки вільного (free) та відкритого (з
відкритим кодом, open source) програмного забезпечення (software). На відміну від
власницьких операційних систем (на кшталт Microsoft Windows та MacOS X), їх
вихідні коди доступні усім для використання, зміни та розповсюдження
абсолютно вільно (в тому числі безкоштовно)
OpenGL – специфікація, що визначає незалежний від мови програмування крос-
платформовий програмний інтерфейс (API) для написання застосунків, що
використовують 2D та 3D графіку
SQLite – бази даних, таблиці
WYSIWYG – (МФА: [ˈwɪziwɪɡ]) – акронім від What You See Is What You Get
(англ. що бачиш, те й отримуєш). Застосовується до комп'ютерних програм, які
надають можливість користувачу бачити щось дуже подібне до кінцевого
результату під час створення документів або зображень. Наприклад, користувач
може бачити на екрані, як виглядатиме документ, видрукований на папері або
відображений у веб-оглядачі.
Android – безкоштовна операційна система, заснована на Linux з мовою
програмування Java.
Android підтримує 2D і 3D-графіку, використовуючи бібліотеки OpenGL, а
також зберігання даних в базі даних SQLite.
Кожний Android-додаток запускається в своєму власному процесі. Тому
додаток ізольовано від інших запущених додатків і неправильно працюючий
додаток не може зашкодити іншим додаткам.
Створення проекту в Eclipse
1. Натисніть New на панелі інструментів.
2. У вікні (рис 3.1), відкрийте Android папку,
виберіть Android Application Project, і
натисніть Next.
Майстер створення нового проекту
Android програми в Eclipse.
1. Заповніть форму, яка з'явилася:
 Application Name це ім'я додаток, яке
відображається користувачам. Для
цього проекту, використовуйте «My
First App».

110
 Project Name це ім'я в каталозі проекту і назва видима в Eclipse.
 Package Name є простором імен пакету для вашого Рис 3.1 застосування
(дотримуйтесь тих же правил для пакетів мови програмування Java). Ваше
ім'я пакета повинно бути унікальним. Рекомендовано використовувати ім'я,
яке починається з записаного в зворотному порядку доменного імені вашої
організації або видавця. Для цього проекту ви можете використовувати
щось на зразок «com.example.myfirstapp». Але ви не можете опублікувати
ваш додаток на Google Play, використовуючи простір імен «com.example».
 Minimum Required SDK є найнижчою версією Android, яку додаток
підтримує. Вказується, використовуючи рівень API. Для підтримки якомога
більше пристроїв, потрібно встановити її в найменшу доступну версію, що
дозволяє вашому додатку забезпечити основний набір функцій. Якщо будь-
яка функціональність вашого застосування можлива тільки на більш нових
версіях Android, і вона не є критичною для основного набору функцій
програми,то ви можете включити функцію для платформ, що працюють на
версіях, які її підтримують. Залиште значення за замовчуванням для цього
проекту.
 Target SDK вказує на найвищу версію Android, з якої ви перевірили додаток.
Із появою нових версій Android, ви повинні протестувати додаток на новій
версії і оновити це значення відповідно до останнього рівня API, для того,
щоб скористатися новими функціями платформи.
 Compile With є версією платформи, на якій компілюється додаток. За
замовчуванням цей параметр встановлений на останню версію Android,
доступну в вашому SDK. (Вона повинна бути Android 4.1 або вище, якщо у
вас така версія не доступна, необхідно встановити її використовуючи SDK
менеджер). Ви все ще можете побудувати ваш додаток для підтримки
старих версій, але установка цільової версії в значення останньої версії
дозволяє включити нові функції і оптимізувати додаток.
 Theme визначає застосовуваний стиль користувацького інтерфейсу Android
для вашого додатку. Ви можете залишити параметр за замовчуванням.
 Натисніть Next.
2. У наступному вікні для налаштування проекту залиште обрані значення за
замовчуванням і натисніть Next.
3. Наступний екран може допомогти вам створити значок запуску для вашого
додатка. Ви можете змінити значок декількома способами. І інструмент
згенерує іконку для всіх екранів. Перед публікацією додатка впевніться, що
значок відповідає специфікаціям. Натисніть Next.
4. Тепер можете вибрати шаблон діяльності, з якої почнемо створення додатку.
Для цього проекту, виберіть BlankActivity і натисніть Next.
5. Залиште всі деталі для діяльності в стані за замовчуванням і натисніть Finish.
Ваш Android-проект в даний час є базовим «Hello World» додатком, який
містить деякі файли за замовчуванням.
Запуск Android-проекту
Запускати Android-проекти можна на реальному пристрої або на емуляторі.
Розглянемо спосіб запуску на емуляторі.

111
Чи ви використовуєте Android Studio, Eclipse або командний рядок, щоб
запустити свій додаток на емуляторі потрібно спочатку створити віртуальний
пристрій Android (AVD від англійського Android Virtual Device). AVD є
конфігурацією пристрою для Android-емулятора, який дозволяє моделювати різні
пристрої.
Менеджер AVD (рис 3.2)які показують кілька віртуальних пристроїв.

Рис. 3.2

Для створення AVD:


1. Запустіть Управління віртуальними пристроями Android:
 В Android Studio натисніть значок AVD Manager на панелі інструментів.
 В Eclipse, натисніть Android Virtual Device Manager на панелі інструментів.
 З командного рядка, перейдіть в каталог <sdk>/tools/ і виконайте: android
avd
2. В Android Virtual Device Manager панелі, натисніть New.
3. Заповніть всю необхідну інформацію для AVD. Дайте йому ім‘я, цільову
платформу, розміру SD карти, і скін (HVGA замовчуванням).
4. Натисніть Create AVD.
5. Виберіть новий AVD в Android Virtual Device Manager і натисніть Start.
6. Після того як емулятор завантажиться, розблокуйте екран емулятора.
Щоб запустити додаток з Eclipse:
1. Відкрийте один з файлів вашого проекту і натисніть Запустити на панелі
інструментів.
2. У вікні Запуск від імені, яке з‘являється, виберіть Android Application і
натисніть OK .
Eclipse встановлює додаток на AVD і запускає його.
Або запустіть додаток з командного рядка:
1. Перейдіть до кореневого каталогу вашого Android проекту і виконайте: ant
debug
2. Переконайтеся, що Android SDK platform-tools/ папка включена в вашу PATH
змінну середовища, потім виконайте: adb install bin/MyFirstApp-debug.apk
3. В емуляторі, знайдіть MyFirstActivity і відкрийте його.

112
6.2. Прості Android-проекти із використанням текстів та
кнопок
Створення лінійного макета
Відкрийте fragment_main.xml файл з res/layout/ папки.
В Eclipse, коли ви відкриваєте файл макета, спочатку відкриється редактор
графічних макетів. Це редактор, який допоможе вам побудувати макети з
використанням WYSIWYG інструментів. Натисніть fragment_main.xml вкладку в
нижній частині екрана, щоб відкрити XML редактор.
Шаблон BlankActivity, який ви вибрали при створенні цього проекту,
включає в себе fragment_main.xml файл з RelativeLayout поданням верхнього рівня
і TextView дочірнім поданням.
Спочатку видаліть <TextView> елементі змініть <RelativeLayout> елемент
на <LinearLayout> Потім додайте android:orientation атрибут і встановіть його в
«horizontal».
Результат виглядає наступним чином:
<LinearLayout
xmlns:android=»http://schemas.android.com/apk/res/android»
xmlns:tools=»http://schemas.android.com/tools»
android:layout_width=»match_parent»
android:layout_height=»match_parent»
android:orientation=»horizontal» >
</LinearLayout>
LinearLayout являє собою групу View (підклас ViewGroup), який розкладає
дочірні View в вертикальному або горизонтальному положенні, як зазначено в
android:orientation атрибуті. Кожен дочірній елемент LinearLayout з‘являється на
екрані в тому порядку, в якому він з‘являється в XML.
Два інших атрибута, android:layout_width і android:layout_height, потрібні
для всіх View для того, щоб вказати їх розмір.
Оскільки LinearLayout є коренем в макеті, він повинен заповнити всю
область екрану, яка доступна для додатка, встановивши ширину і висоту в
«match_parent». Це значення вказує, що подання має змінити свою ширину або
висоту до відповідності ширини або висоти батьківського View.
Додавання текстового поля
Щоб створити текстове поле, що редагується користувачем, додайте
<EditText> елемент всередині <LinearLayout>.
Як і будь-якому View об‘єкту, необхідно задати певні XML атрибути для
вказівки EditText властивостей об‘єкта. Ось як ви повинні оголосити його в
<LinearLayout> елементі:
<EditText android:id=»@+id/edit_message»
android:layout_width=‛wrap_content‛
android:layout_height=‛wrap_content‛
android:hint=‛@string/edit_message‛/>

Про об’єкти ресурсів


Об‘єкт ресурсу – це просто унікальне ціле ім‘я, яке асоціюється з ресурсом
додатка, таким як растрове зображення, файл макета або рядки.

113
Кожен ресурс має відповідний об‘єкт ресурсу, визначений у проекті в
gen/R.java файлі. Ви можете використовувати імена об‘єктів в R класі для
позначення ваших ресурсів, наприклад, коли необхідно вказати значення рядка для
android:hint атрибуту. Також можна створювати довільні ідентифікатори ресурсів,
вони будуть асоціювати з поданням за допомогою android:id атрибута, який
дозволяє посилатися на це поданням з іншого коду.
Інструменти SDK створюють R.java щоразу при компіляції програми. Ви
ніколи не повинні змінювати цей файл вручну.
Атрибут android:id забезпечує унікальний ідентифікатор для подання, який
можна використовувати для посилання на об‘єкт з коду програми для читання і
маніпулювання об‘єктом. Знак «@» потрібний тоді, коли ви посилаєтеся на будь-
який об‘єкт ресурсу з XML. За ним йде тип ресурсу (id в даному випадку), похила
риска («/», slash), а потім ім‘я ресурсу (edit_message). Знак плюс (+) перед типом
ресурсу потрібно тільки тоді, коли ви визначаєте ідентифікатор ресурсу вперше.
При компіляції програми, інструменти SDK використовують це ім‘я
ідентифікатора ресурсу для створення нового ідентифікатора ресурсу в проекті в
gen/R.java файлі, який належить до EditText елементу. Після того, як ідентифікатор
ресурсу оголошується один раз таким чином, інші посилання на нього не
вимагають знака (+). Використовувати знак (+) необхідно тільки при вказівці
нового ідентифікатора ресурсу, але не потрібно для конкретних ресурсів (рядки
або макети). Замість того, щоб використовувати конкретні розміри по ширині і
висоті, wrap_content вказує, що подання має бути великим на стільки, щоб
вмістити контент. Якщо використовувати замість нього match_paren, то EditText
елемент заповнив би весь екран, тому що це б відповідало розміру батьківського
LinearLayout. Це рядок за замовчуванням для відображення, коли текстове поле
пусте. Замість використання жорстко запрограмованого рядка в якості значення,
@string/edit_message значення посилається на ресурс рядка, визначеного в
окремому файлі. Оскільки це відноситься до конкретного ресурсу (це не просто
ідентифікатор), йому не потрібно вказувати знак (+). Але через те, що ще не
визначений рядковий ресурс, на цьому етапі буде помилка компілятора.
Примітка: Цей рядковий ресурс має те ж ім‘я, що і елемент ідентифікатора:
edit_message. Однак посилання на ресурси завжди об‘єднані за типом ресурсів
(таких як id або string), тому використання такого ж імені не викликає колізій.
Додавання рядкових ресурсів
Щоб додати текст в призначений для користувача інтерфейс, треба вказати
кожен рядок як ресурс. Рядкові ресурси дозволяють керувати всім текстом
призначеного для користувача інтерфейсу в одному місці, що дозволяє його легше
знайти і оновити текст. Використання зовнішніх ресурсів для рядків також
дозволяє локалізувати додаток різними мовами, надаючи альтернативні визначення
для кожного рядкового ресурсу.
За замовчуванням, Android-проект включає в себе файл рядкових ресурсів
res/values/strings.xml. Додайте новий рядок з ім‘ям «edit_message» і задайте йому
значення «Enter a message». Поки ви перебуваєте в цьому файлі, також додайте
рядок «Send» для кнопки з ім‘ям «button_send», яку ви скоро додасте,.
В результаті strings.xml виглядає наступним чином:
<?xml version=»1.0» encoding=»utf-8»?>

114
<resources>
<string name=‛app_name‛>My First App</string>
<string name=‛edit_message‛>Enter a message</string>
<string name=‛button_send‛>Send</string>
<string name=‛action_settings‛>Settings</string>
<string name=‛title_activity_main‛>MainActivity</string>
</resources>
Додавання кнопки
Додамо кнопку <Button> в макет, відразу після <EditText> елемента:
<Button
android:layout_width=‛wrap_content‛
android:layout_height=‛wrap_content‛
android:text=‛@string/button_send‛ />
Висота і ширина встановлені в «wrap_content», тому кнопка буде такого
розміру, щоб вмістити текст кнопки. Цій кнопці не потрібен android:id атрибут,
тому що на неї не будуть посилатися.
Зробити поле введення за шириною екрану
Це можна зробити всередині LinearLayout за допомогою weight властивості,
яке можна вказати за допомогою android:layout_weight атрибута.
<EditText
android:layout_weight=‛1‛
… />
Встановлення ширини, рівної 0 підвищує продуктивність макета, тому що
використання «wrap_content» в якості ширини вимагає від системи обчислення цієї
ширини, яка в кінцевому рахунку не має значення.
<EditText
android:layout_weight=‛1‛
android:layout_width=‛0dp‛
… />

Файл макета повинен виглядати так:


<?xml version=‛1.0‛ encoding=‛utf-8‛?>
<LinearLayout xmlns:android=‛http://schemas.android.com/apk/res/android‛
xmlns:tools=‛http://schemas.android.com/tools‛
android:layout_width=‛match_parent‛
android:layout_height=‛match_parent‛
android:orientation=‛horizontal‛>
<EditText android:id=‛@+id/edit_message‛
android:layout_weight=‛1‛
android:layout_width=‛0dp‛
android:layout_height=‛wrap_content‛
android:hint=‛@string/edit_message‛ />
<Button
android:layout_width=‛wrap_content‛
android:layout_height=‛wrap_content‛
android:text=‛@string/button_send‛ />
</LinearLayout>
Цей макет застосовується за умовчанням до Activity класу, який інструменти
SDK згенерували при створенні проекту, так що тепер можна запустити додаток,
щоб побачити результати:
 В Eclipse, виберіть пункт Run на панелі інструментів.

115
 Або з командного рядка перейдіть в кореневий каталог вашого Android-
проекту і виконайте:
ant debug
adb install bin/MyFirstApp-debug.apk
Завдання для самостійного виконання
1. Розробіть одновіконний калькулятор.

6.3. Навігація між вікнами Android-проекту


До цього часу ми розглядали проекти, які містили лише один екран
(Activity). Але, якщо ви користуєтесь смартфоном з Android, то помітили, що
екранів у додатку зазвичай більше.
Для того, щоб створити багатовіконний проект, спершу створимо проект.
Project name: P0211_TwoActivity
Build Target: Android 2.3.3
Application name: TwoActivity
Package name: ru.startandroid.develop.p0211twoactivity
Create Activity: MainActivity
Відкриємо main.xml та створимо наступний екран:
<?xml version=‛1. 0‛ encoding=‛utf-8‛?>
<LinearLayout
xmlns: android=‛http: //schemas. Android. Com/apk/res/android‛
android: orientation=‛vertical‛
android: layout_width=‛fill_parent‛
android: layout_height=‛fill_parent‛>
<Button
android: layout_width=‛wrap_content‛
android: layout_height=‛wrap_content‛
android: text=‛Go to Activity Two‛
android: id=‛@+id/btnActTwo‛>
</Button>
</LinearLayout>
На екрані кнопка, яка буде викликати наступний екран. Відкриваємо
MainActivity.java і пишемо код:
package com.example.twoactivity;
import android app Activity;
import android os Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity implements OnClickListener {
Button btnActTwo;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState) ;
setContentView(R.layout.main) ;
btnActTwo = (Button)findViewById(R.id.btnActTwo) ;
btnActTwo.setOnClickListener(this) ;
}
@Override
public void onClick(View v) {

116
switch (v.getId()) {
case R.id.btnActTwo:
// TODO Call second activity
break;
default:
break;
}
}
}
Ми визначили кнопку btnActTwo та присвоїли їй Activity в якості обробника.
Реалізація методу onClick для кнопки поки заповнена частково – визначаємо, яка
кнопка була натиснута. Пізніше будемо викликати другий екран, але спершу його
потрібно створити.
При створенні проекта у нас по замовчванню створюється Activity. Нам
потрібно лише вказати ім‘я цього Activity (рис. 3.3) – зазвичай вказуємо
MainActivity.

Рис. 3.3
Нам відомо, що створюється клас MainActivity.java, який відповідає за
поведінку Activity. Окрім цього, Activity «реєструється» в системі за допомогою
маніфест-файлу – AndroidManifest.xml (рис. 3.4).

Рис. 3.4
Відкриємо цей файл (рис. 3.5):

Рис. 3.5

117
Нас цікавить вкладка Application. Зліва ми бачимо MainActivity. Якщо його
розкрити, то всередині побачимо Intent Filter з визначеними параметрами, де
android.intent.action.MAIN вказує системі, що Activity є основним та буде першим
відображатися при запуску додатку. А android.intent.category.LAUNCHER вказує
на те, що додаток буде відображено в загальному переліку додатків Android.
Справа в полі Name написано .MainActivity. Це ім‘я класу, яий відповідає за
роботу Activity. Отже, якщо ми хочемо створити ще один Activity, потрібно
створити клас (рис. 3.6) та прописати Activity у AndroidManifest.xml.

Рис. 3.6

У вікні, що з‘явиться (рис. 3.7):

Рис. 3.7
Клас ActiityTwo створено. Він є порожнім. Тепер потрібно реалізувати
метод onCreate, який викликається при створенні Activity.

118
package com.example.twoactivity;
import android.app.Activity;
import android.os.Bundle;
public class ActivityTwo extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
Не вистачає виклику методу setContentView, який би вказував класу, чим
заповнити екран. Цьому методу на вхід потрібен layout-файл. Створимо його в
папці layout, там само де й main.xml. Назвемо файл two.xml (рис. 3.8)

Рис. 3.8
Заповнемо його наступним кодом:
<?xml version="1. 0" encoding="utf-8"?>
<LinearLayout
xmlns: android="http: //schemas. android. com/apk/res/android"
android: orientation="vertical"
android: layout_width="match_parent"
android: layout_height="match_parent">
<TextView
android: id="@+id/textView1"
android: layout_width="wrap_content"
android: layout_height="wrap_content"
android: text="This is Activity Two">
</TextView>
</LinearLayout>
Екран буде відображати TextView з текстом "This is Activity Two".
Використовуємо файл two.xml у методі setContentView в ActivityTwo.java.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView( R.layout.two);
}
(додаєте тільки підкреслений код!) Збережіть все. Клас ActivityTwo готовий, при
створенні він виведе на екран те, що ми налаштували в layout-файлі two.xml.
Теперь потрібно прописати Activity в маніфесті. Відкриваємо AndroidManifest.xml,
вкладка Application. Натискаємо кнопку Add (рис. 3.9).

119
Рис. 3.9
Далі, у вікні що з‘явилося, обираємо пункт «Create a new element at the top
level …» (якщо є вибір), а зі списку обираємо Activity (рис. 3.10).

Рис. 3.10
Тиснемо OK, Activity створилось та з‘явилося в переліку. Залишилося
вказати йому клас, який буде відповідати за його роботу. Справа (рис. 3.11) в полі
Name можемо написати вручну ім‘я класу який ми створили, або обрати його з
переліку. Збережемо все. Тепер у маніфесті прописано два Activity.

Рис. 3.11
Нам залишилося повернутися до MainActivity.java та завершити реалізацію
методу onClick (натискання кнопки), а саме – прописати виклик ActivityTwo.
Відкриваємо MainActivity.java та додаємо рядки:
case R.id.btnActTwo:

120
Intent intent = new Intent(his, ActivityTwo.class);
startActivity(intent);
break;
(додаємо тільки підкреслений код!). Оновіть імпорт (CTRL+SHIFT+O),
збережіть все та можемо запускати проект.
Завдання для самостійного виконання
1. Розробіть двовіконний калькулятор із виведенням результатів в другому вікні

6.4. Використання адаптерів для виведення списків в


Android-проекті
Віджет ListView є списком елементів, що прокручуються. Він є дуже
популярним на мобільних пристроях за рахунок своєї зручності. Знаходиться у
розділі Containers. Робота зі списком проходить у два етапи: спершу додаємо на
форму ListView, а потім заповнюємо його елементами.
Розглянемо для початку найпростіший приклад. Розмістіть на формі
компонент ListView та присвойте ідентифікатор. Список буде містити декілька
елементів Item та SubItem. Але, якщо ми подивимось XML-код, то там нічого не
побачимо.
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
Переходимо до класу та пишемо в методі onCreate() наступний код:
//отримуємо екземпляр елементу ListView
ListView listView = (ListView)findViewById(R.id.listView);
//визначаємо масив типу String
final String[] catNames = new String[] {
"Рижик", "Барсік", "Мурзік", "Мурка", "Васька",
"Томас", "Пушок", "Димка", "Кузя",
"Кітті", "Масяня", "Сімба"
};
//використовуємо адаптер даних
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
android.R.layout.simple_list_item_1, catNames);
listView.setAdapter(adapter);
Адаптери – заповнення списку даними
Компоненту ListView потрібні дані для заповнення. Джерелом можуть бути
масиви, бази даних. Щоб зв‘язати дані зі списком, використовується адаптер.
Адаптер для стандартного списку зазвичай створюється за допомогою
конструкції
new ArrayAdapter(Context context, int textViewResourceId, String[] objects):
 context – поточний контекст
 textViewResourceId – ідентифікатор ресурсу з розміткою для кожного рядка.
Можна використовувати системну розмітку з ідентифікатором
android.R.layout.simple_list_item_1 або створити власну розмітку
 objects - масив рядків
 Метод setAdapter(ListAdapter) зв‘язує підготований список з адаптером.

121
Переходимо до java-коду. Спочатку отримуємо екземпляр елементу
ListView в методі onCreate(). Далі ми визначаємо масив типу String, а тоді вже
використовуємо адаптер даних. Вибір адаптера залежить від типу даних, що
використовується. В даному випадку використовуємо клас ArrayAdapter.
Якщо будемо брати рядки з ресурсів, то отримаємо наступний код:
final String[] catNames = {
getResources().getString(R.string.name1),
getResources().getString(R.string.name2),
getResources().getString(R.string.name3),
getResources().getString(R.string.name4),
getResources().getString(R.string.name5)};
Але, краще використовувати спеціально призначений ресурс <string-array>.
У файлі res/values/strings.xml додайте наступне:
<string-array name="cat_names">
<item>Рижик</item>
<item>Барсік</item>
<item>Мурзік</item>
<item>Мурка</item>
<item>Васька</item>
<item>Томас</item>
<item>Пушок</item>
<item>Димка</item>
<item>Кузя</item>
<item>Кітті</item>
<item>Масяня</item>
<item>Сімба</item>
</string-array>
Тоді в коді для оголошення масиву рядків використовуйте:
String[] catNames = getResources().getStringArray(R.array.cat_names);
Запустивши проект, побачимо список, що прокручується. Але створений
список поки що не реагує на натискання.
Власна розмітка
У прикладі ми використовуємо готову системну розмітку
android.R.layout.simple_list_item_1, в якій налаштовані кольори, фон, висота
пунктів тощо. Але можна і самому створити власну розмітку для свого додатку.
Наш simple_list_item_1 виглядає ось так (вміст може змінюватись, залежно
від версії):
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2006 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"

122
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItemSmall"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:minHeight="?android:attr/listPreferredItemHeightSmall" />
В якості розмітки тут використовується TextView з набором атрибутів.
Якщо говорити про системні розмітки, то є декілька варіантів:
 android.R.layout.simple_list_item_1 (складається з одного TextView)
android.resource.id.text1
 android.R.layout.simple_list_item_2 (складається з двух TextView – один,
більший, згори, другий, поменше, під ним)
android.resource.id.text1
android.resource.id.text2
 android.R.layout.simple_list_item_checked (праворуч від CheckedTextView буде
знаходитись прапорець)
android.resource.id.text1
Створимо свій шаблон для окремого пункту списку. Для цього у папці
res/layout/ створимо новий файл list_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
android:textColor="#00FF00"
android:textSize="20sp"
android:textStyle="bold" >
</TextView>
В деяких випадках бажано встановити атрибут:
android:background= "?android: attr/ activatedBackgroundIndicator"
батьківського елементу, щоб елементи списку реагували на натискання зміною
кольору. Можна налаштовувати всі атрибути в TextView, окрім властивості Text,
оскільки текст буде автоматично заповнюватися елементом ListView програмним
шляхом. Ну, а далі просто змінюємо в коді системну размітку на свою:
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
R.layout.list_item, catNames);
При створенні власного елементу списку, який складається з TextView
можна використовувати спеціальний стиль для мінімального розміру тексту.
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight" />

Динамічне заповнення списку


Розглянемо приклад динамічного заповнення списку. Розташуємо на екрані
текстове поле, в якому користувач буде вводити відомі йому імена котів. Після
натискання на Enter на клавіатурі, введене ім‘я буде додаватися до списку.
123
Приклад.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// отримуємо екземпляр ListView
ListView listView = (ListView) findViewById(R.id.listView);
final EditText editText = (EditText) findViewById(R.id.editText);

// Створюємо порожній масив для зберігання імен котів


final ArrayList<String> catNames = new ArrayList<>();
// Створюємо адаптер ArrayAdapter, щоб прив’язати масив до ListView
final ArrayAdapter<String> adapter;
adapter = new ArrayAdapter<>(this,
android.R.layout.simple_list_item_1, catNames);
// Прив’язуємо масив через адаптер к ListView
listView.setAdapter(adapter);
// слухаємо кнопку
editText.setOnKeyListener(new View.OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN)
if (keyCode == KeyEvent.KEYCODE_ENTER) {
catNames.add(0, editText.getText().toString());
adapter.notifyDataSetChanged();
editText.setText("");
return true;
}
return false;
}
});
}
При натисканні на Enter отримаємо текст з текстового поля та заносимо
його в масив. А також сповіщуємо адаптер про зміни, щоб список автоматично
оновив свій вміст.
Ми отримали каркас для чату, коли користувач вводить текст і він
заноситься до списку.
Слухання подій елементу ListView
Нам потрібно реагувати на певні події, які генеруються елементом ListView,
зокрема, нас цікавить подія, яка виникає при натисканні користувачем одного з
пункту списку.
Для цього використовуємо метод setOnItemClickListener елементу ListView
й метод OnItemClick() інтерфейсу AdapterView.OnItemClickListener.
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View itemClicked,
int position, long id) {
Toast.makeText(getApplicationContext(), ((TextView)
itemClicked).getText(),
Toast.LENGTH_SHORT).show();
}
});

124
Тепер при натисканні на будь-який елемент списку отримаємо спливаюче
повідомлення, яке мість текст обраного пункту.
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View itemClicked,
int position, long id) {
TextView textView = (TextView) itemClicked;
String strText = textView.getText().toString(); /*отримуємо
текст натиснутого елементу*/

if(strText.equalsIgnoreCase(getResources().
getString(R.string.name1))) {
/* запускаємо активність, прив’язану до
конкретного кота*/
startActivity(new Intent(this,
BarsikActivity.class));
}
}
});
В метод onItemClick() передається вся необхідна інформація для визначення
натиснутого пункту в списку. В наведеному вище прикладі використовувався
простий спосіб – приводимо обраний елемент до об‘єкта TextView (для додаткової
перевірки можна використовувати оператор instanceOf), отримуємо текст з
вибраного пункту й порівнюємо його зі своїм рядком. Також можна перевіряти
атрибут id для визначення натискання пункту списку.
Налаштування зовнішнього вигляду ListView
У ListView є декілька корисних атрибутів, які дозволяють зробити список
більш привабливим. Наприклад, у нього є атрибут divider, який відповідає за
зовнішній вигляд розділювача, а також атрибут dividerHeight, який відповідає за
висоту розділювача. Для розділювача можна встановити будь який колір або
зображення. Створимо для розділювача кольоровий ресурс з червоним кольором, а
також ресурс розміру для його висоти:
<color name="reddivider">#FF0000</color>
<dimen name="twodp">2dp</dimen>
Далі присвоємо ресурс атрибуту divider, а також задамо його висоту в
атрибуті dividerHeight у нашого елемента ListView:
<ListView
android:id="@+id/listView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@color/reddivider"
android:dividerHeight="@dimen/twodp" >
</ListView>
Якщо не влаштовує стандартний розділювач, можна намалювати будь-що та
зберегти малюнок у PNG-файлі і використати як drawable-ресурс.

Завдання для самостійного виконання


1. Розробити багатофункціональний записник на основі телефонного довідника.

125
ТЕМА 7. Робота з базами даних
7.1. Основи баз даних - таблиці, ключі, зв’язки таблиць
Словничок
Java Data Base Connectivity (JDBC) – з'єднання з базами даних на Java
Uniform Resource Locator (URL) – єдиний вказівник на ресурс, стандартизована
адреса певного ресурсу
База даних – це звичайний каталог, що містить виконавчі файли певного
формату – таблиці. Таблиці складаються із записів, а записи, в свою чергу,
складаються з полів. Поле має два атрибути – ім‘я і тип. Таблиці можна зв‘язувати
між собою.
Тип поля може бути:
 цілим;
 дійсним;
 рядковим;
 бінарним;
 дата та час;
 перерахування і множини.

Цілочисельні типи даних


Тип Діапазон
TINYINT -128 ... + 127
SMALLINT -32768 ... + 32767
MEDIUMINT -8 388 608 ... +8 388 607
INT -2 147 483 648 ... +2 147 483 647
BIGINT -9 223 372 036 854 775 808 ... + 9 223 372 036 854 775 807
Дійсні типи записуються у вигляді:
ТИП (ДОВЖИНА, ЗНАКИ) [UNSIGNED]
ДОВЖИНА - це кількість знаків, в яких буде розміщено все число при його
передачі, а ЗНАКИ - це кількість знаків після десяткового дробу, які будуть
враховуватися. Якщо вказано модифікатор UNSIGNED, знак числа враховуватися
не буде.
Дійсні типи числа
Тип Опис
FLOAT невелика точність
DOUBLE подвійна точність
REAL Те ж, що і DOUBLE
DECIMAL Дробове число, яке зберігається у вигляді рядка
NUMERIC Те ж, що і DECIMAL
Будь-який рядок – це масив символів. При пошуку за допомогою оператора
SELECT (ми розглянемо його далі) не враховується регістр символів: рядки
«HELLO» і «Hello» вважаються однаковими.
Можна налаштувати базу даних на автоматичне перекодування символів – в
цьому випадку в базі даних рядки будуть зберігатися в одному кодуванні, а
виводитися – в інший.

126
У більшості випадків застосовується тип VARCHAR або просто CHAR, що
дозволяє зберігати рядки, що містять до 255 символів. У дужках після типу
вказується довжина рядка:
VARCHAR(48);
CHAR(73);
Якщо 255 символів для вашого завдання недостатньо, можна
використовувати інші типи, наприклад, TEXT.
Рядкові типи
Тип Опис
TINYTEXT Максимальна довжина 255 символів
TEXT Максимальна довжина 65535 символів (64 Кб)
MEDIUMTEXT Максимальна довжина 16 777 215 символів
LONGTEXT Максимальна довжина 4 294 967 295 символів
Бінарні типи даних також можна використовувати для зберігання тексту,
але при пошуку буде враховуватися регістр символів. До того ж, будь-який
текстовий тип можна перетворити в бінарний, вказавши модифікатор BINARY:
VARCHAR(30) BINARY;
Бінарні типи даних
Тип Опис
TINYBLOB Максимум 255 символів
BLOB Максимум 65535 символів
MEDIUMBLOB Максимум 16 777 215 символів
LONGBLOB Максимум 4 294 967 295
Примітка: Бінарні дані не перекодуються «на льоту», якщо встановлена
перекодування символів.
Дата та час
Тип Опис
DATE Дата в форматі ГГГ-ММ-ДД
TIME Час в форматі ГГ: ХХ: СС
TIMESTAMP Дата і час в форматі timestamp,
виводиться у вигляді
ГГГГММДДЧЧММСС
DATETIME Дата і час в форматі РРРР-ММ-ДД ГГ: ХХ: СС

Зв’язки
Після створення в базі окремих таблиць по кожній темі необхідно вибрати
спосіб, яким будуть поєднуватись данні таблиць. Насамперед варто визначити
зв‘язки між таблицями. Після цього можна створювати запити, форми і звіти для
одночасного відображення даних із декількох таблиць.
Наприклад, поля в декількох таблицях повинні бути скоординовані таким
чином, щоб відображати відомості про одне й те саме замовлення.
Зв‘язок між таблицями встановлює відношення між співпадаючими
значеннями в ключових полях.
У більшості випадків із ключовим полем однієї таблиці (головної таблиці),
що є унікальним ідентифікатором кожного запису, зв‘язується зовнішній ключ
іншої таблиці (підлеглої таблиці).

127
Зовнішній ключ – одне (або декілька) полів у таблиці, що містять посилання
на поле (або поля) первинного ключа в іншій таблиці.
Поле зовнішнього ключа визначає спосіб зв‘язування таблиць. Вміст поля
зовнішнього ключа повинен збігатися з вмістом ключового поля, хоча імена полів
можуть при цьому не збігатися.
Існують зв‘язки з відношенням «один-до-одного», «один-до-багатьох» і
«багато-до-багатьох».
При відношенні «один-до-багатьох» кожному запису в таблиці A можуть
відповідати кілька записів у таблиці B, але запис у таблиці B не може мати більш
одного відповідного йому запису в таблиці A. Це найбільш вживаний тип зв'язку
між таблицями. Він дозволяє уникнути повторень однотипної інформації.
При відношенні «один-до-одного» запис у таблиці A може мати не більш
одного зв‘язаного запису в таблиці B і навпаки. Зв‘язок цього типу
використовуються не дуже часто, оскільки велика частина даних, зв‘язаних таким
чином, може бути поміщена в одну таблицю. Відношення «один-до-одного» може
використовуватися для поділу дуже широких таблиць, для відділення частини
таблиці, яку треба захистити, а також для збереження даних, що відносяться до
підмножини записів у головній таблиці.
При відношенні «багато-до-багатьох» одному запису в таблиці A можуть
відповідати кілька записів у таблиці B, а одному запису в таблиці B кілька записів
у таблиці A. Цей тип зв‘язку можливий тільки за допомогою третьої (сполучної)
таблиці, первинний ключ якої складається з двох полів, що є зовнішніми ключами
таблиць A і B. Відношення «багато-до-багатьох» ,по суті справи , являє собою два
відношення «один-до-багатьох» із третьою таблицею.

7.2. Основи мови SQL - оператори


select, insert, update, delete
SQL – проста мова програмування, розшифровується як Structured Query
Language – мова структурованих запитів. Вона була розроблена для роботи з БД, а
саме, щоб отримувати (додавати, змінювати) дані, мати можливість опрацьовувати
великі масиви інформації та швидко отримувати структуровану та згруповану
інформацію. Є багато варіантів мови SQL, але у них всіх основні команди майже
однакові. Також існує і багато СУБД, але основними з них являються: Microsoft
Access, Microsoft SQL Server, MySQL, Oracle SQL, IBM DB2 SQL, PostgreSQL та
SybaseAdaptive Server SQL.
Мова SQL має два основних компоненти:
 мову DDL (Data Definition Language), призначену для визначення
структур бази даних та управління доступом до даних (оператори
CREATE TABLE; DROP TABLE; ALTER TABLE; CREATE INDEX;
DROP INDEX);
 мову DML (Data Manipulation Language), призначену для вибірки і
оновлення даних (оператори SELECT, INSERT, UPDATE, DELETE,
COMMIT – фіксація змін, ROLLBACK – відміна внесених змін).
Формат операторів
Всі конструкції на мові SQL закінчуються крапкою з комою.

128
Вирази в SQL не чутливі до регістру і ігнорують додаткові роздільники
(проміжок, табуляція, перехід на новий рядок).
Для текстових даних, що вводяться користувачем в базу і не пов‘язані з
елементами СУБД, використовуються звичайні або подвійні лапки: 'текст',
«великий текст».
В текстових даних лапки та крапка з комою екрануються символом \.
Наприклад «\» назва \«».
Робота з структурою баз даних.
Створення таблиць. Оператор CREATE
Створити таблицю через SQL-запит дозволяє оператор CREATE. Його
синтаксис:
CREATE TABLE Ім‘я_таблиці
(
Ім‘я _поля1 Тип Модифікатор
...
Ім‘я _поляN Тип Модифікатор
[первинний ключ]
[зовнішній ключ]
)
За допомогою оператора CREATE можна створювати і інші об‘єкти, але ми
їх розглядати не будемо, оскільки їх застосування досить обмежене
В якості модифікаторів можна використовувати наступні значення:
NOT NULL – поле не може містити невизначеного значення (NULL), тобто
поле повинно бути явно ініціалізованим;
PRIMARY KEY – поле буде первинним ключем (ідентифікатором запису), за
яким можна однозначно ідентифікувати запис;
AUTO_INCREMENT – при вставці нового запису значення цього поля буде
автоматично збільшено на одиницю, тому в таблиці не буде двох записів з
однаковим значенням цього поля;
DEFAULT – задає значення, яке буде використано за замовчуванням, якщо
при вставці запису поле не буде ініціалізоване явно. Значення за замовчуванням
задається так:
ім‘я_поля Тип DEFAULT Значення,
наприклад:
NO INT DEFAULT 0
NAMECHAR(10)DEFAULT 'Михайло'
Для прикладу створимо таблицю - "books":
CREATE TABLE books
(
ID int NOT NULL,
TITLE char(40) NOT NULL,
DATE date NOT NULL,
AVTOR char (30) NOT NULL,
PAGES int NOT NULL,
PRICE double (9,2) NOT NULL
);

129
Тип double (9,2) означає, що 9 знаків виділяємо під цілу частину, і два – під
дробову.
Додавання даних в таблицю. Оператор INSERT
Для додавання записів використовується оператор INSERT:
INSERT INTO Ім‘я_таблиці [(Список полів)]
VALUES (Список констант);
Після виконання оператора INSERT буде створений новий запис, в якому як
значення полів будуть використані відповідні константи, зазначені в списку
VALUES.
Тепер додамо дані в нашу таблицю. Додати дані можна за допомогою
оператора INSERT. Розглянемо приклад використання оператора INSERT:
INSERT INTO books
VALUES(1, 'Твердиня', TO_DATE('01/11/13', 'DD/MM/YY'), 'Кідрук', 120,
120.10);
Додавання дати в поле DATE ми виконали за допомогою функції
TO_DATE.
Значення, що додаються повинні відповідати тому порядку, в якому поля
перераховані в операторі CREATE. Якщо ви хочете додавати інформацію в іншому
порядку, то потрібно вказати цей порядок в операторі INSERT, наприклад:
INSERT INTO books (AVTOR, TITLE, ID, PRICE , PAGES)
VALUES ('Кідрук', 'Bot', 2, 100, 92);
За допомогою INSERT ми можемо додавати дані і в певні поля, наприклад,
ID і TITLE:
INSERT INTO books (ID, TITLE)
VALUES (1, 'Фарбований лис');
Хоча в нашому випадку запис не буде доданий, оскільки в таблиці всі поля є
обов‘язковими для заповнення.
Зверніть увагу на те, що не вказані первинні ключі, тому в таблицю можна
додавати однакові записи
Оновлення записів. Оператор UPDATE
Синтаксис оператора UPDATE, який використовується для оновлення
записів, виглядає так:
UPDATE Ім‘я_таблиці
SET Поле1 = Значення1, ... , Поле N = Значення N
[WHERE Умова];
Якщо не буде вказана умова WHERE, буде модифікована вся таблиця, а це
може спричинити непередбачувані наслідки, оскільки для всіх записів будуть
встановлені однакові значення полів. Тому завжди необхідно вказувати умову
WHERE.
Припустимо, необхідно оновити запис, якщо, наприклад, книга «Bot»
змінила свою ціну. Зробимо наступне:
UPDATE books
SETPRICE = 300
WHEREID = 2;
Даний запит потрібно розуміти так: знайти запис, поле ID якого = 2 (це код
заданої книги), і встановити значення PRICE рівним «300».

130
Видалення записів. Оператор DELETE
Якщо необхідно видалити всі книжки, номери яких перевищують 5, то
потрібно вчинити так:
DELETE FROM books
WHERE ID> 5;
За допомогою оператора DELETE можна видалити всі записи таблиці,
задавши умову, яка підійде для всіх записів, або не задавши її зовсім. Наприклад:
DELETE FROM books;
Вибір записів. Оператор SELECT
Додавання, зміна та видалення записів – це, звичайно, дуже важливі
команди, але при цьому часто будете використовуватись оператор SELECT, який
вибирає дані з таблиці. Синтаксис цього оператора більш складний:
SELECT [DISTINCT|ALL] {*| [поле1 AS псевдонім] [,..., полеN AS
псевдонім]}
FROM Ім‘я_таблиці1 [,...,Ім‘я_таблиціN]
[WHERE умова]
[GROUPBY список полів] [HAVING умова]
[ORDERBY список полів]
Розглянемо оператор SELECT на конкретних прикладах.
Виведення записів з таблиці book:
SELECT*FROMbooks;
Відповідь сервера:
ID TITLE DATE AVTOR PAGES PRICE
1 Твердиня 01/11/17 Кідрук 120 120.10
1 Твердиня 01/11/17 Кідрук 120 120.10
2 Bot 01/11/17 Кідрук 100 92.00
Увага: перші два записи – однакові. Теоретично, додавання однакових
записів можливо – адже не вказано первинний ключ таблиці. Для виведення
записів без повторень використовують запит:
SELECT DISTINCT *
FROMbooks;
Для виведення назви та автора використовується наступний запит:
SELECT DISTINCT TITLE, AVTOR
FROM books;
Для виведення книг, ціна на які перевищує 100, потрібен такий запит:
SELECT *
FROM books
WHERE PRICE >100;
У запитах можна використовувати наступні оператори відношень: <,>, =,
<>, <=,> =.
Умова або оператор LIKE використовується в запиті для пошуку даних, які
відповідають певному шаблону, наприклад:
SELECT *
FROM books
WHERE AVTOR LIKE '%Кідрук%';

131
Наведений запит може звучати так: вивести інформацію про книжки,
прізвище автора яких схоже на 'Кідрук'.
Внутрішні функції MIN, MAX, AVG, SUM
При роботі з оператором SELECT доступні кілька дуже корисних
внутрішніх функцій, що обчислюють кількість елементів (COUNT), суму елементів
(SUM), максимальне і мінімальне значення (MAX і MIN), а також середнє значення
(AVG).
Наступні оператори виведуть, відповідно, кількість записів в таблиці books,
найдорожчу книгу і вартість усіх книг:
SELECT COUNT(*)
FROM books;
SELECT MAX(PRICE)
FROM books;
SELECT SUM(PRICE)
FROM books;
Ключі
Припустимо, що в таблицю books додано запис:
1 Прикарпаття 01/12/17 Боднаренко 1200 1000
У той же час, до цього номер 1 був закріплений за книгою «Твердиня».
Отже вийшло, що один і той же номер закріплений за різним книжками. Щоб
уникнути такої плутанини, необхідно застосовувати первинні ключі:
ALTER TABLE books
ADD PRIMARY KEY (ID);
Після цього запиту поле ID може містити тільки унікальні значення. В
якості первинного ключа можна використовувати поле, що дозволяє приймати
значення NULL. Створити певинний ключ можна і простіше – при створенні
таблиці наступним чином:
CREATE TABLE books
(
ID int NOT NULL,
TITLE char(40) NOT NULL,
DATE date NOT NULL,
AVTOR char (30) NOT NULL,
PAGES int NOT NULL,
PRICE double(9,2) NOT NULL,
PRIMARY KEY (ID);
);
Команда ALTER використовується не тільки для додавання ключів, але й
для реорганізації таблиці в цілому. Для додавання полів або встановлення списку
допустимих значень для кожного з полів використовують команду

ALTER:
ALTER TABLEbooks
ADD ZIP char(7) NULL;
Цей оператор додає в таблицю books нове поле ZIP типу char. Зверніть
увагу на те, що можна додати нове поле зі значенням NOT NULL в таблицю, в якій

132
вже є дані. Наприклад, якщо книгарня працює тільки з клієнтами Боднаренко і
Кідрук, то доцільно ввести список допустимих значень для таблиці books:
ALTER TABLE books
ADD CONSTRAINTINVALID_STATE SHECK (AVTOTIN ('Кідрук',
'Боднаренко'));
7.3. Встановлення з’єднання з базою даних засобами
JDBC. Класи DriverManager та Connection.
JavaDataBaseConnectivity(JDBC) – прикладний програмний інтерфейс Java,
який визначає методи, з допомогою яких програмне забезпечення на Java здійснює
доступ до бази даних. JDBC – це платформо-незалежний промисловий стандарт
взаємодії Java-застосунків з різноманітними СУБД, реалізований у вигляді пакета
java.sql, що входить до складу Java SE.
В основі JDBC лежить концепція так званих драйверів, що дозволяють
отримувати з‘єднання з базою даних по спеціально описаному URL. Драйвери
можуть завантажуватись динамічно (під час роботи програми). Завантажившись,
драйвер сам реєструє себе й викликається автоматично, коли програма вимагає
URL, що містить протокол, за який драйвер «відповідає».
JDBC надає інтерфейс для розробників, які використовують відмінності
СУБД. За допомогою JDBC відсилаються SQL-запити тільки до реляційних баз
даних (БД), для яких існують драйвери і які знають спосіб спілкування з реальним
сервером бази даних.
Власне кажучи, JDBC не має прямого відношення до J2EE, але оскільки
взаємодія з СУБД є невід‘ємною частиною Web-додатків, то ця технологія
розглядається в даному контексті.
Послідовність дій
1. Завантаження класу драйвера бази даних при відсутності об’єкта цього
класу.
Наприклад:
StringdriverName = "com.mysql.jdbc.Driver";
для СУБД MySQL,
StringdriverName = "sun.jdbc.odbc.JdbcOdbcDriver";
для СУБД MSAccess або
StringdriverName = "org.postgreesql.Driver";
для СУБД PostgreeSQL.
Після цього виконується власне завантаження драйвера в пам‘ять:
Class.forName (driverName);
і стає можливим з‘єднання з СУБД.
Ці ж дії можна виконати, імпортуючи бібліотеку і створюючи об‘єкт явно.
Наприклад, для СУБД DB2 від IBM об‘єкт-драйвер можна створити наступним
чином:
new com.ibm.db2.jdbc.net.DB2Driver();
2. Встановлення з’єднання з БД.
Для встановлення з‘єднання з БД викликається статичний метод
getConnection() класу DriverManager. Як параметри методу передаються URL бази
даних, логін користувача БД і пароль доступу. Метод повертає об'єкт Connection.

133
URL бази даних, що складається з типу і адреси БД, може створюватися у вигляді
окремого рядка, наприклад:
Connectioncon = DriverManager.getConnection
("jdbc:mysql://localhost/dblib","root", "pass");
В результаті буде створено об'єкт Connection і буде встановлено з‘єднання
з БД dblib. Клас DriverManager надає ресурси для управління набором драйверів
баз даних. За допомогою методу registerDriver() драйвери реєструються, а
методом getDrivers() можна отримати список всіх драйверів.
3. Створення об’єкта для передачі запитів.
Після створення об'єкта Connection і установки з‘єднання можна розпочати
роботу з БД за допомогою операторів SQL. Для виконання запитів застосовується
об'єкт stmt класу Statement, який створюється викликом методу createStatement()
класу Connection.
Statement stmt = con.createStatement();
Об'єкт класу Statement використовується для виконання SQL-запиту без
його попередньої підготовки. Можуть застосовуватися також об'єкти класів
PreparedStatement і CallableStatement для виконання підготовлених запитів і
збережених процедур. Створені об‘єкти можна використовувати для виконання
запиту SQL, передаючи його в один з методів
executeQuery (Stringsql) або executeUpdate (Stringsql).
4. Виконання запиту.
Результати виконання запиту поміщаються в об‘єкт ResultSet:
ResultSet res = stmt.executeQuery ("SELECT * FROM my_table");
// вибірка всіх даних таблиці my_table
Щоб додати, видалити або змінити інформацію в таблиці замість методу
executeQuery () запит поміщається в метод executeUpdate ().
5. Обробка результатів виконання запиту.
Здійснюється методами інтерфейсу ResultSet, де найпоширенішими є
методи next() і getString(intpos), а також аналогічні методи, що починаються з
getТип(intpos) (getInt(intpos), getFloat(intpos) і ін.) і updateТип(). Серед них слід
виділити методи getClob(intpos) і getBlob(intpos), що дозволяють витягати з полів
таблиці специфічні об'єкти (CharacterLargeObject, BinaryLargeObject), які можуть
бути, наприклад, графічними або архівними файлами. Ефективним способом
вилучення значення поля з таблиці відповіді є звернення до цього поля по його
позиції в рядку.
При першому виклику методу next() покажчик переміщається на таблицю
результатів вибірки в позицію першого рядка таблиці відповіді. Коли рядки
закінчаться, метод поверне значення false.
6. Закриття з’єднання
con.close();
Після того як база більше не потрібна, з‘єднання закривається.
Для того щоб правильно користуватися наведеними методами, програмісту
потрібно знати типи полів БД.
Приклад роботи з СУБД MySQL
СУБД MySQL сумісна c JDBC. Остання версія CУБД може бути
завантажена з сайту www.mysql.com. Для коректної установки необхідно
користуватися інструкціями майстра установки. Каталог краще вибирати за

134
замовчуванням. В процесі установки слід створити адміністратора СУБД з ім'ям
root і паролем pass. Якщо планується розгортати реально працюючий додаток,
необхідно виключити тривіальних користувачів сервера. Для запуску слід
використовувати команду з папки /mysql/bin:mysqld-nt -standalone
Якщо не з‘явиться повідомлення про помилку, то СУБД MySQL запущена.
Для створення БД і таблиць використовуються команди мови SQL.
Додатково потрібно підключити бібліотеку, яка містить драйвер
MySQLmysql-connector-java-5.1.44-bin.jar, і розмістити її в каталозі /lib проекту.

Просте з’єднання і простий запит


Тепер потрібно скористатися всіма попередніми інструкціями і створити
для користувача БД з ім‘ям dblib і однією таблицею books. Таблиця повинна
містити два поля: числове – id i символьне – name і кілька введених записів.
Сервлет, який здійснює найпростіші запити на додавання інформацію в таблицю і
запит на вибір всієї інформації з таблиці.
Цей код виглядає наступним чином:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Disp {
// JDBC адреса, користувач і пароль сервера бази даних
private static final String url = "jdbc:mysql://127.0.0.1:3306/dblib";
private static final String user = "root";
private static final String password = "pass";
// JDBC змінні відкриття і управління з’єднанням
private static Connection con;
private static Statement stmt;
private static ResultSet res;
static String driverName = "com.mysql.jdbc.Driver";
public static void main(String[] args) throws ClassNotFoundException {
String query1 = "SELECT id, titleFROM books";
String query2 = "INSERT INTO dblib.books (id, title) \n" +
" VALUES (5, 'HeadFirstJava');";
try {
// Завантажуємо драйвер бази даних
Class.forName (driverName);
// Створюємо з'єднання з сервером бази даних
con = DriverManager.getConnection(url, user, password);
// отримуємо Statement об'єкт для запуску запитів
stmt = con.createStatement();
// запускаємо запити
stmt.executeUpdate(query2);
// запит, що додає запис
res = stmt.executeQuery(query1);
// запит, що вибирає дані
// вивід отриманої множини на екран
while (res.next()) {
int id = res.getInt(1);
String name = res.getString(2);
System.out.println("id" + id+ " "+name);

135
}
} catch (SQLExceptionsqlEx) {
sqlEx.printStackTrace();
} finally {
//закриваємо connection,stmt і resultset
try { con.close(); } catch (SQLExceptionse) { }
try { stmt.close(); } catch (SQLExceptionse) { }
try { rs.close(); } catch (SQLExceptionse) { }
}
}
}
Даний код може викликати ClassNotFoundException або SQLException.
Відповідно потрібно або опрацювати ці винятки, або передати вище.
Тести
1. Яка SQL команда використовується для вибірки даних з бази?
a) OPEN
b) GET
c) EXTRACT
d) SELECT
2. Як вивести кількість записів, що зберігаються в таблиці "Persons"?
a) SELECT COLUMNS(*) FROM Persons
b) SELECT COUNT(*) FROM Persons
c) SELECT COLUMNS() FROM Persons
d) SELECT COUNT() FROM Persons
3. Як видалити записи, де значення поля "FirstName" рівне "Peter"?
a) DELETE FirstName='Peter' FROM Persons
b) DELETE FROM Persons WHERE FirstName = 'Peter'
c) DELETE ROW FirstName='Peter' FROM Persons
4. Як змінити значення "Hansen" на "Nilsen" в колонці "LastName", таблиці Persons?
a) UPDATE Persons SET LastName='Hansen' INTO LastName='Nilsen'
b) UPDATE Persons SET LastName='Nilsen' WHERE LastName='Hansen'
c) MODIFY Persons SET LastName='Nilsen' WHERE LastName='Hansen'
d) MODIFY Persons SET LastName='Hansen' INTO LastName='Nilsen
5. Виберіть правильний SQL запит для вставки нового запису в таблицю "Persons",
щоб в поле "LastName" вставити значення "Olsen".
a) INSERT ('Olsen') INTO Persons (LastName)
b) INSERT INTO Persons ('Olsen') INTO LastName
c) INSERT INTO Persons (LastName) VALUES ('Olsen')
6. Як вибрати всі записи з таблиці "Persons", де значення поля "FirstName"
починаєтся з букви "a"?
a) SELECT * FROM Persons WHERE FirstName LIKE '%a'
b) SELECT * FROM Persons WHERE FirstName='%a%'
c) SELECT * FROM Persons WHERE FirstName LIKE 'a%'
d) SELECT * FROM Persons WHERE FirstName='a'

Завдання для самостійного виконання


1. Завантажити і встановити драйвер com.mysql.jdbc.Driver. Запустити на
своєму комп‘ютері сервер бази даних MySQL. (Це можна зробити інсталювавши

136
пакет Denwer або OpenServer. Обидва вони безкоштовні і містять MySQL, яка
легко конфігурується). Розробити базу даних «AutoService» з таблицею, що
містила б інформацію про назву сервіс-фірми, її адресу, геодані (довгота та
широта), рейтинг, години роботи. Саму таблицю створити в панелі управління
базою даних – phpMyAdmin. Типи полів підібрати відповідно даним. Розробити
методи для:
a) додавання в таблицю інформацію про новий сервіс;
b) підвищення, чи зниження рейтингу сервісу за його кодом;
c) пошуку сервіс центрів в заданому радіусі від вказаного;
d) видалення сервісу з бази при досягненні від‘ємного рейтингу;
e) пошуку найкращого за рейтингом сервісу у вказаному місті;
f) вивід списку всіх сервісних центрів.
В головному методі організувати додавання декількох записів в базу та
продемонструвати роботи кожного метода.
2. Створити Таблицю

Область Місто Населення Рік Річка


заснування
Івано- Калуш 67 400 1437 Лімниця
Франківська
Львівська Львів 721 878 1272
Київська Біла Церква 211 000 1115 Рось
Івано- Яремче 7 850 1787 Прут
Франківська
Івано- Івано- 243 900 1662 Бистриця
Франківська Франківськ
Івано- Коломия 60 000 1241 Прут
Франківська
Використовуючи можливості SQL, виконати завдання:
a) міста в яких є річки
b) міста, в яких кількість населення більша 200 тис. але менша 300 тис.
c) кількість міст Івано-Франківської області
d) скільки є міст біля кожної з річок
e) область де міста починаються з букви «К»
f) визначити вік кожного міста
g) середню кількість жителів в Івано-Франківській області
h) посортувати міста в порядку зростання кількості населення
i) міста в назві яких є символ «-»

3. Розробити набір SQL-запитів для створення бази даних «PhoneBook». А


саме:
a) створити таблицю PhoneBook з полями ID, NAME, PHONE, ADRESS;
b) зробити ID ключовим полем;
c) додати до таблиці декілька записів про абонентів телефонної книги;

137
d) додати поле DATE_CONNECTз значенням по замовчуванням, що дорівнює
даті написання запиту.
e) видалити з таблиці запис з ID=1;
f) вибрати з бази даних абонентів в яких поле PHONE починається на
«+3803422»

ТЕМА 8. Основи багатопотоковості


8.1. Поняття багатопотоковості.
Прості методи класу Threads
Словничок
Thread – нитка

Java є багатопотоковою мовою, що підтверджується типом методів класу


Object – базового для усіх класів Java. Шість загальнодоступних методів класу
Object призначені для управлінням потоками і міжпотоковим обміном
інформацією. Java інкапсулює поняття потоку в класі Thread, який має два типи
конструкторів. Вони відповідають двом способам формування об‘єкту для
виконання у власному потоці.
Простий потік
Найпростішим є потік основної програми. Потік основної програми – це
лінійна послідовність виконання команд. Кожна програма має щонайменше один
потік, як наприклад наступна програма
public class Main {
public static void main(String[] args) {
System.out.println("HelloWorld");
}
}
або декілька потоків.
При запуску наведеної програми метод main() з одним рядком коду створює
потік. Потік досягає кінця методу і пропадає. Програма здійснює вихід. Без
використання потоків і наявності багатьох рядків коду виконується тільки один
рядок.
Методи потоків
int activeCount() – поточна кількість активних потоків у групі, до якої вони
належать;
void checkAccesss() – поточному потоку дозволяється змінювати об‘єкт Thread;
int countStackFrames() – визначення кількості фреймів у стеку;
Thread currentThread() – визначення поточного робочого потоку;
void destroy() – примусове завершення роботи потоку;
void dumpStack() – вивід поточного вмісту стеку для налагодження;
int enumerate(Thread tarray[]) – отримання всіх об‘єктів Thread даної групи;
String getName() – визначення імені потоку;
int getPriority() – визначення поточного пріоритету потоку;

138
ThreadGroup getThreadGroup() – визначення групи, до якої належить потік;
void interrupt() – переривання потоку;
boolean interrupted() – визначається чи є потік перерваним;
boolean isAlive() – визначення чи виконується потік, чи ні;
boolean isDaemon() – визначається чи є потік демоном;
boolean isInterrupted() – визначається чи є потік перерваним;
void join() – очікування завершення потоку;
void join(log millis) – очікування завершення потоку впродовж деякого часу. Час
задається у мілісекундах;
void join(longmillis, intnanos) – очікування завершення потокувпродовж деякого
часу. Час задається у мілісекундах та наносекундах;
void resume() – запуск тимчасово призупиненого потоку;
void setDaemon(boolean on) – метод викликається у тому випадку, якщо потік був
створений як об‘єкт з інтерфейсом Runnable run();
void setDaemon(boolean on) – встановлення для потоку режиму демона;
void setName(String name) – встановлення імені потоку;
void setPriority(int newPriority) – встановлення пріорітету потоку;
void sleep(long millis) – затримка потоку на заданий час. Час задається у
мілісекундах;
void sleep(longmillis, intnanos) – затримка потоку на заданий час. Час задається у
мілісекундах та наносекундах;
void start() – запуск потоку на виконання;
void stop() – зупинка виконання потоку;
void stop(Throwable obj) – аварійна зупинка виконання потоку із заданим винятком;
void suspend() – призупинення потоку;
void yield() – призупинення поточного потоку для того, щоб керування було
передано іншому потоку;
String toString() – рядок, який представляє об‘єкт-потік.
Об‘єкт класу Thread формується конструктором і завершується
поверненням з деструктора. Конструктор Thread і метод start() виконуються в
потоці, у якому вони викликаються. Метод run() виконується в новому потоці як і
будь-який інший, викликаний з run(), також буде виконуватись в ньому. Метод
run() є ядром об‘єкту класу Thread. Він створює команди тіла потоку. Цей метод
призначений для реалізації поведінки потоку. Метод run(), як правило, містить
цикл, часом нескінченний, наприклад,
publi cvoid run () {

while (true) {

}

}
Методи start() і stop() використовуються для створення і знищення базового
потоку. Для запуску потоку необхідно викликати start(), але stop() не обов‘язковий
для його знищення. Більшість аплетів розраховані на те, що повернення з методу
run() зруйнує потік. Потік призупиняється (перевід в режим «очікування») за
допомогою методу suspend(), який, зупиняючи виконання потоку, залишає його в

139
пам‘яті. Метод resume() поновлює виконання з того місця, де потік був
призупинений. Метод join(), який використовується для запуску потоків є також
важливим механізмом управління.

Конструктори
Thread() – створення нового об‘єкту Thread
Thread(Runnable target) – створення нового об‘єкта Thread з вказуванням об‘єкту
для якого буде викликатися метод run()
Thread(Runnable target, String name) – аналогично до попереднього, але додатково
задається ім‘я нового об‘єкту Thread
Thread(String name) – створення об‘єкту Thread із вказуванням його імені
Thread(ThreadGroup group, Runnable target) – створення нового об‘єкта Thread з
вказуванням групи потоку та об'єкту, для якого викликається метод run()
Thread(ThreadGroup group, Runnable target, String name) – аналогічно до
попереднього, але додатково задається ім‘я нового об‘єкта Thread
Thread(ThreadGroup group, String name) – створення нового об‘єкта Thread із
вказуванням групи та імені об‘єкта.

Тести
7. Розглянемо код. Яким буде результат його виконання?
public class Hotel {
private static void book() {
System.out.print("book");
}
public static void main(String[] args) throws
InterruptedException {
Thread.sleep(1);
book();
}
}
a) помилка при компіляції
b) генерується виняток при виконанні
c) виводиться "book"
8. Розглянемо код. Яким буде результат його виконання?
public class Cruiser implements Runnable {
public void run() {
System.out.print("go");
}
public static void main(String arg[]) {
Thread t = new Thread(new Cruiser());
t.run();
t.run();
t.start();
}
}
a) помилка при компіляції
b) помилка при запуску на виконання
c) виведеться "go"
d) виведеться "gogogo"

140
e) виведеться "gogo"
9. Розглянемо код:
public class Threads5 {
public static void main (String[] args) {
new Thread (new Runnable() {
public void run() {
System.out.print("bar");
}
}).start();
}
}
Яким є результат виконання?
a) помилка компіляції
b) помилка при виконанні
c) код виконається без помилок, виведеться "bar"
d) програма виконається, але нічого не виведеться .

8.2. Способи створення потоків.


Невидимі та інші потоки
В мові Java існують невидимі потоки. Бездіяльний потік, збирач сміття та
фіналізатор працюють разом. Бездіяльний потік має дуже низький пріоритет і
підтримує прапорець сигналу виконання потоку. Пріоритет бездіяльного потоку
вибирається таким, щоб він ніколи не виконувався, за винятком моменту, коли всі
інші потоки заблоковані.
Є два способи побудови і запуску потоків. Найпростіший полягає у
наслідуванні стандартного класу потоку Thread. Нижче наведено приклад
програми як сукупності трьох потоків:
public class MyThread ехtends Thread{ //планується виконання у потоці
public void run () { // метод викликається при запуску потоку

while (true) {

}

}
public static void main(String args[]) {
Thread MyThread1 = new MyThread.start();/* створення і
Старт*/
Thread MyThread2 = new MyThread.start();
Thread MyThread3 = new MyThread.start();
}
}
Другий спосіб використовується, якщо є потреба створити декілька потоків
з доступом до спільного об‘єкту. Він полягає у створенні класу з методом run() і
наслідуванням інтерфейсу Runnable (для вказання компілятору, що клас буде
виконуватись як потік). Запуск потоку можливий з допомогою методу join():
public class Server implements Runnable { // клас у потоці
public static void main(String args[]) {
new Thread(new Server()).start();

141
try {
Thread.currentThread().join();
} catch (InterruptedException e) {}
}
}
або без нього
class MyThread implements Runnable {
public voidrun() {
// основний метод класу
}
}
Runnable runnable = new MyThread(); // створення об’єкту
Thread thread = new Thread(runnable); // створення об’єкту
thread.start(); // стартування потоку і методу runв runnable
А що станеться при запуску багатьох потоків? Перед демонстрацією
відповідного прикладу спочатку розглянемо, як вивести ім‘я потоку, що
виконується. Можна використати метод getName() класу Thread, тоді кожний
Runnable виведе ім‘я потоку, що виконує метод run() цього об‘єкту Runnable. В
наступному прикладі створюється екземпляр потоку, якому надається ім‘я, а потім
це ім‘я виводиться методом run():
class NameRunnable implements Runnable {
public void run() {
System.out.println("NameRunnable running");
System.out.println("Run by "
+ Thread.currentThread().getName());
}
}
public class NameThread {
public static void main (String [] args) {
NameRunnable nr = new NameRunnable();
Thread t = new Thread(nr);
t.setName("User");
t.start();
}
}
Виконання цього коду сформує дуже специфічний вивід:
% java NameThread
NameRunnable running
Run by User
Для отримання імені потоку викликаємо метод getName() з об‘єкту Thread.
Але адресат об‘єкт Runnable навіть не має посилання на екземпляр Thread, тому
спочатку викликається статичний метод Thread.currentThread(), що повертає
посилання на поточний виконуваний потік, з якого потім викликається getName().
Навіть, якщо ім‘я потоку не задане безпосередньо, він все одно його має.
Закоментуємо вираз попереднього коду із встановленням імені потоку:
public class NameThread {
public static void main (String [] args) {
NameRunnable nr = new NameRunnable();
Thread t = new Thread(nr);
// t.setName("Fred");
t.start();
}

142
}
Отримаємо вивід:
% java NameThread
NameRunnable running
Run by Thread-0
Як тільки за допомогою статичного методу Thread.currentThread() отримано
ім‘я поточного потоку, можна навіть отримати ім‘я потоку, що виконує код із
методом main():
public class NameThreadTwo {
public static void main (String [] args) {
System.out.println("thread is "
+ Thread.currentThread().getName());
}
}
Що виведе
% java NameThreadTwo
thread is main
Це правильно – головний потік завжди має ім‘я main.
8.3. Стани потоків. Планувальник черги потоків.
Методи join(), yield()
Стани потоку
Потік може знаходитись тільки у п‘яти станах:
 Новий. Це є стан потоку після створення екземпляруThread, але перед
викликом методу start(). Об‘єкт Thread є живим, але потік ще не
виконується. В цьому стані потік вважається недіючим.
 Готовий до запуску. В цьому стані потік є здатним до запуску, але
планувальник черги не обирає потік для виконання. Потік переходить до
стану готовності до запуску в двох випадках: 1) перед викликом методу
start(), 2) потік може повернутись назад до стану готовності до запуску або
після його виконання, або після стану блокування/очікування/засинання. В
стані готовності до запуску потік вважається діючим.
 Стан виконання. Це найважливіший стан активності потоку. В цьому стані
потік є обраним планувальником черги серед інших потоків у списку
готових до запуску для поточного виконання процесу. Потік може вийти зі
стану виконання по декількох причинах, включно «так вважає
планувальник черги».
 Очікування/блокування/засинання (Waiting/blocked/sleeping). В цьому
стані потік здатний до виконання. Таким чином, цей стан є поєднанням
трьох фактичних станів, спільних в одному: потік залишається діючим, але
в поточний момент не здатним до виконання. Іншими словами, це не є стан
готовності до запуску, але пізніше з нього можна повернутись до стану
готовності до запуску, якщо відбудеться відповідна подія. Потік може бути
заблокованим очікуванням на ресурси (подібно введенню-виведенню або
ключу об‘єкту), в такому випадку подією, що поверне потік назад до стану
готовності до запуску буде наявність ресурсів – наприклад, якщо програмою
будуть прочитані дані, що надійшли через вхідний потік, або стане
доступним ключ об‘єкту. Потік може заснути, якщо код методу run()

143
потоку викличе метод sleep() на деякий період часу, в результаті чого
подією, здатною повернути потік до стану готовності до запуску буде
прокидання потоку після завершення часу засинання. Або потік може
очікувати, якщо код методу run() потоку спричинить очікування, в такому
випадку подією, здатною повернути потік до стану готовності до запуску
буде відправлення іншим потоком повідомлення про неможливість
подальшого очікування. Важливим є те, що один потік не може повідомити
інший потік про блокування. Деякі методи немовби повідомляють іншим
потокам про блокування, проте насправді цього не роблять. Якщо t– є
посиланням на інший потік, то можна записати наступне:
t.sleep(); або t.yield()
Проте ці методи є статичними в класі Thread – вони не впливають на
екземпляр; натомість вони завжди впливають на потік, що наразі
виконується. Це підтверджує помилковість використання змінних
екземпляру статичними методами. В класі Thread iснує метод suspend(),
яким один потік повідомляє іншому про призупинення, проте цей метод є не
рекомендованим для використання і відсутнім на іспиті (аналогічно, як і
методи resume(), stop()). Методи suspend () та stop() усунено внаслідок їх
небезпечності, тому вони надалі не використовуються, є не
рекомендованими та відсутніми на іспиті. Відмітимо також, що потік у
заблокованому стані продовжує вважатись діючим.
 Мертвий. Потік вважається мертвим, коли його метод run() завершився.
Потік може продовжувати вважатись життєздатним, проте він більше не
буде обраним для виконання. Як тільки потік помре, він ніколи не
повернеться до життя. Виклик методу start() для мертвого екземпляру
Thread викличе виняток часу виконання (не часу компіляції). Якщо потік
помер, він більше не вважається діючим.
Розглянемо, як програма методом main() встановлює інші потоки, запускає
їх та передає їм управління.
Створення потоку здійснюється аналогічно до створення об‘єкту будь-якого
класу Java. Створений, але ще не запущений потік знаходиться в стані «новий
потік». Після запуску потоку він знаходиться в стані «виконання». При завершення
методу потоку run() він переходить в «мертвий» стан.
Атрибути потоку
Кожен потік має три атрибути, які можна встановити і визначити: ім‘я,
пріоритет і демон – статус. Ім‘я потоку є простим ідентифікатором, яке
використовується для налагодження та системного моніторингу. Виконання
потоків планується автоматично на основі пріоритетів. Потоки з вищим
пріоритетом мають більший доступ до ресурсів комп‘ютера, вони плануються для
більш частого використання порівняно з потоками з нижчим пріоритетом.
Пріоритет змінюється в процесі за допомогою методу Thread.setPriority(). Різні
операційні системи використовують різні діапазони для значень пріоритетів. В Java
встановлення пріоритету потоку базується на трьох константах: MIN_PRIORITY,
NORM_PRIORITY, MAX_PRIORITY.

144
Статус демона визначається методом Thread.setDaemon(), який необхідно
викликати перед звертанням до методу start() (тобто до початку реального
виконання базового потоку операційної системи).
Приклад 1. Для демонстрації паралельної роботи потоків розглянемо
програму, в якій два потоки сперечаються на предмет філософського питання «що
було раніше: яйце чи курка?». Головний потік впевнений, що першою була курка,
про що він буде сповіщати кожну секунду. Другий потік кожну секунду буде
спростовувати твердження опонента. Суперечка буде тривати 5 секунд. Переможе
потік, який останнім дасть відповідь на питання.
class EggVoice extends Thread{
@Override
public void run(){
for(int i = 0; i< 5; i++){
try{
sleep(1000); //Призупиняє потік на 1 секунду
} catch(InterruptedException e){}
System.out.println("яйце!");
}
//Слово «яйце» сказано 5 разів
}
}
public class ChickenVoice {
static EggVoice mAnotherOpinion; //Побічний потік
public static void main(String[] args){
mAnotherOpinion = new EggVoice(); //Створення потоку
System.out.println("Суперечка розпочата...");
mAnotherOpinion.start(); //Запуск потоку
for (int i = 0; i< 5; i++){
try{
Thread.sleep(1000); // Призупиняє потік на 1 секунду
} catch(InterruptedException e){}
System.out.println("курка!");
}
//Слово «курка» сказано 5 раз
if(mAnotherOpinion.isAlive()){ /*Якщо опонент не сказав
останнє слово*/
try{
mAnotherOpinion.join(); /* Почекати поки
опонент не закінчить говорити */
}catch(InterruptedException e){}
System.out.println("Першим з’явилось яйце!");
}else {//якщо опонент завершив говорити
System.out.println("Першою з’явилась курка!");
}
System.out.println("Суперечка завершена!");
}
}
Консоль:
Суперечка розпочата...
курка!
яйце!
курка!
яйце!

145
курка!
яйце!
курка!
яйце!
курка!
яйце!
Першим з’явилось яйце!
Суперечка завершена!
В наведеному прикладі два потоки паралельно протягом 5 секунд виводять
інформацію у консоль. Точно передбачити, який потік завершить висловлюватися
останнім, неможливо. Це відбувається через «асинхронне виконання коду».
Асинхронність означає те, що неможна стверджувати, що деяка інструкція одного
потоку буде виконана раніше чи пізніше інструкції іншого. Або, інакше кажучи,
паралельні потоки незалежні один від одного, за умови, якщо розробник не вкаже
про їх залежність.
Метод yield()
Щоб зрозуміти метод yield(), потрібно зрозуміти концепцію пріоритетів
потоку. Потоки завжди запускаються з певним пріоритетом, зазвичай
представленім числом між 1 і 10 (хоча в деяких випадках число є меншим десяти).
Планувальник черги в більшості JVM використовує розподіл ресурсів ОС з
витисканням, що базується на пріоритеті (тобто визначає певний порядок
розподілу часу процесора). Це не означає, що всі JVM використовують розподіл
часу. Специфікація JVM не вимагає розподіляти процесорний час таким чином,
щоб кожен потік мав рівне право з іншими на запуск протягом певного проміжку
часу, а потім переходив до стану готовності до запуску для надання можливості
іншим потокам бути запущеними. Хоча багато JVM використовують розподіл часу,
деякі можуть використовувати планувальник черги, що дозволяє одному потоку
залишатись запущеним, доки він не завершить виконання свого методу run().
Проте в більшості JVM, планувальник робить розподіл пріоритетів в один
важливий спосіб. Якщо потік знаходиться в стані готовності до запуску, і має
вищий пріоритет, ніж інші потоки в списку, а також вищий пріоритет, ніж потік,
що виконується в даний момент, виконуваний потік з нижчим пріоритетом буде
переведений до стану готовності до запуску, а на виконання буде запущено потік із
вищим пріоритетом. Іншими словами, в будь-який момент часу потік, що
виконується, як правило, не буде мати пріоритету, нижчого від будь-якого з
потоків у списку. В більшості випадків, виконуваний потік буде мати більший або
такий самий пріоритет, як найвищий пріоритет серед потоків, готових до запуску.
Аналогічні гарантії щодо планування черги можна одержати від специфікації JVM,
тому ніколи не можна покладатись на пріоритети потоків, щоб гарантувати
коректну поведінку програми.
Від yield() очікується переведення виконуваного потоку до стану готовності
до запуску, щоб дозволити іншим потокам того самого пріоритету отримати свою
чергу. Тобто призначення методу yield() – забезпечення справедливого розподілу
черги між потоками одного пріоритету. Насправді, даний метод не гарантує те, що
від нього вимагається, і навіть якщо yield() зупиняє потік та переводить його до
стану готовності до запуску, немає гарантії, що цей зупинений методом yield()
потік не буде вибраний для запуску ще раз, незважаючи на всі інші потоки! Таким

146
чином, хоча метод yield() може – і часто виконує – зупинку виконуваного потоку,
щоб надати часовий інтервал для інших потоків, готових до запуску, все одно цей
процес не є гарантованим.
Метод yield() не буде кожного разу переводити потік у стан очікування/
засинання/блокування. Щонайбільше, метод yield() переведе потік, що
виконується, до стану готовності до виконання, але, знову ж таки, це може не мати
жодного ефекту.
Метод join()
Нестатичний метод join() класу Thread дозволяє одному потоку
«приєднатись до кінця» іншого потоку. Якщо потік В не може робити свою роботу,
доки інший потік А не завершить свою роботу, необхідно, що б потік В приєднався
до потоку А. Тоді потік В не буде готовим до запуску, доки потік А не
завершиться, не перейде до «мертвого» стану. Наприклад:
Thread t = new Thread();
t.start();
t.join();
Вищенаведений код приєднує виконуваний потік (якщо це було в методі
main(), тоді це є головний потік) до кінця потоку, який має посилання t. Це блокує
виконуваний потік від стану готовності до запуску до того часу, поки потік із
посиланням t не буде більше «живим». Іншими словами, код t.join() означає
«Приєднай мене (виконуваний потік) до кінця t, тож t має завершитись перед тим,
як я (виконуваний потік) зможу знов бути запущеним». Також можна викликати
одну з перевантажених версій join(), яка використовує часову паузу, в цьому
випадку сказано наступне: «чекай, поки t не буде виконаним, але якщо це
триватиме більше 5000 мілісекунд, припини очікування і повертайся до стану
готовності у будь-якому випадку».
Тести
10. Розглянемо код. Яким буде результат його виконання?
public class Cruiser implements Runnable {
public static void main(String[] args) {
Thread a = new Thread(new Cruiser());
a.start();
System.out.print("Begin");
a.join();
System.out.print("End");
}
public void run() {
System.out.print("Run");
}
}
a) помилка при компіляції
b) помилка часу виконання
c) виведеться "BeginRunEnd"
d) виведеться "BeginEndRun"
e) виведеться "BeginEnd"
Завдання для самостійного виконання
1. Дослідити циклічні процеси виконання арифметичних операцій в
багатопотоковому асинхронному режимі.

147
8.4. Приклади аналізу багатопотокових кодів
Приклад 1. Скласти програму, яка запускає головний потік, який:
 повідомляє про початок роботи
 генерує в циклі створення та роботу ще 6 потоків, кожен з яких:
o виводить інформацію «потік … почав роботу»
o засинає на 500 мілісекунд
o завершує роботу повідомленням «Потік … завершив роботу»
 завершує роботу повідомленням «головний потік завершив роботу»
package prog1;
public class Main {
public static void main(String[] args) {
System.out.println("Головний потік почав роботу");
for (inti = 1; i<=6; i++){
new MyThread("Thread_"+i).start();
}
System.out.println("Головний потік завершив роботу");
}
}
package prog1;
public class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println("Потік "+
MyThread.currentThread().getName() +
" почавроботу");

try {
MyThread.sleep(500);
} catch (InterruptedException e) {
System.out.println("Потік " +
MyThread.currentThread().getName() +
" спить");
}
System.out.println("Потік " +
MyThread.currentThread().getName() +
" завершивроботу");
}
}
Для того, щоб головний потік завершував свою роботу тільки по закінченню
роботи всіх потоків необхідно використати метод join(), який заставляє потік, що
викликає інші потоки очікувати завершення роботи всіх потоків, для яких
викликано метод join().
Приклад 2. Змініть програму так, щоб головний потік очікував на
завершення роботи потоку mt
MyThread mt = new MyThread("Thread_");
mt.start();
for (inti = 1; i<=6; i++){
mt = new MyThread("Thread_"+i);
mt.start();

148
}
mt.join();
Приклад 3. Напишіть програму, в якій метод run() потоку виконує наступні
дії:
 Виводить інформацію про старт потоку
 Через switch-case визначає час очікування для потоку – First – 5000, Second –
1000
 Засинає на відповідну кількість мілісекунд
 Повідомляє про завершення роботи потоку
В main() створюються два потоки First та Second, потік main має дочекатися
завершення роботи потоку First, потік Second працює незалежно.
package prog3;
public class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
int temer = 0;
@Override
public void run() {
String name = MyThread.currentThread().getName();
System.out.println("Потік " + name + " почавроботу");
switch (name) {
case"First": temer = 5000; break;
case"Second": temer = 1000; break;
}
try {
Thread.sleep(temer);
} catch (InterruptedException e) {}
System.out.println("Потік " + name + " завершивроботу");
}
}
package prog3;
public class Main {
public static void main (String[] args) {
System.out.println("Потік main почав роботу");
MyThread mt1 = new MyThread("First");
MyThread mt2 = new MyThread("Second");
mt1.start();
mt2.start();
try {
mt1.join(); /* main зупинено поки
незавершить роботу потік mt1 потік mt2 буде */
/*працювати на відміну від main, який може завершити
свою роботу тільки після потоку mt1 */
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Потік main завершив свою робооту");
}
}

149
Менш надійним способом призупинення виконання потоку є метод yield(),
який може зробити деяку паузу і дозволяє іншим потокам почати виконання свої
роботи. Цей метод блокує роботу потоку, в якому він викликаний, до тих пір, поки
не буде завершено виконання роботи потоку для якого викликано даний метод або
поки не завершиться час очікування.
Наприклад, у випадку потоку з високим пріоритетом після обробки частини
пакета даних, коли наступна частина ще не готова, необхідно поступитися
частиною часу іншим потокам. Якщо необхідна надійна зупинка, то слід
використовувати інші методи.
Приклад 4. Демонстрація роботи потоку через використання анонімних
класів з можливістю призупинки виконання потоку через метод yield().
package MyThread2_urok_4_yield;
public class YieldRunner {
public static void main(String[] args) {
new Thread() { // анонімний клас
public void run() {
System.out.println("стартпотоку 1");
Thread.yield();
/* скоріш за все перший потік буде призупинено
на деякий проміжок часу, що дасть можливість другому
потоку виконати свій код */
System.out.println("завершення 1");
}
}.start(); // запуск потоку
new Thread() {
public void run() {
System.out.println("старт потоку 2");
System.out.println("завершення 2");
}
}.start();
}
}
Основна складність, з якою зустрічаються розробники, що створюють
багатопотокові додатки, це організація взаємодії одночасно працюючих потоків.
Однопотокова програма, при запуску отримує в монопольне розпорядження
всі ресурси комп‘ютера і використовує ці ресурси в тій послідовності, яка
відповідає логіці роботи програми.
В багатопотоковій системі можливі ситуації, коли деякий потік змушений
чекати завершення виконання етапу алгоритму іншого потоку. Потоки можуть
також намагатися звертатися одночасно до одних і тих же ресурсів, що може
привести до неправильної роботи програми. Спроба одночасної модифікації
спільно використовуваних даних може привести до порушення цілісності цих
даних.
Таким чином, в багатопотоковому середовищі необхідна синхронізація
спільної роботи потоків для забезпечення коректності використання критичних
ресурсів. В Java передбачені різні засоби для цього завдання.

8.5. Синхронізація потоків

150
Для захисту критичних ділянок програми використовується ключове слово
synchronized. Використання ключового слова synchronized дає гарантію, що в
даний момент часу якийсь оператор або блок буде виконуватися тільки в одному
потоці. Інші потоки автоматично призупиняються при спробі звернення до ресурсу
зайнятому іншим потоком.
Синтаксис створення блоку синхронізації при використанні критичного
ресурсу object наведено нижче:
synchronized(object){
// critical operations
}
У разі потреби можна синхронізувати і метод:
public synchronized void anyMethod(){
//тіло методу
}
Синхронізація використовується як засіб блокування потоків, який не
дозволяє одному потоку спостерігати об‘єкт в проміжному стані, поки той
модифікується іншим потоком.
В межах синхронізованої області потрібно виконувати якомога менше
роботи:
 заблокувати ресурс;
 перевірити дані, що використовуються спільно;
 опрацювати дані при необхідності;
 розблокувати ресурс.
У деяких випадках синхронізацію доступу до полів, що спільно
використовуються можна опустити, якщо оголосити таке поле з модифікатором
volatile (неблокуюча синхронізація). Цей модифікатор гарантує, що будь-який
потік, який буде читати це поле, побачить найостанніше записане значення.
Існує ціла група класів, що забезпечують неблокуючу синхронізацію.
Атомарні класи створені для організації структур даних, що не блокуються.
Методи synchronized
Нерідко виникає ситуація, коли декілька потоків мають доступ до деякого
об‘єкта і починають заважати один одному.
Приклад 1. Написати програму, яка записує в один файл інформацію двома
синхронними потоками.
В методі main() класу SynchroRun створюємо два потоки а також екземпляр
класу Resource, що містить поле типу FileWriter, яке пов‘язане з файлом на диску.
Екземпляр цього класу передається обом потокам в якості параметру. В цьому
класі є метод writing(), який записує рядок в файл, метод є синхронізованим, щоб
уникнути невірного запису у файл.
1. Клас Resource містить поле типу FileWriter метод synchronized writing(), що
записує інформацію у файл та метод close() закриття файлу
package temp1;
import java.io.FileWriter;
import java.io.IOException;
public class Resource {
private FileWriter fileWriter;
public Resource (String file) throws IOException {
fileWriter = new FileWriter(file, true);

151
// перевіркаіснуванняфайлу
}
public synchronized void writing(String str, inti) {
try {
fileWriter.append(str + i);
System.out.print(str + i);
Thread.sleep((long)(Math.random() * 50));
fileWriter.append("->" + i + " ");
System.out.println("->" + i + " ");
} catch (IOException e) {
System.err.print("помилка файла: " + e);
} catch (InterruptedException e) {
System.err.print("помилка потоку: " + e);
}
}
public void close() {
try { fileWriter.close();
} catch(IOException e) {System.err.print("помилка " +
+ "закриття файлу: " + e);}
}
}
2. Клас SyncThread, який описує потік, в методі run() якого 5 разів викликається
метод запису у файл (записується назва потоку)
package temp2;
public class SyncThread extends Thread {
private Resource rs;
public SyncThread(String name, Resource rs) {
super(name);
this.rs = rs;
}
public void run() {
for (inti = 0; i< 5; i++) {
rs.writing(getName(), i);
// місце де спрацьовує синхронізація
}
}
}
3. Клас SynchroRun, який створює два потоки, запускає їх і для кожного з них
викликає при зупинку main, використовуючи метод join()
package temp3;
import java.io.IOException;
public class SynchroRun {
public static void main(String[] args) {
Resource s = null;
try {
s = new Resource ("result.txt");
SyncThread t1 = new SyncThread("First", s);
SyncThread t2 = new SyncThread("Second", s);
t1.start();
t2.start();
t1.join();
t2.join();
} catch (IOException e) {
System.err.print("помилка файлу: " + e);

152
} catch (InterruptedException e) {
System.err.print("помилка потоку: " + e);
} finally {
s.close();
}
}
}
Тести
11. Що з переліченого буде компілюватися без помилок? (Виберіть три вірні
відповіді)
a) private synchronized SomeClass a;
b) void book() { synchronized () {} }
c) public synchronized void book() {}
d) public synchronized(this) void book() {}
e) public void book() { synchronized(Cruiser.class) {} }
f) public void book() {synchronized(a){}}
12. Розглянемо код:
public class TestThings extends Thread {
private static int x;
public synchronized void doThings() {
int current = x;
current++;
x = current;
}
public void run() {
doThings();
}
}
Яке з тверджень є вірним?
a) помилка компіляції
b) помилка при вивконанні
c) синхронізація методу run() зробить клас безпечним для потоку
d) дані в змінній "x" захищені від проблем, пов‘заних із спільним доступом
e) оголошення методу doThings () як статичного робить клас потокобезпечним

8.6. Взаємодія потоків. Методи wait(), notify()


Клас Object у Java містить три final методи для взаємодії потоків. Це методи
wait(), notify() та notifyAll().
Потік, який викликає ці методи в будь-якому об‘єкті, повинен мати так
званий монітор (механізм багатопоточного доступу до об‘єкту). Якщо ж його
немає, тоді виникає виняток java.lang.IllegalMonitorStateException.

Метод wait()
У методу wait() існує три варіанти. Один метод wait() нескінченно чекає
інший потік, поки не буде викликаний метод notify() або notifyAll() для об‘єкту.
Інші два варіанти методу wait() ставлять поточний потік в режим очікування на
деякий визначений час. По завершенні цього часу потік прокидається та продовжує
роботу.
Метод notify()

153
Виклик методу notify() пробуджує лише один потік, після чого цей потік
починає виконання. Якщо об‘єкт очікують декілька потоків, то цей метод
пробудить лише один з них. Вибір потоку залежить від системної реалізації
керування потоками.
Метод notifyAll()
Метод notifyAll() пробуджує всі потоки, хоча в якій послідовності вони
будуть прокидатися залежить від реалізації ОС.
Кожен об‘єкт у Java має не тільки блокування для synchronized блоків та
методів, але й для, так званих, wait-set наборів потоків виконання. Будь який потік
може викликати метод wait() будь якого об‘єкта й таким чином потрапити в його
wait-set. При цьому виконання такого потоку призупиняється до того часу, поки
інший потік не викличе у цього ж об‘єкта метод notifyAll(), який пробуджує всі
потоки з wait-set. Метод notify() пробуджує один, випадково обраний потік з цього
набору.
Однак застосування цих методів пов‘язано з одним дуже важливим
обмеженням. Будь який з них може бути викликаний потоком з об‘єкта тільки
після встановлення блокування на цей об‘єкт. Тобто, якщо всередині synchronized-
блоку з посиланням на цей об‘єкт в якості аргументу, чи звертанням до методів
повинні бути в синхронізованих методах класу самого об‘єкту.
Приклад 1.
public class WaitThread implements Runnable { private Object shared;
public WaitThread(Object o) {
shared=o;
}
public void run() {
synchronized (shared) {
try {
shared.wait();
} catch (InterruptedException e) {}
System.out.println("after wait");
}
}
public static void main(String s[]) {
Object o = new Object();
WaitThread w = new WaitThread(o); new Thread(w).start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
System.out.println("before notify");
synchronized (o) {
o.notifyAll();
}
}
}
Результатом програми буде:
before notify after wait
Потрібно звернути увагу на те, що метод wait(), так само як і sleep(),
потребує обробки InterruptedException, тобто його виконання також можна
перервати методом interrupt().
Приклад 2. Розлянемо проект з трьома потоками:

154
public class ThreadTest implements Runnable {
final static private Object shared = new Object();
private int type;
public ThreadTest(int i) {
type = i;
}
public void run() {
if (type == 1 || type == 2) {
synchronized (shared) {
try {
shared.wait();
} catch (InterruptedException e) {}
System.out.println("Thread " + type +
" after wait()");
}
} else {
synchronized (shared) {
shared.notifyAll();
System.out.println("Thread " + type +
"after notifyAll()");
}
}
}
public static void main(String s[]) {
ThreadTest w1 = new ThreadTest(1);
new Thread(w1).start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
ThreadTest w2 = new ThreadTest(2);
new Thread(w2).start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
ThreadTest w3 = new ThreadTest(3);
new Thread(w3).start();
}
}
Результатом роботи програми буде:
Thread 3 after notifyAll()
Thread 1 after wait()
Thread 2 after wait()
По-перше, був запущений потік 1, який тут же викликав метод wait() та
зупинив своє виконання. Далі те саме сталося з потоком 2, а потім вже починає
виконуватися потік 3.

Переривання
Переривання – вказівка потоку, що він має припинити поточну роботу і
зробити ще щось. Потік може послати переривання викликом методу interrupt() в
об‘єкта Thread, якщо потрібно перервати асоційований з ним потік. Механізм
переривання реалізовано з використання внутрішньої мітки interruptstatus (мітка
переривання) класу Thread. Виклик Thread.interrupt() активує цю мітку. Згідно з

155
узгодженнями будь-який метод, який завершується викиданням
InterruptedException скидає мітку переривання. Перевірити чи встановлена та чи
інша мітка можна двома способами. Перший спосіб – викликати метод bool
isInterrupted() об'єкту потоку, другий – викликати статичний метод bool
Thread.interrupted(). Перший метод повертає стан мітки переривання і залишає цю
мітку недоторканою. Другий метод повертає стан мітки і скидає її. Зауважте,
що Thread.interrupted() – статичний метод класу Thread, і його виклик повертає
значення мітки переривання того потоку, з якого він був викликаний.
Демони
В Java процес завершується тоді, коли завершується останній його потік.
Навіть коли метод main() вже завершився, але ще виконуються породжені ним
потоки, система буде чекати їх завершення. Але це правило не відносить до
особливого виду потоків – демонів. Якщо завершився останній звичайний потік
процесу, і залишилися тільки потоки-демони, то вони будуть примусово завершені
і виконання процесу закінчиться. Найчастіше потоки-демони використовуються
для виконання фонових задач, які обслуговують процес протягом його життя.
Оголосити потік демоном досить просто – потрібно перед запуском потоку
викликати його метод setDaemon(true); перевірити чи є потік демоном, можна
викликавши його метод boolean isDaemon().
Завершення потоку
У Java існують засоби для примусового завершення потоку. Зокрема метод
Thread.stop() завершує потік безпосередньо після свого виконання. Однак цей
метод, а також Thread.suspend(),який зупиняє потік, й Thread.resume(), який
продовжує виконання потоку, були оголошено застарілими.
Замість примусового завершення потоку застосовується схема, в якій кожен
потік сам відповідає за своє завершення. Потік може зупинитися або тоді, коли він
закінчить виконання методуrun(), (main() – для головного потоку), або за сигналом
з іншого потоку.
Розробка синхронізованої багатопотокової системи продажу квитків
Приклад 3. Розробити проект з продажу квитків в різних касах, квитки
перебувають в стані очікування.
Оскільки квитку купуються касирами, то саме в класі, який описує квитки,
необхідно описати синхронізований метод buy(). Це означає, що якщо якийсь
квиток буде перебувати в процесі закупівлі, то всі інші касири не мають мати
доступ до переліку квитків, бо в іншому випадку один і той же квиток зможуть
купити декілька касирів.
Клас Ticket, який описує квитки містить:
 поле place (місце в транспорті),
 поле boolean isBought, яке буде визначати чи місце куплене
 конструктор (приймає place)
 метод buy(), який
package laboratorna;
public class Ticket {
int place;
boolean isBought;
Ticket (int place){
this.place = place;

156
}
synchronized boolean buy(){
if (!isBought){
try {
Thread.sleep(10);
} catch (Exception e){}
isBought = true;
return true;
} else {return false;}
}
}
Клас Casyr, який описує роботу касирів, містить:
 поле id – номер касира;
 ArrayList al – масив квитків;
 конструктор, в який передається id та al;
 метод run(), в якому перебираються всі квитки в масиві, і обирається вільне
місце для купівлі, якщо воно куплено, то про це видається повідомлення.
import java.util.ArrayList;
public class Casyr implements Runnable{
int id;
ArrayList <Ticket> al;
Casyr (int id, ArrayList<Ticket>al){
this.id=id;
this.al = al;
}
public void run(){
for (Ticket temp : al){
if (temp.buy()){
System.out.println(id +" касиркупив " +
" Ticket " + temp.place);
}
}
}
}
Клас з main, в якому:
 створюється масив ArrayList;
 в масив додається 10 квитків;
 створюються три об‘єкта типу Casyr, кожен в окремому потоці, та
потоки запускаються на виконання.
import java.util.ArrayList;
public class Despetcher {
public static void main(String[] args) {
ArrayList <Ticket>myTickets = new ArrayList <Ticket>();
myTickets.add(new Ticket(1));
myTickets.add(new Ticket(2));
myTickets.add(new Ticket(3));
myTickets.add(new Ticket(4));
myTickets.add(new Ticket(5));
myTickets.add(new Ticket(6));
myTickets.add(new Ticket(7));
myTickets.add(new Ticket(8));
myTickets.add(new Ticket(9));

157
new Thread (new Casyr (1, myTickets)).start();
new Thread (new Casyr (2, myTickets)).start();
new Thread (new Casyr (3, myTickets)).start();
}
}
Тести
13. Дано частину коду. Що з переліченого є вірним?
public void waitForSomething() {
SomeClass o = new SomeClass();
synchronized (o) {
o.wait();
o.notify();
}
}
а) цей код може викинути InterruptedException
б) цей код може викинути IllegalStateException
в) цей код може викинути TimeOutException
г) якщо поміняти місцями о.wait () та o.notify (), то робота методу буде
коректною
Завдання для самостійного виконання
1. Розробити проект, в якому:
Постачальник:
 відправляє товар в чергу тоді, коли його немає;
 коли є перебуває в стані очікування,
а Одержувач:
 забирає товар з черги, якщо він є,
 якщо його немає, то перебуває в стані очікування
Оскільки товар ставитися і забирається, то саме в класі Q (черги товару) мають
бути описані синхронізовані методи put() та get(), які і будуть передавати ключ для
роботи потоків.

2. Створіть проект, в якому всі потоки мають можливість змінювати значення


поля х на 1. Дослідіть роботу потоків, коли вони синхронізовані та
несинхронізовані:
a) Клас CommonResource містить одне поле цілочисельне поле х рівне 0.
b) Клас CountThread описує роботу потоку, використовуючи клас Runnable.
Містить конструктор класу (параметром має ім‘я потоку, задане з використанням
номера) та об‘єкт класу CommonResource; метод run(), який задає значення 1
змінній х чотири рази, виводить на екран назву потоку та значення змінної х,
збільшує х на 1, засинає на 100 мілісекунд;
c) Клас з main(), який створює об‘єкт класу CommonResource та шість потоків і
запускає їх на виконання;
Запустіть на виконання, проаналізуйте значення змінної х (воно накопичується
для всіх потоків. Змініть програму, синхронізувавши об‘єкт cr класу
CommonResource в методі run():
public void run() {

158
synchronized(cr){
cr.x = 1; ....
Запустіть на виконання, проаналізуйте значення змінної х.

159
ВІДПОВІДІ ДО ТЕСТІВ
РОЗДІЛ ІІІ. ПРОГРАМУВАННЯ ANDROID,
БАЗ ДАНИХ, БАГАТОПОТОКОВЕ
ПРОГРАМУВАННЯ
Відповіді до тестів (Тема 7)
1. d
2. b
3. b
4. b
5. с
6. с
Відповіді до тестів (Тема 8.1):
7. с (відбудеться затримка потоку на 1мс, а далі виконається метод book())
8. d (спершу двічі викликається метод run(), а потім викликається метод start(),
який знову викличе метод run())
9. c(метод start(), який викличе метод run())
Відповіді до тестів (Тема 8.3):
10. с (спершу запускається потік методом start(), який в свою чергу викличе метод
run(), а вже останнім викликається метод join())
Відповіді до тестів (Тема 8.5):
11. с, e, f
12. e
Відповіді до тестів (Тема 8.6):
13. а (при використанні синхронізації потрібно описувати виняток)

160
РОЗДІЛ ІV. ТЕХНОЛОГІЇ JAVA SE7, SE8
ТЕМА 9. Основи паралельного API
9.1. Пакети java.util.concurrent, java.util.concurrent.atomic,
java.util.concurrent.locks. Потокобезпечні колекції
Пакети java.util.concurrent, java.util.concurrent.atomic,
java.util.concurrent.locks.
Якщо виникає необхідність забезпечити паралельну роботу над різними
завданнями, то для цього є такі можливості: за допомогою класу Thread або за
допомогою інтерфейсу Runnable. У кожного з них є свої плюси і мінуси.
Перевагою наслідування класу Thread є повний доступ до всіх
функціональних можливостей потоку, а недоліком є сам факт наслідування,
оскільки можна наслідувати тільки один клас.
Використання інтерфейсу Runnable не має даного недоліку, але для запуску
все одно потрібен об‘єкт Thread, а також зникає можливість прямого керування
потоком із задачі. Хоча це можна обійти за допомогою статичних методів класу
Thread, наприклад, curentThread() повертає посилання на поточний потік.
Тому зробити однозначний висновок про переваги одного з методів важко.
Часто в програмах використовуються обидва способи, але кожний для реалізації
свого напрямку.
Вважається, що наслідування класу Thread використовується тоді, коли
необхідно створити «новий» потік, який має доповнити функціональність класу
java.lang.Thread, наприклад, при розробці системного програмного забезпечення
(серверів програм).
Інтерфейс Runnable використовується тоді, коли «необхідно виконувати
декілька задач одночасно» і немає потреби вносити зміни в сам механізм
багатопотоковості, тому в бізнес-орієнованих програмах використовується
інтерфейс.
Пакет java.util.concurrent – це альтернативний інструмент для
багатопотокового програмування на мові Java, він бере на себе більшу частину
роботи при написанні багатопотокових проектів. Працюючи з потоками за
допомогою даного набору утиліт, можна писати потокобезпечні програми, не
виконуючи синхронізацію потоків вручну, а тому зменшуючи ймовірність
виникнення помилок.
Утиліти паралельності входять в пакет java.util.concurrent і двох його
підпакетів java.util.concurrent.atomic і java.util.concurrent.locks.
В пакеті concurrent визначено такі основні засоби паралельності:
 Atomic – набір класів, для атомарної роботи з примітивними типами –
гарантується виконання операції за «1 одиницю процесорного часу». Атомарні
класи гарантують виконання певних операцій, таких, як збільшення і зменшення,
оновлення та додавання значень, потокобезпечним способом. До таких класів
належать класи AtomicInteger, AtomicBoolean, AtomicLong, AtomicIntegerArray та
інші;

161
 Locks (замки) – мають більш гнучкі способи одержання і зняття
блокування, підтримують рекурсивне одержання і звільнення від блокуваня для
одного потоку;
 Синхронізатори – дають можливість синхронізувати взаємодію
декількох потоків на високому рівні. Класів синхронізаторів є декілька, кожен з
них оптимізує певну роботу, раніше їх необхідно було створювати вручну, один з
них це Semaphore;
 (Executors) Виконавці – керують виконанням потоків. З ними пов‘язані
інтерфейси Future (містить значення, яке повертає потік після виконання) та
Callable (визначає потік виконання, який повертає значення);
 (Collections) Паралельні колекції ConcurrentHashMap,
ConcurrentLinkedQueue і CopyOnWriteArrayList та інші – дотримуються «чесної»
черги при передачі повідомлень з одного потоку до іншого, підтримують блокуючі
та не блокуючі матоди, забороняють null-значення. Об‘єм черги має бути вказаний
при створенні;
 Каркас Fork/Join Framework – підтримує паралельне програмування;
 Exchanger (бар’єр) – await() – очікує конкретної кількості викликів
іншими потокам; коли кількість потоків буде досягнута, буде викликано callback і
блокування зніметься. Бар‘єр обнулює свій стан і може бути використаний
повторно.

Потокобезпечні колекції
 ArrayBlockingQueue – черга впорядкованих елементів (першим прибув –
першим вибув). Нові елементи додаються в хвіст черги, а з голови черги беруться
елементи для опрацювання. Об‘єм черги не може бути зміненим;
 ConcurrentHashMap – схожа інтерфейсом взаємодії на HashMap, –
операція читання не потребує блокування, елементи карти мають значення value,
оголошене як volatile (модифікатор, який накладає деякі умови на змінну – операції
зчитування та запису volatile-змінної є атомарними, результат запису в таку змінну
одним потоком стає видимим всім іншим потокам, які використовують цю змінну
для зчитування її значень);
 ConcurrentLinkedDeque – необмежена паралельна двостороння черга
(паралельне додавання, видалення і доступ виконуються безпечно завдяки
багатопотоковості);
 ConcurrentLinkedQueue – багато потоків можуть одночасно працювати з
чергою;
 ConcurrentSkipListMap – відсортована багатопотокова реалізація Maps;
 ConcurrentSkipListSet – є багатопотоковим аналогом TreeSet;
 CopyOnWriteArrayList – реалізація List інтерфейсу, аналогічна ArrayList,
але при кожній зміні списку створюється нова копія всієї колекції. Це вимагає
дуже великих ресурсів при кожній зміні колекції, проте для даного виду колекції
не потрібно синхронізації, навіть при зміні колекції при ітерації;
 CopyOnWriteArraySet – аналогічно CopyOnWriteArrayList, при кожній
зміні створює копію всього, тому рекомендується при дуже рідкісних змінах
колекції та вимогах thread-safe;
 DelayQueue – черга з затримкою для кожного елемента;

162
 LinkedBlockingDeque / LinkedBlockingQueue – блокуюча черга;
 LinkedTransferQueue – може служити для передачі елементів;
 PriorityBlockingQueue – багатопотокова PriorityQueue;
 SynchronousQueue – проста багатопотокова черга.
Ці колекції є паралельною альтернативою відповідним класам колекцій з
каркасу Collections Framework. Вони працюють так як і інші колекції, але
використовують паралельність.

9.2. Клас Executors


В паралельному API для роботи безпосередньо з потоками підтримується
концепція ExecutorService. Це Виконавці – вони призначені для створення потоків
виконання та керування ними, здатні виконувати асинхронні завдання і керувати
пулом потоків, тому немає необхідності створювати нові потоки вручну. Всі
потоки пулу будуть повторно використані. В цьому відношенні Виконавець
служить альтернативою керуванню потоками виконання засобами класу Thread.
В основу виконавця покладений інтерфейс Executor, в якому визначається
наступний метод:
void execute (Runnable потік)
В результаті виклику цього методу виконується вказаний потік. Відповідно,
метод execute() запускає вказаний потік на виконання.
Інтерфейс ExecutorService розширює інтерфейс Executor, доповнюючи його
методами, які допомагають управляти виконанням потоків та контролювати їх.
Наприклад, в інтерфейсі ExecutorService визначається метод shutdown(),
форма якого наведена нижче. Цей метод зупиняє всі потоки виконання, що
знаходяться в даний момент під керуванням екземпляра інтерфейсу
ExecutorService.
void shutdown ()
В інтерфейсі ExecutorService визначаються також методи, які запускають
потоки виконання, віддають результати, виконують ряд потоків, визначають стан
зупинки. Є також інтерфейс ScheduledExecutorService, який розширює інтерфейс
ExecutorService для підтримки планування потоків виконання.
Крім того, у паралельному API існують три класи виконавців:
ThreadPoolExecutor, ScheduledThreadPoolExecutor і ForkJoinPool. Клас
ThreadPoolExecutor реалізує інтерфейси Executor і ExecutorService і забезпечує
підтримку пула потоків виконання. Клас ScheduledThreadPoolExecutor також
реалізує інтерфейс ScheduledExecutorService для підтримки планування пула
потоків виконання. А клас ForkJoinPool реалізує інтерфейси ExecutorService і
застосовується в каркасі Fork/Join Framework.
Пул потоків надає ряд потоків виконання для вирішення різноманітних
задач. Замість того, щоб створювати окремий потік виконання для кожного
завдання, використовуються потоки з пулу. Це дозволяє скоротити навантаження,
пов‘язане зі створенням безлічі окремих потоків. Хоча класи ThreadPoolExecutor і
ScheduledThreadPoolExecutor можна використовувати напряму, але виконавця
частіше за все потрібно отримувати, викликаючи один з наступних статичних
методів, визначених у класі Executors.
Приклад.

163
static ExecutorService newCachedТhreadPool()
static ExecutorService newFixedТhreadPool (int кількість потоків)
static ScheduledExecutorService
newScheduledТhreadPool (int кількість потоків)
Метод newCachedThreadPool () створює пул потоків виконання, які не
тільки вводять потоки виконання по мірі необхідності, але і по можливості
повторно використовують їх. Метод newFixedThreadPool () створює пул потоків
виконання, який складається із вказаної кількості потоків. А метод
newScheduledThreadPool () створює пул потоків виконання, в якому можна
здійснювати планування потоків виконання. Кожний з них повертає посилання на
інтерфейс ExecutorService, призначений для управління пулом потоків виконання.
9.3. Інтерфейси Callable та Future.
До найкращих засобів в паралельному АРІ належить інтерфейс Callable. Він
є потоком виконання, який повертає значення. Об‘єкти інтерфейсу Callable можна
використовувати в прикладній програмі для обчислення результатів, які потім
повертаються потоку, який викликає. Це доволі ефективний механізм, оскільки він
полегшує написання коду для найрізноманітніших числових систем, в яких
часткові результати обчислюються окремо. Його можна використовувати і для
запуску потоку виконання, який повертає код стану, який засвідчує успішне
виконання потоку.
Оголошення інтерфейсу: interface Callable <V>,
де параметр <V> визначає тип даних, який повертає потік виконання. В
інтерфейсі визначено єдиний метод call():
V call() throws Exception
В тілі методу описується задача, яку необхідно виконати. Якщо результат не
можна обчислити, то повертається виняток.
Для виконання задачі типу Callable викликається метод submit(),
визначений в інтерфейсі ExecutorService:
<T> Future <T> submit (Callable <T> задача)
де параметр задача описує об‘єкт типу Callable, який буде виконуватись в
потоці.
Інтерфейс Future представляє значення, яке повертається об‘єктом типу
Callable. Оскільки це значення буде повернуто через деякий час в майбутньому, то
його ім‘я цілком відповідає його призначенню.
Щоб одержати значення, необхідно викликати метод get() з інтерфейсу
Future, який може викликати два винятки ExecutionException,
InterruptedException. В даному методі можна вказати часовий параметр, протягом
якого має очікуватися одержання результатів; якщо він відсутній, то очікування
буде безкінечне.
Приклад. Дано три задачі, які виконують три різні обчислення. Перша обчислює
суму, друга знаходить довжину гіпотенузи прямокутного трикутника за катетами, а
третя обчислює факторіал заданого значення. Всі три види обчислень виконуються
одночасно.
import java.util.concurrent.*;
public class CallableDemo{
public static void main(String[] args){

164
ExecutorService es = Executors.newFixedThreadPool(3);
Future<Integer> f;
Future<Double> f2;
Future <Integer> fЗ;
System.out.println ("Зaпycк");
f = es.submit (new Sum(10));
f2 = es.submit (new Hypot(3, 4));
fЗ = es.submit (new Factorial (5));
try {
System.out.println (f.get());
System.out.println (f2.get());
System.out.println (fЗ.get());
}catch(InterruptedException ехс){}
catch(ExecutionException ехс) {}
es.shutdown() ; /*ініціалізує завершення роботи всіх потоків*/
System.out.println ("Зaвepшeння");
}
}
// Три потоки виконання обчислень

class Sum implements Callable <Integer>{


int stop;
Sum(int v) {
stop = v; }
public Integer call() {
int sum = 0;
for (int i = 1; i <= stop; i++)
sum += i;
return sum;
}
}

class Hypot implements Callable <Double>{


double side1, side2;
Hypot (double s1, double s2) {
side1 = s1;
side2 = s2;
}
public Double call() {
return Math.sqrt ((side1*side1) + (side2*side2));
}
}

class Factorial implements Callable <Integer>{


int stop;
Factorial(int v) {
stop = v;
}
public Integer call(){
int fact = 1;
for (int i = 2; i <= stop; i++) {
fact *= i;
}
return fact;

165
}
}

Вивід:
Зaпycк
55
5.0
120
Зaвepшeння

9.4. Класи – синхронізатори


Синхронізатори надають високорівневі способи синхронізації взаємодії
декількох потоків. У пакеті java.util.concurrent визначено ряд класів
синхронізаторів.
Класи синхронізаторів Таблиця 4.1
Клас Опис
Semaphore Реалізує класичний семафор
CountDownLatch Чекає до тих пір, поки не відбудеться певна кількість подій
CyclicBarrier Дозволяє групі потоків виконання увійти в режим
очікування в попередньо заданій точці виконання
Exchanger Здійснює обмін даними між двома потоками виконання
Phaser Синхронізує потоки виконання, що проходять через кілька
фаз операції
Слід мати на увазі, що кожен синхронізатор надає конкретне рішення задачі
синхронізації. Завдяки цьому можна оптимізувати роботу кожного синхронізатора.
Раніше подібні типи об‘єктів синхронізації необхідно було створювати вручну.
Паралельний API стандартизує їх і робить доступними для всіх, хто програмує на
Java.
Клас Semaphore
Клас Semaphore – об‘єкт синхронізації – Семафор – керує доступом до
спільного ресурсу за допомогою лічильника. Якщо лічильник більший за 0, то
доступ дозволено, якщо дорівнює 0, то в доступі відказано.
Як правило, потік виконання, якому необхідно мати доступ до спільного
ресурсу, намагається одержати дозвіл, щоб скористатися семафором. Якщо
лічильник семафора буде більшим 0, потік виконання одержить доступ, після чого
значення лічильника семафора зменшиться на 1. В іншому випадку потік буде
заблоковано до тих пір, поки він не зможе одержати доступ. Якщо потоку
виконання доступ до спільного ресурсу більше не потрібен, то він вивільняє
дозвіл, в результаті чого лічильник семафора збільшується на 1. Якщо в цей
момент інший потік виконання очікує дозволу, то він одразу його одержує. Даний
механізм реалізується за допомогою класу Semaphore, який має два конструктори:
 Semaphore (int число);
 Semaphore (int число, boolean спосіб).
Число визначає початкове значення лічильника дозволів (визначає кількість
потоків виконання, яким може бути надано доступ одночасно в спільному ресурсі;

166
якщо число приймає значення 1, то до ресурсу має право доступу тільки один
потік. По замовчуванню дозволи надаються в довільному порядку. Якщо присвоїти
параметру спосіб true, то дозволи будуть надані в тому порядку, в якому вони
подавали запит на дозвіл.
Щоб одержати дозвіл, необхідно викликати метод acquire(). Можна
вказувати в дужках число дозволів.
Щоб вивільнити дозвіл, викликати метод release().
Приклад. Програма, яка використовує семафор для управління роботою потоків.
Дано спільну змінну count, описану в класі Shared, та два класи, які
описують роботу потоків, в одному з яких дана змінна збільшується 5 разів на 1, а
в другому зменшується 5 разів на 1. Для захисту даної змінної від одночасного
доступу до неї даних потоків використовується семафор. Хоч і в методі run() є
метод sleep(), який призупиняє процес виконання програми потоком, але це не
впливає на порядок роботи, бо завдяки семафору другий потік почне роботу тільки
після вивільнення дозволу на доступ. Отже, спочатку змінна збільшиться на 5, а
потім зменшиться на 5 строго по порядку.
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) {
Semaphore sem = new Semaphore (1) ;
new ThreadPlus (sem, "Plus") ;
new ThreadMinus (sem, "Minus") ;
}
}
//спільний ресурс
class Shared{
static int count = 0;
}
/*Потік виконання, що збільшує значення лічильника на одиницю*/
class ThreadPlus implements Runnable {
String name;
Semaphore sem;
ThreadPlus (Semaphore s, String n) {
sem = s;
name = n;
new Thread (this).start();
}
public void run() {
System.out.println();
System.out.println ("Зaпycк потоку збільшення " + name);
try {
/*спочатку одержати дозвіл*/
System.out.println("Потік " + name + " очікує на дозвіл");
sem.acquire();
System.out.println ("Пoтік " + name + " одержує дозвіл");

/* а тепер одержати доступ до спільного ресурсу*/


for (int i=0; i < 5; i++){
Shared.count++;
System.out.println (name + ": " + Shared.count);
/*дозволити, якщо можна, переключити контекст*/
Thread.sleep (1000);

167
}
}catch ( InterruptedException exc) {
System.out.println (exc);
}
System.out.println ("Пoтік " + name + " вивільняє дозвіл");
sem.release();//вивільнити дозвіл
}
}

class ThreadMinus implements Runnable{/*Потік виконання, що зменшує


значення лічильника на одиницю*/
String name;
Semaphore sеm;
ThreadMinus (Semaphore s, String n) {
sеm = s;
name = n;
new Thread (this).start();
}
public void run() {
System.out.println(" Зaпycк потоку зменшення " + name) ;
try {
//одержуємо дозвіл
System.out.println("Пoтік " + name + " очікує дозволу");
sеm.acquire() ;
System.out.println (" Пoтік " + name + " одержує дозвіл") ;

for ( int i=0 ; i < 5; i++ ) {


/*а тепер одержує доступ до спільного ресурсу*/
Shared.count--;
System.out.println ( name + ": "+ Shared.count );
/*дозволити доступ, якщо можна,
переключення контексту*/
Thread.sleep (100);
}
}
catch (InterruptedException exc) {
System.out.println (exc) ;
}
//звільнити дозвіл
System.out.println ("Пoтік " + name + " звільняє дозвіл ");
sеm.release ();
}
}

Клac CountDownLatch
Іноді необхідно, щоб потік виконання знаходився в режимі очікування до
тих пір, поки не настане одна (або більше) подія. Для цих цілей в паралельному
API призначений клас CountDownLatch, який реалізує самоблокування зі
зворотним відліком. Об‘єкт цього класу з самого початку створюється з кількістю
подій, які повинні відбутися до того моменту, як буде зняте самоблокування.
Кожен раз, коли відбувається подія, значення лічильника зменшується. Як тільки
значення лічильника досягне нуля, самоблокування буде знято.

168
У класі CountDownLatch є наведений нижче конструктор, де параметр число
визначає кількість подій, які повинні відбутися до того, як буде зняте
самоблокування.
CountDownLatch (int число)
Для очікування по самоблокуванню в потоці виконання викликається метод
await(). Загальні форми даного методу.
vоid await () throws InterruptedException
boolean await (long очікування, TimeUnit одиниця_часу) throws
InterruptedException
У першій формі очікування триває до тих пір, поки відлік, пов‘язаний із
об‘єктом типу CountDownLatch, не досягне нуля. А в другій формі очікування
триває тільки протягом певного періоду часу, обумовленого параметром
очікування. Час очікування вказується в одиницях, які позначаються параметром
одиниця_часу, який приймає об‘єкт перерахування TimeUnit. Метод await()
повертає логічне значення false, якщо досягнута межа часу очікування, або логічне
значення true, якщо зворотний відлік досягає нуля.
Щоб сповістити про подію, слід викликати метод void countDown().
Кожного разу, коли викликається метод countDown(), відлік, пов‘язаний з
викликаючим об‘єктом, зменшується на одиницю.

Клac CyclicBarrier
Клас CyclicBarrier дозволяє визначити об‘єкт синхронізації, який
призупиняється до тих пір, поки деяка кількість потоків виконань не досягне
певної бар‘єрної точки.
У класі CyclicBarrier визначені наступні конструктори:
CyclicBarrier(int кількість потоків)
CyclicBarrier(int кількість потоків, Runnable дія)
де параметр кількість потоків визначає число потоків, які повинні досягнути
деякого бар‘єру до того, як їх виконання буде продовжено. У другій формі
конструктора параметр дія визначає потік, який буде виконуватися після
досягнення бар‘єру.
Загальна процедура застосування класа CyclicBarrier наступна. Спочатку
необхідно створити об‘єкт класу CyclicBarrier, вказавши кількість очікуваних
потоків виконання. А коли кожний потік виконання досягне бар‘єру, необхідно
викликати метод await() для даного об‘єкту. В результаті виконання потоку буде
призупинено до тих пір, поки метод await() не буде викликаний у всіх інших
потоках виконання. Як тільки вказана кількість потоків виконання досягне бар‘єру,
відбудеться повернення з методу await(), і виконання буде відновлено. А якщо
додатково вказати деяку дію, то буде виконаний відповідний потік.
У методі await() є такі загальні форми:
int await() throws InterruptedException, BrokenBarrierException
int await(long очікування, TimeUnit одиниця_часу) throws
InterruptedException, BrokenBarrierException, TimeoutException
У першій формі очікування триває до тих пір, поки кожний потік виконання
не досягне бар‘єрної точки. А в другій формі очікування триває тільки протягом
певного періоду часу, обумовленого параметром очікування. Час очікування
вказується в одиницях, які вказуються параметром одиниця_часу. В обох формах

169
повертається значення, яке вказує порядок, в якому потоки виконання
досягатимуть бар‘єрної точки. Перший потік виконання повертає значення, яке
дорівнює кількості очікуваних потоків мінус 1, а останній потік повертає нульове
значення.
Клас Exchanger
Принцип дії класу Exchanger дуже простий: він очікує до тих пір, поки два
окремих потоки виконання не викличуть його метод exchange(). Як тільки це
відбудеться, він зробить обмін даними, наданими обома потоками. Такий механізм
обміну даними є простим у застосуванні. Наприклад, один потік виконання готує
буфер для прийому даних через мережеве з‘єднання, а інший – заповнює цей буфер
даними, які отримує через мережеве з‘єднання. Обидва потоки виконання діють
спільно, тому кожний раз, коли потрібна нова буферизація, здійснюється обмін
даними.
Клас Exchanger є узагальненим і описується наступним чином:
Exchanger <V>, де параметр V визначає тип обмінюваних даних.
У класі Exchanger визначається єдиний метод exchange(), що має наступні
форми:
V exchange(V буфер) throws InterruptedException
V exchange(V буфер, long очікування, TimeUnit одиниця_часу) throws
InterruptedException, TimeoutException,
де параметр буфер позначає посилання на обмінювані дані. Повертаються дані,
отримані з іншого потоку виконання. Друга форма методу exchange() дозволяє
визначити час очікування. Головна особливість методу exchange() полягає в тому,
що він не завершиться успішно до тих пір, поки не буде викликаний для одного і
того ж об‘єкта типу Exchanger з двох окремих потоків виконання. Подібним чином
метод exchange() синхронізує обмін даними.
Клас Phaser
Головне призначення класу Phaser – це синхронізувати потоки виконання,
які представляють одну або кілька стадій (фаз) виконання дій. Наприклад, в
прикладній програмі може бути кілька потоків виконання, які реалізують три стадії
обробки замовлень. На першій стадії окремі потоки виконання використовуються
для того, щоб провірити дані про клієнта, наявність товару на складі і його ціну. По
завершенні цієї стадії залишається два потоки виконання, де на другій стадії
обчислюється вартість доставки і сума відповідного податку, а на завершальній
стадії підтверджується оплата і визначається час доставки.
Клас Phaser дозволяє визначити об‘єкт синхронізації, що очікує завершення
певної фази. Потім він переходить до наступної фази і знову очікує її завершення.
Також клас Phaser можна використовувати і для синхронізації тільки одної фази. В
цьому відношенні він діє подібно до класу CyclicBarrier, хоча головне його
призначення – синхронізація кількох фаз. В класі Phaser визначають чотири
конструктори. Розглянемо два конструктори цього класу:
Phaser ()
Phaser (int кількість сторін)
Перший конструктор створює синхронізатор фаз з нульовим реєстраційним
рахунком, а другий встановлює значення реєстраційного рахунку, рівного заданій

170
кількості сторін. Об‘єкти, що реєструються синхронізатором фаз, часто
позначаються терміном сторона.
Клас Phaser використовується наступним чином. Спочатку створюється
новий екземпляр класу Phaser. Потім синхронізатор фаз реєструє одну або кілька
сторін, викликаючи метод register() або вказуючи потрібну кількість сторін в
конструкторі класу Phaser. Синхронізатор фаз очікує, поки зареєстровані сторони
не завершать фазу. Сторона повідомляє про це, викликаючи один із методів класу
Phaser, наприклад метод arrive() або arriveAndAwaitAdvance(). Як тільки всі
сторони досягнуть даної фази, то вона вважається завершеною, і синхронізатор фаз
може перейти до наступної фази або завершить свою роботу.
Для реєстрації сторони після створення об‘єкта класу Phaser потрібно
викликати метод register(). Форма цього методу:
int reqister()
Щоб повідомити про завершення фази, сторона повинна визвати метод
arrive(). Метод arrive() має форму:
int arrive()
Цей метод повідомляє, що сторона завершила завдання. Він повертає
поточний номер фази. Якщо робота синхронізатора фаз завершена, то метод
повертає від‘ємне значення. Метод arrive() не зупиняє виконання потоку. Це
означає, що він не очікує завершення фази. Цей метод може бути викликаний
тільки зареєстрованою стороною.
Якщо потрібно вказати завершення фази, а потім чекати завершення цієї
фази всіма іншими зареєстрованими сторонами, потрібно викликати метод
arriveAndAwaitAdvance (). Форма цього методу:
int arriveAndAwaitAdvance()
Цей метод чекає до тих пір, поки всі сторони не досягнуть даної фази, а
тоді повертає номер наступної фази або від‘ємне значення, якщо синхронізатор фаз
завершив свою роботу.
Потік виконання може досягнути даної фази, а тоді знятися з реєстрації,
викликавши метод arriveAndDeregister(). Форма методу:
int arriveAndDereqister()
Цей метод повертає номер даної фази або від‘ємне значення, якщо
синхронізатор фаз завершив свою роботу. Він не очікує завершення фази.
Щоб отримати номер даної фази, потрібно викликати метод getPhase().
Форма методу:
final int getPhase()
Коли створюється об‘єкт класу Phaser, перша фаза отримує нульовий
номер, друга фаза – номер 1, третя фаза – номер 2 і т.д. Якщо об‘єкт класу Phaser
завершив свою роботу, то повертається від‘ємне значення.
Завдання для самостійного виконання
1. Існує парковка, яка одночасно може вміщувати не більше 5 автомобілів.
Якщо парковка заповнена повністю, то новоприбулий автомобіль повинен
почекати, поки не звільниться хоча б одне місце. Після цього він зможе
припаркуватися. Скласти програму, яка моделює дану ситуацію, використовуючи
клас Semaphore.

171
2. Необхідно провести автомобільні перегони. У перегонах беруть участь
п‘ять автомобілів. Для початку перегонів потрібно, щоб виконалися наступні
умови:
 кожен з п‘яти автомобілів під‘їхав до стартової прямої;
 була дана команда «На старт!»;
 була дана команда «Увага!»;
 була дана команда «Руш!».
Важливо, щоб всі автомобілі стартували одночасно. Скласти програму, яка
моделює дану ситуацію, використовуючи клас CountDownLatch.
3. Існує паромна переправа. Паром може переправляти одночасно по три
автомобілі. Щоб не ганяти пором зайвий раз, потрібно відправляти його, коли біля
переправи збереться мінімум три автомобілі. Скласти програму, яка моделює дану
ситуацію, використовуючи клас CyclicBarrier.
4. Є дві вантажівки: одна їде з пункту A в пункт D, інша з пункту B в пункт
С. Дороги AD і BC перетинаються в пункті E. З пунктів A і B потрібно доставити
посилки в пункти C і D. Для цього вантажівки в пункті E повинні зустрітися і
обмінятися відповідними посилками. Скласти програму, яка моделює дану
ситуацію, використовуючи клас Exchanger.
5. Є п‘ять зупинок. На перших чотирьох з них можуть стояти пасажири і
чекати автобус. Автобус виїжджає з парку і зупиняється на кожній зупинці на
деякий час. Після кінцевої зупинки автобус їде в парк. Нам потрібно забрати
пасажирів і висадити їх на потрібних зупинках. Скласти програму, яка моделює
дану ситуацію, використовуючи клас Phaser.
6. Розробити проект, в якому постачальник (клас Producer) відправляє товар
в чергу, коли товару немає, і перебуває в стані очікування, коли він є, а одержувач
(клас Consumer) забирає товар з черги, якщо товар є, і перебуває в стані
очікування, коли його немає.
7. В класах Consumer та Producer, які імплементують Runnable є поле q,
типу Q (черга); конструктор, який приймає q, та створює і запускає відповідний
потік; а також метод run(), який викликає для q метод get(), якщо це Consumer або
метод put(), якщо це Producer, декілька разів.
8. Оскільки товар ставитися і забирається, то саме в класі Q (черга товару)
мають бути описані методи put() та get(), які і будуть передавати семафор для
роботи потоків.
Клас Q – черга, в якій працюють класи,– містить два методи і поля:
– метод put() – «поставка числа», використовує постачальник (Producer);
– метод get() – «забирає число», використовує одержувач (Consumer);
– поле n – число, яке постачає постачальник і забирає одержувач;
– статичне поле semCon, типу Semaphore, для одержувача є недоступне, отже
лічильник 0;
– статичне поле semProd, типу Semaphore, для постачальника є доступне –
лічильник 1, тобто семафор готовий надати доступ.
Метод get() – перебуває в стані очікування, посилає запит на доступ, очікує
на виконання методу put(), як тільки лічильник стане хоча б 1, метод зможе
виконуватись і значення лічильника стане на 1 менше.

172
Метод put() – посилає запит на дозвіл, оскільки на початковому етапі
лічильник 1, то дозвіл надано.
В класі з main() створюються: об‘єкт q, типу Q, об‘єкти постачальника та
одержувача, яким передається відповідно q.

9.5 Фреймворк Fork/Join


Каркас Fork/Join удосконалив багатопотокове програмування, він дозволяє
автоматично нарощувати обчислювальні потужності в прикладних програмах,
збільшуючи кількість задіяних процесорів, тому його рекомендовано
використовувати у випадках, коли необхідне паралельне опрацювання.
Наприклад, коли необхідно опрацьовувати великі масиви, то можна розбити
масив на частини для того, щоб роботу над кожною з частин виконувало окреме
ядро процесора.
Ядро каркасу складають класи:
 ForkJoinTask <V> – абстрактний клас, який визначає виконувану задачу,
параметр типу <V> визначає тип результату виконання програми. Від потоку
Thread він відрізняється тим, що він є абстракцією задачі, а не потоком виконання.
В класі є багато методів:
final ForkJoinTask <V> fork() передає задачу для асинхронного виконання,
це означає, що потік, з якого передалася задача (викликався метод), продовжує
працювати. Як тільки задача буде запланована для виконання, метод поверне
посилання this на об‘єкт задачі;
final V join() очікує завершення виконання задачі та повертає результат
виконання. Таких задач може бути запущено декілька;
final V invoke() – об‘єднує операції вилкового з‘єднання в єдиний виклик,
оскільки спочатку запускає задачу на виконання, а потім очікує її завершення та
повертає результат виконання;
final V invokeAll (ForkJoinTask зaдaчaA, ForkJoinTask задача) – може
викликати одночасно декілька задач. Потік виконання буде очікувати виконання
всіх задач.
 ForkJoinPool керує виконанням задач типу ForkJoinTask, це пул потоків
типу ForkJoinPool, які керують задачами. Такий спосіб організації роботи дозволяє
використовувати невелику кількість потоків виконання.
 RecursiveAction – унаслідується від ForkJoinTask <V> для виконання
задач, які не повертають значення. Має декілька методів, найбільш важливим є
метод protected abstract void compute() – містить код, що є конкретною задачею
(обчислювальна частина).
Як правило, клас служить для реалізації рекурсивної стратегії виконання
задач, які не повертають результатів.
 Recursive <V> –наслідується від ForkJoinTask <V> для виконання задач,
які повертають значення. Містить аналогічний метод, тільки він в цьому випадку
вже буде повертати значення результату.
При створенні об‘єктів типу ForkJoinPool можна вказати кількість потоків
(рівень паралельності), що можуть працювати одночасно. Зрозуміло, що кількість
задач не має перевищувати кількість ядер процесора. Однак рівень паралельності
не обмежує кількість задач, якими може керувати пул потоків.

173
Як тільки буде створено об‘єкт класу ForkJoinPool, задачу можна запускати
на виконання. Задача, запущена першою, вважається основною. Найпростіший
спосіб запустити програму – це виклик методу invoke().
Щоб запустити задачу на виконання і не очікувати на виконання, можна
скористатися методом execute().
Пул типуForkJoinPool керує виконанням своїх потоків за принципом
перехоплення роботи. Це означає, що як тільки один з потоків звільнився, то він
бере задачу з іншого потоку. Слід зазначити, що в пулі можуть існувати потокові
демони, вони завершаться разом із своїми потоками. Завершити їхню роботу
можна за допомогою методу shutdown().
Стратегія «розділяй і володарюй» передбачає, що задача має ділитись на
підзадачі, підзадачі ще ділитись і так до того часу, поки не буде досягнуто
порогового поділу (який слід вірно визначити, щоб він був ефективним), коли вже
немає сенсу виконувати поділ, бо обчислення уже не будуть виконуватись швидше,
якщо ділити ще. При цьому обробка всіх частин виконується паралельно.
Наприклад, масив на половину, половини на четвертину і так дальше. Це
буде швидше, ніж перебирати масив поелементно.
Отже, по-перше необхідно виконувати поділ (кількість задач має бути між
100 до 10000), по-друге необхідно враховувати час, який витрачається на
виконання обчислень. При великих обчисленнях на кожному етапі слід ставити
малі пороги, якщо етап обчислення короткий,– то великі.
Приклад. Використання каркасу та вилкового з‘єднання.
Дано масив чисел дійсного типу. Необхідно перетворити його в масив
коренів елементів даного масиву.
import java.util.concurrent.*;
import java.util.*;
/*Клас ForkJoinTask перетворює (через клас RecuraiveAction)значення
елементів масиву типу double в їх квадратні корені*/
//демонстрація паралельного виконання
public class ForkJoinDemo {
public static void main(String[] args) {
// створити пул задач
ForkJoinPool fjp = new ForkJoinPool();
double [] nums = new double [100000];

//присвоїти деякі значення


for ( int i = 0; i < nums.length; i++)
nums[i] = (double)i;

System.out.println ("Чacтина вихідної послідовності: " );


for ( int i=0; i < 10; i ++)
System.out.print (nums [i] +" " ) ;
System.out.println (" \n ");

SqrtTransform task = new SqrtTransform (nums, 0,


nums.length);
/*запустити основну задачу типа ForkJoinTask на виконання*/
fjp.invoke (task);

System.out.println( "Чacтина перетвореної послідовності " +

174
"(з точністю до 4 знаків після коми):");
for ( int i=0; i < 10; i++)
System.out.format ( "%.4f", nums[i]);
System.out.println ();

}
}

class SqrtTransform extends RecursiveAction {


/*В даному прикладі порогове значення встановлюється довільно
рівним 1000. В реальному коді його оптимальне значення може бути
визначено в результаті аналізу системи чи експериментально*/

final int seqThreshold = 1000; /*вказує, коли буде застосоване


послідовне опрацювання (кількість елементів, захоплених для
обробки, має бути меншою заданого числа)*/
double [] data;

//визначаємо частину опрацьовуваних даних


int start , end ;
SqrtTransform (double [] vals, int s, int е) {
data = vals;
start = s;
end = е;
}

/*Цей метод виконує паралельне обчислення*/


protected void compute () {
/* Якщо кількість елементів менша порогового значення, то
виконати подальшу обробку послідовно */
if ((end – start) < seqThreshold ) {
/*перетворення кожного елемента масиву на квадратний
корінь елемента*/
for ( int i = start; i < end; i++)
data [i] = Math.sqrt (data [i]);
}else {
/*в іншому випадку необхідно виконувати поділ даних
на менші частини, знайшовши середину */
int middle = (start + end) / 2;

/* запустити нові підзадачі на виконання,


використовуючи поділ на частини */
invokeAll (new SqrtTransform (data, start, middle),
new SqrtTransform (data, middle, end));
}
}
}

Вивід:
Чacтина вихідної послідовності:
0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0

Чacтина перетвореної послідовності (з точністю до 4 знаків після коми):


0,00001,00001,41421,73212,00002,23612,44952,64582,82843,0000

175
Завдання для самостійного виконання
1 Змініть програму так, щоб вона працювала, виводячи інформацію про всі
елементи масиву. Проаналізуйте результат виводу.
Вивід:
Чacтина вихідної послідовності:
0.0 1.0 2.0
Чacтина перетвореної послідовності (з точністю до 4 знаків після коми):
0,0000 1,0000 1,4142
Чacтина вихідної послідовності:
3.0 4.0 5.0 6.0
Чacтина перетвореної послідовності (з точністю до 4 знаків після коми):
1,7321 2,0000 2,2361 2,4495
Чacтина вихідної послідовності:
7.0 8.0 9.0 10.0

ТЕМА 10. ШАБЛОНИ ПРОЕКТУВАННЯ


10.1 Призначення та типи шаблонів проектування
Основні поняття
Процес проектування є непростим. Ще складнішим він стає тоді, коли
окремі компоненти системи потрібно використати повторно. Необхідно визначити
правильні об‘єкти, віднести їх до різноманітних класів, зберігаючи розумний
рівень абстракції та деталізації, визначити інтерфейси цих класів, ієрархію їх
наслідування, встановити взаємозв‘язки між ними.
З одного боку, спроектована структура повинна відповідати поставленому
завданню, з іншого боку – вона повинна бути й дещо узагальнюючою, щоб
максимально врахувати всі вимоги, які можуть виникнути в майбутньому. При
цьому дуже бажаним результатом є зведення до мінімуму необхідності
перепроектування.
В таких ситуаціях програмісти-початківці можуть легко розгубитися серед
великої кількості можливих варіантів вирішення задачі. Нерідко проходить дуже
багато часу, перш ніж їм вдасться створити по справжньому працездатний дизайн
підсистеми.
Перш за все, досвідчений розробник розуміє, що кожну задачу не потрібно
вирішувати щоразу «з нуля». Замість цього він намагається повторно використати
попередні рішення, які в минулому були вдалими. Знайшовши хороше вирішення
завдання, проектувальник щоразу буде його застосовувати. Саме завдяки
накопиченню досвіду спеціаліст і стає експертом в своїй області. Тому в багатьох
об‘єктно-орієнтованих системах можна зустріти повторювані фрагменти, які
складаються з класів та об‘єктів, що взаємодіють між собою. Суть в тому, що саме
за допомогою таких фрагментів-шаблонів вирішуються конкретні задачі
проектування, в результаті чого об‘єктно-орієнтований дизайн підсистеми стає
більш гнучким. Як наслідок, проектувальник, знайомий із шаблонами, відразу
може застосовувати їх для рішення нової задачі, не намагаючись щоразу
«винаходити велосипед».

176
Шаблон проектування – опис взаємодії об‘єктів та класів, адаптований для
вирішення узагальненої задачі проектування в конкретному контексті. Як правило,
це певна формалізована логічна одиниця підсистеми, яка напряму перетворюється
в програмний код.
Основні елементи шаблону проектування:
1. Назва. Вказавши ім‘я шаблону, можна відразу описати проблему
проектування, її рішення та наслідки. Присвоювання імен шаблонам дозволяє
проектувати систему на більш високому рівні абстракції. За допомогою такого
«словника шаблонів» можна вести дискусії з колегами, згадувати про шаблони в
розроблюваній документації, в деталях розуміти дизайн системи.
2. Завдання. Опис ситуації, в якій потрібно застосовувати шаблон.
Необхідно сформулювати задачу та її контекст. Може описуватися конкретна
проблема проектування, також може бути включено перелік умов, за яких є зміст
використовувати цей шаблон.
3. Рішення. Опис елементів дизайну, відношень між ними, функцій
кожного елемента. При цьому не мається на увазі конкретний дизайн чи реалізація,
оскільки шаблон – це абстрактна конструкція, застосовна в різноманітних
ситуаціях.
4. Результати. Це наслідки застосування шаблону, а також різного роду
компроміси. Хоча при описі шаблонів про наслідки зазвичай не згадують, однак
знати це необхідно для того, щоб можна було вибирати між різними варіантами
шаблонів і оцінити переваги та недоліки даного шаблону. Також немалу роль тут
грає і мова програмування, оскільки часто вибір того чи іншого шаблону залежить
від можливостей, які надає конкретна мова реалізації проекту.

Шаблони проектування
1. Породжуючі шаблони. Допомагають абстрагувати процес створення
об‘єктів. Вони дозволяють зробити систему незалежною від способу створення,
композиції та представлення об‘єктів. Ці шаблони найбільш важливі, коли система
більше залежить від композиції об‘єктів, ніж від наслідування класів.
2. Структурні шаблони. В цих шаблонах розглядається питання про те, як
із класів та об‘єктів утворюються більш складні структури. Структурні шаблони
класового рівня використовують наслідування для формування композицій із
інтерфейсів та реалізацій. Такі шаблони є корисними, коли потрібно організувати
спільну роботу кількох незалежних бібліотек.
3. Шаблони поведінки. Вони пов‘язані з алгоритмами та розподіленням
обов‘язків між об‘єктами. При цьому мова йде не тільки про самі об‘єкти та класи,
а й про типові способи взаємодії. Такі шаблони характеризують складний потік
керування, який не просто прослідкувати під час виконання програми.

Породжуючі шаблони
1. Шаблон Singleton (одиночка). Гарантує, що у класі є тільки один
екземпляр, і надає до нього глобальну точку доступу. Суттєво те, що можна
користуватися саме екземпляром класу, тому що при цьому в багатьох випадках
стає доступною більш широка функціональність. Наприклад, до описаних

177
компонентів класу можна звертатися через інтерфейс, якщо така можливість
підтримується мовою.
2. Шаблон Prototype (прототип). Визначає види створюваних об‘єктів за
допомогою примірника-прототипа і створює нові об‘єкти шляхом копіювання
цього прототипу. Простіше кажучи, це паттерн (з англ. створення) об‘єкта через
клонування іншого об‘єкта замість створення через конструктор.
3. Шаблон Factory method (фабричний метод). Визначає інтерфейс для
створення об‘єкту, але залишає підкласам рішення про те, який клас описувати.
Фабричний метод дозволяє класу делегувати створення підкласів.
Використовується, коли:
 класу заздалегідь невідомо, об‘єкти яких підкласів йому потрібно
створювати;
 клас спроектований так, щоб об‘єкти, які він створює, специфікувати
підкласами;
 клас делегує свої обов‘язки одному з декількох допоміжних підкласів, і
планується локалізувати знання про те, який клас приймає ці обов‘язки на себе.
4. Шаблон Reflection (рефлексія). Забезпечує можливість створення
об‘єктів по імені класу для мов програмування, що не підтримують рефлексію
явно. Шаблон розуміє організацію програми як набір динамічно завантажених
бібліотек, що може бути в деяких випадках неприйнятним.
5. Шаблон Creator (створювач). Визначає спосіб розподілу обов‘язків,
пов‘язаний з розподілом об‘єктів. В об‘єктно-орієнтованих системах це завдання є
одним з найбільш поширених. Основним призначенням шаблону Creator є
виявлення об‘єкта-творця, який при виникненні будь-якої події має бути
пов‘язаний з усіма створеними ним об‘єктами. При такому підході забезпечується
низька ступінь зв‘язаності. Об‘єкт встановлює свої частини, контейнер зберігає
свій вміст, реєстратор веде облік. Всі ці взаємозв‘язки є дуже поширеними
способами взаємодії класів у діаграмах класів. У шаблоні Creator визначається, що
зовнішній контейнер або клас-реєстратор – це хороші кандидати на виконання
обов‘язків, пов‘язаних зі створенням сутностей, які вони будуть містити або
реєструвати. Звичайно, це твердження є лише рекомендацією.
Структурні шаблони
1. Шаблон Adapter (адаптер). Іноді клас з інструментальної бібліотеки не
вдається використовувати тільки тому, що його інтерфейс не відповідає
створюваному додатку. У деяких випадках ми можемо поміняти інтерфейс на
потрібний, але якщо у нас немає вихідного коду бібліотеки, то таке рішення не
підходить. Ще один варіант – визначити інший клас таким чином, що він
адаптуватиме інтерфейс одного класу до іншого інтерфейсу. Шаблон Adapter
перетворює інтерфейс одного класу в інтерфейс іншого, який очікують клієнти.
Адаптер забезпечує спільну роботу класів з несумісними інтерфейсами, яка
без нього була б неможлива. Перший варіант шаблону називається адаптером-
об‘єктів, другий адаптером-класів. Результати застосування адаптерів-об‘єктів і
адептерів-класів трохи розрізняються.
2. Шаблон Composite (складальник). Проектувальник може згрупувати
дрібні компоненти для формування більших, які, в свою чергу, можуть стати
основою для створення ще більших.
178
3. Шаблон Decorator (декоратор). Об‘єкт, який передбачається
використовувати, виконує основні функції. Однак, може знадобитися додати до
нього деяку додаткову функціональність, яка буде виконуватися до, після або
навіть замість основної функціональності об‘єкта. Декоратор передбачає
розширення функціональності об‘єкта без визначення підкласів.
4. Шаблон Facade (фасад). Надає уніфікований інтерфейс замість набору
інтерфейсів деякої підсистеми. Фасад визначає інтерфейс більш високого рівня,
який спрощує використання підсистеми.
Розбиття на підсистеми полегшує проектування складної системи в цілому.
Загальна мета будь-якого проектування – звести до мінімуму залежність підсистем
одна від одної і обмін інформацією між ними. Один із способів вирішення цього
завдання – введення об‘єкта Facade, що надає єдиний спрощений інтерфейс до
більш складних системних засобів.
5. Шаблон Low Coupling (низька зв’язність). LowCoupling – це шаблон,
який дозволяє розподілити обов‘язки між об‘єктами таким чином, щоб ступінь
пов‘язаності між системами залишалася низькою. Ступінь зв‘язаності (coupling) –
це міра, яка визначає, наскільки жорстко один елемент пов‘язаний з іншими
елементами, або якою кількістю даних про інші елементи він володіє. Елемент з
низьким ступенем зв‘язаності (або слабким зв‘язуванням) залежить від не дуже
великого числа інших елементів і має такі властивості:
 мале число залежностей між класами (підсистемами);
 слабка залежність одного класу (підсистеми) від змін в іншому класі
(підсистемі);
 високий ступінь повторного використання підсистем.
Шаблони поведінки
1. Шаблон Iterator (ітератор). Являє собою об‘єкт, що дозволяє отримати
послідовний доступ до елементів об‘єкта-агрегату без використання описів
кожного з об‘єктів, що входить до складу агрегації.
2. Шаблон Mediator (посередник). Визначає об‘єкт, що інкапсулює спосіб
взаємодії безлічі об‘єктів. Посередник забезпечує слабку зв‘язаність системи,
позбавляючи об‘єкти від необхідності явно посилатися один на одного і
дозволяючи тим самим незалежно змінювати взаємодії між ними.
3. Шаблон Observer (спостерігач). Визначає залежність типу один до
багатьох між об‘єктами таким чином, що при зміні стану одного об‘єкта всі
залежні від нього об‘єкти сповіщаються про це і автоматично оновлюються.
4. Шаблон State (стан). Дозволяє об‘єкту варіювати свою поведінку в
залежності від внутрішнього стану. Ззовні створюється враження, що змінився
клас об‘єкта.
5. Шаблон Strategy (стратегія). Визначає сімейство алгоритмів,
інкапсулює кожен з них і робить їх взаємозамінними. Стратегія дозволяє
змінювати алгоритми незалежно від клієнтів, які ними користуються.
6. Шаблон Template Method (шаблонний метод). Шаблонний метод
визначає основу алгоритму і дозволяє підкласам перевизначити деякі кроки
алгоритму, не змінюючи його структуру в цілому.
7. Шаблон Chain Of Responsibility (ланцюжок обов’язків). Дозволяє
уникнути прив‘язки відправника запиту до його одержувача, даючи шанс обробити
179
запит декількох об‘єктів. Пов‘язує об‘єкти-одержувачі в ланцюжок і передає запит
вздовж цього ланцюжка, поки його не оброблять.
8. Шаблон Command (команда). Інкапсулює запит, як об‘єкт,
дозволяючи тим самим задавати параметри клієнтів для обробки відповідних
запитів, ставити запити у чергу або протоколювати їх, а також підтримувати
скасування операцій.
9. Шаблон High Cohesion (слабке зчеплення – проблема). За допомогою
цього шаблону можна забезпечити можливість управління складністю,
розподіливши обов‘язки, підтримуючи високу ступінь зчеплення.
Зчеплення – міра сфокусованості класу. При високому зчепленні обов‘язки
класу тісно пов‘язані між собою, і клас не виконує робіт непомірних обсягів. Клас з
низьким ступенем зчеплення виконує багато різнорідних дій або непов‘язаних між
собою обов‘язків.
10. Шаблон Don’t Talk to Strangers (не розмовляй з незнайомцями).
Шаблон регламентує, яким об‘єктам метод нашого об‘єкта може посилати
повідомлення. Завдяки даному шаблону немає необхідності забезпечувати
видимість непрямих об‘єктів, що в свою чергу підвищує зчеплення і знижує
ступінь зв‘язності об‘єктів.
11. Шаблон Polymorphism (поліморфізм). За допомогою поліморфних
операцій дозволяє забезпечити змінювати поведінку без перевірки типу. Даний
шаблон є узагальненням кількох GoF-шаблонів.
12. Шаблон Pure Fabrication (чиста синтетика). Об‘єктно-орієнтовані
системи характерні тим, що програмні класи часто реалізують поняття ділового
середовища, як наприклад, Sale (продаж) і Employee (співробітник). Проте, існує
безліч ситуацій, коли розподіл обов‘язків тільки між такими класами створює
сильне зв‘язування і слабке зчеплення. Саме в таких ситуаціях рішенням проблеми
може з‘явитися паттерн Pure Fabrication.
13. Шаблон Controller. Згідно шаблону Controller, проводиться
делегування обов‘язків з обробки системних повідомлень класу, якщо він
представляє:
 всю організацію або всю систему в цілому (зовнішній контролер);
 активний об‘єкт з реального світу, який може брати участь у вирішенні
завдання (контролер ролі);
 штучний обробник всіх системних подій прецеденту і називається
Прецедент Handler (контролер прецеденту).

10.2 Шаблон Singleton


Шаблони проектування вирішують типові завдання в програмуванні. Але це
не готове рішення, яке можна завантажити і застосувати у себе на практиці. Це –
швидше за все метод, або ідея того, як можна вирішити ту чи іншу задачу.
Використання шаблонів допомагає значно прискорити процес розробки, так як не
витрачається час на «винахід велосипеда», допомагає виробити загальну стратегію
в побудові ієрархії в створюваному коді. Так само використання шаблонів
проектування допомагає в документуванні, так як, пояснюючи роботу ділянки
коду, можна сказати, що він побудований на базі шаблону Singleton, і обізнана

180
людина відразу зрозуміє, в чому справа. Тому, знати, що таке шаблони
проектування, повинен кожен хороший розробник.

Шаблон Singleton
Шаблон Singleton накладає обмеження на створення екземпляра класу і
гарантує, що в JVM (віртуальна Java машина) існує тільки один екземпляр даного
класу. Клас Singleton-а повинен мати глобальну точку доступу для отримання
екземпляру класу. Шаблон використовують для логування (протоколювання
роботи) об‘єктів, драйверів, кешування і наборів потоків.
Також Singleton використовують в інших шаблонах проектування, таких як
«абстрактна фабрика» (Abstract Factory), Будівельник (Builder), Прототип
(Prototype), Фасад (Facade) і т.д.
Цей шаблон також використовується і самою Java в її ядрі, наприклад, в
java.lang.Runtime і java.awt.Desktop.
Існує кілька різних підходів реалізації шаблону Singleton, але всі вони
мають загальні принципи.
 private конструктор – для заборони ініціалізації екземпляра класу з іншого
класу через конструктор.
 private static – змінна того ж класу, яка і буде єдиним екземпляром цього
класу.
 public static – метод, який повертає екземпляр класу. Це – глобальна точка
доступу до зовнішнього світу, дозволяє отримати екземпляр класу Singleton.

Підходи реалізації шаблону Singleton, а також проблеми, пов’язані з кожною


реалізацією. Рання реалізація (Eager initialization)
У ранній реалізації екземпляр класу Singleton ініціалізується одночасно із
завантаженням класу. Це найпростіший варіант створення класу Singleton, але він
має недолік: екземпляр створюється в будь-якому випадку, навіть якщо ним ніхто
так і не скористається.
Розглянемо статичну ініціалізацію класу.
Приклад 1.
package singleton;
public class EagerInitializedSingleton {
private static final EagerInitializedSingleton instance =
new EagerInitializedSingleton();
/* конструктор private, щоб не було можливості створити екземпляр класу
ззовні.*/
private EagerInitializedSingleton(){}
public static EagerInitializedSingleton getInstance(){
return instance;
}
}
Якщо ваш клас не використовує велику кількість ресурсів, то такий підхід
виправданий. Однак найчастіше Singleton-класи створюються для роботи з
файловими системами і базами даних, коли слід уникати ініціалізації до моменту
виклику класом-клієнтом методу getInstance. Також цей метод ніяк не оброблює
виключення.

181
Ініціалізація в статичному блоці
Реалізація статичного блоку ініціалізації схожа на «ранню ініціалізацію», за
винятком того, що екземпляр класу створюється в статичному блоці, це забезпечує
можливість обробки винятків.
Приклад 2.
package singleton;
public class StaticBlockSingleton {
private static StaticBlockSingleton instance;
private StaticBlockSingleton(){}
/*блок статичної ініціалізації з можливістю обробки виняткових
ситуацій*/
static{
try{
instance = new StaticBlockSingleton();
}catch(Exception e){
throw new RuntimeException("При створенні об’єкта
«Singleton» виникла помилка");
}
}
public static StaticBlockSingleton getInstance(){
return instance;
}
}
Як рання ініціалізація, так і ініціалізація в статичному блоці створюють
екземпляр класу ще до того, як він викликається, що не робить їх кращими
засобами.
Лінива ініціалізація (Lazy Initialization)
Відкладена (лінива) ініціалізація (англ. Lazy initialization) – прийом в
програмуванні, коли деяка ресурсномістка операція (створення об‘єкта,
обчислення значення) виконується безпосередньо перед тим, як буде використаний
її результат. Таким чином, ініціалізація виконується «на вимогу», а не завчасно.
Приклад 3.
package singleton;
public class LazyInitializedSingleton {
private static LazyInitializedSingleton instance;
private LazyInitializedSingleton(){}
public static LazyInitializedSingleton getInstance(){
if(instance == null){
instance = new LazyInitializedSingleton();
}
return instance;
}
}
Наведена вище реалізація відмінно працює, якщо використовується тільки
один потік, але якщо справа доходить до багатопотокової системи, вона може
викликати проблеми, коли всередині циклу одночасно присутні кілька потоків. В
цьому випадку втрачається суть шаблону Singleton ,і два різних потоки
отримуватимуть різні об‘єкти класу.

182
Потоко-безпечний Singleton
Найпростіший спосіб створити потоко-безпечний Singleton клас – це
синхронізувати глобальний метод доступу за допомогою synchronized, таким
чином тільки один потік зможе використовувати даний метод. Загальна реалізація
такого класу наведена нижче.
Приклад 4.
package singleton;
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton(){}
public static synchronized ThreadSafeSingleton getInstance(){
if(instance == null){
instance = new ThreadSafeSingleton();
}
return instance;
}
}
Наведена вище реалізація прекрасно працює і забезпечує потокобезпеку, але
при цьому знижується продуктивність через витрати, пов‘язані з синхронізацією,
хоча остання необхідна тільки для перших декількох потоків, які могли б створити
незалежний екземпляр класу. Щоб прискорити роботу програми, використовується
принцип подвійного блокування з перевіркою. При такому підході всередині
синхронізованого блоку за допомогою конструкції іf проводиться додаткова
перевірка для гарантії того, що буде створений тільки один екземпляр класу
Singleton.
Нижче наведений фрагмент коду забезпечує принцип подвійного
блокування з перевіркою.
Приклад 5.
public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
if(instance == null){
synchronized (ThreadSafeSingleton.class) {
if(instance == null){
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}

Рішення Білла П’ю (Bill Pugh) для шаблону Singleton


До Java5 вищенаведені реалізації підходів, при певних умовах, були
приречені на провал, зокрема, коли занадто багато потоків намагаються одночасно
отримати екземпляр класу. Білл П‘ю придумав інший підхід до створення
Singleton класу, використавши внутрішній допоміжний статичний клас.
Реалізація Білла П‘ю шаблону Singleton виглядає наступним чином:
Приклад 6.
package singleton;
public class BillPughSingleton {
private BillPughSingleton(){}
private static class SingletonHelper{

183
private static final BillPughSingleton INSTANCE = new
BillPughSingleton();
}
public static BillPughSingleton getInstance(){
return SingletonHelper.INSTANCE;
}
Внутрішній допоміжний статичний private клас містить реалізацію
Singleton. Коли основний Singleton клас завантажений, клас SingletonHelper ще не
завантажений в пам‘ять; і тільки тоді , коли буде викликаний метод getInstance,
цей клас завантажиться і створить екземпляр Singleton класу.
Це найбільш поширене рішення для класу Singleton, оскільки не вимагає
наявності синхронізації, а також просте для розуміння та реалізації.

Використання рефлексії (reflection) для руйнування шаблону Singleton.


Рефлексія може зруйнувати всі описані вище підходи реалізації шаблону.
Приклад 7.
package singleton;
import java.lang.reflect.Constructor;
public class ReflectionSingletonTest {
public static void main(String[] args) {
EagerInitializedSingleton instanceOne =
EagerInitializedSingleton.getInstance();
EagerInitializedSingleton instanceTwo = null;
try {
Constructor[] constructors =
EagerInitializedSingleton.class.getDeclaredConstructors();
for (Constructor constructor : constructors) {
/*нижче наведений код знищить шаблон singleton*/
constructor.setAccessible(true);
instanceTwo = (EagerInitializedSingleton)
constructor.newInstance();
break;
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(instanceOne.hashCode());
System.out.println(instanceTwo.hashCode());
}
}
Коли ви запустите клас вище, ви відзначите, що hashCode обох примірників
зовсім однаковий, що руйнує ідею Singleton.
Рефлексія – це дуже потужна техніка і використовується багатьма
фреймворками такими як Spring, Hibernate.

Singleton за допомогою Enum


Для подолання цієї ситуації з рефлексією Joshua Bloch рекомендує
використовувати Enum для реалізації шаблону Singleton. Тільки так java гарантує,
що будь-яке Enum значення має тільки один екземпляр в програмі Java. З тих пір,
як Enum значення можуть мати глобальний доступ, вони використовуються в

184
Singleton. Недоліком є те, що тип повернення Enum дещо негнучкий, наприклад,
не підтримує ліниву ініціалізацію.
Приклад 8.
package singleton;
public enum EnumSingleton {
INSTANCE;
public static void doSomething(){
//реалізація
}
}
Серіалізация і Singleton
Іноді в розподілених системах нам потрібно реалізувати інтерфейс
Serializable в класі Singleton, щоб ми могли зберегти його стан в файловій системі і
отримати його пізніше. Ось невеликий Singleton клас, який реалізує інтерфейс
Serializable.
Приклад 9.
package singleton;
import java.io.Serializable;
public class SerializedSingleton implements Serializable{
private static final long serialVersionUID = -7604766932017737115L;
private SerializedSingleton(){}
private static class SingletonHelper{
private static final SerializedSingleton instance = new
SerializedSingleton();
}
public static SerializedSingleton getInstance(){
return SingletonHelper.instance;
}
}
Проблема з серіалізацією для класу Singleton полягає в тому, що як тільки
ми відтворимо об‘єкт, він тут же ініціалізує екземпляр класу. Розглянемо це на
простому прикладі.
Приклад 10.
package singleton;
public class SingletonSerializedTest {
public static void main(String[] args) throws FileNotFoundException,
IOException, ClassNotFoundException {
SerializedSingleton instanceOne = SerializedSingleton.getInstance();
ObjectOutput out = new ObjectOutputStream(new
FileOutputStream("filename.ser"));
out.writeObject(instanceOne);
out.close();
/*десеріалізувати з файлу в об’єкт*/
ObjectInput in = new ObjectInputStream(new
FileInputStream("filename.ser"));
SerializedSingleton instanceTwo = (SerializedSingleton)
in.readObject();
in.close();
System.out.println("instanceOne hashCode="+instanceOne.hashCode());
System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());
}
}

185
Вищенаведена програма виведе:
instanceOne hashCode = 2011117821
instanceTwo hashCode = 109647522
Таким чином вона руйнує шаблон Singleton. Для запобігання такій ситуації
все, що нам треба зробити, це забезпечити реалізацію методу readResolve ().
Приклад 11.
protected Object readResolve() {
return getInstance();
}
Якщо ви протестуєте роботу програми тепер, то побачите, що hashCode
збігається для обох примірників.

10.3 Шаблон Factory


Factory (фабрика) – це спосіб створення об‘єкта, одного з декількох
можливих класів, грунтуючись на представлених даних. Зазвичай, можливі класи
успадковують один загальний клас або реалізують спільний інтерфейс і
розрізняються між собою реалізацією.

Рис.4.1
Класична схема фабричного методу представлена вище (рис.4.1). Тут Х –
базовий клас, а може бути навіть інтерфейс. Класи XY і XZ по-різному реалізують
інтерфейс Х. Для вибору конкретної реалізації використовується клас Factory, де в
методі getClass відбувається аналіз зовнішніх параметрів abc, і, грунтуючись на
цих даних, метод повертає об‘єкт класу X, використовуючи реалізацію XY або XZ.
Таким чином, зовнішня програма не має уявлення про те, об‘єкт якого саме класу –
XY або XZ – їй повернули, оскільки обидва ці класи реалізують спільний
інтерфейс Х, отже, виклик їх зовнішніх методів нічим не відрізняється.

Використання і переваги Шаблону Factory:


 дозволяє зв‘язати паралельні ієрархії класів;
 коли конкретна реалізація об‘єктів даного класу Х задається за
допомогою підкласів;
 коли заздалегідь не відомі конкретні реалізації даного класу Х;
 там, де необхідно використовувати інтерфейс Х замість конкретних
класів;
 щоб приховати конкретні класи від клієнта;
 фабричні методи можуть бути параметризовані;

186
 дозволяє відповідати загальноприйнятим правилам іменування,
допомагає реорганізувати код при необхідності.
Реалізації шаблону фабрики можуть бути абсолютно різними, оскільки
шаблон – це не алгоритм.
Розглянемо декілька прикладів.
Для тестування всіх прикладів створено пакет
org.test.technerium.patterns.factory, а в ньому клас RunTestFactory зі звичайним
main’ом, в якому будуть запускатись класи прикладів з тестовими аргументами.
package org.test.technerium.patterns.factory;
public class RunTestFactory {
public static void main (String [] args) {
log ("> Старт тесту Фабрики");
/* тут будуть викликатися тести*/
}
public static void log (String msg) {
/* А це для виведення повідомлень в консоль, для зручності */
if (msg == null) msg = "null";
System.out.println(msg);
}
}
Приклад 1. Класична фабрика
Як приклад розглянемо ситуацію, в якій в нашій програмі ми створюємо
об‘єкт якогось клієнта, а потім звертаємося до його методів. При цьому існує
кілька реалізацій клієнта – для різних операційних систем. Природно, що в такому
випадку зручно написати якийсь інтерфейс Client, який визначає набір методів, до
яких ми хочемо звертатися. А конкретну реалізацію цих методів для різних ОС
визначити в класах ClientLinuxImpl і ClientWinImpl. Потім створити клас-фабрику,
який буде приймати, скажімо, id операційної системи і повертати об‘єкт
відповідного клієнта.
Інтерфейс Client задає наявність всього одного методу – getTargetOS, який,
очевидно, повертає назву операційної системи, для якої призначений клієнт:
public interface Client {
public String getTargetOS();
}
Потім реалізації клієнтів для Linux і для Windows:
public class ClientLinuxImpl implements Client {
@Override
public String getTargetOS() {
return "Gentoo Linux";
}
}

public class ClientWinImpl implements Client{


@Override
public String getTargetOS() {
return "Windows Vista";
}
}

187
Далі пишемо найпростішу фабрику, в якій метод getClient отримує id
операційної системи і повертає об‘єкт відповідного класу, або null, якщо id не
відповідає жодній ОС:
public class Factory {
public Client getClient(String currentOS){
if(currentOS.equals("win"))
return new ClientWinImpl();
else if(currentOS.equals("linux"))
return new ClientLinuxImpl();
return null;
}
}
Для тестування прикладу створимо ще клас RunTestFactoryEx1:
import org.test.technerium.patterns.factory.RunTestFactory;
public class RunTestFactoryEx1 extends RunTestFactory{
public static void main(String[] args){
log("----> Старт прикладу 1");
String currentOS = args[0];
Factory factory = new Factory();
log(" OS id: [" + currentOS + "], створення
клієнта");
Client client = factory.getClient(currentOS);
if(client!=null){
log(" Клієнт створено для OS:");
log(client.getTargetOS());
}else{
log(" Немає клієнта!");
}
log("<---- кінець прикладу 1");
}
}
id ОС одержуємо з параметрів методам main, створюємо змінну клієнта,
ініціалізуємо фабрику і отримуємо об‘єкт нашого клієнта. Клієнт типу Client, тобто
в момент написання коду ми поняття не маємо про конкретні реалізації інтерфейсу
для різних ОС. Таким чином, робота з клієнтом абсолютно вільна від того, як той
реалізований (ClientWinImpl або ClientLinuxImpl). Сьогодні це може бути Win,
завтра Linux, а післязавтра програмісти з іншого підрозділу додадуть реалізацію
під MacOS, а код цього навіть не помітить, досить буде додати пару рядків в код
фабрики. Фабричний метод getClient, до речі, міг бути і статичним, різниці в
даному випадку не було б.
Далі перевіряємо, що клієнт створений нормально і звертаємося до його
методу getTargetOS, в іншому випадку виводимо повідомлення про помилку.
Додаємо виклики нашого тесту в клас RunTestFactory, про який йшла мова
на самому початку:
public class RunTestFactory {
public static void main(String[] args){
log(">Старт тесту Фабрики");
RunTestFactoryEx1.main(new String[]{"win"});
RunTestFactoryEx1.main(new String[]{"linux"});
RunTestFactoryEx1.main(new String[]{"mac"});
log("==");

188
}

public static void log(String msg){


if(msg==null)
msg = "null";
System.out.println(msg);
}
}
Отже, спочатку має бути створений клієнт для Windows, потім Linux, а далі
має бути повідомлення про помилку. Запускаємо наш тест і в консолі отримуємо:
> Старт тесту Фабрики
----> Старт прикладу 1
OS id: [win], створення клієнта
Клієнт створено для OS:
Windows Vista
<---- Фініш прикладу 1
----> Старт прикладу 1
OS id: [linux], створення клієнта
Клієнт створено для OS:
Gentoo Linux
<---- Фініш прикладу 1
----> Старт прикладу 1
OS id: [mac], створення клієнта
Немає клієнта!
<---- Фініш прикладу 1
==
Що ж, фабрика працює. При бажанні, тепер можна додати навіть10
реалізацій клієнта, визначити відповідні умови в Factory, і все буде працювати,
правити інший код не доведеться.

Приклад 2. Некласична фабрика


Насправді, якщо в фабриці зробити не один параметризований метод для
всіх клієнтів, а по одному непараметризованому методу для кожної реалізації
клієнта, і вибирати, який метод викликати, прямо в класі для тестування (тобто, в
нашому мікро-додатку), то це також можна розглядати як фабричний патерн.
Змінимо код фабрики:
public class Factory {
public Client getWinClient(){
return new ClientWinImpl();
}
public Client getLinuxClient(){
return new ClientLinuxImpl();
}
}
Відповідно, поміняємо і код програми:
public class RunTestFactoryEx1 extends RunTestFactory{
public static void main(String[] args){
log("----> Старт прикладу 2");
String currentOS = args[0];
Client client = null;
Factory factory = new Factory();
log(" OS id: [" + currentOS + "], створення клієнта");

189
if(currentOS.equals("win"))
client = factory.getWinClient();
else if(currentOS.equals("linux"))
client = factory.getLinuxClient();
if(client!=null){
log(" Клієнт створено для OS:");
log(client.getTargetOS());
}else{
log(" Надано помилковий id OS!");
}
log("<---- Фініш прикладу 2");
}
}
Додамо в наш головний тестовий клас код для виклику другого тесту з
трьома різними параметрами:
public class RunTestFactory {
public static void main(String[] args){
log(">Старт тесту Фабрики");
RunTestFactoryEx1.main(new String[]{"win"});
RunTestFactoryEx1.main(new String[]{"linux"});
RunTestFactoryEx1.main(new String[]{"mac"});
log("==");
}

public static void log(String msg){


if(msg==null)
msg = "null";
System.out.println(msg);
}
}
Запустимо тест, в консолі побачимо нові повідомлення, які говорять про те,
що все працює:
> Старт тесту Фабрики
----> Старт прикладу 2
OS id: [win], створення клієнта
Клієнт створено для OS:
Windows Vista
<---- Фініш прикладу 2
----> Старт прикладу 2
OS id: [linux], створення клієнта
Клієнт створено для OS:
Gentoo Linux
<---- Фініш прикладу 2
----> Старт прикладу 2
OS id: [mac], створення клієнта
Надано помилковий id OS!
<---- Фініш прикладу 2
==
Перевантаження конструктора фабрики
Можна взагалі відмовитися від прямого використання об‘єкта клієнта в коді
програми, а створювати клієнта всередині фабрики і запускати його в спеціальному
методі тієї ж фабрики. Те, яку реалізацію клієнта вибрати, може бути задано
використанням декількох конструкторів фабрики (або, знову ж таки, перевіркою

190
якого-небудь параметра в методі фабрики, використовуваному для запуску клієнта,
але перевірка параметра в прикладі вже є, тому це не цікаво), що розрізняються,
наприклад , кількістю аргументів (як в цьому прикладі), або типом аргументу.
Створюємо новий пакет org.test.technerium.patterns.factory.ex3,
міняємо код фабрики на щось на зразок цього:
Приклад 3.
public class Factory {
private Client innerClient;
public Factory() {
log("Ініціалізувати фабрику без аргументів");
innerClient = new ClientLinuxImpl();
}
public Factory(String arg) {
log(" Ініціалізувати фабрику з аргументом: " + arg);
innerClient = new ClientWinImpl();
}
public void runClient(){
log("Внутрішній клієнт, створений для ОС:" +
innerClient.getTargetOS());
}

public static void log(String msg){


if(msg==null)
msg = "null";
System.out.println(msg);
}
}
Тут два конструктори: один без аргументів – створює клієнт для Linux, а
другий з одним аргументом типу String – створює клієнт під Windows. Можна було
б зробити перший конструктор з параметром int, наприклад. Як ми бачимо, знову
робочий код (метод runClient) буде незалежний від реалізації клієнта.
Тестовий клас:
public class RunTestFactoryEx3 extends RunTestFactory{
public static void main(String[] args){
log("----> Старт прикладу 3");
String parameter = null;
if(args!=null && args.length>0)
parameter = args[0];
Factory factory = null;
if(parameter == null)
factory = new Factory();
else
factory = new Factory(parameter);
factory.runClient();
log("<---- фініш прикладу 3");
}
}
Як бачимо, ми просто створюємо фабрику, використовуючи той
конструктор, який вважатимемо за потрібне, а потім запускаємо метод фабрики,
що виконує потрібні дії.
Додамо ще пару рядків в головний тестовий клас:
public class RunTestFactory {

191
public static void main(String[] args){
log(">Старт тесту фабрики");

RunTestFactoryEx3.main(null);
RunTestFactoryEx3.main(new String[]{"arg"});
log("==");

public static void log(String msg){


if(msg==null)
msg = "null";
System.out.println(msg);
}
}
Запустимо його і в консолі побачимо:
>Старт тесту фабрики
... == ----> Старт прикладу 3 Ініціалізувати фабрику без аргументів
Внутрішній клієнт, створений для ОС: Gentoo Linux
<---- Фініш прикладу 3
Старт прикладу 3 Ініціалізувати фабрику з аргументами: arg
Внутрішній клієнт, створений для ОС: Windows Vista
<---- Фініш прикладу 3
==

Перевантаження методу фабрики


Так званий фабричний метод – використання перевантаження методу
фабрики. Тут обійдемося без клієнтського інтерфейсу і його імплементацій, взагалі
видаливши їх, а також залишимо незмінним тестовий клас з попереднього
прикладу, змінивши тільки фабрику:
Приклад 4.
public class Factory {
public Factory() {
log("Виклик методу фабрики без аргументів");
getTargetOS();
}

public Factory(String arg) {


log("Виклик методу фабрики з аргументами: " + arg);
getTargetOS(arg);
}

private void getTargetOS(){


log("Gentoo Linux");
}

private void getTargetOS(String arg){


log("Windows Vista");
}
public static void log(String msg){
if(msg==null)
msg = "null";
System.out.println(msg);

192
}

}
Цей приклад ілюструє так званий Factory Method (фабричний метод).
У головному тестовому класі додамо ще пару рядків
public class RunTestFactory {
public static void main(String[] args){
log(">Старт тесту фабрики");

RunTestFactoryEx1.main(new String[]{"win"});
RunTestFactoryEx1.main(new String[]{"linux"});
RunTestFactoryEx1.main(new String[]{"mac"});
log("==");

RunTestFactoryEx2.main(new String[]{"win"});
RunTestFactoryEx2.main(new String[]{"linux"});
RunTestFactoryEx2.main(new String[]{"mac"});
log("==");

RunTestFactoryEx3.main(null);
RunTestFactoryEx3.main(new String[]{"arg"});
log("==");

RunTestFactoryEx4.main(null);
RunTestFactoryEx4.main(new String[]{"arg"});
log("==");
}

public static void log(String msg){


if(msg==null)
msg = "null";
System.out.println(msg);
}
}
При запуску тесту побачимо, що фабрика відпрацювала правильно:
... == ----> Старт прикладу 4 Виклик методу фабрики без аргументів
Gentoo Linux <---- Фініш прикладу 4 ----> Старт прикладу 4 Виклик
методу фабрики з аргументами : arg Windows Vista < ---- Фініш прикладу
4 ==

10.4 Проектування класів із врахуванням властивостей


сoupling та cohesion
Словничок
GRASP (General Responsibility Assignment Software Patterns) – набір патернів
(шаблонів, принципів), що дозволяють вирішувати проблеми розподілу обов’язків
між різними класами

При створенні класів, розподілом обов‘язків і способів взаємодії об‘єктів


перед програмістом виникає серйозна проблема моделювання взаємодії класів і

193
об‘єктів. Надаються широкі можливості вибору. Неоптимальний вибір може
зробити системи і їх окремі компоненти непридатними для підтримки, сприйняття,
повторного використання і розширення. Систематизація прийомів програмування і
принципів організації класів отримала назву шаблону.
Основні принципи об‘єктно-орієнтованого проектування, застосовуваного
при створенні діаграми класів і розподілу обов‘язків між ними, систематизовані в
шаблонах GRASP (General Responsibility Assignment Software Patterns).
При цьому були сформульовані основні принципи і загальні стандартні
рішення, дотримуючись яких можна створювати добре структурований і
зрозумілий код.

Шаблон Low Coupling


Ступінь зв‘язаності класів визначає, наскільки клас пов‘язаний з іншими
класами, і якою інформацією про інші класи він володіє. При проектуванні
відносин між класами слід розподілити обов‘язки таким чином, щоб ступінь
зв‘язаності залишалася низькою.
Наявність класів з високим рівнем зв‘язаності небажане, тому що:
 зміни в пов‘язаних класах призводять до локальних змін в даному класі;
 ускладнюється розуміння кожного класу окремо;
 ускладнюється повторне використання, оскільки для цього потрібен
додатковий аналіз класів, з якими пов‘язаний даний клас.
Нехай потрібно створити екземпляр класу Quest і зв‘язати його з об‘єктом
Test. У предметній області реєстрація об‘єкта Test виконується об‘єктом Course
(рис. 10.1) class 123

Quest Test Course

Рис. 4.2 Класи, які необхідно зв’язати

Далі екземпляр об‘єкта Course повинен передати повідомлення makeQuest()


об‘єкту Test. Це означає, що в поточному тесті були отримані ідентифікатори всіх
питань, що становлять тест і стає можливим створення об‘єктів типу Quest і
наповнення власне тесту.
class 123

Course Test

Quest

Рис. 4.3 Приклад поганої реалізації шаблону Low Coupling

194
Приклад 1.
public class Course {
private int id;
private String name;

public void makeTest(){


Test test = new Test(параметри);
// реалізація
while(умова){
Quest quest = new Quest(параметри);
// реалізація
test.addTest(quest);
}
// реалізація
}
}

public class Test {


// поля
public void addTest(Quest quest){
// реалізація
}
}

public class Quest {


// поля і методи
}
При такому розподілі обов‘язків передбачається, що клас Course зв‘язується
з класом Quest.
Другий варіант розподілу обов‘язків з ухилянням класу Course від
створення об‘єктів питань представлений на рис. 4.3.
class 123

Course Test

Quest

Рис. 4.4 Приклад правильної реалізації шаблону Low Coupling

Приклад 2.
public class Course {
private int id;
private String name;

public void makeTest() {


Test test = new Test(параметри);
// реалізація

195
test.addTest();
// реалізація
}
}
public class Test {
// поля
public void addTest() {
// реалізація
while(умова) {
Quest quest = new Quest(параметри);
// реалізація
}
}
}
public class Quest {
// поля і методи}
Який з методів проектування, заснований на розподілі обов‘язків,
забезпечує низький ступінь зв‘язаності?
В обох випадках передбачається, що об‘єкту Test має бути відомо про
існування об‘єкта Quest.
При використанні першого способу, коли об‘єкт Quest створюється
об‘єктом Course, між цими двома об‘єктами додається новий зв‘язок, тоді як
другий спосіб ступінь зв‘язування об‘єктів не збільшує. Віддати перевагу краще
другому способу, так як він забезпечує низьку зв‘язаність.
В ООП є деякі стандартні способи зв‘язування об‘єктів A і B:
 об‘єкт A містить атрибут, який посилається на екземпляр об‘єкта B;
 об‘єкт A містить метод, який посилається на екземпляр об‘єкта B, що
передбачає використання B в якості типу параметра, локальної змінної або
значення, що повертається;
 клас об‘єкта A є підкласом об‘єкта B;
 B є інтерфейсом, а клас об‘єкта A реалізує цей інтерфейс.
Не існує абсолютної міри для визначення надто високого ступеня
зв‘язування.
Переваги слідування шаблону Low Coupling:
 зміна компонентів класу мало позначається на інших класах;
 принципи роботи і функції компонентів можна зрозуміти, не вивчаючи
інші класи.
Шаблон High Cohesion
За допомогою цього шаблону можна забезпечити можливість управління
складністю, розподіливши обов‘язки, підтримуючи високий ступінь зчеплення.
Зчеплення – міра сфокусованості класу. При високому зчепленні обов‘язки
класу тісно пов‘язані між собою, і клас не виконує робіт непомірних обсягів. Клас з
низьким ступенем зчеплення виконує багато різнорідних дій або не пов‘язаних між
собою обов‘язків.
Виникають проблеми, пов‘язані з тим, що клас:
 важкий в розумінні, так як необхідно приділяти увагу незв‘язним
(неспоріднених) ідеям;

196
 складний в підтримці і повторному використанні через те, що він
повинен бути використаний разом з залежними класами;
 ненадійний, постійно зазнає змін.
Класи зі слабким зчепленням виконують обов‘язки, які можна легко
розподілити між іншими класами.
Нехай необхідно створити екземпляр класу Quest і зв‘язати його з заданим
тестом. Який клас повинен виконувати цей обов‘язок? У предметній області
відомості про питання на поточний момент часу при проходженні тесту
записуються в об‘єкті Course, згідно шаблону для створення екземпляра об‘єкта
Quest можна використовувати об‘єкт Course. Тоді екземпляр об‘єкта Course зможе
відправити повідомлення makeTest() об‘єкту Test. За проходження тесту відповідає
об‘єкт Course, тобто об‘єкт Course частково несе відповідальність за виконання
операції makeTest(). Однак якщо і далі покладати на клас Course обов‘язки по
виконанню все нових функцій, пов‘язаних з іншими системними операціями, то
цей клас буде занадто перевантажений і буде володіти низьким ступенем
зчеплення.
Цей шаблон необхідно застосовувати при оцінці ефективності кожного
проектного рішення.
Види зчеплення:
1. Дуже слабке зчеплення. Одноосібне виконання безлічі різнорідних
операцій.
Приклад 1.
public class Initializer {
public void createTCPServer(String port) {
// реалізація}
public int connectDataBase(URL url) {
// реалізація}
public void createXMLDocument(String name) {
// реалізація}
}
2. Слабке зчеплення. Одноосібне виконання складного завдання з однієї
функціональної області.
Приклад 2.
public class NetLogicCreator {
public void createTCPServer() {
// реалізація}
public void createTCPClient() {
// реалізація}
public void createUDPServer() {
// реалізація}
public void createUDPClient() {
// реалізація}
}
3. Середнє зчеплення. Нескладні обов‘язки в декількох різних областях,
логічно пов‘язаних з концепцією цього класу, але не пов‘язаних між собою.
Приклад 3.
public class TCPServer {
public void createTCPServer() {
// реалізація}

197
public void receiveData() {
// реалізація}
public void sendData() {
// реалізація}
public void compression() {
// реалізація}
public void decompression() {
// реалізація}
}
4. Високе зчеплення. Середня кількість обов‘язків з однієї функціональної
області при взаємодії з іншими класами.
Приклад 4.
public class TCPServerCreator {
public void createTCPServer() {
// реалізація}
}
public class DataTransmission {
public void receiveData() {
// реалізація}
public void sendData() {
// реалізація}
}
public class CodingData {
public void compression() {
// реалізація}
public void decompression() {
// реалізація}
}
Якщо буде виявлено, що використовується негнучкий дизайн, який
складний в підтримці, слід звернути увагу на класи, які не володіють властивістю
зчеплення або залежать від інших класів. Ці класи легко пізнавані, оскільки вони
сильно взаємопов‘язані з іншими класами або містять безліч неспоріднених
методів.
Як правило, класи, які не володіють сильною зв‘язаністю з іншими класами,
мають властивість зчеплення і навпаки. При наявності таких класів необхідно
реорганізувати їх структуру таким чином, щоб вони по можливості мали низьку
ступінь зв‘язаності і високе зчеплення.

Тести
1. Враховуючи таке визначення класу Television:
public class Television {
public int channel;
private boolean on;
private int volume;

public void changeChannel(int newChannel) {


channel = newChannel;
}
public int getChannel() {
return channel;
}
public void turnOn() {

198
on = true;
}
public void turnOff() {
on = false;

}
public void turnUp() {
volume += 1;
}
public void turnDown() {
volume -= 1;
}
}

який із наведених нижче моделей дизайну OO більше підходить для


класу Television?
a) щільна інкапсуляція
b) висока звязність
c) сильне зчеплення
d) слабке зчеплення
2. Розглянемо взаємодію :
1. ClassA has a ClassD
2. Methods in ClassA use public methods in ClassB
3. Methods in ClassC use public methods in ClassA
4. Methods in ClassA use public variables in ClassB
Що, швидше за все, правда? (Виберіть найбільш імовірні)
a) ClassD має слабке зчеплення
b) ClassA має слабку інкапсуляцію
c) ClassB має слабку інкапсуляцію
d) ClassB має сильну інкапсуляцію
e) Клас C тісно пов'язаний з ClassA

ТЕМА 11. ВИКОРИСТАННЯ LAMBDA-ВИРАЗІВ


11.1 Призначення Lambda-виразів
Словничок
marker interface – маркерний інтерфейс
functional interface – функціональний інтерфейс
deferred execution – відкладене виконання

Java проектувалась як повністю об‘єктно-орієнтована мова. За


виключенням примітивних типів, все в Java – це об‘єкти. Навіть масиви є
об‘єктами. Екземпляри кожного класу – об‘єкти. При такому підході не існує
можливості створити метод поза класом, а також передати метод як аргумент
іншому методу або повернути його, як результат виконання методу. До виходу
Java 8 такі питання частково вирішувались використанням анонімних класів. Серед

199
новинок, які появились в JDK 8, можна виділити Lambda-вирази. Можна сказати,
що це свого роду анонімні функції, тобто методи без оголошення. Часто анонімний
клас реалізує інтерфейс, який містить тільки один абстрактний метод. В цьому
випадку код може бути коротшим та зрозумілішим, якщо використати Lambda-
вирази.
Одним з ключових моментів використання лямбда-виразів є відкладене
виконання (deferred execution). Тобто, визначений в певному місці програми
лямбда-вираз може бути викликаний при необхідності будь-яку кількість разів в
різних частинах цієї програми. Відкладене виконання може знадобитись в
наступних випадках:
 виконання коду в окремому потоці;
 виконання одного і того ж коду декілька разів;
 виконання коду в результаті певної події.

11.2 Синтаксис Lambda-виразів


Lambda-вирази мають наступний синтаксис:
(арг1, арг2...) –> { тіло }
(тип1 арг1, тип2 арг2...) –> { тіло }
Основу Lambda-виразу складає Lambda-оператор, який є стрілкою –>. Він
розділяє вираз на дві частини: ліва є списком параметрів виразу, а права – тіло
виразу, де й виконуються всі дії.
Наприклад:
(int a, int b) -> { return a + b; }
() -> System.out.println("Hello World");
(String s) -> { System.out.println(s); }
() -> 42
() -> { return 3.1415 };

Структура Lambda-виразу:
 Lambda-вирази можуть мати від 0 і більше вхідних параметрів;
 тип параметрів можна вказувати явно або ж вони будуть отримані з
контексту (наприклад (int a) можна записати як (a);
 параметри беруться в круглі дужки і розділяються комами;
 якщо параметри відсутні, використовуються порожні круглі дужки
(приклад: () -> 42);
 якщо параметр один, і тип не вказується явно, дужки можна не ставити
(приклад: a -> return a*a);
 тіло Lambda-виразу може містити 0 та більше операторів;
 якщо тіло складається з одного оператора, його можна не поміщати в
фігурні дужки, а значення ,яке він повертає, може бути без слова return;
 якщо тіло виразу містить більше одного оператора, то фігурні дужки
обов‘язкові, а в кінці треба вказувати return (інакше тип результату виразу буде
void).

200
Передача параметрів в лямбда-вирази. Термінальні лямбда-вирази
Дуже часто інтерфейси в Java представляють одну функцію, такі інтерфейси
отримали назву функціональні інтерфейси. Вони використовуються для реалізації
механізму зворотнього виклику, обробки подій тощо. Але для їх реалізації
необхідний окремий клас – звичайний, вкладений чи анонімний.
З появою лямбда-виразів запис значно скоротився.
Параметри лямбда-виразу повинні відповідати по типу параметрам методу з
функціонального інтерфейсу. При написанні самого лямбда-виразу тип параметрів
писати не обов‘язково. Якщо ж метод не приймає ніяких параметрів, то пишуться
порожні дужки.
Приклад 1.
()-> 30 + 20;
Якщо метод приймає тільки один параметр, то дужки можна не ставити:
n-> n * n;
Вище був розглянутий приклад лямбда-виразів, які повертають певне
значення. Але можуть бути і термінальні лямбди, які нічого не повертають.
Приклад 2.
interface Printable{
void print(String s);
}

public class Mane {


public static void main(String[] args) {
Printable printer = s->System.out.println(s);
printer.print("Hello Java!");
}
}

Лямбда-вирази та локальні змінні


Лямбда-вирази можуть використовувати змінні, які оголошені зовні в більш
загальній області видимості – на рівні класу чи методу, в якому лямбда-вираз
визначений. Але, в залежності від того, як і де визначені змінні, можуть
відрізнятись способи їх використання в лямбдах.
Приклад 3.
public class Main {
static int x = 10;
static int y = 20;
public static void main(String[] args) {
Operation op = ()->{
x=30;
return x+y;
};
System.out.println(op.calculate()); // 50
System.out.println(x); /*30 – значення x змінилось*/
}
}
interface Operation{
int calculate();
}

201
Якщо змінні х та у оголошені на рівні класу, то їхніми значеннями можна
оперувати і навіть їх змінити в лямбда-виразі. Це демонструє вищенаведений
приклад.
В іншому прикладі локальні змінні на рівні методу.
Приклад 4.
interface Operation{
int calculate();
}

public class Main {


public static void main(String[] args) {
int n=70;
int m=30;
Operation op = ()->{
/*n=100; – так не можна*/
return m+n;
};
/* n=100; – так теж не можна*/
System.out.println(op.calculate()); // 100
}
}
Локальні змінні рівня методу можна використовувати в лямбда-виразах, але
міняти їх значення не дозволяється. Не можна також міняти значення змінної, що
використовується в лямбда-виразі, поза цим виразом. Бо хоч така змінна не
оголошена константою, але вона по суті нею є.

Блоки коду в лямбда-виразах


Існують два типи лямбда-виразів: однорядковий вираз і блок коду.
Однорядкові вирази розглянуті вище. Блочні вирази розміщуються всередині
фігурних дужок. В них також можна використовувати внутрішні вкладені блоки,
цикли, конструкції if, switch, створювати змінні і т. д. Якщо блочний лямбда-вираз
повинен повертати значення, то потрібно явно вказувати return:
Приклад 5.
interface Operation{
int calculate(int x, int y);
}

public class Main {


public static void main(String[] args) {
Operation operation = (int x, int y)-> {
if(y==0)
return 0;
else
return x/y;
};
System.out.println(operation.calculate(20, 10)); //2
System.out.println(operation.calculate(20, 0)); //0
}
}

202
Переваги застосування лямбда-виразів
Потік Thread можна проініціювати двома способами. Без застосування
лямбда-виразів це буде виглядати так:
Приклад 6.
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello from thread");
}
}).start();
Із застосуванням лямбда-виразів виглядає так:
Приклад 7.
new Thread(
() -> System.out.println("Hello from thread")
).start();

Управління подіями в Java 8 також можна здійснювати через Lambda-


вирази. Додавання обробника подій ActionListener в компонент користувацького
інтерфейсу без застосування лямбда-виразів:
Приклад 8.
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Кнопка натиснута. Старий спосіб!");
}
});

Із застосуванням лямбда-виразів:
Приклад 9.
button.addActionListener( (e) -> {
System.out.println("Кнопка натиснута. Lambda!");
});

Виведення елементів масиву без використання лямбда-виразів


List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list)
System.out.println(n);
Використання лямбда- виразів при виведенні елементів масиву з різними
способами їх побудови:
/*Використання простого синтаксису*/
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
/*Використання синтаксису оператора*/
list.forEach(System.out::println);

Посилання на метод-оператор «::», він відділяє назву методу від назви


об‘єкта чи класу. Є три його варіанти:
object::instanceMethod;
Class::staticMethod;
Class::instanceMethod.

203
В перших двох випадках посилання на метод еквівалентне лямбда-виразу,
який є параметром методу:
System.out::pprintln це те ж, що x->System.out.println(x);
Math::pow це те ж, що (x, y)->Math.pow(x, y);
В третьому випадку параметр стає цільовим об‘єктом методу, наприклад,
String::compareToIgnoreCase це те ж, що (x, y)->x.compareToIgnoreCase(y);

11.3 Зміни в структурі інтерфейсів


Функціональні інтерфейси
В Java маркерні інтерфейси (Marker interface) – це інтерфейси без
оголошення методів та полів, тобто порожні інтерфейси. Аналогічно,
функціональні інтерфейси (Functional Interface) – це інтерфейси тільки з одним
абстрактним методом, оголошеним в ньому. java.lang.Runnable – це приклад
функціонального інтерфейсу. В ньому оголошено тільки один метод void run().
Інтерфейс ActionListener – теж функціональний.
Лямбда-вираз не виконується сам по собі, а є реазізацією методу,
визначеного в функціональному інтерфейсі. При цьому важливо, що
функціональний інтерфейс повинен містити тільки один єдиний метод без
реалізації.
Приклад 1.
public class Main {
public static void main(String[] args) {
/*визначення посилання на функціональний інтерфейс*/
Operationable operation;
/*створення лямбда-виразу з параметрами, що відповідають
параметрам єдиного методу інтерфейсу*/
operation = (x,y)->x+y;
/*використання лямбда-виразу в вигляді виклику методу інтерфейсу*/
int result = operation.calculate(10, 20);
System.out.println(result); //30
}
}
interface Operationable{
int calculate(int x, int y);
}
В ролі функціонального інтерфейсу виступає інтерфейс Operationable, в
якому визначений один метод без реалізації – calculate. Метод приймає два
параметри – цілі числа та повертає ціле цисло. Фактично, лямбда-вирази є, певною
мірою, скороченою формою внутрішніх анонімних класів. Зокрема, наведений
вище приклад можна представити так:
Приклад 2.
public class Main {
public static void main(String[] args) {
Operationable op = new Operationable(){
public int calculate(int x, int y){
return x + y;
}
};
int z = op.calculate(20, 10);

204
System.out.println(z); // 30
}
}
interface Operationable{
int calculate(int x, int y);
}
Для одного функціонального інтерфейсу ми можемо визначити множину
лямбда-виразів.
Приклад 3.
public class Main {
public static void main(String[] args) {
Operationable operation1 = (int x, int y)-> x + y;
Operationable operation2 = (int x, int y)-> x – y;
Operationable operation3 = (int x, int y)-> x * y;

System.out.println(operation1.calculate(20, 10)); //30


System.out.println(operation2.calculate(20, 10)); //10
System.out.println(operation3.calculate(20, 10)); //200
}
}
interface Operationable{
int calculate(int x, int y);
}

Зміни в функціональному інтерфейсі


Функціональний інтерфейс може бути узагальненим, але в лямбда-виразах
узагальнення не допускаються. В цьому випадку потрібно типізувати об‘єкт
інтерфейсу визначеним типом, який потім буде використовуватись в лямбда-
виразі.
Приклад 4.
public class Main {
public static void main(String[] args) {
Operationable<Integer> operation1 = (x, y)-> x + y;
Operationable<String> operation2 = (x, y) -> x + y;
System.out.println(operation1.calculate(20, 10)); //30
System.out.println(operation2.calculate("20", "10")); //2010
}
}
interface Operationable<T>{
T calculate(T x, T y);
}
Таким чином, при оголошенні лямбда-виразу йому вже буде відомо, якого
типу будуть параметри і якого типу буде результат.

11.4 Пакет java.util.function.


Ще одне нововведення JDK – безліч функціональних інтерфейсів, які будуть
дуже корисні. Короткий опис деяких з них:
 Function <T, R> – інтерфейс, за допомогою якого реалізується функція,
яка отримує на введення екземпляр класу T і повертає на виході екземпляр класу
R;
 Predicate <T> – на введення – T, повертає результат типу boolean;
205
 Consumer <T> – на введення – T, виробляє якусь дію і нічого не
повертає;
 Supplier <T> – нічого не приймає на введення, повертає T;
 BinaryOperator <T> – на введення приймає два – два примірники T,
повертає один T.
Пакет також забезпечений примітивними реалізаціями даних інтерфейсів
для типів int, long і double.

Функції
Функції приймають один аргумент і повертають деякий результат. Методи
за замовчуванням можуть використовуватись для побудови ланцюжка викликів
(compose, andThen).
Приклад 1.
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString =
toInteger.andThen(String::valueOf);
backToString.apply("123"); // "123"

Предикати
Предикати – це функції, які приймають один аргумент і повертають
значення типу boolean. Інтерфейс містить різні методи по замовчуванню, які
дозволяють будувати складні умови (and, or, negate).
Predicate<String> predicate = (s) -> s.length() > 0;
predicate.test("foo"); // true
predicate.negate().test("foo"); // false
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();

Постачальники
Постачальники (suppliers) представляють результат заданого типу. На
відміну від функцій не приймають аргументів.
Приклад 2.
Supplier<Person> personSupplier = Person::new;

personSupplier.get(); // new Person

Споживачі
Споживачі (consumers) представляють собою операції, які виконуються над
одним вхідним аргументом.
Приклад 3.
Consumer<Person> greeter = (p) -> System.out.println("Hello, " +
p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));

Компаратори
Компаратори добре відомі з попередніх версій.

206
Приклад 4.
Comparator<Person> comparator = (p1, p2) ->
p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2); // > 0
comparator.reversed().compare(p1, p2); // < 0

Опціональні значення
Опціональні значення (optionals) не є функціональним інтерфейсом, але є
зручним засобом попередження NullPointerException. По суті, це контейнер для
значення, яке може дорівнювати null.
Приклад 5. Нам потрібен метод, який повертає якесь значення, але інколи може
повернути порожнє значення. Замість того, щоб повернути null, можна вернути
опціональні значення.
Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
Приклад 6. Припустимо, деяка функція обчислює і повертає зворотну величину та
повертає «порожній» об'єкт, якщо аргумент дорівнює 0. У функції main()
здійснюємо обчислення зворотних величин для чисел з масиву:
public class Main {
static Optional<Double> reciprocal(double x) {
Optional<Double> result = Optional.empty();
if (x != 0) {
result = Optional.of(1 / x);
}
return result;
}
public static void main(String[] args) {
double[] arr = { -2, 0, 10 };
Optional<Double> y;
for (double x : arr) {
System.out.printf("x = %6.3f ", x);
y = reciprocal(x);
if (y.isPresent()) {
System.out.printf("y = %6.3f%n", y.get());
}
else {
System.out.printf("Значення не може бути визначене %n");
} } } }

11.5 Посилання на методи і конструктори


В Java є можливість передавати посилання на методи або конструктори.
Приклад 1.
interface Converter<F, T> {
T convert(F from);
}

207
public class Main {
public static void main(String[] args) {
Converter<String, Integer> converter =
(from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted); // 123
}
}

Але можна також посилатись і на екземплярний метод.


Приклад 2.
public class Main {
public static void main(String[] args) {
class Something {
String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
Something something = new Something();
Converter<String, String> converter =
something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted); // "J"
}
}
Передачу посилань на конструктори демонструє такий приклад:
class Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
interface PersonFactory<P extends Person> {
P create(String firstName, String lastName);
}

public class Main {


public static void main(String[] args) {
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter",
"Parker");
}
}

Тепер замість реалізації інтерфейсу ми з‘єднуємо все за допомогою


посилання на конструктор.
Приклад 3.
PersonFactory<Person> personFactory = Person::new;

208
Тут створюється посилання на конструктор за допомогою Person::new.
Компілятор автоматично вибирає потрібний конструктор, сигнатура якого
співпадає з сигнатурою PersonFactory.create.

Завдання для самостійного виконання


1. В заданому списку чисел знайти суму квадратів всіх елементів звичайним
способом та з застосуванням лямбда-виразів.
2. Визначення в багатопотоковому режимі середнього арифметичного
елементів списку цілих чисел.
3. Скласти програму, яка сортує студентів, заданих своїми іменем та
прізвищем, за іменем. Для сортування використовувати лямбда-вираз.
4. Скласти програму, використовує лямбда-вирази. Описати власний
інтерфейс з методом test. В задачі використати даний інтерфейс, реалізувавши
метод трьома способами:
a) Знайдіть кількість всіх натуральних трьохзначних чисел, середня цифра
яких 5 і які кратні 13.
b) Знайдіть суму всіх цілих чисел з інтервалу [200, 4000], в записі яких на
парному місці стоїть цифра 5.
c) Знайдіть добуток всіх цілих чисел від A до B, що кратні 7 і закінчуються
цифрою 5 або цифрою 0 (значення A та B введіть з клавіатури).

ТЕМА 12. Основи JavaScript та використання


інтерпретатора Nashorn
12.1. Основи JavaScript. Функції JavaScript
Словничок
undefined – властивість глобального об’єкта, змінна, якій не присвоєне значення,
має тип undefined
parseInt, parseFloat – функції,що текстовий рядок перетворюють у чисельне
значення
for-in – оператор циклу,призначений для перегляду усіх властивостей об’єкта

Мова програмування JavaScript має дуже схожий на Java синтаксис (обидві


мови запозичили його з C) але принцип роботи у них дуже різний. Програма на
Java перед виконанням мусить бути відкомпільована, а всі змінні, класи і т. д.
мають бути визначеними ще на етапі компіляції. На відміну від цього, програми на
мові JavaScript (їх часто називають скриптами) можуть виконуватись
безпосередньо, а сама мова є динамічною. Це означає, що змінні можуть
створюватись, знищуватись і навіть міняти свій тип під час виконання програми.
Також можливим є створення нових функцій та динамічна зміна структури класів
(не тільки створених користувачем, але і вбудованих).
Така гнучкість дає JavaScript перевагу над Java у певних ситуаціях. Тому у
склад Java входить інтерпретатор Nashorn, який дозволяє виконувати сценарії
JavaScript всередині програми Java. Оскільки скрипт є звичайним текстом – то його

209
можна отримати як параметр під час виконання програми або завантажити з файлу.
Це дозволяє заміняти частину функціоналу програми без перекомпіляції. При
цьому скрипт та програма можуть взаємодіяти між собою: отримувати доступ до
змінних, об‘єктів, функцій.
Змінні в JavaScript
У JavaScript можна використовувати змінні, звертаючись до них по імені.
Змінні можуть бути як глобальні, так і локальні. Глобальні змінні доступні з будь-
якого місця сценарію. Область дії локальних змінних обмежується функцією,
усередині котрої ці змінні оголошені.
При виконанні скриптів можна використовувати змінні без їхнього
попереднього оголошення. Виняток із цього правила – локальні змінні, визначені у
функціях.
Оголошення змінних
Всі змінні в JavaScript оголошуються за допомогою ключового слова var, як
це показано нижче:
Приклад 1.
var szMsg;
При виборі імен змінних треба притримуватися таких простих правил:
 ім‘я змінної повинно починатися з букви або із символів _, $ і може
складатися тільки з букв, цифр, а також символів _, $;
 ім‘я змінної не повинно збігатися з зарезервованими ключовими словами
JavaScript.
Список зарезервованих ключових слів JavaScript приведений нижче:
Break do if throw
case else import true
catch enum in try
elass export new type of
const extends null var
continue false return void
debugge r finally super while
default for switch with
dpiete function this
Рекомендується уникати їх використання в програмах у якості імен змінних.
Потрібно також стежити, щоб імена змінних не збігалися з іменами
вмонтованих об‘єктів, методів і функцій.

Присвоєня значеня змінним


Можливо надати значення змінної за допомогою оператора присвоєння =.
Нижче оголошується змінна і потім записується в неї текстовий рядок.
Приклад 2.
var szHelloMsg;
szHelloMsg = "Hello, world! ";
У будь-якому місці програми можна надати змінній szHelloMsg чисельне
значення.
Приклад 3.
szHelloMsg = 4;

210
Виконання такої операції спричинить зміну типу змінної, причому в процесі
інтепретації сценарію браузер не відобразить ніяких попереджуючих повідомлень.
Можливо надати змінній спеціальне значення null:
Приклад 4.
szHelloMsg = null;
Таке присвоєння не призначає змінній ніякого типу. Воно застосовується в
тих випадках, коли потрібно оголосити змінну і ініціалізувати її, не надаючи цій
змінній ніякого початкового значення і типу.

Типи даних
У мові JavaScript усе ж існує декілька типів даних. Це числа, текстові рядки,
логічні дані, об‘єкти, дані невизначеного типу, а також спеціальний тип null.
Числа. Припускається використання чисел у різноманітних форматах
(таблиця12.1). Це ціле число, числа у форматі з плаваючою десятковою крапкою і
числа в науковій нотації. Цілі числа можуть бути подані в різних системах
числення – 8, 10 або 16.
Таблиця 12.1
Приклад Опис
25 Ціле число за основою 10
0137 Ціле число за основою 8
OxFF Ціле число за основою 16
386.7 Число з плаваючою десятковою крапкою
25е5 або 25Е5 Число у науковій нотації, дорівнює 2500000
У деяких випадках арифметичні функції можуть повертати "не число", що
називається в JavaScript як Na (Not a Number); – це спеціальне значення, що не
відповідає ніякому числу.
Текстові рядки. Це послідовність символів Unicode, взятих в одинарні або
подвійні лапки.
Приклад 5.
"Hello, world! ", "12345", ‘Це текстовий рядок’
Рядок "" – порожній. Зауважимо, що такі два присвоєння не еквівалентні:
szStr=""
szStrl=null
У першому випадку в змінної szStr зберігається текстовий рядок (хоча б і
порожній), у другому – зовсім нічого.
Логічні дані. Логічні дані можуть мати тільки два значення: true і false. Ці
значення ніяк не співвідносяться з числами 1 і 0.
Дані невизначеного типу. Якщо змінна була оголошена, але їй ще жодного
разу не присвоювалося значення, вона має невизначений тип. Наприклад, у
наступному рядку сценарію оголошена змінна MyVariable, що має невизначений
тип:
var MyVariable;
Якщо ж цій змінній надати значення null, то тип змінної зміниться – тепер
це буде змінна, що містить значення null:
MyVariable = null;

211
Перетворення типів даних
Інтерпретатор JavaScript може автоматично перетворювати чисельні дані в
текстові рядки. Обернене ж перетворення (рядків у числа) виконується за
допомогою спеціальних функцій, таких, як parseInt і parseFloat.
Приклад 6.
var szTextBuf = "";
szTextBuf = 4 + " – число чотири";
szBuf2 = (parseInt("2") + 2) + " – число чотири";
Тут змінна szTextBuf оголошується і ініціалізується як порожній рядок. Далі
присвоюється цьому рядку сума числа 4 і текстового рядка. Дія додавання виконає
з‘єднання числа 4, яке буде автоматично перетворене в текстовий рядок, і тексту
« – число чотири».
При обчисленні значення змінної szBuf2 текстовий рядок "2"
перетворюється у числове значення за допомогою функції parseInt, до результату
перетворення додається число 2, а потім виконується з‘єднання двох елементів. У
результаті змінна szBuf2 буде містити той самий рядок, що містить і змінна
szTextBuf.
Оператори мови JavaScript
Унарні оператори – застосовуються для зміни знака, виконання операції
заперечення, інкременту та декременту (таблиця 12.1):
Таблиця 12.2
Унарний
Призначення
оператор
- Зміна знака на протилежний
Використовується для зміни значення логічної змінної
!
на протилежне
Збільшення значення змінної. Може застосовуватися
++
як префікс змінної або як її суфікс
Зменшення значення змінної. Може застосовуватися
--
як префікс змінної або як її суфікс
Приклади використання унарних операторів:
i=0; // початкове значення змінної i дорівнює 0
i++; // значення i дорівнює 1
--i; // значення i знову дорівнює 0
var j=3; // значення змінної j дорівнює 3
i = -j; // значення змінної i дорівнює -3
var fYes = true; // значення змінної fYes дорівнює true
testFlag (! fYes); // функції testFlag передається значення false
Бінарні оператори – для віднімання, додавання, множення, ділення й
обчислення остачі від ділення (таблиця 12.3):
Таблиця 12.3
Бінарний оператор Призначення
- Віднімання
+
Додавання
* Множення
/ Ділення

212
Наприклад:
i=0; // початкове значення змінної i дорівнює 0
i = i + 1; // значення i дорівнює 1
var j=9; // значення змінної j дорівнює 9
i = j / 2; // значення змінної i дорівнює 4
k = j % 2; // значення змінної i дорівнює 1

Оператори відношення – використовуються для порівняння значень


змінних, повертають логічні значення true або false у залежності від результату
порівняння і застосовуються в умовних операторах.
Нижче поданий список операторів відношення мови JavaScript
(таблиця 12.4):
Таблиця 12.4
Оператор відношення Умова, при який оператор повертає значення true
> Лівий операнд більше правого
>= Лівий операнд більше або дорівнює правому
< Лівий операнд менше правого
<= Лівий операнд менше або дорівнює правому
== Лівий операнд дорівнює правому
!= Лівий операнд не дорівнює правому
В умовних операторах також часто застосовуються логічні оператори.
Таблиця 12.5
Логічний оператор Опис
Оператор OR. Повертає значення true, коли один з
||
операндів дорівнює true
Оператор AND. Повертає значення true, коли обидва
&&
операнда рівні true
Оператор присвоювання застосовується для присвоювання значень
змінним. У мові JavaScript припускається комбінування цього оператора з іншими
для зміни вмісту змінних (таблиця 12.6).
Таблиця 12.6
Оператор Опис
= Просте присвоювання
+= Збільшення чисельного значення або злиття рядків
з присвоєнням
-= Зменшення чисельного значення з присвоєнням
*= Множення з присвоєнням
/= Ділення з присвоєнням
%= Остача від ділення з присвоєнням
Приклад застосування оператора += для збільшення вмісту числової
змінної.
Приклад. Оголошується змінна з ім‘ям nCounter і їй присвоюється початкове
значення 1, а потім це значення збільшується на 5:
var nCounter = 1;
nCounter += 5;// теж що nCounter = nCounter + 5;

213
Умовні оператори
Оператор if-else – умовний оператор, що дозволяє виконувати різні
програмні рядки в залежності від умови.
Загальний вид оператора if-else поданий нижче:
if (умова) рядок 1 [else рядок 2]
Частина оператора, виділена квадратними дужками, є необов‘язкова.
Оператор ? спеціальний умовний оператор. Цей оператор у загальному виді
записується так:
вираз ? рядок 1 : рядок 2
При обчисленні оператора ? спочатку перевіряється логічний вираз,
розташований у лівій частині. Якщо він дорівнює true, виконується рядок 1, а якщо
false – рядок 2.
Оператори циклу
Оператор for. Загальний вид:
for ( [ініціалізація;] [умова;] [ітерація]){
...
тіло циклу
}

У області ініціалізації виконується присвоювання початкових значень


змінним циклу, припустимо, опис нових змінних за допомогою ключового слова
var.
Друга область задає умову виходу з циклу. Ця умова оцінюється кожного
разу при проходженні циклу. Якщо умова істинна (true), то виконується тіло
циклу.
Область ітерації застосовується для зміни значень змінних циклу,
наприклад, для зменшення лічильника циклу.
Оператор for-in. Призначений для перегляду усіх властивостей об‘єкта і
записується в такому вигляді:
for (змінна in об’єкт){
...
тіло циклу
}
Оператор while. Для організації циклів із перевіркою умови перед
виконанням ітерації використовується оператор while:
while (умова){
...
рядки тіла циклу
}
Якщо умова істина (true), то ітерація виконується, якщо хибна (false) – цикл
переривається.
Оператор break. За допомогою оператора break можна перервати
виконання циклу, створеного операторами for або while, у будь-якому місці.
Наприклад:
var i = 0;
while (true){
...
i++;

214
if (i > 10) break;
...
}
Оператор continue. Виконання оператора continue усередині циклу for або
while призводить до того, що ітерація переривається, а потім відновлюється
спочатку. Цей оператор не перериває циклу.
Нижче наведений приклад використання оператора continue:
var i = 0;
while (1 < 100){
i++;
if (i < 10)
continue;
...
}
Тут фрагмент тіла циклу, відзначений крапками, буде виконуватися тільки
після того, як значення змінної i стане рівним 10. Коли ж це значення досягне 100,
цикл буде завершений.
Інші оператори
Серед інших операторів мови JavaScipt є ще такі (таблиця 12.6):
Таблиця 12.6
Оператор Опис
. Доступ до поля об‘єкта
[] Індексування масиву
() Дужки
, Кома
Перший із цих операторів застосовується для виклику методів, визначених в
об‘єктах, а також для доступу до полів об‘єктів або, як їх ще називають, до
властивостей об‘єктів.
Нижче, наприклад, викликаний метод write, визначений в об‘єкті document:
document.write("Hello, world! ");
Квадратні дужки використовуються для індексації масивів, аналогічно тому,
як це робиться в інших мовах програмування.
Круглі дужки застосовуються або для зміни порядку обчислення виражень,
або для передачі параметрів функціям.
Оператор "кома" призначений для опису виразів, що повинні
обчислюватись послідовно. Цей прийом називається багатократним обчисленням.
Наприклад, у фрагменті вихідного тексту, показаному нижче, на кожній ітерації
циклу виконується збільшення вмісту змінних i і nCycleCount:
Приклад.
var i;
var nCycleCount = 0;
for (i = 0; i < 25; i++, nCycleCount++){
...
}
Пріорітети операторів JavaScript
Варто враховувати, що всі логічні операції виконуються зліва направо.

215
Першими обчислюються оператори, розташовані на початку таблиці
(таблиця 12.7):
Таблиця 12.7
Оператори Опис
[]() Доступ до поля, індексування в масиві, виклик функції
++ -- – ~ ! Унарні оператори
*/% Множення, ділення, обчислення залишку від ділення
+–+ Додавання, віднімання, злиття текстових рядків
<< >> >>> Бітові зсуви
Менше, менше або дорівнює, більше, більше або
< <= > >=
дорівнює
== != Рівність, нерівність
& Логічна операція «І» (And)
^ Логічна операція ВИКЛЮЧНЕ АБО
| Логічна операція АБО
&& Логічний оператор відношення «І» (And)
|| Логічний оператор відношення АБО
?: Умовний оператор
= += -= *= /= %= >>= Присвоювання
>>>= <<= |= &= ^=
, Багатократне обчислення

12.2 Призначення та способи виконання інтерпретатора


Nashorn
Словничок
Nashorn – високопродуктивний двигун JavaScript, котрий працює поверх
віртуальної машини Java (JVM)
Date – вмонтований клас

Командний рядок
JDK 1.8 включає в себе інтерпретатор командного рядка jjs, який може
використовуватися для запуску файлів JavaScript або, якщо запущено без
аргументів, як REPL (інтерактивна оболонка):
$ $JAVA_HOME/bin/jjshello.js
Hello World
Тут файл hello.js містить одну команду:
print(―Hello World‖);
Той же код можна запустити в інтерактивному режимі:
$ $JAVA_HOME/bin/jjs
jjs> print("Hello World")
Hello World

216
Вбудований скриптовий двигун
Другий і більш поширений спосіб запуску JavaScript з JVM – через
ScriptEngine. Давайте створимо двигун JavaScript:
package jstest;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class Main {
public static void main(String[] args) {
ScriptEngine engine = new
ScriptEngineManager().getEngineByName("nashorn");
try {
Object result = engine.eval(
"var greeting='hello world';" +
"print(greeting);" +
"greeting");
} catch (ScriptException e) {
e.printStackTrace();
}
} }
Тут ми створюємо новий ScriptEngineManager і відразу отримуємо з нього
ScriptEngine під назвою nashorn. Потім ми передаємо кілька інструкцій у метод
eval() і отримуємо результат, який буде рядком «hello world».
Функція eval(code) дозволяє виконати код, переданий у вигляді рядка, а
також може повернути результат.
Передача даних в скрипт
Дані можуть бути передані в двигун шляхом визначення об‘єкта Bindings і
передачі його в якості другого параметра для функції eval:
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class Main {
public static void main(String[] args) {
ScriptEngine engine = new
ScriptEngineManager().getEngineByName("nashorn");
try {
Bindings bindings = engine.createBindings();
bindings.put("count", 3);
bindings.put("name", "Irina ");
String script = "var greeting='Hello ';" +
"for(var i=count;i>0;i--) { " +
"greeting+=name + ''" +
"}" +
"greeting";
Object bindingsResult = engine.eval(script, bindings);
System.out.println(bindingsResult);
} catch (ScriptException e) {
e.printStackTrace();
}
}}

217
Запуск цього фрагмента виводить: «Hello, baeldung baeldung baeldung».

Виклик функцій JavaScript


Звичайно, можна викликати функції JavaScript з вашого Java-коду:
public class Main {
public static void main(String[] args) {
ScriptEngine engine = new
ScriptEngineManager().getEngineByName("nashorn");
try {
engine.eval("function composeGreeting(name) {" +
"return ‘Hello ‘ + name" +
"}");
Invocable invocable = (Invocable) engine;
Object funcResult = invocable.invokeFunction("composeGreeting",
"baeldung");
} catch (ScriptException e) {
}
Цей код поверне «Hello baeldung».
Використання об’єктів Java
Оскільки ми працюємо в JVM, можна використовувати власні Java-об‘єкти в
коді JavaScript. Це досягається за допомогою об‘єкта Java:
public class Main {
public static void main(String[] args) {
ScriptEngine engine = new
ScriptEngineManager().getEngineByName("nashorn");
try {
Object map=engine.eval("var HashMap=Java.type(‘java.util.HashMap’);" +
"var map = new HashMap();" +
"map.put(‘hello’, ‘world’);" +
"map");
} catch (ScriptException e) {
}

Розширення мови
Nashorn націлений на підтримку ECMAScript 5.1, але він також надає деякі
розширення мови JavaScript.
Ітерація колекцій з використанням for-each – зручне розширення мови,
для спрощення ітерації по різним наборам даних:
public class Main {
public static void main(String[] args) {
ScriptEngine engine = new
ScriptEngineManager().getEngineByName("nashorn");
try {
String script = "var list = [1, 2, 3, 4, 5];" +
"var result = '';" +
"for each (var i in list) {" +
"result+=i+'-';" +
"};" +
"print(result);";
engine.eval(script);
} catch (ScriptException e) {
}

218
Тут ми об‘єднуємо елементи масиву, використовуючи конструкцію ітерації
for-each.
Результатом виконання буде: 1-2-3-4-5-
Функціональні літерали. При описі простих функцій можна опускати
фігурні дужки:
function increment(in) ++in
Очевидно, що це можна зробити тільки для простих однорядкових функцій.
Умовні catch блоки. Можна додати захищені блоки catch, які виконуються
тільки в тому випадку, якщо зазначена умова є істинною:
try {
throw "BOOM";
} catch(e if typeof e === ‗string‘) {
print("String thrown: " + e);
} catch(e) {
print("this shouldn‘t happen!");
}
Буде виведено «String throw: BOOM».

Типізовані масиви і перетворення типів


Можна перетворювати типізовані масиви Java в масиви JavaScript та
навпаки:
//додати виклик функції
function arrays(arr) {
var javaIntArray = Java.to(arr, "int[]");
print(javaIntArray[0]);
print(javaIntArray[1]);
print(javaIntArray[2]);
}
Тут Nashorn виконує деякі перетворення типів, щоб переконатися, що всі
значення з динамічно типізованого масиву JavaScript можуть бути записані в масив
Java з цілих чисел.
Результатом виконання вищенаведеної функції з аргументом [100, ―1654‖,
true] є виведення 100, 1654 і 1 (всі значення числові). Рядкові і логічні значення
були неявно перетворені в їх цілочисельне представлення.

Налаштування прототипу об’єкта з використанням Object.setPrototypeOf


Nashorn визначає розширення API, яке дозволяє нам змінити прототип
об‘єкта:
Object.setPrototypeOf(obj, newProto)
Ця функція вважається кращою альтернативою до Object.prototype.proto.

Магічні методи noSuchProperty і noSuchMethod


Можна визначити методи для об‘єкта, які будуть викликатися щоразу, коли
звертаються до невизначеного параметра або викликається невизначений метод:
var demo = {

219
noSuchProperty: function (propName) {
print("Accessed non-existing property: " + propName);
},

noSuchMethod: function (methodName) {


print("Invoked non-existing method: " + methodName);
}
};
demo.doesNotExist;
demo.callNonExistingMethod();
Буде виведено:
Accessed non-existing property: doesNotExist
Invoked non-existing method: callNonExistingMethod

Прив’язка параметрів об’єкта з використанням Object.bindProperties


Object.bindProperties можна використовувати для прив‘язки параметрів з
одного об‘єкта до іншого:
//запустити з командного рядка або через eval
var first = {
name: "Whiskey",
age: 5
};
var second = {
volume: 100
};
Object.bindProperties(first, second);
print(first.volume);
second.volume = 1000;
print(first.volume);
Виведе: 1000
Зверніть увагу, що це створює «живу» прив‘язку, і будь-які оновлення
початкового об‘єкта також відображаються на другомуоб‘єкті.

Локації
Поточне ім‘я файла, каталог і рядок можуть бути отримані з глобальних
змінних __FILE__, __DIR__, __LINE__:
print (__FILE__, __DIR__, __LINE__)

Розширення для String.prototype


Є два простих, але дуже корисних, розширення, які Nashorn додає до
прототипу String. Це функції trimRight і trimLeft, які повертають копію String з
видаленими пропусками (пробіли, табуляція, переноси рядка) зліва або справа:
print(" hello world".trimLeft());
print("hello world ".trimRight());
Друкуватиме «hello world» два рази без пробілів на початку і в кінці.

220
Функція Java.asJSONCompatible
Використовуючи цю функцію, ми можемо отримати об‘єкт, сумісний з
бібліотекою Java JSON.
public class Main {
public static void main(String[] args) {
ScriptEngine engine = new
ScriptEngineManager().getEngineByName("nashorn");
try {
Object obj = engine.eval("Java.asJSONCompatible(
{ number: 42, greet: ‘hello’, primes: [2,3,5,7,11,13] })");
Map<String, Object> map = (Map<String, Object>)obj;
System.out.println(map.get("greet"));
System.out.println(map.get("primes"));
System.out.println(List.class.isAssignableFrom(map.get("primes").
getClass()));
} catch (ScriptException e) {
}
}}
Буде виведено «hello», потім [2, 3, 5, 7, 11, 13], а потім true.

Завантаження скриптів
Також з використанням ScriptEngine можна завантажити JavaScript з файла:
load(‗classpath:script.js‘)
Сценарій також можна завантажити з URL-адреси:
load(‗/script.js‘)
Майте на увазі, що JavaScript не має концепції просторів імен, тому все
знаходиться в глобальній області видимості. Це дозволяє завантаженим сценаріями
створювати конфлікти імен з вашим кодом або один з одним. Цього можна
частково уникнути використовуючи функцію loadWithNewGlobal:
public class Main {
public static void main(String[] args) {
ScriptEngine engine = new
ScriptEngineManager().getEngineByName("nashorn");
try {
var math = loadWithNewGlobal(‗classpath:math_module.js‘)
math.increment(5);
} catch (ScriptException e) {
}
}
}

Вміст файла math_module.js:


//це це javascript, який треба зберегти у файлі з назвою math_module.js
var math = {
increment: function(num) {
return ++num;
}
};
Тут ми визначаємо об‘єкт з ім‘ям math, який має один метод з назвою
increment.

221
12.3 Робота з числами, рядками, масивами, колекціями,
методами, об’єктами
Функції в мові JavaScript
Можливо оформити фрагменти вихідного тексту у виді функції,
викликаючи цю функцію в міру необхідності з різноманітних місць у JavaScript.
Оголошувати функції можна у будь-якій частині скрипта, але функція
повинна бути визначена до її виклику.
Загальний вигляд визначення функції :
function ім’я([параметр 1] [,параметр 2] [...,параметр N])
...
рядки тіла функції
[return значення]
}
Всі параметри передаються функції за значенням. Тому функція не може
змінити вміст змінних, переданих їй у якості параметрів.
За допомогою ключового слова return функція може повернути значення.

Класи і об’єкти
Мова JavaScript є об‘єктно-орієнтованою. Об‘єкти JavaScript являють собою
набори властивостей і методів. Можна сказати, що властивості об‘єктів – це дані,
пов‘язані з об‘єктом, а методи – функції для опрацювання даних об‘єкта.
Адресація властивостей у JavaScript можлива або по іменах властивостей,
або по їхніх номерах. Всі властивості об‘єкта зберігаються як елементи масиву і
тому кожна властивість має свій номер.

Три типи об’єктів JavaScript


У мові JavaScript є три типи об‘єктів: вмонтовані об‘єкти, об‘єкти браузера
й об‘єкти, що програміст створює самостійно.
Вмонтовані об’єкти. Нижче перераховані (таблиця 12.8) умонтовані
об‘єкти, властивості і методи яких доступні в JavaScript без попереднього
визначення цих об‘єктів:
Таблиця 12.8
Об‘єкт Опис
Array Масив
Boolean Логічні дані
Date Календарна дата
Function Функція
Global Глобальні методи
Math Математичні константи і функції
Number Числа
Object Об‘єкт
String Рядки

Тут скрипт створює об‘єкт Data, застосовуючи для цього ключове слово
new і конструктор Date без параметрів:
222
var dt = new Date();
Створений у такий спосіб об‘єкт Data ініціалізується поточною локальною
датою.
У такому рядку формується текстовий рядок дати:

szDate = "Date: " + dt.getDate() + "."+ dt. getMonth() + ". " + dt. getYear();
Значення календарного числа, номера місяця і року тут утворюється
відповідно за допомогою методів getDate, getMonth і getYear. Ці методи
викликаються для об‘єкта dt, що містить поточну дату.
Об‘єкт Date містить також інформацію про поточний час. Ця інформація
витягається для відображення за допомогою методів getHours, getMinutes і
getSeconds (відповідно години, хвилини і секунди):
szTime="Time: "+dt.getHours() + ":" + dt. getMinutes() + ":" + dt. getSeconds());

Об’єкти на базі класів, утворюваних програмістом


Клас JavaScript створюється як функція, у якій визначені властивості, що
грають роль даних. Що ж стосується методів, то вони теж визначаються як функції,
але окремо.
Нехай потрібно створити клас, призначений для збереження запису
уявлюваної телефонної бази даних. У цьому класі потрібно передбачити
властивості для збереження імені, прізвища, номера телефону, адреси, а також
спеціальної ознаки для записів, закритих від несанкціонованого доступу.
Додатково потрібно розробити методи, призначені для відображення вмісту
об‘єкта в табличному виді.
Насамперед, треба зайнятися створенням власного класу з назвою
myRecord. Нехай поки що в ньому не буде методів, додамо їх пізніше.
Шуканий клас створюється в такий спосіб:
function myRecord(name, family, phone, address){
this.name = name;
this.family = family;
this.phone = phone;
this.address = address;
this. secure = false;
}
Опис класу є не що інше, як функція конструктора. Параметри конструктора
призначені для налаштування властивостей об‘єкта при його створенні на базі
класу.
Властивості визначаються простими посиланнями на їхні імена з вказівкою
ключового слова this. Це ключове слово тут указує, що в операторі виконується
посилання на властивості того об‘єкта, для якого викликаний конструктор, тобто
для утворюваного об‘єкта.
Конструктор ініціалізує властивість з ім‘ям secure, записуючи в нього
значення false. Відповідний параметр у конструкторі не передбачений, що цілком
припустимо.

223
На базі цього класу можна створити довільну кількість об‘єктів. Нижче
приведений фрагмент скрипта, де на базі класу myRecord створюється два об‘єкти:
reс1 і гес2:
var reс1;
var гес2;
reс1 = new myRecord("Іван", "Іванів", "000-322-223", "Мала Велика вул., д.
225, кв. 226");
гес2 = new myRecord("Петро", "Петрів", "001-223-3334", "Велика Мала вул.,
д. 552, кв. 662");
гес2. secure = true;
Об‘єкти створюються за допомогою оператора new. Тут передаються
конструктору параметри для ініціалізації властивостей утворюваних об‘єктів.
У об‘єкті гес2 властивості з ім‘ям secure ініціалізується вже після створення
останнього. У нього записується значення true. Властивість secure об‘єкта reс1 не
змінювалося, тому в ньому зберігається значення false.
Така задача – додавання у визначений нами клас нових методів з іменами
printTableHead, printTableEnd і printRecord. Перші два з цих методів виводять у
документ HTML відповідно початковий і кінцевий фрагмент таблиці, а третій –
рядки таблиці, що показують вміст записів.
У скороченому виді нове визначення класу myRecord подано нижче:

function printTableHead(){
...
}
function printTableEnd(){
...
}
function printRecord(){
...
}
function myRecord(name, family, phone, address){
this.name = name;
this.family = family;
this.phone = phone;
this.address = address;
this.secure = false;
this.printRecord = printRecord;
this.printTableHead = printTableHead;
this. printTableEnd = printTableEnd;
}
Тут перед визначенням конструктора розташовані визначення для функцій-
методів класу. Крім того, у конструктор додано визначеня нових властивостей:

this.printRecord = printRecord;
this. printTableHead = printTableHead;
this. printTableEnd = printTableEnd;

224
Ці властивості бережуть посилання на методи, визначені вище. Після такого
визначення класу можна створювати об‘єкти й повертатися до визначених методів:
rec1.printTableHead();
rec1.printRecord();
rec1. printTableEnd();
гес2.printTableHead();
гес2.printRecord();
гес2. printTableEnd();

Масиви в JavaScript
Мова JavaScript припускає роботу з масивами вмонтованих об‘єктів,
об‘єктів браузера й об‘єктів, створених програмістом. Масив можна створити як
об‘єкт вмонтованого класу Array:

var myArray;
myArray = new Array(256);
...
myArray[0] = 255;
myArray[1] = 254;
myArray[255] = 0;
Клас Array вмонтований,тому не потрібно визначати його самостійно.

Вмонтований клас Math. Властивості


Всі властивості класу є математичними константами, тому JavaScript не
може змінювати їхнє значення.
Попередньо описана змінна n:
var n;
Е – ця властивість являє собою константу е. Приблизне значення цієї константи
дорівнює 2,72. От приклад використання властивості Е:
n = Math.E;
Тут записується в змінну n значення константи е.
РI – це число π. Воно також є константою з приблизним значенням, рівним 3,14.
Приклад використання властивості PI:
n = 2 * Math.PI * n;
Тут властивість PI використовується для обчислення довжини кола по його
радіусу. Обчислення виконується по такій формулі:l = 2π, де R – радіус кола.
LN2 – константа зі значенням натурального логарифма числа 2, тобто ln2.
n = Math. LN2;
N10 – константа зі значенням натурального логарифма числа 10, тобто ln 10.
n = Math. LN10;
LOG2E – це константа зі значенням, рівним логарифму числа 2 з основою е, тобто
loge2.
n = Math. LOG2E;
LOG10E – це логарифм числа е з основою 10, тобто log10e.
n = Math. LOG10E;

225
SQRT2 – це значення квадратного кореня з двох.
n = Math. SQRT2;
SQRT1_2 – це значення квадратного кореня з 0,5.
n = Math. SQRTl_2;

Методи класу Math


abs – обчислення абсолютного значення.
nValueAbs = Math.abs(nValue);
acos – обчислення арккосинуса.
nValue = Math. acos(nAngle);
asin – обчислення арксинуса.
nValue = Math. asin(nAngle);
atan – обчислення арктангенса.
nValue = Math. atan(nAngle);
ceil – обчислення найменшого цілого значення, більшого або рівного аргументу
функції.
nValue = Math.ceil(nArg);
cos – обчислення косинуса.
nValue = Math. cos(nAngle);
exp – експоненціальна функція, значення якої дорівнює числу е, в ступені n.
nValueExp = Math. exp(nValue);
floor – обчислення найбільшого цілого значення, меншого або рівного аргументу
функції.
nValue = Math. floor(nArg);
log – обчислення натурального логарифма аргумента функції.
nValue = Math. log(nArg);
max – визначення найбільшого з двох значень.
nValueMax = Math. max(nVaiue1 nVaiue2);
min – визначення найменшого з двох значень.
nValueMin = Math. min(nVaiue1, nVaiue2);
pow – знаходження числа в заданому степені
nValue = Math. pow(2, 3);
Тут число 2 підноситься до степеня 3
random – повертає випадкове число в інтервалі від 0 до 1.
nRandomValue = Math. random();
round – виконує округлення значення аргумента до найближчого цілого. Якщо
десяткова частина числа дорівнює 0,5 або більше цього значення, то виконується
округлення у більшу сторону, якщо менше – у меншу.
nValue = Math. round(1.8);
Після виконання заокруглення значення nValue буде дорівнювати двом.
sin – обчислення синуса.
nValue = Math. sin(nAngle);
sqrt – обчислення квадратного кореня від аргумента.
nValueSqrt = Math. sqrt(nArg);
tan – обчислення тангенса.
nValue = Math. tan(nAngle);

226
Вмонтований клас Date
Конструктори класу Date
Для використання більшості методів класу Date необхідно створити об‘єкт
цього класу за допомогою одного з трьох конструкторів.
Конструктор першого виду:
var dtNewDate;
dtNewDate = new Date();
Тут створюється об‘єкт Date, у якому зберігається інформація про поточну
дату і час. Цей час задається за Гринвічем або, користуючись більш сучасним
визначенням, із використанням часу Universal Coordinated Time.
Конструктор другого виду дозволяє зазначити дату через єдиний параметр:
var dtNewDate;
dtNewDate = new Date(nMilliseconds);
Параметр nMilliseconds задає дату в мілісекундах, рахуючи від 1 січня 1970
року.
Конструктор третього виду призначений для утворення дати з окремих
компонентів й у багатьох випадках більш зручний для використання:
var dtNewDate;
dtNewDate = new Date(year, month, date, hours, min, sec, ms);
Параметри цього конструктора описані нижче:
Таблиця 12.9
Параметр Опис
year Рік, наприклад 1998 або 2012
month Номер місяця від 0 (січень) до 11 (грудень)
date Календарна дата, задається в діапазоні від 1 до 31
Необов‘язковий параметр, що задає перша година дня в діапазоні від
hours 0 до 23. Ви зобов‘язані вказувати цей параметр тільки в тому
випадку, якщо заданий параметр min
Необов‘язковий параметр, що задає хвилини в діапазоні від 0 до 59.
min Ви зобов‘язані вказувати цей параметр тільки в тому випадку, якщо
заданий параметр sec
Необов‘язковий параметр, що задає секунди в діапазоні від 0 до 59.
sec Ви зобов‘язані вказувати цей параметр тільки в тому випадку, якщо
заданий параметр ms
Необов‘язковий параметр, що задає мілісекунди в діапазоні від 0 до
ms
999
getDate – визначення дати, що зберігається в об‘єкті класу Date. Метод повертає
значення календарної дати в діапазоні від 1 до 31.
var dtNewDate;
var nDate;
dtNewDate = new Date();
nDate = dtNewDate. getDate();
getDay – визначення номера дня тижня, що зберігається в об‘єкті класу Date.
Метод повертає 0 для неділі, 1 – для понеділка, і т.д.
nDay = dtDate. getDay();
getHours – визначення кількості годин, що минули після опівночі
nHours = dtDate. getHours();
getMinutes – визначення кількості хвилин, минулих після опівночі

227
nMinutes = dtDate. getMinutes();
getMonth – визначення кількості місяців, минулих із січня.
nMonth = dtDate.getMonth();
getSeconds –визначення кількості секунд, минулих із початку хвилини.
nSeconds = dtDate. getSeconds();
getTime – визначення часу для заданого об‘єкта класу Date. Метод getTime
повертає кількість мілісекунд, що минули з 1 січня 1970 року.
nMilliseconds = dtDate. getTime();
getYear – повертає рік, що зберігається в об‘єкті класу Date.
nYear = dtDate. getYear();
parse – повертає кількість мілісекунд, що пройшли із 00 годин 00 хвилин 1 січня
1970 року до часу, зазначеного у параметрі функції. Для виклику цього методу вам
не потрібно створювати об‘єкт класу Date, а можна просто посилатися на ім‘я
цього класу:
nMS = Date. parse(szDataString);
Через параметр szDataString ви можете зазначити час, наприклад, так:
"12 Oct 1998 12:00:00"
"12 Oct 1998 12:00:00 GMT"
"12 Oct 1998 12:00:00 GMT+0330"
Перший з цих рядків задає локальну дату і час, другий – дату і час за
Гринвічем, і, нарешті, останній – час і дату за Гринвічем із зсувом на 3 год. і 30 хв.
Метод parse, зазвичай, застосовують разом із конструктором об‘єкта Date
або з методом setTime.
setDate – використовується для встановлення календарної дати в об‘єкті клacу
Date.
dtNewDate. setDate(nDateNumber) ;
Параметр nDateNumber може приймати значення від 1 до 31.
setHours – використовується для задання кількості годин, що минули після півночі,
в об‘єкті класу Date.
dtNewDate. setHours (nHours);
Параметр nHours може приймати будь-які позитивні або негативні значення.
При необхідності відбувається відповідна зміна календарної дати, записаної в
об‘єкті класу Date.
Аналогічні можливості мають методи setMinutes, setMonth, setSeconds,
setTime, setYear.
toGMTString – призначений для перетворення дати в рядок, записаний у
стандартному форматі часу за Гринвічем (GMT).
toLocaleString – замість часу GMT використовується локальний час.
UTC – перетворює дату, задану параметрами методу, у кількість мілісекунд,
минулих із 1 січня 1970 року.
При використанні методу UTC, так само як і методу parse, немає
необхідності створювати об‘єкт класу Date:
nMillisecond = Date. UTC(year, month, date, hours, min, sec, ms);
Параметри методу UTC задаються так, як і описані вище параметри
конструктора об‘єкта класу Date третього виду.

228
Вмонтовані функції в JavaScript
eval – призначена для перетворення текстового рядка в чисельне значення. Через
єдиний параметр вона одержує текстовий рядок і обчислює його значення
відповідно до правил мови JavaScript. Функція повертає результат обчислення:
vаг nValue = Eval(szStr);
parseInt – призначена для перетворення текстового рядка в цілочисельне значення.
Рядок передається у функцію через параметр:
var nValue = parseInt(szStr);
parseFloat – намагається перетворити текстовий рядок у число з плаваючою
точкою. Текстовий рядок передається цій функції через перший параметр, основа
системи числення – через другий:
var nFloat = parseFloat(szStr, nRadix);
escape – скрипт може закодувати текстовий рядок із застосуванням URL-
кодування. У цьому кодуванні спеціальні символи, такі, як "пробіл" або
"табуляція", претворються до вигляду %ХХ, де XX – шістнадцятковий код
символу. Приклад використання цієї функції:
var szURL = escape(szStr);
unescape – виконує дію, прямо протилежну дії функції escape – перекодовує рядок
із URL-кодування в звичайний текстовий рядок:
var szStr = unescape(szURL);

12.4.Функція Java.extend()
Щоб уникнути двозначності, синтаксис розширення абстрактних класів не
допускається для конкретних класів. Оскільки конкретний клас може бути
створений, такий синтаксис можна інтерпретувати як спробу створити новий
екземпляр класу і передати йому об‘єкт типу, очікуваного конструктором (в разі,
коли очікуваний тип об‘єкта є інтерфейсом). В якості ілюстрації розглянемо
наступний приклад:
var t = new java.lang.Thread({ run: function() {
print("Thread running!") } });
Цей код можна інтерпретувати як розширення класу Thread з вказаною
реалізацією методу run (), так і створення екземпляра класу Thread, передавши його
конструктору об‘єкт, який впроваджує інтерфейс Runnable (додаткову інформацію
див. В розділі «Впровадження Java-інтерфейсів»).
Щоб розширити конкретний Java-клас, передайте його об‘єкт типу функції
Java.extend (), яка повертає об‘єкт типу підкласу. Потім використовуйте об‘єкт
типу підкласу як адаптера JavaScript до Java для створення екземплярів підкласу з
зазначеними реалізаціями методу. У наступному прикладі показано, як розширити
клас Thread з вказаною реалізацією методу run ():
Приклад
var Thread = Java.type ( "java.lang.Thread");
var threadExtender = Java.extend (Thread);
var t = new threadExtender () {
run: function () {print ( "Thread running!")}};
Функція Java.extend () може приймати список об‘єктів кількох типів. Ви
можете вказати не більше одного об‘єкта типу Java-класу і стільки об‘єктів типу
інтерфейсів Java, скільки хочете. Повертається об‘єкт типу розширює вказаний

229
клас (або java.lang.Object, якщо клас не вказано) і реалізує всі інтерфейси. Об‘єкт
типу класу не обов‘язково повинен бути першим у списку.

12.5 Використання інтерпретатора Nashorn в графічних


проектах JavaFX
У цьому розділі описано, як створювати та запускати програми JavaFX за
допомогою скриптів, написаних мовою JavaScript. Ви можете інтерпретувати
програму сценаріїв JavaFX за допомогою Nashhor, використовуючи команду jjs з
опцією -fx. Наприклад, наступна команда виконує файл JavaFXscript.js:
jjs -fx JavaFXscript.js
Скрипт JavaFX схожий на еквівалентну програму Java, але Nashorn дозволяє
спростити багато конструкцій JavaFX. Як правило, сценарій JavaFX містить тільки
функцію start(), яка еквівалентна методу start() у Java. Він також може містити
функції init() та stop().
Наступний приклад містить код для простої програми JavaFX, яка
відображає кнопку, при натисканні на яку повідомлення "Hello World!" виводиться
в консольне вікно.
Приклад програми JavaFX (HelloWorld.java)
Приклад 1.
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class HelloWorld extends Application {


public static void main(String[] args) {
launch(args);
}

@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World!");
Button btn = new Button();
btn.setText("Say ‘Hello World’");
btn.setOnAction(new EventHandler<ActionEvent>() {

@Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});

StackPane root = new StackPane();


root.getChildren().add(btn);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}}

230
Приклад скрипта JavaFX (HelloWorld.js)
Приклад 2.
var Button = javafx.scene.control.Button;
var StackPane = javafx.scene.layout.StackPane;
var Scene = javafx.scene.Scene;

function start(primaryStage) {
primaryStage.title = "Hello World!";
var button = new Button();
button.text = "Say 'Hello World'";
button.onAction = function() print("Hello World!");
var root = new StackPane();
root.children.add(button);
primaryStage.scene = new Scene(root, 300, 250);
primaryStage.show();
}
Обидва приклади дають той самий результат: вікно з заголовком "Hello
World!" і кнопкою "Say ‗Hello World‘". Проаналізувавши ці приклади, ви бачите,
як Nashorn спрощує код Java при написання програми JavaFX як скрипта, а саме –
не потрібно оголошувати типи змінних, імпортувати пакети, використовувати
анотації, вказувати назву класу та реалізувати його метод main().
Необхідно оголосити тільки ті класи JavaFX, які використовуються в
програмі.
JavaBeans не вимагають отримання та встановлення префіксів, і вони
розглядаються як властивості JavaScript, а не як Java.
Реалізація інтерфейсу javafx.event.EventHandler не вимагає явно вказувати
назву методу. Оскільки handle() є єдиним методом, Nashorn автоматично
застосовує надану функцію до нього.
Головна сцена JavaFX доступна для Nashorn як глобальна властивість
$STAGE. Ця глобальна властивість дозволяє розглядати весь сценарій як одну
функцію start() (ви все ще можете додати функції init() і stop()). Наступний приклад
містить код спрощеної версії сценарію JavaFX із попереднього прикладу.
Спрощена версія сценарію JavaFX (HelloWorldSimple.js)
Приклад 3.
var Button = javafx.scene.control.Button;
var StackPane = javafx.scene.layout.StackPane;
var Scene = javafx.scene.Scene;

$STAGE.title = "Hello World!";


var button = new Button();
button.text = "Say 'Hello World'";
button.onAction = function() print("Hello World!");
var root = new StackPane();
root.children.add(button);
$STAGE.scene = new Scene(root, 300, 250);
$STAGE.show();
У більшості випадків ви повинні додавати лише класи, які ви створюєте або
використовуєте для доступу до статичних полів. Однак для зручності Nashorn
визначає набір сценаріїв, які можна завантажити для імпорту груп пакунків JavaFX

231
і класів. Ви можете завантажити скрипт за допомогою функції load(), яка приймає
рядок з назвою скрипта. У наведеній нижче таблиці перелічено попередньо
визначені об‘єкти скрипта, доступні для включення:
Таблиця 12.10
Назва скрипта Імпортує класи
javafx.stage.Stage
javafx.scene.Scene
javafx.scene.Group
fx:base.js javafx/beans
javafx/collections
javafx/events
javafx/util
javafx/animation
javafx/application
javafx/concurrent
javafx/css
fx:graphics.js
javafx/geometry
javafx/print
javafx/scene
javafx/stage
javafx/scene/chart
fx:controls.js
javafx/scene/control
fx:fxml.js javafx/fxml
fx:web.js javafx/scene/web
fx:media.js javafx/scene/media
fx:swing.js javafx/embed/swing
fx:swt.js javafx/embed/swt
Наступний приклад містить код спрощеної програми сценарію JavaFX із
попереднього прикладу з функціями load(), які використовуються для імпорту
необхідних пакетів і класів.
Сценарій JavaFX із завантаженими скриптами
Приклад 4.
load("fx:base.js");
load("fx:controls.js");
load("fx:graphics.js");
$STAGE.title = "Hello World!";
var button = new Button();
button.text = "Say ‘Hello World’";
button.onAction = function() print("Hello World!");
var root = new StackPane();
root.children.add(button);
$STAGE.scene = new Scene(root, 300, 250);
$STAGE.show();

Тести
1. Як можна виконати код JavaScript в інтерпритаторі Nashorn?
a) з командного рядка, з використанням команди jjs

232
b) з коду Java, з використанням методу eval
c) з коду JavaScript, з використанням функції load
d) всіма вищенаведеними способами

2. Яким буде значення змінної, якщо її оголосили, але не присвоїли ніякого


значення?
a) 0
b) довільне число
с) undefined
d) спроба прочитати значення такої змінної спричинить помилку під час
виконання скрипта

3) Яким буде значення виразу parseInt(5+"5")-10 у мові JavaScript?


a) 15
b) 0
c) -5
d) 45

4) Яким буде результат виконання наступного коду?


var x=5;
var y=7;
function test() {
var x=10;
y=20;
x=x+y;
}
x=x+y;
test();
x=x-y;
console.log(x);
a) 5
b) -8
c) 10
d) 18

Завдання для самостійного виконання


1. Написати програму на мові Java, яка зчитує з клавіатури шлях до файла
JavaScript та запускає його на виконання.
2. Реалізувати на JavaScript функцію, яка повертає більше з двох чисел.
Написати програму на Java, яка виводить на екран більше з двох введених чисел з
використанням цієї функції.
3. Написати скрипт, що зчитує заздалегідь невідому кількість чисел та
виводить їх у порядку зростання. Вбудувати його в програму Java. Можна
використовувати будь-які стандартні класи, як з бібліотеки Java, так і з JavaScript.
4. Написати скрипт, який відображає вікно JavaFX з кнопкою та текстовим
полем вводу. При натисканні на кнопку текст з поля має виводитись в терміналі.

233
ВІДПОВІДІ ДО ТЕСТІВ
РОЗДІЛ ІV. ТЕХНОЛОГІЇ JAVA SE7, SE8
Тема 10.4
1) с (клас телебачення має канал загального користування, тому не
відповідає тісній інкапсуляції, отже A – неправильний. Це не відноситься до
жодного іншого класу, тому степінь зв’язаності низька – B невірно. Методи
телебачення виконують завдання, що нагадують телевізор, і тісно пов’язані між
собою, що є метою високого зчеплення, D невірно)
2) с (в загальному – загальнодоступні змінні є ознакою слабкої інкапсуляції.
A, B, D та E невірні, оскільки на основі наданої інформації жодне із цих
тверджень не може бути підтримане)

Тема 12.5
1) d (всі способи A-C можливі)
2) с (undefined – це спеціально зарезервоване значення для неініціалізованих
змінних)
3) d (через неявне приведення типів значенням виразу 5+"5" буде рядок "55".
Функція parseInt конвертує його в ціле число і після віднімання 10 результатом
буде 45)
4) b (важливою є відмінність в поведінці операторів var "var x=10;" та
"y=20;" всередині функції. Перший оголошує нову локальну змінну з назвою x. Тому
задання значення і присвоєння "x=x+y;" не змінюють значення глобальної змінної x.
Але оператор "y=20;" не оголошує нової змінної, а присвоює значення глобальній
змінній y. Тому в основній програмі значення y до і після виклику функції test будуть
різними)

234
Список використаних джерел

1. Бернакевич І.Є. Програмування мовою Java: використання


фундаментальних класів/ І.Є. Бернакевич, П.П. Вагін. – Львів: Видавничий
центр ЛНУ імені Івана Франка, 2002. - 48 с
2. Блинов И. Н. Java. Промышленное программирование: практ. пос. / И. Н
Блинов, В. С. Романчик. – Минск : УниверсалПресс, 2007. – 704 с.
3. Герберт Шилдт. Java 8. Полное руководство. [9-е изд.]:
Пер. с англ./ Шилдт Герберт – М.: ООО «И. Д. Вильямс», 2015. – 1376 с.:
ил. – Парал. тит. агнл.
4. Герберт Шилдт. Полный справочник по Java. [7 e издание]: Научно-
популярное издание/ Шилдт Герберт. – Санкт-Петербург – Издательский
дом "Вильямс" – 2007 – 1034 с.
5. Лигуори Роберт. Java 8
6. . Карманный справочник.: Пер. с англ./ Роберт Лигуори, Патрисия Лигуори
– М.: ООО «И. Д. Вильямс», 2015. – 256 с.: ил. – Парал. тит. англ.
7. Патрик Нимейер. Программирование на Java / Патрик Нимейер, Дэниэл
Леук; [пер. с англ. М. А. Райтмана]. – Москва: Эксмо, 2014. – 1216 с. –
(Мировой компьютерный бестселлер).
8. Хорстманн Кей С. Java 2. Библиотека профессионала, том І. Основы. [7-е
изд.]: Пер. с англ./ Хорстманн Кей С., Корнелл Гари.– М.: Издательский дом
«Вильямс», 2007. – 896 с.: ил. – Парал. тит. англ.
9. Хорстманн Кей С. Java 2. Библиотека профессионала, том ІІ. Тонкости
программирования. [7-е изд.]: Пер. с англ./ Хорстманн Кей С., Корнелл
Гари. – М.: Издательский дом «Вильямс», 2007. – 1168 с.: ил. – Парал. тит.
англ.
10. Хорстманн Кей С. Java. Библиотека профессионала, том 1. Основы. [9-е
изд.]: Пер. с англ./ Хорстманн Кей С., Корнелл Гари. – М.: ООО «И. Д.
Вильямс», 2014 – 864 с.: ил. – Парал. тит. англ.
11. Хорстманн Кей С. Java. Библиотека профессионала, том 2. Раширенные
средства. [9-е изд.]: Пер. с англ./ Хорстманн Кей С., Корнелл Гари. – М.:
ООО «И. Д. Вильямс», 2014. – 1008 с.: ил. – Парал. тит. англ.
12. Эванс Б. Java. Новое поколение разработки./ Б. Эванс, М. Вербург – СПб.:
Питер, 2014. – 560 с.: ил.
13. Эккель Б. Философия Java. Библиотека програмиста. [4-е изд.]/ Б. Эккель –
СПб.: Питер, 2009. – 640 с.: ил. – (Серия «Библиотека программиста»).
14. Язык программирования Java SE 8. Подробное описание, 5-е изд.: Пер. с
англ./ Гослинг Джеймс, Джой Билл, Стил Гай и др. – М.: ООО «И. Д.
Вильямс», 2015. – 637 с.: ил. – Парал. тит. англ.
15. Getting Started with JavaFX [Електронний ресурс] – Режим доступу до
ресурсу: http://docs.oracle.com/javafx/2/get_started/fxml_tutorial.htm.
16. Introduction to Nashorn [Електронний ресурс] – Режим доступу до ресурсу:
http://www.baeldung.com/java-nashorn.
17. Java Basic [Електронний ресурс] – Режим доступу до ресурсу:
http://o7planning.org/.

235
18. Java. Алгоритмізація та програмування [Електронний ресурс] – Режим
доступу до ресурсу: https://schoolboyprog10.blogspot.com/p/blog-
page_79.html.
19. Nashorn and JavaFX [Електронний ресурс] – Режим доступу до ресурсу:
https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/javafx.h
tml.
20. Pomarolli A. JavaFX FXML Tutorial [Електронний ресурс] / Andreas Pomarolli
– Режим доступу до ресурсу: https://examples.javacodegeeks.com/desktop-
java/javafx/fxml/javafx-fxml-tutorial/#intro.
21. Start Android - учебник по Android для начинающих и продвинутых
[Електронний ресурс] – Режим доступу до ресурсу: http://startandroid.ru.
22. Блог про програмування. Java [Електронний ресурс] – Режим доступу до
ресурсу: www.progs.in.ua.
23. Введение в JavaFx и работа с layout в примерах [Електронний ресурс] –
Режим доступу до ресурсу: https://habrahabr.ru/post/305282/.
24. Запуск кода из строки: eval [Електронний ресурс] – Режим доступу до
ресурсу: https://learn.javascript.ru/eval.
25. Класс ConcurrentLinkedDeque <E> [Електронний ресурс] – Режим доступу
до ресурсу: http://spec-
zone.ru/RU/Java/Docs/7/api/java/util/concurrent/ConcurrentLinkedDeque.html.
26. Программирование JavaFX [Електронний ресурс] – Режим доступу до
ресурсу: http://o7planning.org/ru/11009/javafx.
27. Разработка для Android. Научись за 30 дней! [Електронний ресурс] –
Режим доступу до ресурсу: http://developer.alexanderklimov.ru.
28. Синхронизация потоков. Оператор synchronized [Електронний ресурс] –
Режим доступу до ресурсу: https://metanit.com/java/tutorial/8.3.php.
29. Шаблони проектування [Електронний ресурс] – Режим доступу до ресурсу:
http://drsoft.com.ua/index/news/112/.
30. Шпаргалка програміста Java 3. Колекції в Java (стандартні, guava, apache,
скарб, gc-collections та інші) [Електронний ресурс] – Режим доступу до
ресурсу: http://it-ua.info/news/2015/10/28/shpargalka-programsta-java-3-kolekc-
v-java-standartn-guava-apache-skarb-gc-collections-ta-nsh.html.

236
ДОДАТОК
РОЗДІЛ І. РОЗШИРЕНІ JAVA-ТЕХНОЛОГІЇ

1. When comparing java.io.BufferedWriter to java.io.FileWriter, which capability exist


as a method in only one of the two?
a) сlosing the stream
b) flushing the stream
c) writing to the stream
d) marking a location in the stream
e) writing a line separator to the stream
5. Consider the following code:
4. File inFile = new File("infile.txt");
5. File outFile = new File("outfile.txt");
6. BufferedReader in = new BufferedReader(inFile);
7. BufferedWriter out = new BufferedWriter(new FileWriter(outFile));
8. String temp;
9. while ((temp = in.readLine()) != null){
10. out.write(temp);
11. }
13. in.close();
14. out.close();
What is the output of this code?
a) A compiler error occurs at line 3.
b) A compiler error occurs at line 6.
c) The code compiles fine but throws an exception during execution at line 6.
d) A compiler error occurs at line 7.
e) The code compiles and executes without any error or exception.
6. Which code inserted, will allow this class to correctly serialize and deserialize?
import java.io.*;
public class Foo implements Serializable {
public int x, y;
public Foo( int x, int y ) {
this.x =x; this.y = y; }
private void writeObject (ObjectOutputStream s ) throws IOException {
s.writeInt(x); s.writeInt(y) ;
}
private void readObject (ObjectInputStream s )
throws IOException,ClassNotFoundException {
// insert code here
}
}
a) s.defaultReadObject();
b) this = s.defaultReadObject();
c) y = s.readInt(); x = s.readInt();
d) x = s.readInt(); y = s.readInt();

7. Which three statements concerning the use of the java.io.Serializable interface are
true? (Choose three.)
a) Objects from classes that use aggregation cannot be serialized.

237
b) An object serialized on one JVM can be successfully deserialized on a different
JVM.
c) The values in fields with the volatile modifier will NOT survive serialization and
deserialization.
d) The values in fields with the transient modifier will NOT survive serialization and
deserialization.
e) It is legal to serialize an object of a type that has a supertype that does NOT
implement java.io.Serializable.
8. Given:
import java.io.*;
public class Forest implements Serializable {
private Tree tree = new Tree();
public static void main(String [] args) {
Forest f = new Forest();
try{
FileOutputStream fs = new FileOutputStream("Forest.ser");
ObjectOutputStream os = new ObjectOutputStream(fs);
os.writeObject(f);
os.close();
} catch (Exception ex) { ex.printStackTrace();}
}
}
class Tree {}

What is the result?


a) Compilation fails.
b) An exception is thrown at runtime.
c) An instance of Forest is serialized.
d) An instance of Forest and an instance of Tree are both serialized.

9. Given the code. What is the result?


import java.io.*;
public class Hotel implements Serializable {
private transient Room room = new Room();
public static void main(String[] args) {
Hotel h = new Hotel();
try {
FileOutputStream fos = new FileOutputStream("Hotel.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(h);
oos.close();
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
class Room { }
a) Compilation fails
b) An exception is thrown at runtime
c) An instance of Hotel is serialized
d) An instance of Hotel and an instance of Room are both serialized

238
10. Assuming that the serializeBanana() and the deserializeBanana() methods will
correctly use Java serialization and given:
import java io.*;
class Food implements Serializable {int good = 3;}
class Fruit extends Food {int juice = 5;}
public class Banana extends Fruit {
int yellow = 4;
public static void main(String [] args) {
Banana b = new Banana(); Banana b2 = new Banana();
b.serializeBanana(b); //assume correct serialization
b2 = b.deserializeBanana(); // assume correct
System.out.println("restore "+b2.yellow+ b2.juice+b2.good);
}
// more Banana methods go here
}
What is the result?
a) restore 400
b) restore 403
c) restore 453
d) Compilation fails.
e) An exception is thrown at runtime.
11. Assuming that the serialize Banana2() and the deserialize Banana2() methods will
correctly use Java serialization and given:
import java.io.*;
class Food {Food() { System.out.print(‚1‛); } }
class Fruit extends Food implements Serializable {
Fruit() { System.out.print("2"); }}
public class Banana2 extends Fruit { int size = 42;
public static void main(String [ ] args) {
Banana2 b = new Banana2();
b.serializeBanana2(b); // assume correct serialization
b = b.deserializeBanana2(b); // assume correct
System.out.println(" restored " + b.size + " "); }
// more Banana2 methods
}
What is the result?
a) Compilation fails.
b) 1 restored 42
c) 12 restored 42
d) 121 restored 42
e) 1212 restored 42
f) An exception is thrown at runtime.

13. Suppose class Аlpha extends Object; class Beta extends Аlpha; and class Gamma
extends Beta. Of, only class Gamma implements java.io.Serializable. Which of the
following must these be true in order to avoid an exception during deserialization of an
instance of Gamma?
a) Аlpha must have a no-args constructor.
b) Beta must have a no-args constructor.

239
c) Gamma must have a no-args constructor.
d) There are no restrictions regarding no-args constructors.

16. What is the result of the following code?


String st = "sdfsd321df848";
System.out.println(st);
Pattern p = Pattern.compile ("(\\d)+");
Matcher m = p.matcher (st);
int start;
int end;
int chuslo;
int suma = 0;
while (m.find()==true){
start = m.start();
end = m.end();
chuslo = Integer.parseInt(sb.substring(start, end));
suma=suma+chuslo;
}
System.out.println(suma);
a) 321848
b) 1169
c) 26
d) 11

19. What is the result of the following code?


String text = "Користувач може надати класам такі імена: myProject, Project,
MyProject ";
Pattern p = Pattern.compile("\\s[A-Z]+([a-z])*");
Matcher m = p.matcher(text);
int count = 0;
while (m.find()){
int start = m.start();
String temp = m.group();
System.out.print(start + " " + temp +" ");
count++;
}
System.out.println(count);
a) 52 Project 61 My 2
b) 52 Project 61 My 2
c) 52Project61My2
d) 52 Project 61 MyProject 2

20. Which of the following are illegal enum definitions?


a) enum Day {SUNDAY, MONDAY, TUESDAY}
b) enum Day {
SUNDAY, MONDAY, TUESDAY,
private String holiday;
}
c) enum Day {
SUNDAY, MONDAY, TUESDAY;
private String holiday;

240
}
d) enum Day {
private String holiday;
SUNDAY, MONDAY, TUESDAY;
}
e) enum Day {
SUNDAY, MONDAY, TUESDAY;
private String holiday;
Day(){
System.out.println("Hello");
}
}
21. Given the following enum declaration:
public enum Mountain {
GOVERLA, HOMYAK, PIP_IVAN
}
what is the result of the following statement?
System.out.println(Mountain.HOMYAK.ordinal());
a) 0
b) 1
c) HOMYAK
d) 9
e) The statement will not compile
22. Given the following enum definition:
public enum Mountain {
GOVERLA, HOMYAK, PIP_IVAN
}
what is the output from the following code?
Mountain f = Mountain. HOMYAK;
switch (f) {
case 0:
System.out.println("goverla");
case 1:
System.out.println("homyak");
case 2:
System.out.println("pip_ivan");
break;
default:
System.out.println("missing mountain");
}

a) goverla
b) homyak
c) pip_ivan
d) missing mountain
e) The code does not compile

23. Given the following enum definition:


enum Dean {

241
MMF("Олійник"), FPMI("Бондаренко"), GEO("Ковальчук");
String name;

Dean(String arg) {
name = arg;
}

String getName() {
return name;
}
}
what is the output from the following code?
Dean dn = Dean.valueOf("FPMI");
System.out.print(dn.ordinal());
System.out.println(" : " + dn + " : " + dn.getName());

a) 1 : FPMI : Бондаренко
b) 1 : GEO : Ковальчук
c) 1 : MMF : Олійник
d) 2 : FPMI : Бондаренко
e) 2 : GEO : Ковальчук
f) 2 : MMF : Олійник
g) 3 : FPMI : Бондаренко
h) 3 : GEO : Ковальчук
i) 3 : MMF : Олійник

24. Given the following enum definition:


enum cafe{
BIG (10),
SMALL (1),
MED(5)
int mySize = 0;
cafe(int size){
mySize = size;
}
}
What happens when this enum is in the code outside a class?
a) Compiler error
b) Compiles fine
c) Runtime Exception occurs if mySize is accessed.

25. Given the fully-qualified class names:


com.foo.bar.Dog
com.foo.bar.blath.Book
com.bar.Car
com.bar.blath.Sun
Which graph represents the correct directory structure for a JAR file from which
those classes can be used by the compiler and JVM (рис. 1.16)?

242
f) Jar A
a) Jar B
b) Jar C
c) Jar D
d) Jar E

Рис. 1. 16

243
РОЗДІЛ ІІ. ФРЕЙМВОРК КОЛЕКЦІЙ

1. Which of the following are valid lines of code to define a multidimensionalint array?
a) int[][] array1 = {{1, 2, 3}, {}, {1, 2,3, 4, 5}};
b) int[][] array2 = new array() {{1, 2, 3}, {}, {1, 2, 3, 4, 5}};
c) int[][] array3 = {1, 2, 3}, {0}, {1, 2,3, 4, 5};
d) int[][] array5 = new int[2][];
2. Consider the following line of code:
int[] x = new int[25];
After execution, which statements are true? (Choose all that apply.)
a) x[24] is 0
b) x[24] is undefined
c) x[25] is 0
d) x[0] is null
e) x.length is 25
3. Which of these array declaration statements are not legal? Select the two correct
answers.
a) int[] i[] = { { 1, 2 }, { 1 }, {}, { 1, 2, 3 } };
b) int i[] = new int[2] {1, 2};
c) int i[][] = new int[][] { {1, 2, 3}, {4, 5, 6} };
d) int i[][] = { { 1, 2 }, new int[ 2 ] };
e) int i[4] = { 1, 2, 3, 4 };

4. Given:
int [ ] arr = {1, 2, 3, 4, 5};
int [ ] arr2 = new int [4];
arr2 = arr;
System.out.println(arr2[4]);
What is result?
a) runtime exception. arrayoutofboundsexception.
b) compile error
c) prints 4
d) compiles with warning
e) prints 5
5. Which of the following are valid declarations of a for in loop that loops through an int
array named j ?
f) foreach (int i : j) {}
g) for (int i : [ ] j) {}
h) for (int i : j) {}
i) for (int i : j [ ] ) {}
j) none of the above

6. Given:
boolean [ ] arr = { true , false , true };

244
Arrays.sort(arr);
System.out.println( Arrays.binarySearch (arr, true) ) ;
What is result?
a) does not compile
b) compiles but has Runtime exceptions
c) prints 0
d) prints 2
e) prints 1
f) none of the above
7. Given:
int [ ]arr = new int[]{1,2,3,4} ;
String [ ]arrstr = Arrays.toString(arr);
for ( String s : arrstr ) {
System.out.println(s);
}
What is result?
a) prints 12 3 4
b) prints nothing
c) compile error
d) runtime error
e) none of the above

8. Given:
System.out.println (Arrays.equals(new int []{1,2,3,4},new Integer []{1,2,3,4}));

What is result?
a) prints true
b) prints false
c) runtime exception
d) compile error
9. Given:
int [] arr = new int []{1,2,3,4};
int [] arr2 = new int []{new Integer(1),2,3,4};
System.out.println( Arrays.equals(arr, arr2));
What is result?
a) runtime error
b) prints false
c) prints nothing
d) compile error
e) prints true
10. What is the result of the following statements?
8. List list = new ArrayList();
9. list.add("one");

245
10. list.add("two");
11. list.add(7);
12. for(String s :list) {
13. System.out.print(s);
14. }

a) onetwo
b) onetwo7
c) onetwo followed by an exception
d) compiler error on line 4
e) compiler error on line 5

11. Given:
12. import java.util.*;
13. class Test {
14. public static void main ( String [ ] args ) {
15. List < Integer > list = new ArrayList < Integer > ();
16. for ( int i = 1 ; i<10 ; i++ ) {
17. list.add(i);
18. }
19. list.remove( new Integer(4) ); // 1
20. list.remove( 1 ); // 2
21. }
22. }

Lines marked 1 and 2 refer to the remove method.


What is result?
a) line one removes integer at index 4 and line 2 removes the integer object one
b) line one removes integer object 4 and line 2 removes integer at index one
c) both methods remove the 4th and the 1th integer objects
d) both methods remove the Integer 4 and 1 from list

12. What is the result of the following statements?


10. List < String > one = new ArrayList < String > ();
11. one.add(―abc‖);
12. List < String > two =new ArrayList < String > ();
13. two.add(―abc‖);
14. if(one == two) System.out.println("A");
16. else if(one.equals(two))
System.out.println("B");
else System.out.println("C");

a) A
b) B
c) C
d) compiler error on line 14
e) compiler error on line 16
246
13. Given:
11. public static void append(List list) {list.add("0042");}
12. public static void main(String[] args) {
13. List<Integer> intList = new ArrayList<Integer>();
14. append(intList);
15. System.out.println(intList.get(0));
16.}

What is the result?


a) 42
b) 0042
c) An exception is thrown at runtime.
d) Compilation fails because of an error in line 13.
e) Compilation fails because of an error in line 14.

14. Given the code. What is the result?


import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class HashTest {
private String str;
public HashTest(String str) {
this.str = str;
}
public String toString() {
return this.str;
}
public static void main(String args[]) {
HashTest h1 = new HashTest("2");
String s1 = new String("1");
List<Object> list = new LinkedList<Object>();
list.add(h1);
list.add(s1);
Collections.sort(list);
for (Object o : list) {
System.out.print(o + " ");
} } }

a) "2 1" is printed.


b) "1 2" is printed.
c) compilation fails.
d) an exception is thrown at runtime.

15. Given the code. What is the result?


import java.util.Collections;
import java.util.Iterator;

247
import java.util.LinkedList;
import java.util.List;
public class TryMe {
public static void main(String args[]) {
List list = new LinkedList<String>();
list.add("one");
list.add("two");
list.add("three");
Collections.reverse(list);
Iterator iter = list.iterator();
for (Object o : iter) {
System.out.print(o + " ");
}
}
}

a) "three two one " is printed


b) "one two three " is printed
c) nothing is printed
d) compilation fails
e) an exception is thrown at runtime

16. Given the code. What is the result?


import java.util.HashSet;
public class HashTest {
private String str;
public HashTest(String str) {
this.str = str;
}
public int hashCode() {
return this.str.hashCode();
}
public boolean equals(Object obj) {
return this.str.equals(obj);
}
public static void main(String args[]) {
HashTest h1 = new HashTest("1");
HashTest h2 = new HashTest("1");
String s1 = new String("2");
String s2 = new String("2");
HashSet<Object> hs = new HashSet<Object>();
hs.add(h1);
hs.add(h2);
hs.add(s1);
hs.add(s2);
System.out.print(hs.size());

248
}
}

a) "4" is printed
b) "3" is printed
c) "2" is printed
d) compilation fails
e) an exception is thrown at runtime

17. Given the code. What is the result?


import java.util.*;
public class TryMe {
public static void main(String args[]) {
Queue<String> q = new PriorityQueue<String>();
q.add("3");
q.add("1");
q.add("2");
System.out.print(q.poll() + " ");
System.out.print(q.peek() + " ");
System.out.print(q.peek());
} }
a) 1 2 3
b) 3 2 1
c) 1 2 2
d) 3 1 1
e) 2 3 3
f) 1 1 2
18. Given:
List<String> list =newArrayList<String>; // 1
list.add ("hello"); // 2
list.add ("My dear"); // 3
for(Object o : list ){ // 4
System.out.println (o); // 5
}
What is result?
a) runtime error
b) compile error
c) prints my dear hello
d) prints hello my dear
e) cannot predict the sequence in which the strings are printed.

249
19. Given:
40. HashMap props = new HashMap();
41. props.put("key45", "some value");
42. props.put("key 12", "some other value");
43. props.put("key39", "yet another value");
44. Set s = props.keySet();
45. //insert code here
What, inserted at line 39, will sort the keys in the props HashMap?
a) Arrays.sort(s);
b) s = new TreeSet(s);
c) Collections.sort(s);
d) s = new SortedSet(s);
20. Given:
import java.util.*;
classTest {
publicstaticvoid main (String[] args ){
Map<Integer,String> map =newLinkedHashMap<Integer,String>();
Map<Integer,String> sap =newHashMap<Integer,String>();
populate( map );
populate( sap );
System.out.println( map.get(1)+ sap.get(1));
}
staticvoid populate (Map m ){
for(int i = 0 ; i < 10 ; i++){
m.put(i,i);
}} }
What is result?
a) prints 11
b) prints 2
c) runtime error
d) compile error
e) nullnull
21. Which two statements are true about the hashCode method? (Choose two.)

a) The hashCode method for a given class can be used to test for object equality and
object inequality for that class.
b) The hashCode method is used by thejava.util.SortedSet collection class to order
the elements within that set
c) The hashCode method for a given class can be used to test for object inequality,
but NOT object equality, for that class.
d) The only important characteristic of the values returned by a hashCode method is
that the distribution of values must follow a Gaussian distribution.
e) The hashCode method is used by the java.util.HashSet collection class to group
the elements within that set into hash buckets for swift retrieval.

250
РОЗДІЛ ІІІ. ПРОГРАМУВАННЯ ANDROID, БАЗ ДАНИХ, БАГАТОПОТОКОВЕ
ПРОГРАМУВАННЯ

7. Given the code. What is the result?


public class Hotel {
private static void book() {
System.out.print("book");
}
public static void main(String[] args) throws
InterruptedException {
Thread.sleep(1);
book();
}
}
a) Compilation fails.
b) An exception is thrown at runtime.
c) "book" is printed
8. Given the code. What is the result?
public class Cruiser implements Runnable {
public void run() {
System.out.print("go");
}
public static void main(String arg[]) {
Thread t = new Thread(new Cruiser());
t.run();
t.run();
t.start();
}
}
a) Compilation fails
b) An exception is thrown at runtime
c) "go" is printed
d) "gogogo" is printed
e) "gogo" is printed
9. Given:
public class Threads5 {
public static void main (String[] args) {
new Thread (new Runnable() {
public void run() {
System.out.print("bar");
}
}).start();
}
}
What is the result?
a) Compilation fails.
b) An exception is thrown at runtime.
c) The code executes normally and prints "bar".
The code executes normally, but nothing prints.

251
10. Given the code. What is the result?
public class Cruiser implements Runnable {
public static void main(String[] args) {
Thread a = new Thread(new Cruiser());
a.start();
System.out.print("Begin");
a.join();
System.out.print("End");
}
public void run() {
System.out.print("Run");
}
}
a) Compilation fails.
b) An exception is thrown at runtime.
c) "BeginRunEnd" is printed.
d) "BeginEndRun" is printed.
e) "BeginEnd" is printed.

11. Which three will compile without exception? (Choose three)


a) private synchronized SomeClass a;
b) void book() { synchronized () {} }
c) public synchronized void book() {}
d) public synchronized(this) void book() {}
e) public void book() { synchronized(Cruiser.class) {} }
f) public void book() {synchronized(a){}}
12. Given:
public class TestThings extends Thread {
private static int x;
public synchronized void doThings() {
int current = x;
current++;
x = current;
}
public void run() {
doThings();
}
}
Which statement is true?
a) Compilation fails.
b) An exception is thrown at runtime.
c) Synchronizing the run() method would make the class thread-safe.
d) The data in variable "x" are protected from concurrent access problems.
Declaring the doThings() method as static would make the class thread-safe.Wrapping the
statements within doThings() in a synchronized (new Object()) { } block would make the
class thread-safe.

252
13. Give a piece of code. What is true?
public void waitForSomething() {
SomeClass o = new SomeClass();
synchronized (o) {
o.wait();
o.notify();
}
}
a) This code may throw an InterruptedException
b) This code may throw an IllegalStateException
c) This code may throw a TimeOutException
d) Reversing the ofrer of o.wait() and o.notify() will cause this method to complete
normally

253
РОЗДІЛ ІV. ТЕХНОЛОГІЇ JAVA SE7, SE8

1. Given the following Television class definition:


public class Television {
public int channel;
private boolean on;
private int volume;

public void changeChannel(int newChannel) {


channel = newChannel;
}
public int getChannel() {
return channel;
}
public void turnOn() {
on = true;
}
public void turnOff() {
on = false;

}
public void turnUp() {
volume += 1;
}
public void turnDown() {
volume -= 1;
}
}

which of the following OO design patterns does the Television class more
closely adhere to?
a) tight encapsulation
b) tight coupling
c) high cohesion
d) low cohesion

2. Given:
5. ClassA has a ClassD
6. Methods in ClassA use public methods in ClassB
7. Methods in ClassC use public methods in ClassA
8. Methods in ClassA use public variables in ClassB
Which is most likely true? (Choose the most likely.)
a) ClassD has low cohesion
b) ClassA has weak encapsulation
c) ClassB has weak encapsulation
d) ClassB has strong encapsulation
e) ClassC is tightly coupled to ClassA

254
ЗМІСТ
Передмова……………………………………………………...........……………………. 3

РОЗДІЛ І. РОЗШИРЕНІ JAVA-ТЕХНОЛОГІЇ


О. Мішагіна, Г. Двояк, С. Дерюга, В. Москалик
ТЕМА 1. Розширені засоби введення-виведення даних
та графічного інтерфейсу
1.1 Байтові потоки. Ієрархія………………………………….………………….. 4
Тести та завдання для самостійного виконання……………..………………………… 8
1.2 Серіалізація…………………………………………………………………… 9
Тести та завдання для самостійного виконання………………….……………………. 14
1.3 Нестандартна серіалізація…………………………………………………… 16
Тести ……………………………………………………………………………………… 18
1.4 Інтерфейс Externalizable……………………………………………………… 18
Тести та завдання для самостійного виконання………………………………………... 20
1.5 Розробка графічного інтерфейсу користувача (ГІК) за
допомогою Java FX. Основи та властивості Java FX…………………………... 20
Завдання для самостійного виконання………………………………………………….. 26
1.6 Мова розмітки FXML………………………………………………………… 28
Завдання для самостійного виконання………………………………………………….. 33

І. Новак, Г. Двояк, С. Дерюга, Т. Багрій


ТЕМА 2. Розкладання рядків на лексеми.
Форматоване виведення даних
2.1 Основи рarsing, клас StringTokenizer……………………………………….. 34
Тести та завдання для самостійного виконання…………………………………….…. 35
2.2 Основи технології регулярних виразів. Класи Pattern, Matcher, Scanner… 35
Тести та завдання для самостійного виконання……………………………………….. 38
2.3 Клас Formatter. Форматоване виведення чисел та грошових даних…….... 40
Завдання для самостійного виконання…………………………………………………. 42

Л. Борисевич, Г. Двояк, С. Дерюга, І. Новак


ТЕМА 3. Розширені Java-технології
3.1 Використання еnum........................................................................................... 43
Тести та завдання для самостійного виконання.............................................................. 46
3.2 Клас java.lang.Class. Основи рефлексії........................................................... 48
3.3 Використання аssertions................................................................................... 54
3.4 Пошук файлів за допомогою classpath............................................................ 60
3.5 Використання jar-файлів.................................................................................. 63
Тести та завдання для самостійного виконання.............................................................. 64
3.6 Aнотації.............................................................................................................. 65
Відповіді до тестів розділу I................................................................................ 67

255
РОЗДІЛ ІІ. ФРЕЙМВОРК КОЛЕКЦІЙ
Г. Двояк, С. Дерюга, С. Зітинюк, О. Рибак
ТЕМА 4. Фреймворк колекцій
4.1 Створення, побудова та ініціалізація одно- та багатовимірних масивів
примітивних та об‘єктних змінних........................................................................ 71
Тести та завдання для самостійного виконання............................................................... 73
4.2 Клас Arrays......................................................................................................... 73
Тести та завдання для самостійного виконання............................................................... 75
4.3 Ієрархія колекцій. Інтерфейс Collections......................................................... 77
Тести та завдання для самостійного виконання............................................................... 80
4.4 Інтерфейси List, Set, Queue. Класи, що реалізують ці інтерфейси............... 81
Тести та завдання для самостійного виконання............................................................... 90
4.5 Інтерфейси Map та MapEntry. Hash-таблиці................................................... 94
4.6 Взаємні перетворення колекцій та карт.......................................................... 97
4.7 Hash-контракт для ключів HashMaps.............................................................. 98
Тести та завдання для самостійного виконання.............................................................. 98

С. Дерюга, Г. Двояк, А. Поліщук, В. Москалик


ТЕМА 5. Технологія GENERICS
5.1 Призначення Generics. Оголошення Generic-класів і методів...................... 99
5.2 Обмеження при оголошенні Generic-класів................................................... 102
5.3 Шаблони в Generic-аргументах методів......................................................... 103
5.4 Алгоритми Generic-колекцій класу Collection............................................... 105
Тести та завдання для самостійного виконання.............................................................. 106
Відповіді до тестів розділу II............................................................................... 108

РОЗДІЛ ІІІ. ПРОГРАМУВАННЯ ANDROID, БАЗ ДАНИХ,


БАГАТОПОТОКОВЕ ПРОГРАМУВАННЯ
О. Рибак, С. Зітинюк, О. Новосельський, І. Новак
ТЕМА 6. Розробка ANDROID-проектів
6.1 Структура Android-проекту.............................................................................. 111
6.2 Прості Android-проекти із використанням текстів та кнопок....................... 114
Завдання для самостійного виконання.............................................................................. 117
6.3 Навігація між вікнами Android-проекту.......................................................... 117
Завдання для самостійного виконання.............................................................................. 122
6.4 Використання адаптерів для виведення списків в Android-проекті............. 122
Завдання для самостійного виконання.............................................................................. 126

С. Дерюга, О. Рибак, Л. Борисевич, І. Матлюк


ТЕМА 7. Робота з базами даних
7.1 Основи баз даних - таблиці, ключі, зв‘язки таблиць...................................... 127
7.2 Основи мови SQL - оператори select, insert, update, delete............................. 129
7.3 Встановлення з‘єднання з базою даних засобами JDBC.
Класи DriverManager та Connection........................................................................ 134
Тести та завдання для самостійного виконання................................................................ 137

256
О. Рибак, О. Мішагіна, С. Зітинюк, Т. Лужний
ТЕМА 8. Основи багатопотоковості
8.1 Поняття багатопотоковості. Прості методи класу Threads............................ 139
Тести ............................................................................................................................. ........ 141
8.2 Способи створення потоків.............................................................................. 142
8.3 Стани потоків. Планувальник черги потоків. Методи join(), yield()............ 144
Тести та завдання для самостійного виконання............................................................... 148
8.4 Приклади аналізу багатопотокових кодів....................................................... 149
8.5 Синхронізація потоків...................................................................................... 152
Тести ............................................................................................................................. ....... 154
8.6 Взаємодіяпотоків. Методиwait(), notify()........................................................ 154
Тести та завдання для самостійного виконання............................................................... 159
Відповіді до тестів розділу III.............................................................................. 161

РОЗДІЛ ІV. ТЕХНОЛОГІЇ JAVA SE7, SE8


В. Москалик, О. Мішагіна, М. Чижик, Т. Багрій
ТЕМА 9. Основи паралельного API
9.1 Пакети java.util.concurrent, java.util.concurrent.atomic,
java.util.concurrent.locks. Потокобезпечні колекції............................................... 162
9.2 Клас Executors..................................................................................................... 164
9.3 Інтерфейси Callable та Future............................................................................ 165
9.4 Класи – синхронізатори..................................................................................... 167
Завдання для самостійного виконання............................................................................... 172
9.5 Фреймворк Fork/Join.......................................................................................... 174
Завдання для самостійного виконання............................................................................... 177

Т. Лужний, О. Мішагіна, М. Чижик, Л. Борисевич


ТЕМА 10. ШАБЛОНИ ПРОЕКТУВАННЯ
10.1 Призначення та типи шаблонів проектування.............................................. 177
10.2 Шаблон Singleton............................................................................................. 181
10.3 Шаблон Factory................................................................................................ 187
10.4 Проектування класів із врахуванням властивостей сoupling та cohesion.. 194
Тести..................................................................................................................................... 199

Г. Двояк, О. Мішагіна, М. Чижик, А. Поліщук


ТЕМА 11. ВИКОРИСТАННЯ LAMBDA-ВИРАЗІВ
11.1 Призначення Lambda-виразів......................................................................... 200
11.2 Синтаксис Lambda-виразів............................................................................. 201
11.3 Зміни в структурі інтерфейсів........................................................................ 205
11.4 Пакет java.util.function..................................................................................... 206
11.5 Посилання на методи і конструктори............................................................ 208
Завдання для самостійного виконання.............................................................................. 210

257
І. Новак, О. Мішагіна, М. Чижик, Л. Борисевич
ТЕМА 12. Основи JavaScript та
використання інтерпретатора Nashorn
12.1 Основи JavaScript. Функції JavaScript........................................................... 210
12.2 Призначення та способи виконання інтерпретатора Nashorn..................... 217
12.3 Робота з числами, рядками, масивами, колекціями,
методами, об‘єктами.............................................................................................. 223
12.4 Функція Java.extend()...................................................................................... 230
12.5 Використання інтерпретатора Nashorn в графічних проектах JavaFX...... 231
Тести та завдання для самостійного виконання............................................................... 233
Відповіді до тестів розділу ІV.............................................................................. 234

Список використаних джерел............................................................................ 235


Додаток………………………………………………………………………… 237

258

You might also like