Professional Documents
Culture Documents
Вивчаємо Java - 11 Клас
Вивчаємо Java - 11 Клас
Івано-Франківськ
2017
Автори:
Г. Двояк, О. Мішагіна, С. Дерюга, О. Рибак, Л. Борисевич, М. Чижик, І. Новак, О.
Новосельський, А. Поліщук, С. Зітинюк, В. Москалик, Т. Багрій, І. Матлюк, Т.
Лужний
Координатор АТМ:
Стрільчик Г. Д., завідувач лабораторії педагогічного досвіду, старший викладач
кафедри менеджменту та освітніх інновацій Івано-Франківського обласного
інституту післядипломної педагогічної освіти
Консультанти:
Іщеряков С. М., кандидат технічних наук, доцент, директор Центру підготовки
сертифікованих програмістів;
Малий П. М., методист лабораторії природничо-математичних дисциплін Івано-
Франківського обласного інституту післядипломної педагогічної освіти
Рецензенти:
Мельничук С. І., доктор технічних наук, доцент, завідувач кафедри
інформаційних технологій Прикарпатського національного університету імені
Василя Стефаника
Кузь М. В., доктор технічних наук, доцент, завідувач кафедри інформаційних
технологій та програмної інженерії Івано-Франківського Університету Короля
Данила
Воробей О. В., учитель інформатики загальноосвітньої школи І-ІІІ ступенів №21
Івано-Франківської міської ради, спеціаліст вищої категорії, учитель-методист
Отже, для зчитування даних із чогось створюється один потік – вхідний, для
виводу даних кудись створюється інший потік – вихідний. Це є два різних об‘єкти,
які уособлюють ці потоки, і в кожному з них вказано, з чим вони можуть
зв‘язатися (рис 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 використовується для зчитування з консолі.
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’);
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().
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.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");
10
//виклик методу переведення об’єкта в байти (серіалізуємо об’єкт)
//він також записує його до файлу
oos.writeObject(c);
oos.close();
11
ObjectInputStream ois = new ObjectInputStream (fis);
Girl readGirl1 = (Girl)ois.readObject();
oos.close();
System.out.println(readGirl1);
}
}
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();
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 об‘єкти на екран.
FileOutputStream fos;
ObjectOutputStream oos = null ;
} catch (IOException e) {
e.printStackTrace();
} finally{
oos.flush();
oos.close();
}
16
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) помилка під час виконання.
18
можуть використовувати інші об‘єкти. І вони теж не зможуть бути серіалізовані,
якщо об‘єкт відмовиться від підтримки серіалізації.
На цей випадок теж є рішення – інтерфейс Externalizable. Досить замінити
інтерфейс Serializable на інтерфейс Externalizable, і клас зможе управляти
процесом серіалізації в ручному режимі.
Інтерфейс Externalizable, на відміну від Serializable, містить два методи
writeExternal та readExternal, які викликаються Java-машиною при серіалізації
об‘єкта.
Приклад.
class Cat implements Externalizable {
public String name;
public int age;
public int weight;
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) не існує жодних обмежень щодо конструкторів без аргументів.
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()
Зазвичай, в реальних додатках ніхто не виводить на консоль. Тут це
використано для того, щоб показати процес роботи методів.
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
23
Для створення ехе-файлу виконуються наступні дії:
контекстне меню проекту / Refresh;
контекстне меню проекту / Export / Runable JAR file / Next;
у launch configuration вибрати необхідний файл з методом main();
у Export destination вибрати цільову папку для збереження;
у Library handling вибрати перший перемикач Extract… / Finish.
24
public void stop(){}
}
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));
}
});
26
Розробити проект, у якому користувач по натиску на кнопку «Старт»
починає проходити тест на знання таблички множення (рис.1.7). Приклади
генеруються з випадкових чисел в межах від 1 до 9. Після введення вірної відповіді
в поле для введення (TextField) (рис. 1.8) при натиску на кнопку Enter у вікні
з‘являється повідомлення: «Ваша відповідь…., вірна відповідь ….», поле для
введення зникає, кнопка «Старт» змінює напис на «Ще раз» (рис. 1.9).
При натиску на кнопку «Оцінка» тестування припиняється, користувачу
виводиться його оцінка (оцінювання придумати самостійно), кнопка «Ще раз»
(рис. 1.10) змінює напис на «Старт» і користувач може почати тестування
спочатку.
Математична модель
Якщо присвоїти кожному символу алфавіту його порядковий номер
(нумеруючи з 0), то шифрування і
дешифрування можна виразити
формулами:
y = (x + k) mod n
x = (y - k) mod n
де y – символ відкритого тексту,
x– символ шифрованого тексту,
n – потужність алфавіту,
k – ключ. Рис. 1.11
Приклад:
Припустимо, що, використовуючи шифр Цезаря, з ключем, який дорівнює 3,
необхідно зашифрувати словосполучення «ШИФР ЦЕЗАРЯ».
Для цього зрушимо алфавіт так, щоб він починався з четвертої букви (Г).
Отже, беручи вихідний алфавіт
АБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ
і зміщуючи всі літери вліво на 3, отримуємо:
ГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯАБВ,
де Г=А, Ґ=Б, Д=В, і т. д.
Використовуючи цю схему, відкритий текст «ШИФР ЦЕЗАРЯ» перетворюємо на
«ЮЙЧУ ЩЗЇГУВ». Для того, щоб одержувач повідомлення міг відновити вихідний
текст, необхідно повідомити йому, що ключ – 3.
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>
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), текст, введений у верхнє поле, відображається
у нижньому полі.
33
У файлі fxml додати до опису кореневого вузла атрибут
fx:controller="MyCont" для зв‘язку методу обробки, який написаний у файлі
MyCont, з кнопкою.
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) лексемами будуть і розділювачі
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().
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)");
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
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);
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-у.
43
Приклад:
Day weekday = Day.SUNDAY;
if (weekday == Day.SUNDAY) weekday = Day.MONDAY;
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.»
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);
49
Об‘єкт типу String, що повертається методом getName(), містить повне ім‘я
класу, тобто, якщо типом об‘єкта myObject буде Integer, то результат буде виду
java.lang.Integer.
Class aclass = myObject.getClass ();
String s = aclass.getName();
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() і ін.
51
getDeclaredConstructors(). Ці методи працюють таким же чином,як і їхні аналоги
getConstructor() і getConstructors()
52
для об‘єкта Method замість методу getMethod() викликаємо getDeclaredMethod(),
потім для отримання доступу викликаємо setAccessible(true).
Class aclass = obj.getClass();
Method method = aclass.getDeclaredMethod("getCalculateRating",
paramTypes);
method.setAccessible(true);
53
З наведеного коду видно, що private поля можна змінювати. Для цього
потрібно отримати об‘єкт типу java.lang.reflect.Field за допомогою методу
getDeclaredField(), викликати метод setAccessible(true) і за допомогою методу set()
встановити значення поля. Необхідно мати на увазі, що поле final при виконанні
даної процедури не видає попереджень, а значення поля залишається колишнім,
тобто final поля залишаються незмінні.
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)>
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 ) ;
} } }
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 Помилка компіляції Код скомпілюється
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 і його субпакетах.
58
AssertionError, заборонено доступ до об'єктів, які його генерують. Вони
представлені у String повідомленні.
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, що викличеться у випадку хибності одного з тверджень.
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). Вона виникає тоді, коли класи були знайдені під час
компіляції, але не можуть бути знайдені під час виконання. Наприклад, їх було
переміщено до іншої папки.
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);
65
Наприклад, анотуватись можуть класи, методи, поля, параметри і константи enum.
Анотованою може бути навіть сама анотація. У всіх випадках анотація передує
оголошенню.
Значення по замовчуванню
Членам анотації можна надати значення по замовчуванню, які будуть
використовуватись при застосуванні анотації. Значення по замовчуванню
задаються додаванням слова 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 – ця інструкція вказує, що одне або більше
попереджень, які можуть бути видані компілятором, слід придушити.
Обмеження
Існує кілька обмежень, що стосуються оголошення анотацій:
одна анотація не може наслідувати другу;
всі методи, оголошені в анотації, не повинні приймати ніяких
параметрів. Більш т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 не може бути безпосередньо пов’язаний з пристроями
введення / виводу, такими як файли)
69
Відповіді до тестів (Тема 2.2)
16. b (виокремлюються числа і знаходиться їхня сума)
17. c (знак «.» екранується)
18. e (знак «\» екранується, «+» вказує на те, що буква буде зустрічатися хоча б
один раз)
19. b (на виводі буде два пропуски між номером позиції і словом, бо в шаблоні є
пропуск; відповідь D не підходить, бо по шаблону слово починається з великої
літери, а потім малі, тому великі не можуть бути)
70
РОЗДІЛ ІІ. ФРЕЙМВОРК КОЛЕКЦІЙ
ТЕМА 4. Фреймворк колекцій
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
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
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. Створити масив цілих чисел, заповнивши його випадковими числами,
відсортувати його в порядку зростання та підрахувати кількість різних
елементів.
Рис. 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
80
c) обидва методи видалять елементи із індексами 4 та 1
d) обидва методи видаляють із значеннями 4 та 1
a) A
b) B
c) C
d) помилка компіляції в рядку 14
e) помилка компіляції в рядку 16
Завдання для самостійного виконання
1. Створити ArrayList із шести елементів типу String. Вивести список,
використовуючи літератор. Модифікувати поточний елемент ітерації,
використовуючи ListIterator, додавши до кожного елемент сьогоднішню
дату. Вивести вміст списку на екран. Виведіть вміст списку у зворотному
порядку.
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
Приклад 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]
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]
Інтерфейс 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
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.}
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.
93
назване, додає його до списку, інакше каже гравцеві, що таке місто вже було.
Вивести список названих міст.
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}
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);}
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.
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);
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.
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
} }
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 неправильний, бо розмірність масиву
вказується при створенні масиву, а не при оголошенні)
108
19. b (варіант а невірний, бо метод sort класу Arrays приймає аргументи типу int;
варіант c невірний, бо метод sort з Collection приймає аргументи типу
List<T>;варіант d невірний, бо конструктор не приймає аргументи такого типу)
20. с (рядок з викликом методу get викличе виняток ClassCastException через
неможливість зробити кастинг аргументу до String)
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
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‛
… />
115
Або з командного рядка перейдіть в кореневий каталог вашого Android-
проекту і виконайте:
ant debug
adb install bin/MyFirstApp-debug.apk
Завдання для самостійного виконання
1. Розробіть одновіконний калькулятор.
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
Клас 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. Розробіть двовіконний калькулятор із виведенням результатів в другому вікні
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" />
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-ресурс.
125
ТЕМА 7. Робота з базами даних
7.1. Основи баз даних - таблиці, ключі, зв’язки таблиць
Словничок
Java Data Base Connectivity (JDBC) – з'єднання з базами даних на Java
Uniform Resource Locator (URL) – єдиний вказівник на ресурс, стандартизована
адреса певного ресурсу
База даних – це звичайний каталог, що містить виконавчі файли певного
формату – таблиці. Таблиці складаються із записів, а записи, в свою чергу,
складаються з полів. Поле має два атрибути – ім‘я і тип. Таблиці можна зв‘язувати
між собою.
Тип поля може бути:
цілим;
дійсним;
рядковим;
бінарним;
дата та час;
перерахування і множини.
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. Відношення «багато-до-багатьох» ,по суті справи , являє собою два
відношення «один-до-багатьох» із третьою таблицею.
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 проекту.
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'
136
пакет Denwer або OpenServer. Обидва вони безкоштовні і містять MySQL, яка
легко конфігурується). Розробити базу даних «AutoService» з таблицею, що
містила б інформацію про назву сервіс-фірми, її адресу, геодані (довгота та
широта), рейтинг, години роботи. Саму таблицю створити в панелі управління
базою даних – phpMyAdmin. Типи полів підібрати відповідно даним. Розробити
методи для:
a) додавання в таблицю інформацію про новий сервіс;
b) підвищення, чи зниження рейтингу сервісу за його кодом;
c) пошуку сервіс центрів в заданому радіусі від вказаного;
d) видалення сервісу з бази при досягненні від‘ємного рейтингу;
e) пошуку найкращого за рейтингом сервісу у вказаному місті;
f) вивід списку всіх сервісних центрів.
В головному методі організувати додавання декількох записів в базу та
продемонструвати роботи кожного метода.
2. Створити Таблицю
137
d) додати поле DATE_CONNECTз значенням по замовчуванням, що дорівнює
даті написання запиту.
e) видалити з таблиці запис з ID=1;
f) вибрати з бази даних абонентів в яких поле PHONE починається на
«+3803422»
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) програма виконається, але нічого не виведеться .
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 передбачені різні засоби для цього завдання.
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 () як статичного робить клас потокобезпечним
Метод 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(), які і будуть передавати ключ для
роботи потоків.
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. Вони працюють так як і інші колекції, але
використовують паралельність.
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ння");
}
}
// Три потоки виконання обчислень
165
}
}
Вивід:
Зaпycк
55
5.0
120
Зaвepшeння
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 + " одержує дозвіл");
167
}
}catch ( InterruptedException exc) {
System.out.println (exc);
}
System.out.println ("Пoтік " + name + " вивільняє дозвіл");
sem.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.
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];
174
"(з точністю до 4 знаків після коми):");
for ( int i=0; i < 10; i++)
System.out.format ( "%.4f", nums[i]);
System.out.println ();
}
}
Вивід:
Чacтина вихідної послідовності:
0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0
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
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 (контролер прецеденту).
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.
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;
}
183
private static final BillPughSingleton INSTANCE = new
BillPughSingleton();
}
public static BillPughSingleton getInstance(){
return SingletonHelper.INSTANCE;
}
Внутрішній допоміжний статичний private клас містить реалізацію
Singleton. Коли основний Singleton клас завантажений, клас SingletonHelper ще не
завантажений в пам‘ять; і тільки тоді , коли буде викликаний метод getInstance,
цей клас завантажиться і створить екземпляр Singleton класу.
Це найбільш поширене рішення для класу Singleton, оскільки не вимагає
наявності синхронізації, а також просте для розуміння та реалізації.
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
збігається для обох примірників.
Рис.4.1
Класична схема фабричного методу представлена вище (рис.4.1). Тут Х –
базовий клас, а може бути навіть інтерфейс. Класи XY і XZ по-різному реалізують
інтерфейс Х. Для вибору конкретної реалізації використовується клас Factory, де в
методі getClass відбувається аналіз зовнішніх параметрів abc, і, грунтуючись на
цих даних, метод повертає об‘єкт класу X, використовуючи реалізацію XY або XZ.
Таким чином, зовнішня програма не має уявлення про те, об‘єкт якого саме класу –
XY або XZ – їй повернули, оскільки обидва ці класи реалізують спільний
інтерфейс Х, отже, виклик їх зовнішніх методів нічим не відрізняється.
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";
}
}
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
}
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("==");
}
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());
}
191
public static void main(String[] args){
log(">Старт тесту фабрики");
RunTestFactoryEx3.main(null);
RunTestFactoryEx3.main(new String[]{"arg"});
log("==");
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("==");
}
193
об‘єктів. Надаються широкі можливості вибору. Неоптимальний вибір може
зробити системи і їх окремі компоненти непридатними для підтримки, сприйняття,
повторного використання і розширення. Систематизація прийомів програмування і
принципів організації класів отримала назву шаблону.
Основні принципи об‘єктно-орієнтованого проектування, застосовуваного
при створенні діаграми класів і розподілу обов‘язків між ними, систематизовані в
шаблонах GRASP (General Responsibility Assignment Software Patterns).
При цьому були сформульовані основні принципи і загальні стандартні
рішення, дотримуючись яких можна створювати добре структурований і
зрозумілий код.
Course Test
Quest
194
Приклад 1.
public class Course {
private int id;
private String name;
Course Test
Quest
Приклад 2.
public class Course {
private int id;
private String name;
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;
198
on = true;
}
public void turnOff() {
on = false;
}
public void turnUp() {
volume += 1;
}
public void turnDown() {
volume -= 1;
}
}
199
новинок, які появились в JDK 8, можна виділити Lambda-вирази. Можна сказати,
що це свого роду анонімні функції, тобто методи без оголошення. Часто анонімний
клас реалізує інтерфейс, який містить тільки один абстрактний метод. В цьому
випадку код може бути коротшим та зрозумілішим, якщо використати Lambda-
вирази.
Одним з ключових моментів використання лямбда-виразів є відкладене
виконання (deferred execution). Тобто, визначений в певному місці програми
лямбда-вираз може бути викликаний при необхідності будь-яку кількість разів в
різних частинах цієї програми. Відкладене виконання може знадобитись в
наступних випадках:
виконання коду в окремому потоці;
виконання одного і того ж коду декілька разів;
виконання коду в результаті певної події.
Структура 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);
}
201
Якщо змінні х та у оголошені на рівні класу, то їхніми значеннями можна
оперувати і навіть їх змінити в лямбда-виразі. Це демонструє вищенаведений
приклад.
В іншому прикладі локальні змінні на рівні методу.
Приклад 4.
interface Operation{
int calculate();
}
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();
Із застосуванням лямбда-виразів:
Приклад 9.
button.addActionListener( (e) -> {
System.out.println("Кнопка натиснута. Lambda!");
});
203
В перших двох випадках посилання на метод еквівалентне лямбда-виразу,
який є параметром методу:
System.out::pprintln це те ж, що x->System.out.println(x);
Math::pow це те ж, що (x, y)->Math.pow(x, y);
В третьому випадку параметр стає цільовим об‘єктом методу, наприклад,
String::compareToIgnoreCase це те ж, що (x, y)->x.compareToIgnoreCase(y);
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;
Функції
Функції приймають один аргумент і повертають деякий результат. Методи
за замовчуванням можуть використовуватись для побудови ланцюжка викликів
(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;
Споживачі
Споживачі (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");
} } } }
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
}
}
208
Тут створюється посилання на конструктор за допомогою Person::new.
Компілятор автоматично вибирає потрібний конструктор, сигнатура якого
співпадає з сигнатурою PersonFactory.create.
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
Рекомендується уникати їх використання в програмах у якості імен змінних.
Потрібно також стежити, щоб імена змінних не збігалися з іменами
вмонтованих об‘єктів, методів і функцій.
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
213
Умовні оператори
Оператор if-else – умовний оператор, що дозволяє виконувати різні
програмні рядки в залежності від умови.
Загальний вид оператора if-else поданий нижче:
if (умова) рядок 1 [else рядок 2]
Частина оператора, виділена квадратними дужками, є необов‘язкова.
Оператор ? спеціальний умовний оператор. Цей оператор у загальному виді
записується так:
вираз ? рядок 1 : рядок 2
При обчисленні оператора ? спочатку перевіряється логічний вираз,
розташований у лівій частині. Якщо він дорівнює true, виконується рядок 1, а якщо
false – рядок 2.
Оператори циклу
Оператор for. Загальний вид:
for ( [ініціалізація;] [умова;] [ітерація]){
...
тіло циклу
}
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)
|| Логічний оператор відношення АБО
?: Умовний оператор
= += -= *= /= %= >>= Присвоювання
>>>= <<= |= &= ^=
, Багатократне обчислення
Командний рядок
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».
Розширення мови
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».
219
noSuchProperty: function (propName) {
print("Accessed non-existing property: " + propName);
},
Локації
Поточне ім‘я файла, каталог і рядок можуть бути отримані з глобальних
змінних __FILE__, __DIR__, __LINE__:
print (__FILE__, __DIR__, __LINE__)
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) {
}
}
}
221
12.3 Робота з числами, рядками, масивами, колекціями,
методами, об’єктами
Функції в мові JavaScript
Можливо оформити фрагменти вихідного тексту у виді функції,
викликаючи цю функцію в міру необхідності з різноманітних місць у JavaScript.
Оголошувати функції можна у будь-якій частині скрипта, але функція
повинна бути визначена до її виклику.
Загальний вигляд визначення функції :
function ім’я([параметр 1] [,параметр 2] [...,параметр N])
...
рядки тіла функції
[return значення]
}
Всі параметри передаються функції за значенням. Тому функція не може
змінити вміст змінних, переданих їй у якості параметрів.
За допомогою ключового слова return функція може повернути значення.
Класи і об’єкти
Мова JavaScript є об‘єктно-орієнтованою. Об‘єкти JavaScript являють собою
набори властивостей і методів. Можна сказати, що властивості об‘єктів – це дані,
пов‘язані з об‘єктом, а методи – функції для опрацювання даних об‘єкта.
Адресація властивостей у JavaScript можлива або по іменах властивостей,
або по їхніх номерах. Всі властивості об‘єкта зберігаються як елементи масиву і
тому кожна властивість має свій номер.
Тут скрипт створює об‘єкт 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());
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 вмонтований,тому не потрібно визначати його самостійно.
225
SQRT2 – це значення квадратного кореня з двох.
n = Math. SQRT2;
SQRT1_2 – це значення квадратного кореня з 0,5.
n = Math. SQRTl_2;
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, якщо клас не вказано) і реалізує всі інтерфейси. Об‘єкт
типу класу не обов‘язково повинен бути першим у списку.
@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!");
}
});
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;
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) всіма вищенаведеними способами
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
Список використаних джерел
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-ТЕХНОЛОГІЇ
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 {}
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.
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
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 : Олійник
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. }
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.}
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 + " ");
}
}
}
248
}
}
a) "4" is printed
b) "3" is printed
c) "2" is printed
d) compilation fails
e) an exception is thrown at runtime
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, БАЗ ДАНИХ, БАГАТОПОТОКОВЕ
ПРОГРАМУВАННЯ
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.
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
}
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
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
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
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
258