Лабораторна робота 5
Робота з винятками та файлами в Java
1 Завдання на лабораторну роботу
1.1 Індивідуальне завдання
Розширити програму роботи з сутностями індивідуального завдання, створену в попередній лабораторній роботі засобами читання і запису даних у текстові файли та файли даних. Окрім відтворення реалізації функціональності, новий проєкт повинен включати:
- виведення даних в текстовий файл з подальшим читанням;
- виведення даних у бінарний файл даних з подальшим читанням.
Для реалізації роботи з файлами створити окремий клас зі статичними методами. Необхідно передбачити обробку винятків, пов'язаних з читанням та записом даних.
1.2 Перелік для опису місяців року
Створити перелік "Місяць". Необхідно визначати у конструкторі та зберігати кількість днів (для невисокосного
року). Додати методи отримання попереднього та наступного місяця, а також функцію, яка повертає сезон для кожного
місяця. Передбачити виведення місяців українською мовою. Створити статичну функцію виведення даних
про усі місяці. Протестувати переліку в функції main()
тестового класу.
1.3 Сортування цілих
Реалізувати програму читання з текстового файлу цілих додатних значень (числа розділені пробілами, слід
читати до кінця файлу), занесення цих чисел у масив, сортування за зменшенням та за збільшенням суми
цифр та зберігання обох результатів у двох нових текстових файлах. Перелічені дії реалізувати в окремій
статичній функції. Для визначення порядку сортування створити класи, які реалізують інтерфейс
Comparator
.
1.4 Реалізація серіалізації й десеріалізації
Описати класи Студент і Академічна група (з полем – масивом студентів). Створити об'єкти, здійснити їх бінарну серіалізацію й десеріалізацію.
1.5 Робота з ZIP-архівом (додаткове завдання)
Описати класи Студент і Академічна група (з полем – масивом студентів). Створити об'єкти, здійснити запис даних про студентів академічної групи в архів. В іншій програмі здійснити читання з архіву.
2 Методичні вказівки
2.1 Переліки
Починаючи з версії JDK 1.5 (Java 5), реалізовано новий тип-посилання – перелік – список можливих значень, які може отримувати змінна цього типу. У найпростішій своїй формі переліки Java аналогічні відповідним конструкціям C++ і C#.
enum DayOfWeek { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }// ... DayOfWeek d = DayOfWeek.WEDNESDAY;
Перелічені константи вважаються відкритими (public
). Тип enum
,
як і клас, може бути відкритим або пакетним. Для імен можливих значень використовують великі літери, оскільки фактично
це константи. З константами зв'язані цілі значення, у попередньому прикладі – відповідно від 0 до 6. Можна
отримати ці цілі значення за допомогою функції ordinal()
, а ім'я константи – за допомогою метода name()
.
Наприклад:
DayOfWeek d = DayOfWeek.WEDNESDAY; System.out.println(d.name() + " " + d.ordinal());
За допомогою статичної функції values()
можна отримати масив елементів переліку:
for (int i = 0; i < DayOfWeek.values().length; i++) { System.out.println(DayOfWeek.values()[i]); }
Статична функція valueOf()
дозволяє отримати елемент переліку за його ім'ям. Наприклад, нам необхідно
отримати ціле значення, пов'язане з певним елементом:
System.out.println(DayOfWeek.valueOf("FRIDAY").ordinal());
У загальному випадку переліки Java надають можливості з визначення і перевантаження методів, створенню додаткових
полів тощо. Наприклад, до переліку DayOfWeek
можна додати статичну функцію printAll()
:
static void printAll() {for (DayOfWeek d : values()) { System.out.println(d); } }
Можна перевантажити виведення переліку через визначення функції toString()
:
enum Gender { MALE, FEMALE; @Overridepublic String toString() {switch (this ) {case MALE:return "чоловіча стать";case FEMALE:return "жіноча стать"; }return "щось неможливе!"; } }public class GenderTest {public static void main(String[] args) { Gender g = Gender.FEMALE; System.out.println(g); } }
Константи, можна зв'язати з відповідними значеннями. Наприклад, перелік "Супутник Марса" містить поле "Відстань від центру Марса". У нашому прикладі необхідно також додати конструктор та додаткові елементи:
package ua.inf.iwanoff.java.fifth;enum MoonOfMars { PHOBOS(9377), DEIMOS(23460);private double distance;private MoonOfMars(double distance) {this .distance = distance; }double getDistance() {return distance; } @Overridepublic String toString() {return name() + ". " + distance + " km. from Mars"; } }public class MoonsOfMarsTest {public static void main(String[] args) { MoonOfMars m = MoonOfMars.PHOBOS; System.out.println(m);// PHOBOS. 9377.0 km from Mars } }
Як видно з тексту, наявність конструктора обумовлює опис констант з визначенням фактичних параметрів.
2.2 Обробка винятків
2.2.1 Основні концепції
Використання механізму обробки винятків є дуже важливою складовою частиною практики програмування на Java. Майже кожна програма на Java містить певні частини цього механізму. Об'єкти-винятки дозволяють програмісту відокремити точки виникнення помилок часу виконання від коду, який ці помилки повинен обробити. Це дозволяє створювати універсальні класи й бібліотеки, які працюють більш надійно.
Виняток – це подія, що виникає під час виконання програми й порушує нормальне виконання
інструкцій коду. Механізм генерації та обробки винятків дозволяє передати інформацію про помилку
з місця виникнення у місце, де ця помилка може бути оброблена. Винятки в Java поділяють на синхронні
(помилка часу виконання, ситуація, згенерована за допомогою throw
) і
асинхронні (системні, збої віртуальної машини Java). Місце виникнення другої групи винятків виявити
досить складно.
Механізм винятків присутній в усіх сучасних мовах об'єктно-орієнтованого програмування. У порівнянні з C++, Java реалізує більш строгий механізм роботи з винятками.
2.2.2 Синтаксис генерації винятків
Для генерації винятку використовується оператор throw
. Після ключового слова
throw
міститься об'єкт класу java.lang.Throwable
, або класів, похідних
від нього. Для програмних винятків найчастіше використовується клас java.lang.Exception
(похідний від Throwable
). Використання Exception
замість
Throwable
дозволяє відокремити власний виняток від системних помилок. Найкраща практика
керування винятками – створювати класи, похідні від Exception
. Такі похідні класи
зазвичай відбивають специфіку конкретної програми.
class SpecificExceptionextends Exception { }
Є також базовий клас для генерації системних помилок – клас Error
. Класи
Exception
і Error
мають загальний базовий клас – Throwable
.
Виняток генерується шляхом використання ключового слова throw
, за яким
розташовують об'єкт-виняток. У більшості випадків об'єкт-виняток створюється в точці генерації винятку
за допомогою оператора new
. Наприклад, типове твердження throw
може виглядати так:
void f() {// ... if (/* помилка */ ) {throw new SpecificException(); }// ... }
У заголовку функції необхідно перелічити усі типи винятків, які генерує ця функція. Це слід зробити за
допомогою ключового слова throws
:
void f()throws SpecificException, AnotherException {// ... if (/* помилка */ ) {throw new SpecificException(); }if (/* інша помилка */ ) {throw new AnotherException(); }// ... }
У наведеному нижче прикладі функція reciprocal()
генерує виняток у випадку ділення на нуль.
class DivisionByZeroextends Exception { }class Test {double reciprocal(double x)throws DivisionByZero {if (x == 0) {throw new DivisionByZero(); }return 1 / x; } }
На відміну від C++, Java не допускає створення винятків примітивних типів. Дозволені тільки об'єкти,
похідні від Throwable
або Exception
.
Під час успадкування для перевизначених функцій список винятків повинен зберігатися.
2.2.3 Синтаксис обробки винятків
Виняток, який був згенерований у певній частині коду, повинен бути перехоплений в іншій частині.
Наприклад, якщо ми хочемо звернутися до функції, яка потенційно може згенерувати виняток, виклик цієї
функції поміщають у блок try { }
:
double x, y;// ... try { y = reciprocal(x); }
Після блоку try
повинен міститись один чи декілька оброблювачів (блоків
catch
).
Кожен такий оброблювач відповідає визначеному типу винятку:
catch (DivisionByZero d) {// обробка винятку }catch (Exception ex) {// обробка винятку }
Класи винятків утворюють ієрархію. Під час порівняння типів винятків оброблювач базового типу сприймає також винятки всіх створених від нього типів. Звідси випливає, що оброблювачі похідних типів варто розміщати до оброблювачів базових типів. Припустимо, є така ієрархія класів винятків:
class BaseExceptionextends Exception { }class FileExceptionextends BaseException { }class FileNotFoundExceptionextends FileException { }class WrongFormatExceptionextends FileException { }class MathExceptionextends BaseException { }class DivisionByZeroextends MathException { }class WrongArgumentextends MathException { }
Припустимо, є деяка функція, яка може згенерувати всі типи винятків:
public class Exceptions {public static void badFunc()throws BaseException {// можуть виникнути різні винятки } }
Залежно від логіки програми різні типи винятків можна обробляти більш детально:
try { Exceptions.badFunc(); }catch (FileNotFoundException ex) {// файл не знайдено }catch (WrongFormatException ex) {// хибний формат }catch (FileException ex) {// інші помилки, пов'язані з файлами }catch (MathException ex) {// усі математичні помилки обробляємо разом }catch (BaseException ex) {// підбираємо всі інші винятки функції badFunc() }catch (Exception ex) {// про всяк випадок }
Після останнього блоку catch
можна розмістити блок finally
. Цей
код завжди виконується незалежно від того, виник чи не виник виняток, навіть якщо в якомусь з блоків був
здійснений вихід з функції.
try { openFile();// інші дії }catch (FileError f) {// обробка винятку }catch (Exception ex) {// обробка винятку }finally { closeFile(); }
У версії Java 7 до синтаксису винятків додані нові конструкції, які роблять роботу з винятками більш зручною. Наприклад, можна створити обробник подій різних типів з використанням побітової операції "АБО":
public void newMultiCatch() {try { methodThatThrowsThreeExceptions(); }catch (ExceptionOne | ExceptionTwo | ExceptionThree e) {// обробка всіх винятків } }
Інші додаткові можливості пов'язані з так званим блоком управління ресурсами ("try-with-resources").
Для об'єкта класу, який реалізує інтерфейс java.lang.AutoCloseable
можна розмістити
створення об'єкта безпосередньо після try
. Для такого об'єкта автоматично
буде викликано метод close()
після завершення блоку try {}
(аналогічно виконанню коду в finally
):
try (ClassThatImplementsAutoCloseable sc =new ClassThatImplementsAutoCloseable()) {// дії, які можуть призвести до винятку }catch (Exception f) {// обробка винятку }// автоматичний виклик sc.close()
На відміну від C++, не можна використовувати catch
(...)
для
перехоплення будь-якого винятку. Замість цього можна використовувати перехоплення винятків базових
класів:
catch (Exception ex) {// обробка винятку }
або
catch (Throwable ex) {// обробка винятку }
Типова реалізація оброблювача винятку – виклик методу printStackTrace()
.
catch (Throwable ex) { ex.printStackTrace(); }
Цей метод здійснює виведення інформації про трасування стека в стандартний потік повідомлень про помилки
System.err
. Нижче наведений приклад роботи функції printStackTrace()
:
java.lang.NullPointerException at SomeClass.g(SomeClass.java:9) at SomeClass.f(SomeClass.java:6) at SomeClass.main(SomeClass.java:3)
Якщо в межах блоку catch
() { }
не можна повністю обробити виняток, його
можна передати далі:
catch (SomeException ex) {// локальна обробка винятку throw ex; }
Іноді для адекватної обробки інформації про виняток необхідно володіти певною додатковою інформацією. Наприклад, ми створюємо функцію, всередині тіла якої необхідно знайти квадратний корінь. Якщо аргумент від'ємний, необхідно генерувати виняток. Для зневадження програми корисно знати, яке саме від'ємне значення було отримане. Можна створити клас-виняток, об'єкт якого зберігатиме це значення. У конструкторі воно встановлюється, а в точці обробки винятку його можна отримати за допомогою гетера. Цей підхід можна продемонструвати на такому прикладі. Створюємо клас-виняток:
public class WrongArgumentExceptionextends Exception {private double arg;public WrongArgumentException(double arg) {this .arg = arg; }public double getArg() {return arg; } }
Виняток може бути згенерований у якійсь функції, якщо неможливо використати аргумент:
public class SomeLib {public static void doSomeUseful(double x)throws WrongArgumentException {// перевірка x if (x < 0) {throw new WrongArgumentException(x); }double y = Math.sqrt(x);// подальша робота } }
Тепер перехоплений об'єкт-виняток може бути застосований для отримання більш детальної інформації.
public class ExceptionTest {public static void main(String[] args) {double x =new java.util.Scanner(System.in).nextDouble();try {// ... SomeLib.doSomeUseful(x);// ... }catch (WrongArgumentException e) { System.err.println(e.getClass().getName() + e.getArg()); } } }
Як видно з наведеного прикладу, за допомогою викликів методу getClass().getName()
можна
отримати ім'я класу. Це можна зробити для будь-якого об'єкта (не тільки винятку).
Виклик функції, що може згенерувати виняток, поза блоком try
приводить до помилки
компіляції. Перевірка повинна обов'язково виконуватися:
double f(double x) {double y;try { y = reciprocal(x); }catch (DivisionByZero ex) { ex.printStackTrace(); y = 0; }return y; }
Неперехоплений виняток може бути передано зовнішньому оброблювачу з використанням ключового слова
throws
:
double g(double x)throws DivisionByZero {double y; y = reciprocal(x);return y; }
Це правило обов'язкове для усіх винятків Java крім об'єктів класу RuntimeException
або його
нащадків. Про генерацію таких винятків не треба вказувати в заголовку функції. Програміст може обробляти
чи ігнорувати такі винятки на свій розсуд. Функції, які генерують такі винятки, не декларують їх у
своєму заголовку. Типовий клас винятків такого виду – NullPointerException
.
Середовище IntelliJ IDEA дозволяє автоматизувати процес створення блоків перехоплення та обробки
винятків. Якщо в тексті функції помітити блок та застосувати функцію try {
}
), а далі будуть додані catch
-блоки, які міститимуть стандартну обробку
всіх можливих винятків.
2.3 Потоки введення та виведення. Потоки символів
2.3.1 Загальні концепції
Як більшість сучасних мов і платформ, Java узагальнює поняття потоків (streams), розповсюджуючи спільні підходи на файлові, консольні, мережеві та інші процеси введення-виведення.
Класи, які здійснюють файлове введення та виведення, а також інші дії з потоками, розташовані у пакеті
java.io
. Класи цього пакету пропонують низку методів для створення таких потоків, читання,
запису тощо. Існує дві підмножини класів – відповідно для роботи з текстовими та бінарними
(двійковими) файлами.
Уся робота з потоками, окрім стандартних потоків System.in
і System.out
,
повинна передбачати перехоплення винятків, пов'язаних з введенням-виведенням. Це
IOException
та його нащадки – FileNotFoundException
,
ObjectStreamException
та інші.
Дуже важливо закрити всі файли, взаємодія з якими мала місце. Під час закриття файлів здійснюється запис
у файл даних, що залишилися в буфері, звільнення буфера та інших ресурсів, пов'язаних з файлом. Закрити
файл можна за допомогою методу close()
. Наприклад, для потоку in
:
in.close();
Якщо програма, яка потребує файлового введення, завантажується у середовищі IntelliJ IDEA, необхідні для читання файли слід розмістити у теці проєкту (не у теці пакета). Саме у теці проєкту можна знайти вислідні файли, які з'являються після завершення виконання програми, що включає файлове виведення.
У програмі можна одночасно відкрити декілька потоків введення і декілька потоків виведення.
2.3.2 Робота з потоками символів
Потоки, призначені для роботи з текстовою інформацією, мають назву потоків символів. Імена класів
таких потоків закінчуються відповідно словами "...Reader"
і
"...Writer"
.
Безпосередню роботу з текстовими файлами здійснюють об'єкти класів FileReader
та
FileWriter
.
Важливий елемент роботи з файловими потоками – це буферизація. Буферизація передбачає
створення в оперативній пам'яті спеціальної області (буферу), у яку дані завантажуються з файлу для
подальшого поелементного читання або поелементно записуються дані з подальшим переписуванням на диск.
Об'єкти класу BufferedReader
здійснюють таке буферизоване читання.
Для буферизованого виведення застосовують об'єкти класу BufferedWriter
. Безпосереднє
форматоване виведення здійснюється методами print()
та println()
об'єкту класу
PrintWriter
.
У наведеному нижче прикладі з файлу з ім'ям
package ua.inf.iwanoff.java.fifth;import java.io.*;import java.util.StringTokenizer;public class FileTest {void readWrite() {try { FileReader fr =new FileReader("data.txt"); BufferedReader br =new BufferedReader(fr); String s = br.readLine();int x;double y;try { StringTokenizer st =new StringTokenizer(s); x = Integer.parseInt(st.nextToken()); y = Double.parseDouble(st.nextToken()); }finally { br.close(); }double z = x + y; FileWriter fw =new FileWriter("results.txt"); PrintWriter pw =new PrintWriter(fw); pw.println(z); pw.close(); }catch (IOException ex) { ex.printStackTrace(); } }public static void main(String[] args) {new FileTest().readWrite(); } }
Для відкриття файлу створюється об'єкт класу FileReader
, у конструкторі якого вказується
рядок – ім'я файлу. Посилання на створений об'єкт передається у конструктор класу
BufferedReader
.
Читання з файлу здійснюється за допомогою методу readLine()
, який повертає посилання на
рядок символів, або null
, якщо досягнуто кінець файлу.
Змінна s
типу String
посилається на рядок, який містить два числа. Для
виділення з цього рядку окремих лексем використовують об'єкт класу StringTokenizer
, у
конструктор якого передається рядок. Посилання на окремі частини рядка поступово отримують за допомогою
методу nextToken()
. Ці посилання можуть бути використані безпосередньо, або
використовуються для перетворення даних у числові значення (статичні методи parseDouble()
та parseInt()
класів Double
та Integer
відповідно).
Для читання з файлу можна використовувати вже знайомий клас Scanner
. Фактичним параметром
конструктора може бути файловий потік. Попередній приклад можна реалізувати за допомогою класу
Scanner
.
Можна також скоротити код шляхом виключення непотрібних змінних. Крім того, доцільно скористатися
конструкцією try () { }
Java 7 для автоматичного закриття потоку:
package ua.inf.iwanoff.java.fifth;import java.io.*;import java.util.Scanner;public class FileTest {void readWrite() {try (Scanner scanner =new Scanner(new FileReader("data.txt"))) {try (PrintWriter pw =new PrintWriter("results.txt")) { pw.println(scanner.nextInt() + scanner.nextDouble()); } }catch (IOException ex) { ex.printStackTrace(); } }public static void main(String[] args) {new FileTest().readWrite(); } }
Перевагою такого підходу є можливість довільного розташування вихідних даних (не обов'язково в одному
рядку). Як видно з наведеного прикладу, кілька блоків try { }
можуть використовувати
один блок catch { }
. Альтернативою є розміщення декількох тверджень всередині
дужок:
try (Scanner scanner =new Scanner(new FileReader("data.txt")); PrintWriter pw =new PrintWriter("results.txt")) { pw.println(scanner.nextInt() + scanner.nextDouble()); }catch (IOException ex) { ex.printStackTrace(); }
Під час роботи з класом Scanner
можна визначити додаткові параметри, наприклад, встановити
символ-роздільник (або послідовність символів). Наприклад, можна перед читанням даних додати такий
рядок:
scanner.useDelimiter(",");
Тепер об'єкт-сканер буде сприймати коми як роздільники (замість пропусків).
2.4 Робота з бінарними потоками (потоками байтів)
Для роботи з нетекстовими (бінарними) файлами використовують потоки, імена яких замість "Reader"
або "Writer"
містять "Stream
", наприклад
InputStream
, FileInputStream
, OutputStream
, FileOutputStream
тощо. Такі потоки мають назву потоків байтів. У наведеному нижче прикладі здійснюється копіювання
двійкового файлу FileCopy.class
у теку проєкту з новим ім'ям:
package ua.inf.iwanoff.java.fifth;import java.io.*;public class FileCopy {public static void copy(String inFile, String outFile) {byte [] buffer =new byte [1024];// Буфер байтів try (InputStream input =new FileInputStream(inFile); OutputStream output =new FileOutputStream(outFile)) {int bytesRead;while ((bytesRead = input.read(buffer)) >= 0) { output.write(buffer, 0, bytesRead); } }catch (IOException ex) { ex.printStackTrace(); } }public static void main(String[] args) { copy("out/production/Labs/ua/inf/iwanoff/java/fifth/FileCopy.class", "FileCopy.copy"); } }
Як видно з наведеного прикладу, Java дозволяє використовувати звичайну риску (/
) замість
зворотної. Це – більш універсальний підхід, прийнятний для різних операційних систем. Крім того,
зворотну риску необхідно було б записати двічі (\\
).
Для роботи з бінарними файлами існують додаткові можливості – використання потоків даних і потоків
об'єктів. Так звані потоки даних (data streams) підтримують бінарне введення / виведення значень
примітивних типів даних (boolean
, char
, byte
,
short
, int
, long
, float
і double
), а також значень типу String
. Усі потоки даних реалізують
інтерфейси DataInput
або DataOutput
. Для більшості задач достатньо стандартних
реалізацій цих інтерфейсів – DataInputStream
і DataOutputStream
. Дані у
файлі зберігаються в такому вигляді, в якому вони представлені в оперативній пам'яті. Для запису рядків
використовують метод writeUTF()
. У наведеному нижче прикладі здійснюється запис даних:
package ua.inf.iwanoff.java.fifth;import java.io.*;public class DataStreamDemo {public static void main(String[] args) {double x = 4.5; String s = "all";int [] a = { 1, 2, 3 };try (DataOutputStream out =new DataOutputStream(new FileOutputStream("data.dat"))) { out.writeDouble(x); out.writeUTF(s);for (int k : a) out.writeInt(k); }catch (IOException e) { e.printStackTrace(); } } }
Тепер дані можна прочитати в іншій програмі:
package ua.inf.iwanoff.java.fifth;import java.io.*;import java.util.*;public class DataReadDemo {public static void main(String[] args) {try (DataInputStream in =new DataInputStream(new FileInputStream("data.dat"))) {double x = in.readDouble(); String s = in.readUTF(); List<Integer> list =new ArrayList<>();try {while (true ) {int k = in.readInt(); list.add(k); } }catch (Exception e) { } System.out.println(x); System.out.println(s); System.out.println(list); }catch (Exception e) { e.printStackTrace(); } } }
Примітка. У наведеній вище програмі вихід з циклу здійснюється через збудження винятку. Такий
підхід не є рекомендованим, оскільки генерація винятків знижує ефективність роботи програми. У нашому
випадку доцільно було б окремо зберігати у файлі довжину масиву перед його елементами, а потім
використовувати цю довжину для організації циклу for
під час читання.
Для читання і запису даних може бути також використаний клас java.io.RandomAccessFile
.
Об'єкт цього класу дозволяє вільно пересуватися всередині файлу в прямому і зворотному напрямку.
Основною перевагою класу RandomAccessFile
є можливість читати й записувати дані в довільне
місце файлу.
Для того, щоб створити об'єкт класу RandomAccessFile
, необхідно викликати його конструктор з
двома параметрами: ім'я файлу для введення / виведення і режимом доступу до файлу. Для визначення режиму
можна використовувати спеціальні рядки, такі як "r"
(для читання),
"rw"
(для читання й запису) тощо. Наприклад, таким може бути відкриття файлу даних:
RandomAccessFile file1 =new RandomAccessFile("file1.dat", "r");// для читання RandomAccessFile file2 =new RandomAccessFile("file2.dat", "rw");// для читання й запису
Після того як файл відкритий, можна використовувати методи на кшталт readDouble()
,
readInt()
,
readUTF()
тощо для читання або writeDouble()
, writeInt()
, writeUTF()
тощо для виведення.
В основі керування файлом лежить вказівник на поточну позицію, де відбувається читання або запис даних.
На момент створення об'єкта класу RandomAccessFile
вказівник встановлюється на початок
файлу і має значення 0. Виклики методів read...()
и write...()
зсувають
позицію поточного вказівника на кількість прочитаних або записаних байтів. Для довільного зсуву
вказівника на деяку кількість байтів можна використовувати метод skipBytes()
, або ж
встановити вказівник у певне місце файлу викликом методу seek()
. Для того, щоб дізнатися
поточну позицію, в якій знаходиться вказівник, потрібно викликати метод getFilePointer()
.
Наприклад, в одній програмі ми записуємо дані в новий файл:
RandomAccessFile fileOut =new RandomAccessFile("new.dat", "rw");int a = 1, b = 2; fileOut.writeInt(a); fileOut.writeInt(b); fileOut.close();
В іншій програмі ми читаємо друге ціле число:
RandomAccessFile fileIn =new RandomAccessFile("new.dat", "rw"); fileIn.skipBytes(4); // переміщаємо файловий указівник до другого числа int c = fileIn.readInt(); System.out.println(c); fileIn.close();
Дізнатися довжину файлу в байтах можна за допомогою функції length()
.
2.5 Бінарна серіалізація об'єктів
Механізм серіалізації (serialization, розміщення у послідовному порядку) передбачає запис об'єктів
у потік бітів для зберігання у файлі або для передачі через комп'ютерні мережі. Десеріалізація
передбачає читання потоку бітів, створення збережених об'єктів та відтворення їхнього стану на момент
збереження. Для того, щоб об'єкти певного класу можна було серіалізувати, клас повинен реалізовувати
інтерфейс java.io.Serializable
. Цей інтерфейс не визначає жодного методу, його наявність
лише вказує, що об'єкти цього класу можна серіалізувати. Однак гарантована серіалізація і десеріалізація
вимагає наявності в таких класах спеціального статичного поля serialVersionUID
, яке
забезпечує унікальність класу.
Класи ObjectOutputStream
та ObjectInputStream
дозволяють здійснювати
серіалізацію та десеріалізацію. Вони реалізують інтерфейси ObjectOutput
та ObjectInput
відповідно. Механізми серіалізації та десеріалізації розглянемо на наведеному нижче прикладі.
Припустимо, описано клас Point
:
package ua.inf.iwanoff.java.fifth;import java.io.Serializable;public class Pointimplements Serializable {private static final long serialVersionUID = -3861862668546826739L;private double x, y;public void setX(double x) {this .x = x; }public void setY(double y) {this .y = y; }public double getX() {return x; }public double getY() {return y; } }
Також створено клас Line
:
package ua.inf.iwanoff.java.fifth;import java.io.Serializable;public class Lineimplements Serializable {private static final long serialVersionUID = -4909779210010719389L;private Point first =new Point(), second =new Point();public void setFirst(Point first) {this .first = first; }public Point getFirst() {return first; }public Point getSecond() {return second; }public void setSecond(Point second) {this .second = second; } }
У наведеній нижче програмі (у тому ж пакеті) здійснюється створення об'єктів з подальшою серіалізацією:
package ua.inf.iwanoff.java.fifth;import java.io.*;public class SerializationTest {public static void main(String[] args) { Line line =new Line(); line.getFirst().setX(1); line.getFirst().setY(2); line.getSecond().setX(3); line.getSecond().setY(4);try (ObjectOutputStream out =new ObjectOutputStream(new FileOutputStream("temp.dat"))) { out.writeObject(line); }catch (IOException e) { e.printStackTrace(); } } }
В іншій програмі можна здійснити десеріалізацію:
package ua.inf.iwanoff.java.fifth;import java.io.*;public class DeserializationTest {public static void main(String[] args)throws ClassNotFoundException {try (ObjectInputStream in =new ObjectInputStream(new FileInputStream("temp.dat"))) { Line line = (Line) in.readObject(); System.out.println(line.getFirst().getX() + " " + line.getFirst().getY() + " " + line.getSecond().getX() + " " + line.getSecond().getY()); }catch (IOException e) { e.printStackTrace(); } } }
Можна також серіалізувати об'єкти, які містять масиви інших об'єктів. Наприклад:
package ua.inf.iwanoff.java.fifth;import java.io.*;class Pairimplements Serializable {private static final long serialVersionUID = 6802552080830378203L;double x, y;public Pair(double x,double y) {this .x = x;this .y = y; } }class ArrayOfPairsimplements Serializable {private static final long serialVersionUID = 5308689750632711432L; Pair[] pairs;public ArrayOfPairs(Pair[] pairs) {this .pairs = pairs; } }public class ArraySerialization {public static void main(String[] args) { Pair[] points = {new Pair(1, 2),new Pair(3, 4),new Pair(5, 6) }; ArrayOfPairs arrayOfPoints =new ArrayOfPairs(points);try (ObjectOutputStream out =new ObjectOutputStream(new FileOutputStream( "temp.dat"))) { out.writeObject(arrayOfPoints); }catch (IOException e) { e.printStackTrace(); } } }
Тепер можна здійснити десеріалізацію:
package ua.inf.iwanoff.java.fifth;import java.io.*;public class ArrayDeserialization {public static void main(String[] args)throws ClassNotFoundException {try (ObjectInputStream in =new ObjectInputStream(new FileInputStream("temp.dat"))) { ArrayOfPairs arrayOfPairs = (ArrayOfPairs) in.readObject();for (Pair p : arrayOfPairs.pairs) { System.out.println(p.x + " " + p.y); } }catch (Exception e) { e.printStackTrace(); } } }
Деякі поля класу, значення яких не впливають на стан об'єкта, можна описати з модифікатором
transient
.
Наприклад:
class SomeClassimplements Serializable {transient int someUnnecessaryField; }
Такі поля не будуть збережені у потоці під час серіалізації та не будуть відтворені під час десеріалізації.
Серіалізація та десеріалізація можуть бути використані замість файлового введення та виведення. Головним недоліком бінарної серіалізації є необхідність роботи з двійковими (нетекстовими) файлами.
2.6 Робота з архівами
2.6.1 Стандартні засоби для роботи з архівами
Пакет java.util.zip
надає можливості роботи зі стандартними файлами ZIP і GZIP форматів.
Для запису в архів застосовують клас ZipOutputStream
. За допомогою функції
setMethod()
цього класу можна визначити метод архівації –
ZipOutputStream.DEFLATED
(з компресією) або ZipOutputStream.STORED
(без компресії). Метод setLevel()
визначає рівень компресії (від 0 до 9, за умовчанням Deflater.DEFAULT_COMPRESSION
,
зазвичай, максимальна компресія). Метод setComment()
дозволяє додати коментар до архіву.
Для кожного запису, який треба помістити в zip-файл, створюється об'єкт ZipEntry
. Бажане
ім'я для файлу передається конструктору ZipEntry
. В ньому можна окремо встановити
аналогічні параметри. Далі за допомогою методу putNextEntry()
класу
ZipOutputStream
"розкривається" відповідна точка входу до архіву. За допомогою
засобів роботи з файловими потоками здійснюється запис даних в архів, потім слід закрити об'єкт
ZipEntry
за допомогою виклику closeEntry()
.
У наведеному нижче прикладі створюється архів Source.zip
, до якого додається вміст вихідного
файлу ZipCreator.java
:
package ua.inf.iwanoff.java.fifth;import java.io.*;import java.util.zip.*;public class ZipCreator {public static void main(String[] args) {try (ZipOutputStream zOut =new ZipOutputStream(new FileOutputStream("Source.zip"))) { ZipEntry zipEntry =new ZipEntry("src/ua/inf/iwanoff/java/fifth/ZipCreator.java"); zOut.putNextEntry(zipEntry);try (FileInputStream in =new FileInputStream("src/ua/inf/iwanoff/java/fifth/ZipCreator.java")) {byte [] bytes =new byte [1024];int length;while ((length = in.read(bytes)) >= 0) { zOut.write(bytes, 0, length); } } zOut.closeEntry(); }catch (IOException e) { e.printStackTrace(); } } }
Новостворений архів містить відносний шлях до файлу. Якщо це не потрібно, під час створення об'єкту
ZipEntry
слід вказати тільки ім'я без шляху:
ZipEntry zipEntry =new ZipEntry("ZipCreator.java");
Для того, щоб прочитати дані з архіву, необхідно скористатися класом ZipInputStream
. У
кожному такому архіві завжди потрібно переглядати окремі записи (entries). Метод
getNextEntry()
повертає об'єкт типу ZipEntry
. Метод read()
класу
ZipInputStream
повертає -1 наприкінці поточного запису (а не тільки в кінці Zip-файлу).
Далі викликається метод closeEntry()
для отримання можливості переходу до зчитування
наступного запису. В наведеному нижче прикладі здійснюється читання запису ZipCreator.java
з раніше створеного архіву та виведення його вмісту в консольне вікно:
package ua.inf.iwanoff.java.fifth;import java.io.*;import java.util.zip.*;public class ZipExtractor {public static void main(String[] args) {try (ZipInputStream zIn =new ZipInputStream(new FileInputStream("Source.zip"))) { ZipEntry entry;byte [] buffer =new byte [1024];while ((entry = zIn.getNextEntry()) !=null ) {int bytesRead; System.out.println("------------" + entry.getName() + "------------");while ((bytesRead = zIn.read(buffer)) >= 0) { System.out.write(buffer, 0, bytesRead); } zIn.closeEntry(); } }catch (IOException e) { e.printStackTrace(); } } }
Аналогічно здійснюється робота з архівами формату GZIP. Відповідні потоки читання та запису – GZIPInputStream
і GZIPOutputStream
.
2.6.2 Створення і використання JAR-файлів
Для розгортання Java-застосунку на комп'ютері клієнта достатньо скопіювати всі необхідні файли
.class
в необхідних теках відповідних пакетів. Ідеологія Java не дозволяє створювати файли, що безпосередньо
виконуються (.exe
). Однак файли, необхідні для виконання програми, можна упакувати в архів
спеціального типу – так званий JAR. За своїм форматом це фактично zip-архів, але він містить файл
MANIFEST.MF
в каталозі META-INF
, в якому повинен бути зазначений головний клас
програми (такий клас повинен містити метод main()
). Цей клас задається параметром
Main-Class
.
Номер версії JAR задається параметром Manifest-Version
.
Програму, зібрану в JAR-файл можна запустити на виконання такою командою:
java -jar ім'я_файлу.jar
Застосунки графічного інтерфейсу користувача слід запускати командою:
javaw -jar ім'я_файлу.jar
У цьому випадку не буде створюватися консольне вікно.
Якщо засоби JDK були встановлені на комп'ютері й правильно визначені всі шляхи, запустити застосунок можна подібно до того, як в операційній системі запускаються виконуються файли (дворазовим натисканням миші на JAR-файлі).
Для того, щоб створити JAR-файл у середовищі IntelliJ IDEA, спочатку слід виконати певні налаштування.
Зокрема, у головному меню вибираємо main()
, яка
цікавить нас як точка старту застосунку). Найкращий варіант вибору головного класу – скористатися
піктограмою вибору файлів (
3 Приклади програм
3.1 Опис та використання переліку
Припустимо, необхідно створити перелік, який описує дні тижня. Раніше був наведений приклад такого переліку. До
нього слід додати функцію отримання наступного дня, при чому після суботи знов повинна бути неділя тощо. Крім того,
необхідна функція перевірки, чи відноситься день до вікенду. Під час виведення також необхідно отримувати номер
дня (0 – неділя, 1 – понеділок тощо. д.). У функції main()
, починаючи з понеділка необхідно
пройтися по днях тижня, вивести дані про день, а також перевірити, чи це вікенд. Програма матиме такий вигляд:
package ua.inf.iwanoff.java.fifth;enum DayOfWeek { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY; @Overridepublic String toString() {return name() + " " + ordinal(); } DayOfWeek next() { DayOfWeek day = values()[(ordinal() + 1) % values().length];return day; }boolean isWeekend() {switch (this ) {case SATURDAY:case SUNDAY:return true ;default :return false ; } } }public class EnumTest {public static void main(String[] args) { DayOfWeek d = DayOfWeek.MONDAY;for (int i = 0; i < 7; i++) { d = d.next(); System.out.println(d + " " + d.isWeekend()); } } }
Як і в інших випадках, формою виведення даних керує перекритий метод toString()
.
3.2 Порядкове копіювання текстових файлів
Припустимо, необхідно створити програму, яка здійснює копіювання текстових файлів рядок за рядком. Імена файлів задаються аргументами командного рядка. Текст програми буде таким:
package ua.inf.iwanoff.java.fifth;import java.io.*;public class TextFileCopy {public static void main(String[] args) {if (args.length < 2) { System.out.println("Необхідні аргументи!");return ; }try (BufferedReader in =new BufferedReader(new FileReader(args[0])); PrintWriter out =new PrintWriter(new FileWriter(args[1]))) { String line;while ((line = in.readLine()) !=null ) { out.println(line); } }catch (IOException e) { e.printStackTrace(); } } }
3.3 Сортування дійсних чисел
Припустимо, необхідно реалізувати програму читання з текстового файлу дійсних значень у діапазоні від -1000 до 1000, сортування за збільшенням і за зменшенням модулів та зберігання обох результатів у двох нових текстових файлах. Числа у вихідному файлі розділені пробілами, їх слід читати до кінця файлу.
У класі DoubleNumbers
, який ми проєктуємо, створюємо вкладений статичний клас для опису
винятку, пов'язаного з хибним дійсним значенням (менше, ніж -1000 або більше, ніж 1000). Крім того, під
час роботи функції sortDoubles()
, яка виконує основне завдання, можуть виникати винятки
типу IOException
(файл не знайдено, файл не можна створити тощо) та
InputMismatchException
(об'єкт типу Scanner
намагається отримати Double
з лексеми, яка не може бути
переведена у число). Для сортування за зменшенням модулів створюємо окрему статичну функцію compareByAbsValues()
,
у якій створюється локальний клас та повертається його об'єкт. Вихідний код матиме такий вигляд:
package ua.inf.iwanoff.java.fifth;import java.io.*;import java.util.*;import static java.lang.Math.*;public class DoubleNumbers {/** * Внутрішній клас-виняток, який дозволяє зберігати хибне дійсне * значення, прочитане з файлу (менше, ніж -1000 або більше, ніж 1000) * */ public static class DoubleValueExceptionextends Exception {private double wrongValue;public DoubleValueException(double wrongValue) {this .wrongValue = wrongValue; }public double getWrongValue() {return wrongValue; } }/** * Статична функція, яка визначає метод порівняння дійсних * чисел під час сортування за зменшенням абсолютної величини * * @return об'єкт, який реалізує інтерфейс Comparator * */ public static Comparator<Double> compareByAbsValues() {// Локальний клас: class LocalComparatorimplements Comparator<Double> { @Overridepublic int compare(Double d1, Double d2) {return -Double.compare(abs(d1), abs(d2)); } }return new LocalComparator(); }/** * Функція здійснює читання дійсних чисел у діапазоні від -1000 до 1000, сортування * за двома ознаками та занесення у два вислідні файли * * @param inFileName - ім'я вихідного файлу * @param firstOutFileName - ім'я файлу, який міститиме числа, відсортовані * за зростанням * @param secondOutFileName - ім'я файлу, який міститиме числа, відсортовані * за зменшенням абсолютних величин * @throws DoubleValueException * @throws IOException * @throws InputMismatchException */ public static void sortDoubles(String inFileName, String firstOutFileName, String secondOutFileName)throws DoubleValueException, IOException, InputMismatchException { Double[] arr = {};try (BufferedReader reader =new BufferedReader(new FileReader(inFileName)); Scanner scanner =new Scanner(reader)) {while (scanner.hasNext()) {double d = scanner.nextDouble();if (abs(d) > 1000) {throw new DoubleValueException(d); } Double[] arr1 =new Double[arr.length + 1]; System.arraycopy(arr, 0, arr1, 0, arr.length); arr1[arr.length] = d; arr = arr1; } } PrintWriter firstWriter =new PrintWriter(new FileWriter(firstOutFileName)); PrintWriter secondWriter =new PrintWriter(new FileWriter(secondOutFileName));try { Arrays.sort(arr);for (Double x : arr) firstWriter.print(x + " "); Arrays.sort(arr, compareByAbsValues());for (Double x : arr) secondWriter.print(x + " "); }// Вислідні файли доцільно закрити у блоці finally: finally { firstWriter.close(); secondWriter.close(); } }public static void main(String[] args) {try { sortDoubles("in.txt", "out1.txt", "out2.txt"); }// Неправильне дійсне значення: catch (DoubleValueException e) { e.printStackTrace(); System.err.println("Wrong value: " + e.getWrongValue()); }// Помилка, пов'язана з файлами: catch (IOException e) { e.printStackTrace(); }// Файл містить щось, що не є дійсним числом: catch (InputMismatchException e) { e.printStackTrace(); } } }
Функція hasNext()
повертає true
, якщо за допомогою об'єкта типу
Scanner
можна прочитати наступне значення.
3.4 Бінарна серіалізація і десеріалізація даних
Припустимо, необхідно створити класи "Країна" (Country
) і "Континент"
(Continent
), створити об'єкт типу Continent
, здійснити його серіалізацію і
десеріалізацію. Клас Country
буде таким:
package ua.inf.iwanoff.java.fifth;import java.io.Serializable;public class Countryimplements Serializable {private static final long serialVersionUID = -6755942443306500892L;private String name;private double area;private int population;public Country(String name,double area,int population) {this .name = name;this .area = area;this .population = population; }public String getName() {return name; }public void setName(String name) {this .name = name; }public double getArea() {return area; }public void setArea(double area) {this .area = area; }public int getPopulation() {return population; }public void setPopulation(int population) {this .population = population; } }
Клас Continent
може бути таким:
package ua.inf.iwanoff.java.fifth;import java.io.Serializable;public class Continentimplements Serializable {private static final long serialVersionUID = 8433147861334322335L;private String name;private Country[] countries;public Continent(String name, Country... countries) {this .name = name;this .countries = countries; }public String getName() {return name; }public void setName(String name) {this .name = name; }public Country[] getCountries() {return countries; }public void setCountries(Country[] countries) {this .countries = countries; } }
Наведена нижче програма здійснює створення та серіалізацію об'єкта Continent:
package ua.inf.iwanoff.java.fifth;import java.io.*;public class DataSerialization {public static void main(String[] args) { Continent c =new Continent("Європа",new Country("Україна", 603700, 46314736),new Country("Франція", 547030, 61875822),new Country("Німеччина", 357022, 82310000) );try (ObjectOutputStream out =new ObjectOutputStream(new FileOutputStream("Countries.dat"))) { out.writeObject(c); }catch (IOException e) { e.printStackTrace(); }; } }
Так можна здійснити десеріалізацію:
package ua.inf.iwanoff.java.fifth;import java.io.*;public class DataDeserialization {public static void main(String[] args)throws ClassNotFoundException {try (ObjectInputStream in =new ObjectInputStream(new FileInputStream("Countries.dat"))) { Continent continent = (Continent) in.readObject();for (Country c : continent.getCountries()) { System.out.println(c.getName() + " " + c.getArea() + " " + c.getPopulation()); } }catch (IOException e) { e.printStackTrace(); }; } }
3.5 Робота з архівом
Дані про об'єкти з прикладу 3.3 можна зберегти в архіві. Наведена нижче програма здійснює створення
об'єкта Continent
і збереження даних в архіві. Кожній країні відповідає своя точка входу
ZipEntry
:
package ua.inf.iwanoff.java.fifth;import java.io.*;import java.util.zip.*;public class StoreToZip {public static void main(String[] args) { Continent continent =new Continent("Європа",new Country("Україна", 603700, 46314736),new Country("Франція", 547030, 61875822),new Country("Німеччина", 357022, 82310000) );try (ZipOutputStream zOut =new ZipOutputStream(new FileOutputStream("Continent.zip")); DataOutputStream out =new DataOutputStream(zOut)) {for (Country country : continent.getCountries()) { ZipEntry zipEntry =new ZipEntry(country.getName()); zOut.putNextEntry(zipEntry); out.writeDouble(country.getArea()); out.writeInt(country.getPopulation()); zOut.closeEntry(); } }catch (IOException e) { e.printStackTrace(); } } }
Так можна здійснити читання з архіву:
package ua.inf.iwanoff.java.fifth;import java.io.*;import java.util.zip.*;public class ReadFromZip {public static void main(String[] args) {try (ZipInputStream zIn =new ZipInputStream(new FileInputStream("Continent.zip")); DataInputStream in =new DataInputStream(zIn)) { ZipEntry entry;while ((entry = zIn.getNextEntry()) !=null ) { System.out.println("Країна: " + entry.getName()); System.out.println("Територія: " + in.readDouble()); System.out.println("Населення: " + in.readInt()); System.out.println(); zIn.closeEntry(); } }catch (IOException e) { e.printStackTrace(); } } }
3.6 Класи "Країна" та "Перепис населення"
Припустимо, необхідно доповнити програму роботи з країнами та переписами населення, створену в попередній лабораторній роботі засобами читання і запису даних у текстові файли та файли даних. Окрім відтворення реалізації функціональності, новий проєкт повинен включати:
- виведення даних в текстовий файл з подальшим читанням;
- виведення даних у бінарний файл даних з подальшим читанням.
Необхідно передбачити обробку винятків, пов'язаних з читанням та записом даних.
Клас FileUtils
відповідатиме за зберігання в текстовому файлі, читання з текстового файлу, а також
відповідні засоби для роботи з бінарними файлами даних. Найбільш придатною реалізацією класу AbstractCountry
видається
реалізація
CountryWithArrayList
. Код класу буде таким:
package ua.inf.iwanoff.java.fifth;import ua.inf.iwanoff.java.fourth.CountryWithArrayList;import ua.inf.iwanoff.java.third.AbstractCountry;import ua.inf.iwanoff.java.third.Census;import java.io.*;import java.util.InputMismatchException;import java.util.Scanner;/** * Клас реалізує запис і читання даних про країну та переписи. * Підтримуються текстові файли та бінарні файли даних */ public class FileUtils {/** * Здійснює запис даних про країну та переписи в указаний текстовий файл * * @param country країна * @param fileName ім'я файлу */ public static void writeToTxt(AbstractCountry country, String fileName)throws IOException {try (PrintWriter out =new PrintWriter(new FileWriter(fileName))) { out.println(country.getName() + " " + country.getArea());for (Census census : country.getCensuses()) { out.print(census.getYear() + " " + census.getPopulation() + " "); out.println(census.getComments()); } } }/** * Здійснює читання даних про країну та переписи зі вказаного текстового файлу * * @param fileName ім'я файлу * @return об'єкт, який було створено * @throws FileNotFoundException Файл не знайдено * @throws InputMismatchException хибний формат файлу */ public static CountryWithArrayList readFromTxt(String fileName)throws FileNotFoundException, InputMismatchException { CountryWithArrayList country =new CountryWithArrayList();try (Scanner scanner =new Scanner(new FileReader(fileName))) { country.setName(scanner.next()); country.setArea(scanner.nextDouble());while (scanner.hasNext()) {int year = scanner.nextInt();int population = scanner.nextInt(); String comments = scanner.nextLine(); comments = comments.trim(); country.addCensus(new Census(year, population, comments)); } }return country; }/** * Здійснює запис даних про країну та переписи в указаний бінарний файл даних * * @param country країна * @param fileName ім'я файлу * @throws IOException помилка роботи з файлом */ public static void writeToDat(AbstractCountry country, String fileName)throws IOException {try (DataOutputStream out =new DataOutputStream(new FileOutputStream(fileName))) { out.writeUTF(country.getName()); out.writeDouble(country.getArea()); out.writeInt(country.censusesCount());for (int i = 0; i < country.censusesCount(); i++) { out.writeInt(country.getCensus(i).getYear()); out.writeInt(country.getCensus(i).getPopulation()); out.writeUTF(country.getCensus(i).getComments()); } } }/** * Здійснює читання даних про країну та переписи зі вказаного бінарного файлу даних * * @param fileName ім'я файлу * @return об'єкт, який було створено * @throws IOException помилка роботи з файлом */ public static CountryWithArrayList readFromDat(String fileName)throws IOException, InputMismatchException { CountryWithArrayList country =new CountryWithArrayList();try (DataInputStream in =new DataInputStream(new FileInputStream(fileName))) { country.setName(in.readUTF()); country.setArea(in.readDouble());int count = in.readInt();for (int i = 0; i < count; i++) {int year = in.readInt();int population = in.readInt(); String comments = in.readUTF(); country.addCensus(new Census(year, population, comments)); } }return country; } }
Методами для демонстрації можливостей, не пов'язаних з файлами, можна скористатися безпосередньо з прикладів попередніх лабораторних робіт. Створюємо клас, який представляє консольний застосунок:
package ua.inf.iwanoff.java.fifth;import ua.inf.iwanoff.java.fourth.CountryWithArrayList;import ua.inf.iwanoff.java.third.AbstractCountry;import java.io.FileNotFoundException;import java.io.IOException;import java.util.InputMismatchException;import static ua.inf.iwanoff.java.third.CountryDemo.*;/** * Програма тестування можливості роботи з країною, * зокрема читання з файлів та запису в файли */ public class FilesDemo {/** * Демонстрація роботи програми * @param args аргументи командного рядка (не використовуються) */ public static void main(String[] args) {try { AbstractCountry country = setCountryData(new CountryWithArrayList()); FileUtils.writeToTxt(country, "Ukraine.txt"); country = FileUtils.readFromTxt("Ukraine.txt"); testSearch(country); testSorting(country); System.out.println("----------------------------------------"); country = setCountryData(new CountryWithArrayList()); FileUtils.writeToDat(country, "Ukraine.dat"); country = FileUtils.readFromDat("Ukraine.dat"); testSearch(country); testSorting(country); }catch (InputMismatchException e) { System.out.println("Хибний формат файлу"); }catch (FileNotFoundException e) { System.out.println("Файл не знайдено"); }catch (IOException e) { System.out.println("Помилка роботи з файлом"); } } }
Після виконання програми в кореневій теці проєкту автоматично створюються текстовий файл ("Ukraine.txt") та бінарний файл ("Ukraine.dat").
4 Вправи для контролю
- Створити перелік "День тижня". Додати методи отримання дня "позавчора" та "післязавтра".
Протестувати перелік у функції
main()
тестового класу. - Створити перелік "Континент". Перевантажити метод
toString()
так, щоб він показував назву континенту українською мовою. Протестувати перелік у функціїmain()
тестового класу. - Прочитати з текстового файлу дійсні значення (до кінця файлу), знайти добуток модулів ненульових елементів та вивести в інший текстовий файл.
- Прочитати з текстового файлу цілі значення (до кінця файлу), знайти добуток парних елементів та вивести в інший текстовий файл.
- Описати класи Факультет та Інститут (з полем – масивом факультетів). Створити об'єкти, здійснити їх серіалізацію й десеріалізацію.
5 Контрольні запитання
- Для чого можуть бути застосовані переліки?
- Для чого визначається конструктор переліку?
- Як у циклі отримати усі константи, описані в переліку?
- Для чого призначений механізм винятків?
- Які існують альтернативи механізму винятків?
- Як створити об'єкт-виняток?
- Яких типів можуть бути об'єкти-винятки?
- Чи можна використовувати основний результат функції, якщо відбулася генерація винятку?
- Як перехопити й обробити виняток?
- Як створити блок обробки всіх винятків?
- Чи можна розмістити виклик функції, що генерує виняток, поза блоком
try
? - У чому призначення функції
printStackTrace()
? - Які додаткові можливості синтаксису перехоплення винятків з'явилися у версії Java 7?
- Чим відрізняються потоки байтів від потоків символів за областю застосування?
- Які класи забезпечують роботу з текстовими файлами й бінарними файлами?
- У чому сенс явного закриття файлів
?
- Чи можна одночасно відкрити кілька потоків введення / виведення?
- Яким чином можна забезпечити автоматичне закриття потоків?
- У чому переваги використання класу
RandomAccessFile
? - Для чого використовують файли даних
DataOutputStream
іDataInputStream
? Які у них переваги й недоліки? - Що таке серіалізація і для чого вона використовується?
- У чому є переваги й недоліки серіалізації?
- Які функції слід визначити для реалізації інтерфейсу
java.io.Serializable
? - Для чого використовують модифікатор
transient
? - Як в Java здійснюється робота з архівами?