Как создать свое исключение java
Перейти к содержимому

Как создать свое исключение java

  • автор:

Создание подклассов исключений

Для того чтобы создать класс собственного исключения, достаточно определить его как производный от класса Exception .

В подклассах собственных исключений совсем не обязательно реализовать что-нибудь. В самом классе Exception не определено никаких методов.

Зачастую указывать описание исключения непосредственно при его создании очень удобно, но иногда для этого лучше переопределить метод toString() .

Рассмотрим пример создания собственного подкласса исключений:

public class MyException extends Exception < private int detail; public MyException(int detail, String message) < super(message); this.detail = detail; >@Override public String toString() < return "MyException"; > >
public class MyExceptionDemo < public static void main(String[] args) < try < compute(1); compute(20); >catch (MyException e) < System.out.println("Перехваченное исключение." + e); >> public static void compute(int a) throws MyException < System.out.println("Вызван метод compute(" + a + ")"); if (a >10) < throw new MyException(a, "Some message"); >System.out.println("Нормальное завершение."); > >
  • Обработка исключений
  • Классы исключений
  • Оператор try-catch-finally
  • Оператор throw
  • Оператор throws
  • Исключения при наследовании
  • Задания

Как создать свое исключение java

Хотя имеющиеся в стандартной библиотеке классов Java классы исключений описывают большинство исключительных ситуаций, которые могут возникнуть при выполнении программы, все таки иногда требуется создать свои собственные классы исключений со своей логикой.

Чтобы создать свой класс исключений, надо унаследовать его от класса Exception. Например, у нас есть класс, вычисляющий факториал, и нам надо выбрасывать специальное исключение, если число, передаваемое в метод, меньше 1:

class Factorial < public static int getFactorial(int num) throws FactorialException< int result=1; if(num<1) throw new FactorialException("The number is less than 1", num); for(int i=1; i<=num;i++)< result*=i; >return result; > > class FactorialException extends Exception < private int number; public int getNumber()public FactorialException(String message, int num) < super(message); number=num; >>

Здесь для определения ошибки, связанной с вычислением факториала, определен класс FactorialException , который наследуется от Exception и который содержит всю информацию о вычислении. В конструкторе FactorialException в конструктор базового класса Exception передается сообщение об ошибке: super(message) . Кроме того, отдельное поле предназначено для хранения числа, факториал которого вычисляется.

Для генерации исключения в методе вычисления факториала выбрасывается исключение с помощью оператора throw: throw new FactorialException(«Число не может быть меньше 1», num) . Кроме того, так как это исключение не обрабатывается с помощью try..catch, то мы передаем обработку вызывающему методу, используя оператор throws: public static int getFactorial(int num) throws FactorialException

Теперь используем класс в методе main:

public static void main(String[] args) < try< int result = Factorial.getFactorial(6); System.out.println(result); >catch(FactorialException ex) < System.out.println(ex.getMessage()); System.out.println(ex.getNumber()); >>

Класс Exception . Создание собственных классов исключений. Примеры

Класс Exception есть базовым для всех пользовательских классов в программе. Для того, чтобы создать класс собственного исключения, достаточно определить его как производный от класса Exception . Поскольку класс Exception есть производным от класса Throwable , то в классе Exception являются доступными все методы из класса Throwable .

То есть, при объявлении класса MyExceptionClass наподобие

class MyExceptionClass extends Exception

экземпляр этого класса сможет использовать все методы класса Throwable . В нижеследующем коде продемонстрировано использование метода getMessage() из класса Throwable .

