JavaRush /Java блог /Random UA /Кава-брейк #220. Як виправляти винятки в Java - докладний...

Кава-брейк #220. Як виправляти винятки в Java - докладний посібник

Стаття з групи Random UA
Джерело: JavaTechOnline Цей посібник допоможе вам навчитися виправляти поширені винятки в Java. Крім теорії, ви побачите приклади коду щодо виправлення таких проблем. Кава-брейк #220.  Як виправляти винятки в Java - докладний посібник - 1Як і в інших мовах програмування, при написанні коду Java розробники можуть зіткнутися з помилками або винятками. До винятків слід ставитися серйозно, оскільки їхня поява забирає робочий час. Однак, маючи деякі знання, ви можете прискорити вирішення більшості подібних проблем. Отже, давайте дізнаємося, як виправляти найпоширеніші винятки в мові Java.

Загальні рекомендації щодо запобігання виключенням у Java

Щоб уникнути появи будь-яких винятків у Java, важливо розуміти і дотримуватися рекомендацій щодо кодування:
  1. Завжди перевіряйте введення користувача, перш ніж використовувати його у своєму коді.
  2. Використовуйте блоки try-catch , throws , throw , залежно від того, що підходить для конкретного сценарію.
  3. Не нехтуйте повідомленнями в коді обробки та наявності помилок. Це допоможе визначити та виправити проблеми.
  4. Обов'язково реєструйте винятки з метою налагодження. Ви можете використовувати повідомлення реєстратора, щоб допомогти виявити проблеми.
  5. Виконуйте рекомендації щодо написання коду, щоб уникнути винятків, і ретельно тестуйте свій код.
  6. Обновлюйте свої залежності API, щоб переконатися, що ви використовуєте найостанніші та найстабільніші версії бібліотек та фреймворків.
  7. Використовуйте необхідні інструменти та середовища тестування для виявлення потенційних проблем у коді, перш ніж вони викличуть винятки.
  8. Слідкуйте за продуктивністю вашої програми та журналів, щоб швидко виявляти та усувати будь-які проблеми.
  9. Будьте в курсі передових практик безпеки, щоб переконатися, що ваш код безпечний та захищений від будь-яких потенційних атак.
  10. Ретельно документуйте свій код та його винятки, щоб іншим розробникам було легше його зрозуміти та підтримувати.

Перелік найпоширеніших винятків у Java

Java має досить довгий список винятків. Давайте розглянемо деякі з найпоширеніших:

NullPointerException

NullPointerException (Виключення нульового покажчика) — це виняток RuntimeException . Воно виникає, коли ми намагаємося використовувати змінну, що має нульове значення. Це означає, що вона вказує на об'єкт або змінну, значення якої дорівнює null . Така помилка може статися, якщо ми намагаємося викликати метод або отримати доступ до поля об'єкта, який не був ініціалізований, або коли ми передаємо null як параметр методу, який його не обробляє. Щоб запобігти виникненню NullPointerException , можна перевірити, чи є нульова змінна змінна, використовуючи оператор == . Якщо це так, тоді потрібно правильно опрацювати нульовий випадок. В якості альтернативи ми можемо ініціалізувати наші змінні значення за промовчанням (де це можливо), щоб гарантувати, що ми ніколи не зіткнемося з нульовим посиланням. Інший спосіб уникнути виключення NullPointerException - використання класу Optional . Java Optional — це об'єкт-контейнер, який може містити або не містити ненульового значення. Він використовується для представлення наявності або відсутності значення, аналогічно тому, як нульове значення використовується для відсутності значення. Якщо програміст спробує отримати доступ до порожнього значення об'єкта Optional , програма не видасть виняток NullPointerException , а натомість поверне порожній об'єкт Optional . Іншими словами, Optional змушує програміста обробляти випадок, коли значення немає, що допомагає уникнути виключення NullPointerException .

IndexOutOfBoundsException

IndexOutOfBoundsException — це виключення середовища виконання, яке виникає, коли індекс, який використовується для доступу до масиву або колекції, має негативне значення або більше або дорівнює розміру масиву або колекції. Щоб запобігти появі цього виключення, ми повинні переконатися, що індекс, що використовується для доступу до масиву або колекції, знаходиться в межах його меж, тобто він повинен бути більшим або рівним нулю і менше розміру масиву або колекції.

ArrayIndexOutOfBoundsException

ArrayIndexOutOfBoundsException - це тип IndexOutOfBoundsException , що виникає при спробі доступу до масиву за неприпустимим індексом. Java індекси масиву починаються з 0 і закінчуються на length()-1 , де length() — це кількість елементів в масиві. Якщо ви спробуєте отримати доступ до елемента за індексом за межами діапазону, Java видасть виняток ArrayIndexOutOfBoundsException . При зіткненні з ArrayIndexOutOfBoundsException слід переглянути свій код, щоб переконатися, що ви використовуєте правильні індекси при доступі до елементів в масиві.

StringIndexOutOfBoundsException

StringIndexOutOfBoundsException - це тип IndexOutOfBoundsException , що виникає при спробі доступу до символу в рядку з неприпустимим індексом. Як і у випадку з попереднім винятком ArrayIndexOutOfBoundsException , пам'ятайте, що Java індекс рядка починається з 0 і закінчується на length()-1 , де length() - це кількість символів у рядку. Якщо ви спробуєте отримати доступ до символу за індексом за межами діапазону, ви можете зіткнутися з винятком StringIndexOutOfBoundsException . Якщо ви зіткнулися з StringIndexOutOfBoundsException , слід переглянути свій код, щоб переконатися, що ви використовуєте правильні індекси при доступі до символів у рядку.

ClassCastException

Це виняток виникає, коли ми намагаємося привести об'єкт до типу, несумісного з фактичним типом. Щоб вирішити подібну проблему, важливо переконатися, що ваша Java-програма намагається привести об'єкти лише до тих класів, екземплярами яких є. Ви можете перевірити тип об'єкта за допомогою оператора встанці перед застосуванням приведення.

IlegallegalArgumentException

IllegalArgumentException - це тип RuntimeException , який виникає, коли метод викликається з неприпустимим або невідповідним аргументом. Іншими словами, така помилка може статися, коли метод викликається з аргументом, що виходить за межі очікуваного діапазону або не має очікуваного формату чи структури. Наприклад, методу потрібно позитивне число як аргумент, а ми надаємо негативне число, що є неприпустимим введенням. Якщо ви зіткнулися з IllegalArgumentException , вам слід перевірити свій код, щоб переконатися, що ви викликаєте методи з допустимими та відповідними аргументами.

IlegalstateException

IllegalStateException — це тип RuntimeException , що виникає, коли об'єкт перебуває у невідповідному стані для виконуваної операції. Це може статися, якщо метод викликається для об'єкта, який не перебуває в очікуваному стані. Наприклад, якщо ми створимо об'єкт підключення до бази даних, то закриємо його. Тоді, якщо ми спробуємо створити об'єкт Statement у закритому з'єднанні, це викликає виняток IllegalStateException , тому що для методу createStatement() потрібне відкрите з'єднання. Якщо ви зіткнулися з IllegalStateException , вам слід переглянути свій код, щоб переконатися, що ви викликаєте методи для об'єктів, які знаходяться у відповідному стані.

UnsupportedOperationException

UnsupportedOperationException — це тип RuntimeException , що виникає при спробі виконання непідтримуваної операції над об'єктом. Ця помилка може статися, коли ми викликаємо метод, який не реалізований або не підтримується об'єктом. Щоб запобігти появі виключення, ми не повинні викликати такі операції, які не підтримуються на об'єктах. Потрібно переглянути наш код, щоб переконатися, що ми викликаємо методи для об'єктів, які підтримують операцію.

ArithmeticException

ArithmeticException - це тип RuntimeException , що виникає, коли арифметична операція дає неприпустимий результат. Це виняток може виникнути, коли ми намагаємося виконати арифметичну операцію з неправильним чи неприпустимим аргументом. Наприклад, при спробі поділу на нуль. Щоб вирішити цю проблему, ми можемо виконати перевірку введення та переконатися, що аргументи відповідають необхідним умовам перед виконанням арифметичної операції.

SecurityException

SecurityException — це RuntimeException , що виникає при порушенні безпеки під час виконання програми. Ця помилка може статися, коли програма намагається виконати операцію, не дозволену політикою безпеки. Щоб вирішити проблему, ми повинні переконатися, що маємо доступ до ресурсів та виконуємо операції, для яких ми маємо спеціальний дозвіл.

NumberFormatException

NumberFormatException — це тип RuntimeException , який виникає, коли викликається метод перетворення рядка в числовий формат, але рядок не має відповідного формату. Щоб вирішити цю проблему, потрібно спочатку перевірити введення користувача, перш ніж намагатися його перетворити. Також перевірте свій код, щоб переконатися, що ви намагаєтеся перетворити рядки, що мають відповідний формат для цільового числового типу.

InterruptedException

InterruptedException - це перевірений виняток, який виникає, якщо потік очікує, спить або блокується для будь-якої події, і ця подія переривається іншим потоком. Помилка може статися, коли потік очікує введення, звільнення блокування або завершення будь-якої іншої операції, а інший потік перериває потік, що очікує. Щоб вирішити цю проблему, можна перехопити InterruptedException і відреагувати, очистивши ресурси, зупинивши потік або здійснивши інші відповідні дії. Якщо ви зіткнулися з InterruptedException , слід перевірити свій код, щоб переконатися, що ви правильно обробляєте переривання потоку.

FileNotFoundException

FileNotFoundException - це перевірений виняток, який виникає, коли програма намагається отримати доступ до файлу, який не існує або не може бути знайдений у вказаному місці. Він може з'явитися, якщо файл написаний з помилкою, переміщений або видалений, або навіть коли програма не має дозволів для доступу до файлу. Щоб виправити помилку, ви можете перевірити введення та переконатися, що шлях до файлу вказано правильно, а програма має необхідні дозволи для доступу до файлу.

IOException

IOException - це перевірений виняток у Java, що представляє помилку, що виникла при виконанні операцій введення або виведення, таких як читання або запис у файл або мережний сокет. Вона може статися з кількох причин, наприклад, через відсутність або відсутність зазначеного файлу, мережевої помилки або недостатніх дозволів. Щоб виправити проблему, потрібно виконати кілька дій, таких як перевірка повідомлення про помилку, обробка виключення за допомогою try-catch , закриття ресурсів, перевірка дозволу на доступ до файлу тощо.

NoSuchMethodException

NoSuchMethodException - це виняток, який виникає під час виконання, коли ми намагаємося викликати метод, що не існує в класі. Такий виняток зазвичай виникає, якщо ми викликаємо метод за допомогою Class.getMethod() або Class.getDeclaredMethod() , а вказане ім'я методу не знайдено у класі чи інтерфейсі. Виняток може виникнути, коли ми намагаємося викликати метод за допомогою класу java.lang.reflect.Method , а вказане ім'я методу не існує в об'єкті. Щоб запобігти такому виключенню, переконайтеся, що ви маєте допустимий метод з правильною сигнатурою методу та специфікатором доступу.

NoSuchFieldException

NoSuchFieldException - це виняток часу виконання, який виникає, коли ми намагаємося отримати доступ до поля, якого немає у класі. Цей виняток зазвичай виникає, коли ми викликаємо метод за допомогою Class.getField() або Class.getDeclaredField() , а вказане ім'я поля не знайдено у класі чи інтерфейсі. Крім того, воно також може бути викликане, якщо ми намагаємося отримати доступ до поля за допомогою класу java.lang.reflect.Field , а назва поля не існує в об'єкті. Щоб уникнути цього виключення, переконайтеся, що ви звертаєтеся до дійсного поля з правильним ім'ям та модифікаторами доступу. Якщо ви звертаєтесь до приватного поля, обов'язково використовуйте метод getDeclaredField() замість методу getField() .

IllegalAccessException

IllegalAccessException — це виняток часу виконання, який виникає, коли ми намагаємося отримати доступ до поля або методу в класі, але ми не маємо необхідних прав доступу. Це виняток зазвичай з'являється, якщо ми намагаємося отримати доступ до приватного поля або методу ззовні класу або коли ми намагаємося отримати доступ до захищеного поля або методу класу, який не є підкласом вихідного класу. Також воно може бути викликане при спробі отримання доступу до поля або методу, позначеного як недоступний за допомогою класу java.lang.reflect.AccessibleObject . Щоб уникнути цієї проблеми, переконайтеся, що ви маєте необхідні права доступу до поля або методу, до якого ви намагаєтеся отримати доступ. Якщо поле або метод є закритими, вам може знадобитися використовувати відображення (reflection) і встановити для AccessibleObject значення true (щоб отримати доступ до нього). Однак будьте обережні при використанні відображення для доступу до закритих полів або методів, оскільки це може порушити інкапсуляцію та порушити цілісність класу.

VerifyError

VerifyError - це помилка часу виконання, яка є підкласом LinkageError . Вона відбувається коли віртуальна машина Java (JVM) зустрічає файл класу, який порушує певні правила перевірки. Коли клас Java компілюється, компілятор перевіряє, чи байт-код відповідає певним правилам та обмеженням, таким як безпека типів та правильне використання стека та локальних змінних. Якщо файл класу порушує ці правила, JVM видасть VerifyError під час завантаження та перевірки класу під час виконання. Щоб уникнути VerifyError , переконайтеся, що ваш код відповідає правильному синтаксису та семантиці мови Java. Якщо ви зіткнулися з VerifyError , ви повинні перевірити свій код, щоб переконатися, що він дійсний і відсутні порушення правил перевірки байт-коду Java.

OutOfMemoryError

OutOfMemoryError — це підклас Error , тип Throwable , який представляє серйозні проблеми, які неможливо усунути під час виконання. Незважаючи на те, що Java 8 включає деякі покращення в складання сміття і керуванні пам'яттю, ви, як і раніше, можете зіткнутися з OutOfMemoryError , якщо ваша програма використовує занадто багато пам'яті або неправильно керує використанням пам'яті. Щоб уникнути помилки OutOfMemoryError , необхідно правильно керувати використанням пам'яті у програмі Java. Це передбачає використання структур даних та алгоритмів, які ефективно використовують пам'ять, уникнення непотрібного створення об'єктів та правильне видалення об'єктів, коли вони більше не потрібні. Крім того, ви можете збільшити максимальний розмір купи для JVM, використовуючи прапорець -Xmx під час запуску Java-програми.

StackOverflowError

StackOverflowError — це тип помилки, коли розмір стека, необхідний програмою, перевищує обсяг виділеної для неї пам'яті. Вона може статися, коли програма викликає занадто багато вкладених методів або коли метод занадто багато разів рекурсивно викликає сам себе, що призводить до нескінченного циклу. Віртуальна машина Java (JVM) виділяє фіксований обсяг пам'яті для стека виконання, який використовується для відстеження викликів методів та локальних змінних. Коли стек переповнюється, JVM видає StackOverflowError . Щоб уникнути StackOverflowError , важливо переконатися, що ваша Java-програма адекватно використовує рекурсію та виклики методів. Якщо ви зіткнулися з помилкою StackOverflowError , ви можете спробувати збільшити розмір стека, використовуючи прапорець -Xss при запуску вашої Java-програми.

InvocationTargetException

InvocationTargetException - це перевірений виняток, який генерується механізмом відображення Java. Він входить до складу пакету java.lang.reflect.InvocationTargetException і використовується для вказівки того, що під час виклику методу чи конструктора виник виняток. Коли метод чи конструктор викликається з використанням механізму відображення Java, викликається метод invoke() класу java.lang.reflect.Method чи java.lang.reflect.Constructor . Якщо метод або конструктор, що викликається, видає виняток, то метод invoke() перехоплює його і укладає в InvocationTargetException . Потім цей виняток передається зухвалій стороні методу invoke() . Щоб виправити InvocationTargetException , потрібно перехопити його, отримати виняток основної причини за допомогою методу getCause() і відповідним чином обробити виняток основної причини. Зверніть увагу, що основною причиною може бути перевірений виняток або виняток часу, тому обов'язково обробіть його правильно.

Способи виправлення найпоширеніших винятків у Java

Як виправити NullPointerException

Сценарій: у вас є метод, який звертається до об'єкта, що має значення null .
String title= null;
System.out.println(title.length()); // Это вызовет NullPointerException
Рішення № 1: Перед використанням перевірте, чи об'єкт є нульовим.
if(title!= null) {
   System.out.println(title.length());
} else {
   System.out.println("title is null");
}
Рішення № 2: Використовуйте Optional , щоб уникнути виключення NullPointerException .
Optional<String> optionalTitle = Optional.ofNullable(getTitle());
if (optionalTitle.isPresent()) {
   String title= optionalTitle.get();
   System.out.println("Title: " + title);
} else {
   System.out.println("Title is not available.");
}

Як виправити ArrayIndexOutOfBoundsException

Сценарій: Ви намагаєтесь отримати доступ до масиву з індексом, який знаходиться за межами його кордонів.
int[] numbers = {4, 5, 6};
System.out.println(numbers[3]);   // Это вызовет ArrayIndexOutOfBoundsException
Рішення: перевірте довжину масиву перед доступом до нього та переконайтеся, що ви використовуєте допустимі індекси.
int[] numbers = {4, 5, 6};
if (numbers.length > 3) {
   System.out.println(numbers[3]);
} else {
   System.out.println("ArrayIndexOutOfBoundsException: Please use valid indexes of the Array");
}

Як виправити ClassCastException

Сценарій: Ви намагаєтеся привести об'єкт до типу, несумісного з його фактичним типом.
Object obj = "Java Exception";
Integer number = (Integer) obj; // Это вызовет ClassCastException
Рішення: Переконайтеся, що об'єкти наводяться лише до тих типів, з якими вони сумісні.
Object obj = "Java Exception";
if(obj instanceof Integer) {
   Integer number = (Integer) obj;
   System.out.println(number);
} else {
   System.out.println("Object cannot caste to Integer");
}

Як виправити IllegalArgumentException

Сценарій: Ви передали неприпустимий аргумент методу.
public void printNumber(int number) {
   if(number <= 0) {
      throw new IllegalArgumentException("You cannot pass a negative number or zero");
   }
   System.out.println(number);
}

printNumber(-1); // Это вызовет IllegalArgumentException
Рішення: Переконайтеся, що ви передаєте допустимі аргументи методам. У такому разі передайте позитивне число.
printNumber(1); //  Это успешно напечатает 1.

Як виправити IllegalStateException

Сценарій: об'єкт перебуває у неприпустимому стані.
public class Bike {

   private Boolean isStarted;

   public void start() {
      if(isStarted) {
        throw new IllegalStateException("Bike is already started");
      }
      isStarted = true;
      System.out.println("Bike started");
   }
}

Bike bike= new Bike();
bike.start();
bike.start(); // Это вызовет IllegalStateException потому что bike is already started
Рішення: переконайтеся, що ви правильно підтримуєте стан об'єкта.
Bike bike= new Bike();
bike.start();

Як виправити UnsupportedOperationException

Сценарій: Ви використовуєте операцію, яка не підтримується об'єктом. Один із популярних прикладів: коли ви використовуєте операцію remove() для незмінної колекції, то ви напевно побачите виняток UnsupportedOperationException .
List<String> list = Arrays.asList("Java", "Angular", "Spring");
list.add("Python"); // Это вызовет UnsupportedOperationException
Оскільки метод Arrays.asList() повертає незмінний список, він не підтримує додавання або видалення елементів. Рішення: Переконайтеся, що ви робите лише підтримувані операції над об'єктами.
List<String> list = new ArrayList<>(Arrays.asList("Java", "Angular" "Spring"));
list.add("Python");
System.out.println(list);

Як виправити ArithmeticException

Сценарій № 1: Ви намагаєтеся виконати операцію цілісного поділу, яка дає дрібний результат.
int i = 10;
int j = 4;
int k = i/j; // Это вызовет исключение ArithmeticException: целочисленное деление будет дробным
Тут результат операції розподілу дорівнює 2,5, що є дробовим значенням. Оскільки цілі числа не можуть зберігати дробових значень, виникає виняток ArithmeticException . Рішення: Щоб уникнути цього виключення, ми можемо використовувати тип даних, що підтримує дрібні значення, наприклад double , для зберігання результату операції поділу. Ось приклад:
int i = 10;
int j = 4;
double k = (double) i/j;
Сценарій № 2: Ви намагаєтеся поділити на нуль, і у вас виникає цей виняток. Це найпоширеніший сценарій.
int i = 4;
int j = 0;
int k = i/j; // Это вызовет исключение ArithmeticException: нельзя делить на ноль
Рішення: Правильно обробляти поділ на нуль. Наприклад, наведений нижче код показує правильну обробку.
int i = 4;
int j = 0;
if(j != 0) {
int k = i/j;
System.out.println(k);
} else {
System.out.println("ArithmeticException: Cannot divide by zero");
}

Як виправити IndexOutOfBoundsException

Сценарій: Ви намагаєтесь отримати доступ до колекції з індексом, який знаходиться за її межами.
List<String> list = Arrays.asList("Apple", "Papaya", "Mango");
System.out.println(list.get(3)); // Это вызовет IndexOutOfBoundsException
Рішення: Перевірте розмір колекції перед доступом до неї та переконайтеся, що Ви використовуєте допустимі індекси.
List<String> list = Arrays.asList("Apple", "Papaya", "Mango");
if (list.size() > 3) {
   System.out.println(list.get(3));
} else {
   System.out.println("You are using the Index which is out of bounds");
}

Як виправити IOException

Сценарій: операція вводу-виводу (input/output) завершується збоєм, оскільки файл недоступний.
try {
   File inputFile = new FileReader("pqr.txt");
   BufferedReader reader = new BufferedReader(inputFile);
   String line = reader.readLine();
   System.out.println(line);
} catch (IOException e) {
   e.printStackTrace();
}
Рішення: Опрацюйте помилки введення-виводу та переконайтеся, що ресурси правильно закриті.
File inputFile = new File("pqr.txt");

if (!inputFile.exists() || !inputFile.canRead()) {
 System.out.println("The input file is missing or not readable.");
 return;
}

try {
BufferedReader reader = new BufferedReader(inputFile);
 String line = reader.readLine();
 System.out.println(line);
  reader.close();
} catch (IOException e) {
 e.printStackTrace();
}
Зверніть увагу, що як альтернатива ми можемо використовувати функцію try-with-resource , представлену в Java 7, для автоматичного закриття ресурсів, як показано нижче. Ми можемо оголосити один або кілька ресурсів в операторі try , і Java автоматично закриє ресурси в кінці блоку, незалежно від того, чи блок завершується нормально або виникає виняток.
try (BufferedReader reader = new BufferedReader(new FileReader("pqr.txt"))) {
....
} catch {
....
}

Як виправити FileNotFoundException

Сценарій: файл не знайдено у вказаному місці.
try {
     BufferedReader reader = new BufferedReader(new FileReader("abc.txt"));
     String line = reader.readLine();
     System.out.println(line);
     reader.close();
} catch (FileNotFoundException | IOException e) {
     System.out.println("An error has occurred while reading the file: " + e.getMessage());
}
Рішення: Переконайтеся, що файл існує, і ви маєте відповідні дозволи для доступу до нього.
try {
    File file = new File("abc.txt");
    if(!file.exists()) {
    throw new FileNotFoundException("File not found at the specified location");
    }
    BufferedReader reader = new BufferedReader(new FileReader(file));
    String line = reader.readLine();
    System.out.println(line);
    reader.close();
} catch (IOException e) {
    e.printStackTrace();
}

Як виправити NoSuchMethodException

Сценарій: якщо ви намагаєтеся отримати доступ до методу, який не можна знайти.
public class TestClass {
   public void sayHello() {
      System.out.println("Hello");
   }
}

TestClass obj = new TestClass();
Method method = obj.getClass().getMethod("printHello"); // Это вызовет NoSuchMethodException
Рішення: Переконайтеся, що метод існує, а також правильність імені та підпису методу.
public class TestClass {
   public void sayHello() {
      System.out.println("Hello");
   }
}

TestClass  obj = new TestClass();
try {
    Method method = obj.getClass().getMethod("sayHello");
} catch (NoSuchMethodException e) {
    e.printStackTrace();
}

Як виправити ConcurrentModificationException

Сценарій: колекція змінюється під час її повторення.
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
for (String str : list) {
  list.remove(str);  // Это вызовет ConcurrentModificationException
}
Рішення: Використовуйте ітератор для перебору колекції та її зміни за допомогою методів ітератора.
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String str = iterator.next();
    iterator.remove();
}
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