public class TrainException < public static void main(String[] args) < MyExceptionClass mc = new MyExceptionClass(); String str = mc.getMessage(); System.out.println("str color: #0000ff;">main()

str = null

Итак, в собственных реализованных подклассах, которые унаследованы от класса Exception , не обязательно реализовывать собственные методы. Часто бывает достаточно методов базового класса Throwable .

2. Пример создания класса Triangle . Вычисление площади треугольника с использованием собственного класса исключения TriangleException

В примере демонстрируется создание собственного класса TriangleException для обработки исключения «неправильно заданы стороны треугольника».

Объявляется два класса:

  • Triangle – реализует треугольник по его сторонам a , b , c ;
  • TriangleException – реализует перехват исключительной ситуации «неправильно заданы стороны треугольника».

Текст программы следующий:

import java.util.Scanner; // Создание собственного класса исключения TriangleException // Создание собственного класса Triangle, реализующего треугольник по его сторонам class TriangleException extends Exception < // переопределить метод toString(), описывающий исключение public String toString() < return "Error. Bad sides of triangle."; > > // объявить класс треугольник class Triangle < // стороны треугольника private double a,b,c; // конструктор по умолчанию public Triangle() < // равносторонний треугольник с длиной стороны 1 a = b = c = 1; > // параметризированный конструктор public Triangle(double _a, double _b, double _c) < // использование класса исключения TriangleException try < // можно ли из сторон _a, _b, _c создать треугольник if (((_a+_b)<_c)||((_a+_c)<_b)||((_b+_c)<_a)) throw new TriangleException(); > catch(TriangleException e) < System.out.println("Exception: "+e.toString()); return; > // если стороны треугольника введены корректно, // записать их во внутренние переменные класса a = _a; b = _b; c = _c; > // метод возвращающий площадь треугольника public double getArea() < // если стороны треугольника имеют корректные размеры, // то проверку на корень из отрицательного числа делать не нужно double p, s; p = (a+b+c)/2; // полупериметр s = Math.sqrt(p*(p-a)*(p-b)*(p-c)); // формула Герона return s; > > public class Train04 < // функция main() тестирует работу классов Triangle и TriangleException public static void main(String[] args) < double a, b, c; Scanner in = new Scanner(System.in); // ввод a, b, c System.out.print("a color: #0000ff;">a = in.nextDouble(); System.out.print("b color: #0000ff;">b = in.nextDouble(); System.out.print("c color: #0000ff;">c = in.nextDouble(); // вычисление площади Triangle tr = new Triangle(a,b,c); double area = tr.getArea(); System.out.println("area color: #0000ff;">TriangleException унаследован от класса Exception, который в свою очередь, унаследован от класса Throwable. Таким образом, в классе TriangleException есть доступными методы класса Throwable, в том числе метод toString(), описывающий исключение. Класс TriangleException переопределяет метод toString() класса Throwable. Это делается с целью большей конкретизации описания самого исключения.

В функции main() для того чтобы ввести данные, используется класс Scanner из пакета java.util. Поэтому вначале программы вводится команда

import java.util.Scanner;

Результат работы программы в случае, если ввести неправильные данные

a = 3 b = 1 c = 1 Exception: Error. Bad sides of triangle. area = 0.0

Результат работы программы в случае, если ввести корректные данные

a = 2 b = 2 c = 2 area = 1.7320508075688772

Исключения

Исключение - это нештатная ситуация, ошибка во время выполнения программы. Самый простой пример - деление на ноль. Можно вручную отслеживать возникновение подобных ошибок, а можно воспользоваться специальным механизмом исключений, который упрощает создание больших надёжных программ, уменьшает объём необходимого кода и повышает уверенность в том, что в приложении не будет необработанной ошибки.

В методе, в котором происходит ошибка, создаётся и передаётся специальный объект. Метод может либо обработать исключение самостоятельно, либо пропустить его. В любом случае исключение ловится и обрабатывается. Исключение может появиться благодаря самой системе, либо вы сами можете создать его вручную. Системные исключения возникают при неправильном использовании языка Java или запрещённых приёмов доступа к системе. Ваши собственные исключения обрабатывают специфические ошибки вашей программы.

Вернёмся к примеру с делением. Деление на нуль может предотвратить проверкой соответствующего условия. Но что делать, если знаменатель оказался нулём? Возможно, в контексте вашей задачи известно, как следует поступить в такой ситуации. Но, если нулевой знаменатель возник неожиданно, деление в принципе невозможно, и тогда необходимо возбудить исключение, а не продолжать исполнение программы.

Существует пять ключевых слов, используемых в исключениях: try, catch, throw, throws, finally. Порядок обработки исключений следующий.

Операторы программы, которые вы хотите отслеживать, помещаются в блок try. Если исключение произошло, то оно создаётся и передаётся дальше. Ваш код может перехватить исключение при помощи блока catch и обработать его. Системные исключения автоматически передаются самой системой. Чтобы передать исключение вручную, используется throw. Любое исключение, созданное и передаваемое внутри метода, должно быть указано в его интерфейсе ключевым словом throws. Любой код, который следует выполнить обязательно после завершения блока try, помещается в блок finally

Схематически код выглядит так:

 try < // блок кода, где отслеживаются ошибки >catch (тип_исключения_1 exceptionObject) < // обрабатываем ошибку >catch (тип_исключения_2 exceptionObject) < // обрабатываем ошибку >finally < // код, который нужно выполнить после завершения блока try >

Существует специальный класс для исключений Trowable. В него входят два класса Exception и Error.

Класс Exception используется для обработки исключений вашей программой. Вы можете наследоваться от него для создания собственных типов исключений. Для распространённых ошибок уже существует класс RuntimeException, который может обрабатывать деление на ноль или определять ошибочную индексацию массива.

Класс Error служит для обработки ошибок в самом языке Java и на практике вам не придётся иметь с ним дело.

Прежде чем научиться обрабатывать исключения, нам (как и нормальному любопытному коту) хочется посмотреть, а что происходит, если ошибку не обработать. Давайте разделим число котов в вашей квартире на ноль, хотя мы и знаем, что котов на ноль делить нельзя!

 int catNumber; int zero; catNumber = 1; // у меня один кот zero = 0; // ноль, он и в Африке ноль int result = catNumber / zero; 

Я поместил код в обработчик щелчка кнопки. Когда система времени выполнения Java обнаруживает попытку деления на ноль, она создаёт объект исключения и передаёт его. Да вот незадача, никто не перехватывает его, хотя это должны были сделать вы. Видя вашу бездеятельность, объект перехватывает стандартный системный обработчик Java, который отличается вредных характером. Он останавливает вашу программу и выводит сообщение об ошибке, которое можно увидеть в журнале LogCat:

Caused by: java.lang.ArithmeticException: divide by zero at ru.alexanderklimov.test.MainActivity.onClick(MainActivity.java:79)

Как видно, созданный объект исключения принадлежит к классу ArithmeticException, далее системный обработчик любезно вывел краткое описание ошибки и место возникновения.

Вряд ли пользователи вашей программы будут довольны, если вы так и оставите обработку ошибки системе. Если программа будет завершаться с такой ошибкой, то скорее всего вашу программу просто удалят. Посмотрим, как мы можем исправить ситуацию.

Поместим проблемный код в блок try, а в блоке catch обработаем исключение.

 int catNumber; int zero; try < // мониторим код catNumber = 1; // у меня один кот zero = 0; // ноль, он и в Африке ноль int result = catNumber / zero; Toast.makeText(this, "Не увидите это сообщение!", Toast.LENGTH_LONG).show(); >catch (ArithmeticException e) < Toast.makeText(this, "Нельзя котов делить на ноль!", Toast.LENGTH_LONG).show(); >Toast.makeText(this, "Жизнь продолжается", Toast.LENGTH_LONG).show(); 

Теперь программа аварийно не закрывается, так как мы обрабатываем ситуацию с делением на ноль.

В данном случае мы уже знали, к какому классу принадлежит получаемая ошибка, поэтому в блоке catch сразу указали конкретный тип. Обратите внимание, что последний оператор в блоке try не срабатывает, так как ошибка происходит раньше строчкой выше. Далее выполнение передаётся в блок catch, далее выполняются следующие операторы в обычном порядке.

Операторы try и catch работают совместно в паре. Хотя возможны ситуации, когда catch может обрабатывать несколько вложенных операторов try.

Если вы хотите увидеть описание ошибки, то параметр e и поможет увидеть ёго.

 catch (ArithmeticException e)

По умолчанию, класс Trowable, к которому относится ArithmeticException возвращает строку, содержащую описание исключения. Но вы можете и явно указать метод e.toString.

Несколько исключений

Фрагмент кода может содержать несколько проблемных мест. Например, кроме деления на ноль, возможна ошибка индексации массива. В таком случае вам нужно создать два или более операторов catch для каждого типа исключения. Причём они проверяются по порядку. Если исключение будет обнаружено у первого блока обработки, то он будет выполнен, а остальные проверки пропускаются и выполнение программы продолжается с места, который следует за блоком try/catch.

 int catNumber; int zero; try < // мониторим код catNumber = 1; // у меня один кот zero = 1; // ноль, он и в Африке ноль int result = catNumber / zero; // Создадим массив из трёх котов String[] catNames = ; catNames[3] = "Рыжик"; Toast.makeText(this, "Не увидите это сообщение!", Toast.LENGTH_LONG).show(); > catch (ArithmeticException e) < Toast.makeText(this, e.toString() + ": Нельзя котов делить на ноль!", Toast.LENGTH_LONG).show(); >catch (ArrayIndexOutOfBoundsException e) < Toast.makeText(this, "Ошибка: " + e.toString(), Toast.LENGTH_LONG).show(); >Toast.makeText(this, "Жизнь продолжается", Toast.LENGTH_LONG).show(); 

В примере мы добавили массив с тремя элементами, но обращаемся к четвёртому элементу, так как забыли, что отсчёт у массива начинается с нуля. Если оставить значение переменной zero равным нулю, то сработает обработка первого исключения деления на ноль, и мы даже не узнаем о существовании второй ошибки. Но допустим, что в результате каких-то вычислений значение переменной стало равно единице. Тогда наше исключение ArithmeticException не сработает. Но сработает новое добавленное исключение ArrayIndexOutOfBoundsException. А дальше всё пойдёт как раньше.

Тут всегда нужно помнить одну особенность. При использовании множественных операторов catch обработчики подклассов исключений должные находиться выше, чем обработчики их суперклассов. Иначе, суперкласс будет перехватывать все исключения, имея большую область перехвата. Иными словами, Exception не должен находиться выше ArithmeticException и ArrayIndexOutOfBoundsException. К счастью, среда разработки сама замечает непорядок и предупреждает вас, что такой порядок не годится. Увидев такую ошибку, попробуйте перенести блок обработки исключений ниже.

Вложенные операторы try

Операторы try могут быть вложенными. Если вложенный оператор try не имеет своего обработчика catch для определения исключения, то идёт поиск обработчика catch у внешнего блока try и т.д. Если подходящий catch не будет найден, то исключение обработает сама система (что никуда не годится).

Оператор throw

Часть исключений может обрабатывать сама система. Но можно создать собственные исключения при помощи оператора throw. Код выглядит так:

 throw экземпляр_Throwable 

Вам нужно создать экземпляр класса Throwable или его наследников. Получить объект класса Throwable можно в операторе catch или стандартным способом через оператор new.

Мы могли бы написать такой код для кнопки:

 Cat cat; public void onClick(View view) < if(cat == null)< throw new NullPointerException("Котик не инициализирован"); >> 

Мы объявили объект класса Cat, но забыли его проинициализировать, например, в onCreate(). Теперь нажатие кнопки вызовет исключение, которое обработает система, а в логах мы можем прочитать сообщение об ошибке. Возможно, вы захотите использовать другое исключение, например, throw new UnsupportedOperationException("Котик не инициализирован");.

В любом случае мы передали обработку ошибки системе. В реальном приложении вам нужно обработать ошибку самостоятельно.

Поток выполнения останавливается непосредственно после оператора throw и другие операторы не выполняются. При этом ищется ближайший блок try/catch соответствующего исключению типа.

Перепишем пример с обработкой ошибки.

 public void onClick(View view) < if (cat == null) < try < throw new NullPointerException("Кота не существует"); >catch (NullPointerException e) < Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show(); >> > 

Мы создали новый объект класса NullPointerException. Многие классы исключений кроме стандартного конструктора по умолчанию с пустыми скобками имеют второй конструктор с строковым параметром, в котором можно разместить подходящую информацию об исключении. Получить текст из него можно через метод getMessage(), что мы и сделали в блоке catch.

Теперь программа не закроется аварийно, а будет просто выводить сообщения в всплывающих Toast.

Оператор throws

Если метод может породить исключение, которое он сам не обрабатывает, он должен задать это поведение так, чтобы вызывающий его код мог позаботиться об этом исключении. Для этого к объявлению метода добавляется конструкция throws, которая перечисляет типы исключений (кроме исключений Error и RuntimeException и их подклассов).

Общая форма объявления метода с оператором throws:

 тип имя_метода(список_параметров) throws список_исключений < // код внутри метода >

В фрагменте список_исключений можно указать список исключений через запятую.

Создадим метод, который может породить исключение, но не обрабатывает его. А в щелчке кнопки вызовем его.

 // Метод без обработки исключения public void createCat() < Toast.makeText(this, "Вы создали котёнка", Toast.LENGTH_LONG).show(); throw new NullPointerException("Кота не существует"); >// Щелчок кнопки public void onClick(View v)

Если вы запустите пример, то получите ошибку. Исправим код.

 // Без изменений public void createCat() throws NullPointerException < Toast.makeText(this, "Вы создали котёнка", Toast.LENGTH_LONG).show(); throw new NullPointerException("Кота не существует"); >// Щелчок кнопки public void onClick(View v) < try < createCat(); >catch (NullPointerException e) < // TODO: handle exception Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show(); >> 

Мы поместили вызов метода в блок try и вызвали блок catch с нужным типом исключения. Теперь ошибки не будет.

Оператор finally

Когда исключение передано, выполнение метода направляется по нелинейному пути. Это может стать источником проблем. Например, при входе метод открывает файл и закрывает при выходе. Чтобы закрытие файла не было пропущено из-за обработки исключения, был предложен механизм finally.

Ключевое слово finally создаёт блок кода, который будет выполнен после завершения блока try/catch, но перед кодом, следующим за ним. Блок будет выполнен, независимо от того, передано исключение или нет. Оператор finally не обязателен, однако каждый оператор try требует наличия либо catch, либо finally.

Встроенные исключения Java

Существуют несколько готовых системных исключений. Большинство из них являются подклассами типа RuntimeException и их не нужно включать в список throws. Вот небольшой список непроверяемых исключений.

  • ArithmeticException - арифметическая ошибка, например, деление на нуль
  • ArrayIndexOutOfBoundsException - выход индекса за границу массива
  • ArrayStoreException - присваивание элементу массива объекта несовместимого типа
  • ClassCastException - неверное приведение
  • EnumConstantNotPresentException - попытка использования неопределённого значения перечисления
  • IllegalArgumentException - неверный аргумент при вызове метода
  • IllegalMonitorStateException - неверная операция мониторинга
  • IllegalStateException - некорректное состояние приложения
  • IllegalThreadStateException - запрашиваемая операция несовместима с текущим потоком
  • IndexOutofBoundsException - тип индекса вышел за допустимые пределы
  • NegativeArraySizeException - создан массив отрицательного размера
  • NullPointerException - неверное использование пустой ссылки
  • NumberFormatException - неверное преобразование строки в числовой формат
  • SecurityException - попытка нарушения безопасности
  • StringIndexOutOfBounds - попытка использования индекса за пределами строки
  • TypeNotPresentException - тип не найден
  • UnsupportedOperationException - обнаружена неподдерживаемая операция

Список проверяемых системных исключений, которые можно включать в список throws.

  • ClassNotFoundException - класс не найден
  • CloneNotSupportedException - попытка клонировать объект, который не реализует интерфейс Cloneable
  • IllegalAccessException - запрещен доступ к классу
  • InstantiationException - попытка создать объект абстрактного класса или интерфейса
  • InterruptedException - поток прерван другим потоком
  • NoSuchFieldException - запрашиваемое поле не существует
  • NoSuchMethodException - запрашиваемый метод не существует
  • ReflectiveOperationException - исключение, связанное с рефлексией

Создание собственных классов исключений

Система не может предусмотреть все исключения, иногда вам придётся создать собственный тип исключения для вашего приложения. Вам нужно наследоваться от Exception (напомню, что этот класс наследуется от Trowable) и переопределить нужные методы класса Throwable. Либо вы можете наследоваться от уже существующего типа, который наиболее близок по логике с вашим исключением.

  • final void addSuppressed(Throwable exception) - добавляет исключение в список подавляемых исключений (JDK 7)
  • Throwable fillInStackTrace() - возвращает объект класса Throwable, содержащий полную трассировку стека.
  • Throwable getCause() - возвращает исключение, лежащее под текущим исключение или null
  • String getLocalizedMessage() - возвращает локализованное описание исключения
  • String getMessage() - возвращает описание исключения
  • StackTraceElement[] getStackTrace() - возвращает массив, содержащий трассировку стека и состояний из элементов класса StackTraceElement
  • final Throwable[] getSuppressed() - получает подавленные исключения (JDK 7)
  • Throwable initCause(Throwable exception) - ассоциирует исключение с вызывающим исключением. Возвращает ссылку на исключение.
  • void printStackTrace() - отображает трассировку стека
  • void printStackTrace(PrintStream stream) - посылает трассировку стека в заданный поток
  • void printStackTrace(PrintWriter stream) - посылает трассировку стека в заданный поток
  • void setStackTrace(StackTraceElement elements[]) - устанавливает трассировку стека для элементов (для специализированных приложений)
  • String toString() - возвращает объект класса String, содержащий описание исключения.

Самый простой способ - создать класс с конструктором по умолчанию.

 // Если этот код работает, его написал Александр Климов, // а если нет, то не знаю, кто его писал. package ru.alexanderklimov.exception; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; public class MainActivity extends AppCompatActivity < @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); >public void testMethod() throws HungryCatException < System.out.println("Возбуждаем HungryCatException из метода testMethod()"); throw new HungryCatException(); // конструктор по умолчанию >public void onClick(View view) < try < testMethod(); >catch (HungryCatException e) < e.printStackTrace(); System.out.println("Наше исключение перехвачено"); >> class HungryCatException extends Exception < >> 

Мы создали собственный класс HungryCatException, в методе testMethod() его возбуждаем, а по нажатию кнопки вызываем этот метод. В результате наше исключение сработает.

Создать класс исключения с конструктором, который получает аргумент-строку, также просто.

 // Если этот код работает, его написал Александр Климов, // а если нет, то не знаю, кто его писал. package ru.alexanderklimov.exception; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; public class MainActivity extends AppCompatActivity < @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); >public void testMethod() throws HungryCatException < System.out.println("Возбуждаем HungryCatException из метода testMethod()"); throw new HungryCatException(); // конструктор по умолчанию >public void testMethod2() throws HungryCatException < System.out.println("Возбуждаем HungryCatException из метода testMethod2()"); throw new HungryCatException("Создано во втором методе"); >public void onClick(View view) < try < testMethod(); >catch (HungryCatException e) < e.printStackTrace(); System.out.println("Наше исключение перехвачено"); >try < testMethod2(); >catch (HungryCatException e) < e.printStackTrace(); >> class HungryCatException extends Exception < HungryCatException() < >HungryCatException(String msg) < super(msg); >> > 

Ещё вариант. Добавим также метод toString().

 class CustomException extends Exception < String message; CustomException(String str) < message = str; >public String toString() < return ("Custom Exception Occurred: " + message); >> // где-то вызываем try < throw new CustomException("This is a custom message"); >catch (CustomException e)

Теперь класс содержит два конструктора. Во втором конструкторе используется конструктор родительского класса с аргументом String, вызываемый ключевым словом super.

Перехват произвольных исключений

Можно создать универсальный обработчик, перехватывающий любые типы исключения. Осуществляется это перехватом базового класса всех исключений Exception:

cacth(Exception e)

Подобная конструкция не упустит ни одного исключения, поэтому её следует размещать в самом конце списка обработчиков, во избежание блокировки следующих за ней обработчиков исключений.

Основные правила обработки исключений

Используйте исключения для того, чтобы:

  • обработать ошибку на текущем уровне (избегайте перехватывать исключения, если не знаете, как с ними поступить)
  • исправить проблему и снова вызвать метод, возбудивший исключение
  • предпринять все необходимые действия и продолжить выполнение без повторного вызова действия
  • попытаться найти альтернативный результат вместо того, который должен был бы произвести вызванный метод
  • сделать все возможное в текущем контексте и заново возбудить это же исключение, перенаправив его на более высокий уровень
  • сделать все, что можно в текущем контексте, и возбудить новое исключение, перенаправив его на более высокий уровень
  • завершить работу программы
  • упростить программу (если используемая схема обработки исключений делает все только сложнее, значит, она никуда не годится)
  • добавить вашей библиотеке и программе безопасности

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *