Обработка ошибок ввода

Добавлено 31 мая 2021 в 22:08

Большинство программ, имеющих какой-либо пользовательский интерфейс, должны обрабатывать вводимые пользователем данные. В программах, которые мы писали, мы использовали std::cin, чтобы попросить пользователя ввести текст. Поскольку ввод текста имеет произвольную форму (пользователь может вводить что угодно), пользователю очень легко ввести данные, которые не ожидаются.

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

В этом уроке мы подробно рассмотрим способы, которыми пользователь может вводить недопустимые текстовые данные через std::cin, и покажем вам несколько разных способов обработки таких случаев.

std::cin, буферы и извлечение

Чтобы обсудить, как std::cin и operator>> могут давать сбой, сначала полезно немного узнать, как они работают.

Когда мы используем operator>> для получения пользовательского ввода и помещения его в переменную, это называется «извлечением». Соответственно, в этом контексте оператор >> называется оператором извлечения.

Когда пользователь вводит данные в ответ на операцию извлечения, эти данные помещаются в буфер внутри std::cin. Буфер (также называемый буфером данных) – это просто часть памяти, отведенная для временного хранения данных, пока они перемещаются из одного места в другое. В этом случае буфер используется для хранения пользовательских входных данных, пока они ожидают извлечения в переменные.

При использовании оператора извлечения происходит следующая процедура:

  • Если во входном буфере уже есть данные, то для извлечения используются они.
  • Если входной буфер не содержит данных, пользователя просят ввести данные для извлечения (так бывает в большинстве случаев). Когда пользователь нажимает Enter, во входной буфер помещается символ ‘n’.
  • operator>> извлекает столько данных из входного буфера, сколько может, в переменную (игнорируя любые начальные пробельные символы, такие как пробелы, табуляции или ‘n’).
  • Любые данные, которые не могут быть извлечены, остаются во входном буфере для следующего извлечения.

Извлечение завершается успешно, если из входного буфера извлечен хотя бы один символ. Любые неизвлеченные входные данные остаются во входном буфере для дальнейшего извлечения. Например:

int x{};
std::cin >> x;

Если пользователь вводит «5a», 5 будет извлечено, преобразовано в целое число и присвоено переменной x. А «an» останется во входном потоке для следующего извлечения.

Извлечение не выполняется, если входные данные не соответствуют типу переменной, в которую они извлекаются. Например:

int x{};
std::cin >> x;

Если бы пользователь ввел ‘b’, извлечение не удалось бы, потому что ‘b’ не может быть извлечено в переменную типа int.

Проверка ввода

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

Есть три основных способа проверки ввода:

  • встроенный (по мере печати пользователя):
    • прежде всего, не позволить пользователю вводить недопустимые данные;
  • пост-запись (после печати пользователя):
    • позволить пользователю ввести в строку всё, что он хочет, затем проверить правильность строки и, если она корректна, преобразовать строку в окончательный формат переменной;
    • позволить пользователю вводить всё, что он хочет, позволить std::cin и operator>> попытаться извлечь данные и обработать случаи ошибок.

Некоторые графические пользовательские интерфейсы и расширенные текстовые интерфейсы позволяют проверять входные данные, когда пользователь их вводит (символ за символом). В общем случае, программист предоставляет функцию проверки, которая принимает входные данные, введенные пользователем, и возвращает true, если входные данные корректны, и false в противном случае. Эта функция вызывается каждый раз, когда пользователь нажимает клавишу. Если функция проверки возвращает истину, клавиша, которую только что нажал пользователь, принимается. Если функция проверки возвращает false, введенный пользователем символ отбрасывается (и не отображается на экране). Используя этот метод, вы можете гарантировать, что любые входные данные, вводимые пользователем, гарантированно будут корректными, потому что любые недопустимые нажатия клавиш обнаруживаются и немедленно отбрасываются. Но, к сожалению, std::cin не поддерживает этот стиль проверки.

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

Чаще всего мы позволяем std::cin и оператору извлечения выполнять эту тяжелую работу. В этом методе мы позволяем пользователю вводить всё, что он хочет, заставляем std::cin и operator>> попытаться извлечь данные и справиться с последствиями, если это не удастся. Это самый простой способ, о котором мы поговорим ниже.

Пример программы

Рассмотрим следующую программу-калькулятор, в которой нет обработки ошибок:

#include <iostream>
 
double getDouble()
{
    std::cout << "Enter a double value: ";
    double x{};
    std::cin >> x;
    return x;
}
 
char getOperator()
{
    std::cout << "Enter one of the following: +, -, *, or /: ";
    char op{};
    std::cin >> op;
    return op;
}
 
void printResult(double x, char operation, double y)
{
    switch (operation)
    {
    case '+':
        std::cout << x << " + " << y << " is " << x + y << 'n';
        break;
    case '-':
        std::cout << x << " - " << y << " is " << x - y << 'n';
        break;
    case '*':
        std::cout << x << " * " << y << " is " << x * y << 'n';
        break;
    case '/':
        std::cout << x << " / " << y << " is " << x / y << 'n';
        break;
    }
}
 
int main()
{
    double x{ getDouble() };
    char operation{ getOperator() };
    double y{ getDouble() };
 
    printResult(x, operation, y);
 
    return 0;
}

Эта простая программа просит пользователя ввести два числа и математический оператор.

Enter a double value: 5
Enter one of the following: +, -, *, or /: *
Enter a double value: 7
5 * 7 is 35

Теперь подумайте, где неверный ввод пользователя может нарушить работу этой программы.

Сначала мы просим пользователя ввести несколько чисел. Что, если он введет что-то, отличающееся от числа (например, ‘q’)? В этом случае извлечение не удастся.

Во-вторых, мы просим пользователя ввести один из четырех возможных символов. Что, если он введет символ, отличный от ожидаемых? Мы сможем извлечь входные данные, но пока не обрабатываем то, что происходит после.

В-третьих, что, если мы попросим пользователя ввести символ, а он введет строку типа «*q hello». Хотя мы можем извлечь нужный нам символ ‘*’, в буфере останутся дополнительные входные данные, которые могут вызвать проблемы в будущем.

Типы недопустимых входных текстовых данных

Обычно мы можем разделить ошибки ввода текста на четыре типа:

  1. извлечение входных данных выполняется успешно, но входные данные не имеют смысла для программы (например, ввод ‘k’ в качестве математического оператора);
  2. извлечение входных данных выполняется успешно, но пользователь вводит дополнительные данные (например, вводя «*q hello» в качестве математического оператора);
  3. ошибка извлечения входных данных (например, попытка ввести ‘q’ при запросе ввода числа);
  4. извлечение входных данных выполнено успешно, но пользователь выходит за пределы значения числа.

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

Давайте разберемся в каждом из этих случаев и в том, как их обрабатывать с помощью std::cin.

Случай ошибки 1: извлечение успешно, но входные данные не имеют смысла

Это самый простой случай. Рассмотрим следующий вариант выполнения приведенной выше программы:

Enter a double value: 5
Enter one of the following: +, -, *, or /: k
Enter a double value: 7

В этом случае мы попросили пользователя ввести один из четырех символов, но вместо этого он ввел ‘k’. ‘k’ – допустимый символ, поэтому std::cin успешно извлекает его в переменную op, и она возвращается в main. Но наша программа не ожидала этого, поэтому она не обрабатывает этот случай правильно (и, таким образом, ничего не выводит).

Решение здесь простое: выполните проверку ввода. Обычно она состоит из 3 шагов:

  1. убедитесь, что пользовательский ввод соответствует вашим ожиданиям;
  2. если да, верните значение вызывающей функции;
  3. если нет, сообщите пользователю, что что-то пошло не так, и попросите его повторить попытку.

Вот обновленная функция getOperator(), которая выполняет проверку ввода.

char getOperator()
{
    while (true) // Цикл, пока пользователь не введет допустимые данные
    {
        std::cout << "Enter one of the following: +, -, *, or /: ";
        char operation{};
        std::cin >> operation;
 
        // Проверяем, ввел ли пользователь подходящие данные
        switch (operation)
        {
        case '+':
        case '-':
        case '*':
        case '/':
          return operation; // возвращаем символ вызывающей функции
        default: // в противном случае сообщаем пользователю, что пошло не так
            std::cout << "Oops, that input is invalid.  Please try again.n";
        }
    } // и попробуем еще раз
}

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

Случай ошибки 2: извлечение успешно, но с посторонними входными данными

Рассмотрим следующий вариант выполнения приведенной выше программы:

Enter a double value: 5*7

Как думаете, что будет дальше?

Enter a double value: 5*7
Enter one of the following: +, -, *, or /: Enter a double value: 5 * 7 is 35

Программа выводит правильный ответ, но форматирование испорчено. Давайте подробнее разберемся, почему.

Когда пользователь вводит «5*7» в качестве вводных данных, эти данные попадают в буфер. Затем оператор >> извлекает 5 в переменную x, оставляя в буфере «*7n». Затем программа напечатает «Enter one of the following: +, -, *, or /:». Однако когда был вызван оператор извлечения, он видит символы «*7n», ожидающие извлечения в буфере, поэтому он использует их вместо того, чтобы запрашивать у пользователя дополнительные данные. Следовательно, он извлекает символ ‘*’, оставляя в буфере «7n».

После запроса пользователя ввести другое значение double, из буфера извлекается 7 без ожидания ввода пользователя. Поскольку у пользователя не было возможности ввести дополнительные данные и нажать Enter (добавляя символ новой строки), все запросы в выводе идут вместе в одной строке, даже если вывод правильный.

Хотя программа работает, выполнение запутано. Было бы лучше, если бы любые введенные посторонние символы просто игнорировались. К счастью, символы игнорировать легко:

// очищаем до 100 символов из буфера или пока не будет удален символ 'n'
std::cin.ignore(100, 'n');

Этот вызов удалит до 100 символов, но если пользователь ввел более 100 символов, мы снова получим беспорядочный вывод. Чтобы игнорировать все символы до следующего символа ‘n’, мы можем передать std::numeric_limits<std::streamsize>::max() в std::cin.ignore(). std::numeric_limits<std::streamsize>::max() возвращает наибольшее значение, которое может быть сохранено в переменной типа std::streamsize. Передача этого значения в std::cin.ignore() приводит к отключению проверки счетчика.

Чтобы игнорировать всё, вплоть до следующего символа ‘n’, мы вызываем

std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n');

Поскольку эта строка довольно длинная для того, что она делает, будет удобнее обернуть ее в функцию, которую можно вызвать вместо std::cin.ignore().

#include <limits> // для std::numeric_limits
 
void ignoreLine()
{
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n');
}

Поскольку последний введенный пользователем символ должен быть ‘n’, мы можем указать std::cin игнорировать символы в буфере, пока не найдет символ новой строки (который также будет удален).

Давайте обновим нашу функцию getDouble(), чтобы игнорировать любой посторонний ввод:

double getDouble()
{
    std::cout << "Enter a double value: ";
    double x{};
    std::cin >> x;
    ignoreLine();
    return x;
}

Теперь наша программа будет работать, как ожидалось, даже если мы введем «5*7» при первом запросе ввода – 5 будет извлечено, а остальные символы из входного буфера будут удалены. Поскольку входной буфер теперь пуст, при следующем выполнении операции извлечения данные у пользователя будут запрашиваться правильно!

Случай ошибки 3: сбой при извлечении

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

Enter a double value: a

Неудивительно, что программа работает не так, как ожидалось, но интересно, как она дает сбой:

Enter a double value: a
Enter one of the following: +, -, *, or /: Enter a double value: 

и программа внезапно завершается.

Это очень похоже на случай ввода посторонних символов, но немного отличается. Давайте посмотрим подробнее.

Когда пользователь вводит ‘a’, этот символ помещается в буфер. Затем оператор >> пытается извлечь ‘a’ в переменную x, которая имеет тип double. Поскольку ‘a’ нельзя преобразовать в double, оператор >> не может выполнить извлечение. В этот момент происходят две вещи: ‘a’ остается в буфере, а std::cin переходит в «режим отказа».

Находясь в «режиме отказа», будущие запросы на извлечение входных данных будут автоматически завершаться ошибкой. Таким образом, в нашей программе калькулятора вывод запросов всё еще печатается, но любые запросы на дальнейшее извлечение игнорируются. Программа просто выполняется до конца, а затем завершается (без вывода результата потому, что мы не прочитали допустимую математическую операцию).

К счастью, мы можем определить, завершилось ли извлечение сбоем, и исправить это:

if (std::cin.fail()) // предыдущее извлечение не удалось?
{
    // да, давайте разберемся с ошибкой
    std::cin.clear(); // возвращаем нас в "нормальный" режим работы
    ignoreLine();     // и удаляем неверные входные данные
}

Вот и всё!

Давайте, интегрируем это в нашу функцию getDouble():

double getDouble()
{
    while (true) // Цикл, пока пользователь не введет допустимые данные
    {
        std::cout << "Enter a double value: ";
        double x{};
        std::cin >> x;
 
        if (std::cin.fail()) // предыдущее извлечение не удалось?
        {
            // да, давайте разберемся с ошибкой
            std::cin.clear(); // возвращаем нас в "нормальный" режим работы
            ignoreLine();     // и удаляем неверные входные данные
        }
        else // иначе наше извлечение прошло успешно
        {
            ignoreLine();
            return x; // поэтому возвращаем извлеченное нами значение
        }
    }
}

Примечание. До C++11 неудачное извлечение не приводило к изменению извлекаемой переменной. Это означает, что если переменная была неинициализирована, она останется неинициализированной в случае неудачного извлечения. Однако, начиная с C++11, неудачное извлечение из-за недопустимого ввода приведет к тому, что переменная будет инициализирована нулем. Инициализация нулем означает, что для переменной установлено значение 0, 0.0, «» или любое другое значение, в которое 0 преобразуется для этого типа.

Случай ошибки 4: извлечение успешно, но пользователь выходит за пределы значения числа

Рассмотрим следующий простой пример:

#include <cstdint>
#include <iostream>
 
int main()
{
    std::int16_t x{}; // x - 16 бит, может быть от -32768 до 32767
    std::cout << "Enter a number between -32768 and 32767: ";
    std::cin >> x;
 
    std::int16_t y{}; // y - 16 бит, может быть от -32768 до 32767
    std::cout << "Enter another number between -32768 and 32767: ";
    std::cin >> y;
 
    std::cout << "The sum is: " << x + y << 'n';
    return 0;
}

Что произойдет, если пользователь введет слишком большое число (например, 40000)?

Enter a number between -32768 and 32767: 40000
Enter another number between -32768 and 32767: The sum is: 32767

В приведенном выше случае std::cin немедленно переходит в «режим отказа», но также присваивает переменной ближайшее значение в диапазоне. Следовательно, x остается с присвоенным значением 32767. Дополнительные входные данные пропускаются, оставляя y с инициализированным значением 0. Мы можем обрабатывать этот вид ошибки так же, как и неудачное извлечение.

Примечание. До C++11 неудачное извлечение не приводило к изменению извлекаемой переменной. Это означает, что если переменная была неинициализирована, в случае неудачного извлечения она останется неинициализированной. Однако, начиная с C++11, неудачное извлечение вне диапазона приведет к тому, что переменной будет присвоено ближайшее значение в диапазоне.

Собираем всё вместе

Вот наш пример калькулятора с полной проверкой ошибок:

#include <iostream>
#include <limits>
 
void ignoreLine()
{
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n');
}
 
double getDouble()
{
    while (true) // Цикл, пока пользователь не введет допустимые данные
    {
        std::cout << "Enter a double value: ";
        double x{};
        std::cin >> x;
 
        // Проверяем на неудачное извлечение
        if (std::cin.fail()) // предыдущее извлечение не удалось?
        {
            // да, давайте разберемся с ошибкой
            std::cin.clear(); // возвращаем нас в "нормальный" режим работы
            ignoreLine();     // и удаляем неверные входные данные
            std::cout << "Oops, that input is invalid.  Please try again.n";
        }
        else
        {
            ignoreLine(); // удаляем любые посторонние входные данные
 
            // пользователь не может ввести бессмысленное значение double,
            // поэтому нам не нужно беспокоиться о его проверке
            return x;
        }
    }
}
 
char getOperator()
{
    while (true) // Цикл, пока пользователь не введет допустимые данные
    {
        std::cout << "Enter one of the following: +, -, *, or /: ";
        char operation{};
        std::cin >> operation;
        ignoreLine();
 
        // Проверяем, ввел ли пользователь осмысленные данные
        switch (operation)
        {
        case '+':
        case '-':
        case '*':
        case '/':
            return operation; // возвращаем символ вызывающей функции
        default: // в противном случае сообщаем пользователю, что пошло не так
            std::cout << "Oops, that input is invalid.  Please try again.n";
        }
    } // и попробуем еще раз
}
 
void printResult(double x, char operation, double y)
{
    switch (operation)
    {
    case '+':
        std::cout << x << " + " << y << " is " << x + y << 'n';
        break;
    case '-':
        std::cout << x << " - " << y << " is " << x - y << 'n';
        break;
    case '*':
        std::cout << x << " * " << y << " is " << x * y << 'n';
        break;
    case '/':
        std::cout << x << " / " << y << " is " << x / y << 'n';
        break;
    default: // Надежность означает также обработку неожиданных параметров,
             // даже если getOperator() гарантирует, что op в этой
             // конкретной программе корректен 
        std::cerr << "Something went wrong: printResult() got an invalid operator.n";
    }
}
 
int main()
{
    double x{ getDouble() };
    char operation{ getOperator() };
    double y{ getDouble() };
 
    printResult(x, operation, y);
 
    return 0;
}

Заключение

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

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

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

Следующий код очистит любые посторонние входные данные:

std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n');

Следующий код будет проверять и исправлять неудачные извлечения или переполнение:

if (std::cin.fail()) // предыдущее извлечение не удалось или закончилось переполнением?
{
    // да, давайте разберемся с ошибкой
    std::cin.clear(); // возвращаем нас в "нормальный" режим работы
    ignoreLine();     // и удаляем неверные входные данные
}

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

Примечание автора


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

Теги

C++ / CppLearnCppstd::cinДля начинающихОбнаружение ошибокОбработка ошибокОбучениеПрограммирование

title description ms.date dev_langs helpviewer_keywords ms.workload

Handling I/O errors in .NET

Learn how to handle I/O errors in .NET. Map error codes to exceptions, handle exceptions in I/O operations, and handle IOException.

08/27/2018

csharp

vb

I/O, exception handling

I/O, errors

dotnet

dotnetcore

Handling I/O errors in .NET

In addition to the exceptions that can be thrown in any method call (such as an xref:System.OutOfMemoryException when a system is stressed or an xref:System.NullReferenceException due to programmer error), .NET file system methods can throw the following exceptions:

  • xref:System.IO.IOException?displayProperty=nameWithType, the base class of all xref:System.IO exception types. It is thrown for errors whose return codes from the operating system don’t directly map to any other exception type.
  • xref:System.IO.FileNotFoundException?displayProperty=nameWithType.
  • xref:System.IO.DirectoryNotFoundException?displayProperty=nameWithType.
  • xref:System.IO.DriveNotFoundException??displayProperty=nameWithType.
  • xref:System.IO.PathTooLongException?displayProperty=nameWithType.
  • xref:System.OperationCanceledException?displayProperty=nameWithType.
  • xref:System.UnauthorizedAccessException?displayProperty=nameWithType.
  • xref:System.ArgumentException?displayProperty=nameWithType, which is thrown for invalid path characters on .NET Framework and on .NET Core 2.0 and previous versions.
  • xref:System.NotSupportedException?displayProperty=nameWithType, which is thrown for invalid colons in .NET Framework.
  • xref:System.Security.SecurityException?displayProperty=nameWithType, which is thrown for applications running in limited trust that lack the necessary permissions on .NET Framework only. (Full trust is the default on .NET Framework.)

Mapping error codes to exceptions

Because the file system is an operating system resource, I/O methods in both .NET Core and .NET Framework wrap calls to the underlying operating system. When an I/O error occurs in code executed by the operating system, the operating system returns error information to the .NET I/O method. The method then translates the error information, typically in the form of an error code, into a .NET exception type. In most cases, it does this by directly translating the error code into its corresponding exception type; it does not perform any special mapping of the error based on the context of the method call.

For example, on the Windows operating system, a method call that returns an error code of ERROR_FILE_NOT_FOUND (or 0x02) maps to a xref:System.IO.FileNotFoundException, and an error code of ERROR_PATH_NOT_FOUND (or 0x03) maps to a xref:System.IO.DirectoryNotFoundException.

However, the precise conditions under which the operating system returns particular error codes is often undocumented or poorly documented. As a result, unexpected exceptions can occur. For example, because you are working with a directory rather than a file, you would expect that providing an invalid directory path to the xref:System.IO.DirectoryInfo.%23ctor%2A constructor throws a xref:System.IO.DirectoryNotFoundException. However, it may also throw a xref:System.IO.FileNotFoundException.

Exception handling in I/O operations

Because of this reliance on the operating system, identical exception conditions (such as the directory not found error in our example) can result in an I/O method throwing any one of the entire class of I/O exceptions. This means that, when calling I/O APIs, your code should be prepared to handle most or all of these exceptions, as shown in the following table:

Exception type .NET Core/.NET 5+ .NET Framework
xref:System.IO.IOException Yes Yes
xref:System.IO.FileNotFoundException Yes Yes
xref:System.IO.DirectoryNotFoundException Yes Yes
xref:System.IO.DriveNotFoundException? Yes Yes
xref:System.IO.PathTooLongException Yes Yes
xref:System.OperationCanceledException Yes Yes
xref:System.UnauthorizedAccessException Yes Yes
xref:System.ArgumentException .NET Core 2.0 and earlier Yes
xref:System.NotSupportedException No Yes
xref:System.Security.SecurityException No Limited trust only

Handling IOException

As the base class for exceptions in the xref:System.IO namespace, xref:System.IO.IOException is also thrown for any error code that does not map to a predefined exception type. This means that it can be thrown by any I/O operation.

[!IMPORTANT]
Because xref:System.IO.IOException is the base class of the other exception types in the xref:System.IO namespace, you should handle in a catch block after you’ve handled the other I/O-related exceptions.

In addition, starting with .NET Core 2.1, validation checks for path correctness (for example, to ensure that invalid characters are not present in a path) have been removed, and the runtime throws an exception mapped from an operating system error code rather than from its own validation code. The most likely exception to be thrown in this case is an xref:System.IO.IOException, although any other exception type could also be thrown.

Note that, in your exception handling code, you should always handle the xref:System.IO.IOException last. Otherwise, because it is the base class of all other IO exceptions, the catch blocks of derived classes will not be evaluated.

In the case of an xref:System.IO.IOException, you can get additional error information from the IOException.HResult property. To convert the HResult value to a Win32 error code, you strip out the upper 16 bits of the 32-bit value. The following table lists error codes that may be wrapped in an xref:System.IO.IOException.

HResult Constant Description
ERROR_SHARING_VIOLATION 32 The file name is missing, or the file or directory is in use.
ERROR_FILE_EXISTS 80 The file already exists.
ERROR_INVALID_PARAMETER 87 An argument supplied to the method is invalid.
ERROR_ALREADY_EXISTS 183 The file or directory already exists.

You can handle these using a When clause in a catch statement, as the following example shows.

[!code-csharpio-exception-handling]
[!code-vbio-exception-handling]

See also

  • Handling and throwing exceptions in .NET
  • Exception handling (Task Parallel Library)
  • Best practices for exceptions
  • How to use specific exceptions in a catch block

Java_Deep_7.4-5020-83cb21.png

JavaSpec_Welcome_970x90-1801-439a19.png

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

Что называют исключением. Исключения в мире программирования

В программировании исключением называют возникновение ошибки (ошибок) и различных непредвиденных ситуаций в процессе выполнения программы. Исключения могут появляться как в итоге неправильных действий юзера, так и из-за потери сетевого соединения с сервером, отсутствии нужного ресурса на диске и т. п. Также среди причин исключений — ошибки программирования либо неверное использование API.

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

Используемые ключевые слова

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

Вышеперечисленные ключевые слова необходимы для создания в коде ряда специальных обрабатывающих конструкций: try{}finally{}, try{}catch, try{}catch{}finally.

Кроме того:
1. Для возбуждения исключения используем throw.
2. Для предупреждения в сигнатуре методов о том, что метод может выбросить исключение, применяем throws.

Давайте на примере посмотрим, как используются ключевые слова в Java-программе:

//метод считывает строку с клавиатуры

public String input() throws MyException {//предупреждаем с помощью throws,
// что метод может выбросить исключение MyException
      BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    String s = null;
//в блок try заключаем код, в котором может произойти исключение, в данном
// случае компилятор нам подсказывает, что метод readLine() класса
// BufferedReader может выбросить исключение ввода/вывода
    try {
        s = reader.readLine();
// в блок  catch заключаем код по обработке исключения IOException
    } catch (IOException e) {
        System.out.println(e.getMessage());
// в блоке finally закрываем поток чтения
    } finally {
// при закрытии потока тоже возможно исключение, например, если он не был открыт, поэтому “оборачиваем” код в блок try
        try {
            reader.close();
// пишем обработку исключения при закрытии потока чтения
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    if (s.equals("")) {
// мы решили, что пустая строка может нарушить в дальнейшем работу нашей программы, например, на результате этого метода нам надо вызывать метод substring(1,2), поэтому мы вынуждены прервать выполнение программы с генерацией своего типа исключения MyException с помощью throw
        throw new MyException("String can not be empty!");
    }
    return s;
}

Зачем нам механизм исключений?

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

То есть мы видим, что из-за правильных действий дорожной службы шоферы крупногабаритных транспортных средств:
1) получили возможность заранее изменить свой путь;
2) были предупреждены об опасности;
3) были предупреждены о невозможности проезжать по мосту при определённых условиях.

Вот как наш жизненный пример соотносится с применением исключения на Java:

1-20219-9bd18c.jpg

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

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

В общем, механизм исключений в Java работает схожим образом. На стадии разработки программы мы выполняем «ограждение» опасных участков кода в отношении наших исключений, используя блок try{}. Чтобы предусмотреть запасные пути, применяем блок catch{}. Код, выполняемый в программе при любом исходе, пишем в блоке finally{}.

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

Что касается программирования на Java, то мы, когда пишем свои классы и методы, далеко не всегда можем предвидеть контекст их применения другими программистами в своих программах, а значит, не можем со стопроцентной вероятностью предвидеть правильный путь для разрешения исключительных ситуаций. Но предупредить коллег о возможной исключительной ситуации мы всё-таки должны, и это не что иное, как правило хорошего тона.

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

Предупреждаем о неприятностях

Если мы не планируем обрабатывать исключение в собственном методе, но желаем предупредить пользователей метода о возможной исключительной ситуации, мы используем, как это уже было упомянуто, ключевое слово throws. В сигнатуре метода оно означает, что при некоторых обстоятельствах метод может выбросить исключение. Это предупреждение становится частью интерфейса метода и даёт право пользователю на создание своего варианта реализации обработчика исключения.

После упоминания ключевого слова throws мы указываем тип исключения. Как правило, речь идёт о наследниках класса Exception Java. Но так как Java — это объектно-ориентированный язык программирования, все исключения представляют собой объекты.

2-20219-ee1e82.jpg

Когда возникают ошибки при выполнении программы, исполняющая среда Java Virtual Machine обеспечивает создание объекта нужного типа, используя иерархию исключений Java — речь идёт о множестве возможных исключительных ситуаций, которые унаследованы от класса Throwable — общего предка. При этом исключительные ситуации, которые возникают в программе, делят на 2 группы:
1. Ситуации, при которых восстановление нормальной дальнейшей работы невозможно.
2. Ситуации с возможностью восстановления.

К первой группе можно отнести случаи, при которых возникают исключения, которые унаследованы из класса Error. Это ошибки, возникающие во время выполнения программы при сбое работы Java Virtual Machine, переполнении памяти либо сбое системы. Как правило, такие ошибки говорят о серьёзных проблемах, устранение которых программными средствами невозможно. Данный вид исключений в Java относят к неконтролируемым исключениям на стадии компиляции (unchecked). К этой же группе относятся и исключения-наследники класса Exception, генерируемые Java Virtual Machine в процессе выполнения программы — RuntimeException. Данные исключения тоже считаются unchecked на стадии компиляции, а значит, написание кода по их обработке необязательно.

Что касается второй группы, то к ней относят ситуации, которые можно предвидеть ещё на стадии написания приложения, поэтому для них код обработки должен быть написан. Это контролируемые исключения (checked). И в большинстве случаев Java-разработчики работают именно с этими исключениями, выполняя их обработку.

Создание исключения

В процессе исполнения программы исключение генерируется Java Virtual Machine либо вручную посредством оператора throw. В таком случае в памяти происходит создание объекта исключения, выполнение основного кода прерывается, а встроенный в JVM обработчик исключений пробует найти способ обработать это самое исключение.

Обработка исключения

Обработка исключений в Java подразумевает создание блоков кода и производится в программе посредством конструкций try{}finally{}, try{}catch, try{}catch{}finally.

3-20219-4ec690.jpg

В процессе возбуждения исключения в try обработчик исключения ищется в блоке catch, который следует за try. При этом если в catch присутствует обработчик данного вида исключения, происходит передача управления ему. Если же нет, JVM осуществляет поиск обработчика данного типа исключения, используя для этого цепочку вызова методов. И так происходит до тех пор, пока не находится подходящий catch. После того, как блок catch выполнится, управление переходит в необязательный блок finally. Если подходящий блок catch найден не будет, Java Virtual Machine остановит выполнение программы, выведя стек вызовов методов под названием stack trace. Причём перед этим выполнится код блока finally при наличии такового.

Рассмотрим практический пример обработки исключений:

public class Print {

     void print(String s) {
        if (s == null) {
            throw new NullPointerException("Exception: s is null!");
        }
        System.out.println("Inside method print: " + s);
    }

    public static void main(String[] args) {
        Print print = new Print();
        List list= Arrays.asList("first step", null, "second step");

        for (String s:list) {
            try {
                print.print(s);
            }
            catch (NullPointerException e) {
                System.out.println(e.getMessage());
                System.out.println("Exception was processed. Program continues");
            }
            finally {
                System.out.println("Inside bloсk finally");
            }
            System.out.println("Go program....");
            System.out.println("-----------------");
        }

    }
    }

А теперь глянем на результаты работы метода main:

Inside method print: first step
Inside bloсk finally
Go program....
-----------------
Exception: s is null!
Exception was processed. Program continues
Inside bloсk finally
Go program....
-----------------
Inside method print: second step
Inside bloсk finally
Go program....
-----------------

Блок finally чаще всего используют, чтобы закрыть открытые в try потоки либо освободить ресурсы. Но при написании программы уследить за закрытием всех ресурсов возможно не всегда. Чтобы облегчить жизнь разработчикам Java, была предложена конструкция try-with-resources, автоматически закрывающая ресурсы, открытые в try. Используя try-with-resources, мы можем переписать наш первый пример следующим образом:

public String input() throws MyException {
    String s = null;
    try(BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))){
        s = reader.readLine();
   } catch (IOException e) {
       System.out.println(e.getMessage());
   }
    if (s.equals("")){
        throw new MyException ("String can not be empty!");
    }
    return s;
}

А благодаря появившимся возможностям Java начиная с седьмой версии, мы можем ещё и объединять в одном блоке перехват разнотипных исключений, делая код компактнее и читабельнее:

public String input() {
    String s = null;
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
        s = reader.readLine();
        if (s.equals("")) {
            throw new MyException("String can not be empty!");
        }
    } catch (IOException | MyException e) {
        System.out.println(e.getMessage());
    }
    return s;
}

Итоги

Итак, применение исключений в Java повышает отказоустойчивость программы благодаря использованию запасных путей. Кроме того, появляется возможность отделить код обработки исключительных ситуаций от логики основного кода за счёт блоков catch и переложить обработку исключений на пользователя кода посредством throws.

Основные вопросы об исключениях в Java

1.Что такое проверяемые и непроверяемые исключения?
Если говорить коротко, то первые должны быть явно пойманы в теле метода либо объявлены в секции throws метода. Вторые вызываются проблемами, которые не могут быть решены. Например, это нулевой указатель или деление на ноль. Проверяемые исключения очень важны, ведь от других программистов, использующих ваш API, вы ожидаете, что они знают, как обращаться с исключениями. К примеру, наиболее часто встречаемое проверяемое исключение — IOException, непроверяемое — RuntimeException.
2.Почему переменные, определённые в try, нельзя использовать в catch либо finally?
Давайте посмотрим на нижеследующий код. Обратите внимание, что строку s, которая объявлена в блоке try, нельзя применять в блоке catch. То есть данный код не скомпилируется.

try {
    File file = new File("path");
    FileInputStream fis = new FileInputStream(file);
    String s = "inside";
} catch (FileNotFoundException e) {
    e.printStackTrace();
    System.out.println(s);
}

А всё потому, что неизвестно, где конкретно в try могло быть вызвано исключение. Вполне вероятно, что оно было вызвано до объявления объекта.
3.Почему Integer.parseInt(null) и Double.parseDouble(null) вызывают разные исключения?
Это проблема JDK. Так как они были разработаны разными людьми, то заморачиваться вам над этим не стоит:

Integer.parseInt(null);
// вызывает java.lang.NumberFormatException: null

Double.parseDouble(null);
// вызывает java.lang.NullPointerException

4.Каковы основные runtime exceptions в Java?
Вот лишь некоторые из них:

IllegalArgumentException
ArrayIndexOutOfBoundsException

Их можно задействовать в операторе if, если условие не выполняется:

if (obj == null) {
   throw new IllegalArgumentException("obj не может быть равно null");

5.Возможно ли поймать в одном блоке catch несколько исключений?
Вполне. Пока классы данных исключений можно отследить вверх по иерархии наследования классов до одного и того же суперкласса, возможно применение только этого суперкласса.
6.Способен ли конструктор вызывать исключения?
Способен, ведь конструктор — это лишь особый вид метода.

class FileReader{
    public FileInputStream fis = null;

    public FileReader() throws IOException{
        File dir = new File(".");//get current directory
        File fin = new File(dir.getCanonicalPath() + File.separator + "not-existing-file.txt");
        fis = new FileInputStream(fin);
    }
}

7.Возможен ли вызов исключений в final?
В принципе, можете сделать таким образом:

public static void main(String[] args) {
    File file1 = new File("path1");
    File file2 = new File("path2");
    try {

        FileInputStream fis = new FileInputStream(file1);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        try {
            FileInputStream fis = new FileInputStream(file2);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Но если желаете сохранить читабельность, объявите вложенный блок try-catch в качестве нового метода и вставьте вызов данного метода в блок finally.

finally. 
public static void main(String[] args) {
    File file1 = new File("path1");
    File file2 = new File("path2");
    try {

        FileInputStream fis = new FileInputStream(file1);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        methodThrowException();
    }
}

JavaSpec_Welcome_970x90-1801-439a19.png

Глава 9

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

Основные навыки и понятия

  • Представление об иерархии исключений
  • Использование ключевых слов try и catch
  • Последствия неперехвата исключений
  • Применение нескольких операторов catch
  • Перехват исключений, генерируемых подклассами
  • Вложенные блоки try
  • Генерирование исключений
  • Представление о членах класса Throwable
  • Использование ключевого слова finally
  • Использование ключевого слова throws
  • Представление о исключениях, встроенные в Java
  • Создание специальных классов исключений

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

Преимущество обработки исключений заключается в том, что она автоматически предусматривает реакцию на многие ошибки, избавляя от необходимости писать вручную соответствующий код. Например, в некоторых старых языках программирования предусматривается возврат специального кода при возникновении ошибки в ходе выполнения метода. Этот код приходится проверять вручную при каждом вызове метода. Такой подход к обработке ошибок вручную трудоемок и чреват погрешностями. Обработка исключений упрощает этот процесс, давая возможность определять в программе блок кода, называемый обработчиком исключения и автоматически выполняющийся при возникновении ошибки. Это избавляет от необходимости проверять вручную, насколько удачно или неудачно была выполнена та или иная операция или вызов метода. Если возникнет ошибка, все необходимые действия по ее обработке выполнит обработчик исключений.

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

Иерархия исключений

В Java все исключения представлены отдельными классами. Все классы исключений являются потомками класса Throwable. Так, если в программе возникнет исключительная ситуация, будет сгенерирован объект класса, соответствующего определенному типу исключения. У класса Throwable имеются два непосредственных подкласса: Exception и Error. Исключения типа Error относятся к ошибкам, возникающим в виртуальной машине Java, а не в прикладной программе. Контролировать такие исключения невозможно, поэтому реакция на них в прикладной программе, как правило, не предусматривается. В связи с этим исключения данного типа не будут описываться в этой книге.

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

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

Для обработки исключений в Java предусмотрены пять ключевых слов: try, catch, throw, throws и finally. Они образуют единую подсистему, в которой использование одного ключевого слова почти всегда автоматически влечет за собой употребление другого. Каждое из упомянутых выше ключевых слов будет подробно рассмотрено далее в этой главе. Но прежде следует дать общее представление об их роли в процессе обработки исключений. Поэтому ниже поясняется вкратце, каким образом они действуют.

Операторы, в которых требуется отслеживать появление исключений, помещаются в блок try. Если в блоке try будет сгенерировано исключение, его можно перехватить и обработать нужным образом. Системные исключения генерируются автоматически. А для того чтобы сгенерировать исключение вручную, следует воспользоваться ключевым словом throw. Иногда возникает потребность обрабатывать исключения за пределами метода, в котором они возникают, и для этой цели служит ключевое слово throws. Если же некоторый фрагмент кода должен быть выполнен обязательно и независимо от того, возникнет исключение или нет, его следует поместить в блок finally.

Использование ключевых слов try и catch

Основными языковыми средствами обработки исключений являются ключевые слова try и catch. Они используются совместно. Это означает, что нельзя указать ключевое слово catch в коде, не указав ключевое слово try. Ниже приведена общая форма записи блоков try/catch, предназначенных для обработки исключений.

try {
    // Блок кода, в котором должны отслеживаться ошибки
}
catch (тип_исключения_1 объект_исключения) {
    // Обработчик исключения тип_исключения_1
}
catch (тип_исключения_2 объект_исключения) {
    // Обработчик исключения тип_исключения_2
}

В скобках, следующих за ключевым словом catch, указываются тип исключения и переменная, ссылающаяся на объект данного типа. Когда возникает исключение, оно перехватывается соответствующим оператором catch, обрабатывающим это исключение. Как следует из приведенной выше общей формы записи, с одним блоком try может быть связано несколько операторов catch. Тип исключения определяет, какой именно оператор catch будет выполняться. Так, если тип исключения соответствует типу оператора catch, то именно он и будет выполнен, а остальные операторы catch — пропущены. При перехвате исключения переменной, указанной в скобках после ключевого слова catch, присваивается ссылка на объект_исключения.

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

На заметку.
В версии JDK 7 внедрена новая форма оператора try, поддерживающая автоматическое управления ресурсами и называемая оператором try с ресурсами. Более подробно она описывается в главе 10 при рассмотрении потоков ввода-вывода, в том числе и тех, что связаны с файлами, поскольку потоки ввода-вывода относятся к числу ресурсов, наиболее употребительных в прикладных программах.

Простой пример обработки исключений

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

// Демонстрация обработки исключений,
class ExcDemol {
    public static void main (String args[]) {
        int nums[] = new int[4];

        // Создание блока try.
        try {
            System.out.println("Before exception is generated.");

            // Попытка обратиться за границы массива.
            nums[7] = 10;
            System.out.println("this won't be displayed");
        }
        // Перехват исключения в связи с обращением за границы массива.
        catch (ArraylndexOutOfBoundsException exc) {
            System.out.println("Index out-of-bounds!");
        }
        System.out.println("After catch statement.");
    }
}

Результат выполнения данной программы выглядит следующим образом:

Before exception is generated.
Index out-of-bounds!
After catch statement.

Несмотря на всю простоту данного примера программы, он наглядно демонстрирует несколько важных особенностей обработки исключений. Во-первых, код, подлежащий проверке на наличие ошибок, помещается в блок try. И во-вторых, когда возникает исключение (в данном случае это происходит при попытке обратиться за границы массива), выполнение блока try прерывается и управление получает блок catch. Следовательно, явного обращения к блоку catch не происходит, но переход к нему осуществляется лишь при определенном условии, возникающем в ходе выполнения программы. Так, оператор вызова метода println(), следующий за выражением, в котором происходит обращение к несуществующему элементу массива, вообще не выполняется. По завершении блока catch выполнение программы продолжается с оператора, следующего за этим блоком. Таким образом, обработчик исключений предназначен для устранения программных ошибок, приводящих к исключительным ситуациям, а также для обеспечения нормального продолжения исполняемой программы.

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

на следующую строку кода:

Теперь исключение не возникнет и блок catch не выполнится.

Важно понимать, что исключения отслеживаются во всем коде в блоке try. К их числу относятся исключения, которые могут быть сгенерированы методом, вызываемым из блока try. Исключения, возникающие в вызываемом методе, перехватываются операторами в блоке catch, связанном с блоком try. Правда, это произойдет лишь в том случае, если метод не обрабатывает исключения самостоятельно. Рассмотрим в качестве примера следующую программу:

/* Исключение может быть сгенерировано одним методом,
   а перехвачено другим. */

class ExcTest {
    // сгенерировать исключение
    static void genException()  {
        int nums[] = new int[4];

        System.out.println("Before exception is generated.");

        // Здесь генерируется исключение в связи с
        // обращением за границы массива.
        nums[7] = 10;
        System.out.println("this won't be displayed");
    }
}

class ExcDemo2 {
    public static void main(String args[])  {
        try {
            ExcTest.genException() ;
        }
        //А здесь исключение перехватывается.
        catch (ArraylndexOutOfBoundsException exc) {
            System.out.println("Index out-of-bounds!");
        }
        System.out.println("After catch statement.");
    }
}

Выполнение этой версии программы дает такой же результат, как и при выполнении ее предыдущей версии. Этот результат приведен ниже.

Before exception is generated.
Index out-of-bounds!
After catch statement.

Метод genException() вызывается из блока try, и поэтому генерируемое, но не перехватываемое в нем исключение перехватывается далее в блоке catch в методе main(). Если бы метод genException() сам перехватывал исключение, оно вообще не дошло бы до метода main().

Последствия неперехвата исключений

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

// Обработка ошибки средствами виртуальной машины Java,
class NotHandled {
    public static void main(String args[]) {
        int nums[] = new int[4];

        System.out.println("Before exception is generated.");

        // Попытка обращения за границы массива,
        nums[7] = 10;
    }
}

При появлении ошибки, связанной с обращением за границы массива, выполнение программы прекращается и выводится следующее сообщение:

Exception in thread "main" java.lang.ArraylndexOutOfBoundsException: 7 at NotHandled.main(NotHandled.java:9)

Оно полезно на этапе отладки, но пользователям программы эта информация вряд ли нужна. Именно поэтому очень важно, чтобы программы обрабатывали исключения самостоятельно и не поручали эту задачу виртуальной машине Java.

Как упоминалось выше, тип исключения должен соответствовать типу, указанному в операторе catch. В противном случае исключение не будет перехвачено. Так, в приведенном ниже примере программы делается попытка перехватить исключение, связанное с обращением за границы массива, с помощью оператора catch, в котором указан тип ArithmeticException еще одного встроенного в Java исключения. При неправильном обращении к массиву будет сгенерировано исключение ArraylndexOutOfBoundsException, не соответствующее типу, указанному в операторе catch. В результате программа будет завершена аварийно.

// Эта программа не будет работать нормально!
class ExcTypeMismatch {
    public static void main(String args[]) {
        int nums[] = new int[4];
        try {
        System.out.println("Before exception is generated.");
        // При выполнении следующего оператора возникает
        // исключение ArraylndexOutOfBoundsException
        nums[7] = 10;
        System.out.println("this won’t be displayed");
        }
        /* Исключение, связанное с обращением за границы массива,
        нельзя обработать с помощью оператора catch, в котором
        указан тип исключения ArithmeticException. */
        catch (ArithmeticException exc) {
        System.out.println("Index out-of-bounds!");
        }
        System.out.println("After catch statement.");
    }
}

Ниже приведен результат выполнения данной программы.

Before exception is generated.
Exception in thread "main" java.lang.ArraylndexOutOfBoundsException: 7
    at ExcTypeMismatch.main(ExcTypeMismatch.java:10)

Нетрудно заметить, что оператор catch, в котором указан тип исключения ArithmeticException, не может перехватить исключение ArraylndexOutOfBoundsException.

Обработка исключений — изящный способ устранения программных ошибок

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

// Изящная обработка исключения и продолжение выполнения программы,
class ExcDemo3 {
    public static void main(String args[])  {
        int numer[] = { 4, 8, 16, 32, 64, 128 };
        int denom[] = { 2, 0, 4, 4, 0, 8 };

        for(int i=0; i<numer.length; i++)   {
            try {
                System.out.println(numer[i] + " / " +
                                   denom[i] + " is " +
                                   numer[i]/denom[i]);
            }
            catch (ArithmeticException exc) {
                // перехватить исключение
                System.out.println("Can't divide by Zero!");
            }
        }
    }
}

Результат выполнения данной программы выглядит следующим образом:

4 / 2 is 2
Can't divide by Zero!
16/ 4 is 4
32 / 4 is 8
Can't divide by Zero!
128 / 8 is 16

Данный пример демонстрирует еще одну важную особенность: обработанное исключение удаляется из системы. Иными словами, на каждом шаге цикла блок try выполняется в программе сызнова, а все возникшие ранее исключения считаются обработанными. Благодаря этому в программе могут обрабатываться повторяющиеся ошибки.

Применение нескольких операторов catch

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

// Применение нескольких операторов catch.  '
class ExcDemo4 {
    public static void main(String args[]) {
        // Здесь массив numer длиннее массива denom.
        int numer[] = { 4, 8, 16, 32, 64, 128, 256, 512 };
        int denom[] = { 2, 0, 4, 4, 0, 8 };

        for(int i=0; i<numer.length; i++)   {
            try {
                System.out.println(numer[i] + " / " +
                                   denom[i] + " is " +
                                   numer[i]/denom[i]);
            }
            // За блоком try следует несколько блоков catch подряд,
            catch (ArithmeticException exc) {
                // перехватить исключение
                System.out.println("Can't divide by Zero!");
            }
            catch (ArraylndexOutOfBoundsException exc) {
                // перехватить исключение
                System.out.println("No matching element found.");
            }
        }
    }
}

Выполнение этой программы дает следующий результат:

4 / 2 is 2
Can't divide by Zero!
16 / 4 is 4
32 / 4 is 8
Can't divide by Zero!
128 / 8 is 16
No matching element found.
No matching element found.

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

Вообще говоря, выражения с операторами catch проверяются в том порядке, в котором они встречаются в программе. И выполняется лишь тот из них, который соответствует типу возникшего исключения. А остальные блоки операторов catch просто игнорируются.

Перехват исключений, генерируемых подклассами

В отношении подклассов следует отметить еще одну интересную особенность применения нескольких операторов catch: условие перехвата исключений для суперкласса будет справедливо и для любых его подклассов. Например, класс Throwable является суперклассом для всех исключений, поэтому для перехвата всех возможных исключений в операторах catch следует указывать тип Throwable. Если же требуется перехватывать исключения типа суперкласса и типа подкласса, то в блоке операторов первым должен быть указан тип исключения, генерируемого подклассом. В противном случае вместе с исключением типа суперкласса будут перехвачены и все исключения производных от него классов. Это правило соблюдается автоматически, и если указать первым тип исключения, генерируемого суперклассом, то будет создан недостижимый код, поскольку условие перехвата исключения, генерируемого подклассом, никогда не будет выполнено. А ведь недостижимый код в Java считается ошибкой.

Рассмотрим в качестве примера следующую программу

//В операторах catch исключения типа подкласса должны
// предшествовать исключениям типа суперкласса,
class ExcDemo5 {
    public static void main(String args[]) {
        // Здесь массив numer длиннее массива denom.
        int numer[] = { 4, 8, 16, 32, 64, 128, 256, 512 };
        int denom[] = { 2, 0, 4, 4, 0, 8 };

        for(int i=0; i<numer.length; i++)   {
            try {
                System.out.println(numer[i] + " / " +
                                   denom[i] + " is " +
                                   numer[i]/denom[i]);
            }
            // Перехват исключения от подкласса.
            catch (ArraylndexOutOfBoundsException exc) {
                System.out.println("No matching element found.");
            }
            // Перехват исключения от суперкласса.
            catch (Throwable exc) {
                System.out.println("Some exception occurred.");
            }
        }
    }
}

Ниже приведен результат выполнения данной программы.

4 / 2 is 2
Some exception occurred.
16 / 4 is 4
32 / 4 is 8
Some exception occurred.
128 / 8 is 16
No matching element found.
No matching element found.

В данном случае оператор catch (Throwable) перехватывает все исключения, кроме ArraylndexOutOfBoundsException. Соблюдение правильного порядка следования операторов catch приобретает особое значение в том случае, когда исключения генерируются в самой программе.

Вложенные блоки try

Блоки try могут быть вложенными друг в друга. Исключение, возникшее во внутреннем блоке try и не перехваченное связанным с ним блоком catch, распростра¬няется далее во внешний блок try и обрабатывается связанным с ним блоком catch. Такой порядок обработки исключений демонстрируется в приведенном ниже примере программы, где исключение ArraylndexOutOfBoundsException не перехватывается во внутреннем блоке catch, но обрабатывается во внешнем.

// Применение вложенных блоков try.
class NestTrys {
    public static void main(String args[]) {
        // Массив numer длиннее, чем массив denom.
        int numer[] = { 4, 8, 16, 32, 64, 128, 256, 512 };
        int denom[] = { 2, 0, 4, 4, 0, 8 };

        // Вложенные блоки try.
        try { // Внешний блок try.
            for(int i=0; i<numer.length; i++)   {
                try { // Внутренний блок try.
                    System.out.println(numer[i] + " / " +
                                       denom[i] + " is " +
                                       numer[i]/denom[i]) ;
                }
                catch (ArithmeticException exc) {
                    // перехватить исключение
                    System.out.println("Can't divide by Zero!");
                }
            }
        }
        catch (ArraylndexOutOfBoundsException exc) {
            // перехватить исключение
            System.out.println("No matching element found.");
            System.out.println("Fatal error - program terminated.");
        }
    }
}

Выполнение этой программы может дать, например, следующий результат:

4 / 2 is 2
Can't divide by Zero!
16 / 4 is 4
32 / 4 is 8
Can't divide by Zero!
128 / 8 is 16
No matching element found.
Fatal error - program terminated.

В данном примере исключение, которое может быть обработано во внутреннем блоке try (в данном случае ошибка деления на нуль), не мешает дальнейшему выполнению программы. А вот ошибка превышения границ массива перехватывается во внешнем блоке try, что приводит к аварийному завершению программы.

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

Генерирование исключений

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

где объект_исключения должен быть объектом класса, производного от класса Throwable.

Ниже приведен пример программы, демонстрирующий применение оператора throw. В этой программе исключение ArithmeticException генерируется вручную.

// Генерирование исключения вручную,
class ThrowDemo {
    public static void main(String args[])  {
        try {
            System.out.println("Before throw.");
                // Генерирование исключения.
                throw new ArithmeticException() ;
        }
        catch (ArithmeticException exc) {
            // перехватить исключение
            System.out.println("Exception caught.");
        }

        System.out.println("After try/catch block.");
    }
}

Выполнение этой программы дает следующий результат:

Before throw.
Exception caught.
After try/catch block.

Обратите внимание на то, что исключение ArithmeticException генерируется с помощью ключевого слова new в операторе throw. Дело в том, что оператор throw генерирует исключение в виде объекта. Поэтому после ключевого слова throw недостаточно указать только тип исключения, нужно еще создать объект для этой цели.

Повторное генерирование исключений

Исключение, перехваченное блоком catch, может быть повторно сгенерировано для обработки другим аналогичным блоком. Чаще всего повторное генерирование исключений применяется с целью предоставить разным обработчикам доступ к исключению. Так, например, повторное генерирование имеет смысл в том случае, если один обработчик оперирует одним свойством исключения, а другой обработчик ориентирован на другое его свойство. Повторно сгенерированное исключение не может быть перехвачено тем же самым блоком catch. Оно распространяется в другие блоки catch.

Ниже приведен пример программы, демонстрирующий повторное генерирование исключений.

//•Повторное генерирование исключений,
class Rethrow {
    public static void genException()   {
        // Массив numer длиннее маесивв denom.
        int numer[] = { 4, 8, 16, 32, 64, 128, 256, 512 };
        int denom[] = { 2, 0, 4, 4, 0, 8 };

        for(int i=0; i<numer.length; i++)   {
            try {
                System.out.println(numer[i] + " / " +
                                   denom[i] + " is " +
                                   numer[i]/denom[i]);
            }
            catch (ArithmeticException exc) {
                // перехватить исключение
                System.out.println("Can11 divide by Zero!");
            }
            catch (ArraylndexOutOfBoundsException exc) {
                // перехватить исключение
                System.out.println("No matching element found.");
                throw exc; // Повторное генерирование исключения.
            }
        }
    }
}

class RethrowDemo {
    public static void main(String args[])  {
        try {
            Rethrow.genException();
        }
        catch(ArraylndexOutOfBoundsException exc) {
            // Перехват повторно сгенерированного включения.
            System.out.println("Fatal error - " +
                               "program terminated.");
        }
    }
}

В данной программе ошибка деления на нуль обрабатывается локально в методе genException(), а при попытке обращения за границы массива исключение генерируется повторно. На этот раз оно перехватывается в методе main().

Подробнее о классе Throwable

В приведенных до сих примерах программ только перехватывались исключения, но не выполнялось никаких действий над представляющими их объектами. В выражении оператора catch указываются тип исключения и параметр, принимающий объект исключения. А поскольку все исключения представлены подклассами, производными от класса Throwable, то они поддерживают методы, определенные в этом классе. Некоторые наиболее употребительные методы из класса Throwable приведены в табл. 9.1.

Таблица 9.1. Наиболее употребительные методы из класса Throwable

Метод Описание
Throwable filllnStackTrace() Возвращает объект типа Throwable, содержащий полную трассировку стека исключений. Этот объект пригоден для повторного генерирования исключений
String getLocalizedMessage() Возвращает описание исключения, локализованное по региональным стандартам
String getMessage() Возвращает описание исключения
void printStackTrace() Выводит трассировку стека исключений
void printStackTrace(PrintStream stream) Выводит трассировку стека исключений в указанный поток
void printStackTrace(PrintWriter stream) Направляет трассировку стека исключений в указанный поток
String toString() Возвращает объект типа String, содержащий полное описание исключения. Этот метод вызывается из метода println() при выводе объекта типа Throwable

Среди методов, определенных в классе Throwable, наибольший интерес представляют методы pr intStackTrace() и toString(). С помощью метода printStackTrace() можно вывести стандартное сообщение об ошибке и запись последовательности вызовов методов, которые привели к возникновению исключения, А метод toString() позволяет получить стандартное сообщение об ошибке. Этот метод также вызывается в том случае, когда объект исключения передается в качестве параметра методу println(). Применение этих методов демонстрируется в следующем примере программы:

// Применение методов из класса Throwable.
class ExcTest {
    static void genException()  {
        int nums[] = new int[4];

        System.out.println("Before exception is generated.");

        // сгенерировать исключение в связи с попыткой
        // обращения за границы массива
        nums[7] = 10;
        System.out.println("this won't be displayed");
    }
}

class UseThrowableMethods {
    public static void main(String args[])  {

        try {
            ExcTest.genException() ;
        }
        catch (ArraylndexOutOfBoundsException exc) {
            // перехватить исключение
            System.out.println("Standard message is: ");
            System.out.println(exc) ;
            System.out.println("nStack trace: ");
            exc.printStackTrace();
        }

        System.out.println("After catch statement.");
    }
}

Результат выполнения данной программы выглядит следующим образом:

Before exception is generated.
Standard message is:
java.lang.ArraylndexOutOfBoundsException: 7

Stack trace:
java.lang.ArraylndexOutOfBoundsException: 7
    at ExcTest.genException(UseThrowableMethods.java:10)
    at UseThrowableMethods.main(UseThrowableMethods.java:19)
After catch statement.

Использование ключевого слова finally

Иногда требуется определить кодовый блок, который должен выполняться по завершении блока try/catch. Допустим, в процессе работы программы возникло исключение, требующее ее преждевременного завершения. Но в программе открыт файл или установлено сетевое соединение, а следовательно, файл нужно закрыть, а соединение разорвать. Для выполнения подобных операций нормального завершения программы удобно воспользоваться ключевым словом finally.

Для того чтобы определить код, который должен выполняться по завершении блока try/catch, нужно указать блок finally в конце последовательности операторов try/catch. Ниже приведена общая форма записи блока try/catch вместе с блоком finally.

try {
    // Блок кода, в котором отслеживаются ошибки.
}
catch (тип_исключения_1 объект_исключения) {
    // Обработчик исключения тип_исключения_1
}
catch (тип_исключения_2 объект_исключения) {
    // Обработчик исключения тип_исключения_2
}
//. . .
finally {
// Код блока finally
}

Блок finally выполняется всегда по завершении блока try/catch независимо от того, какое именно условие к этому привело. Следовательно, блок finally получит управление как при нормальной работе программы, так и при возникновении ошибки. Более того, он будет вызван даже в том случае, если в блоке try или в одном из блоков catch будет присутствовать оператор return для немедленного возврата из метода.

Ниже приведен краткий пример программы, демонстрирующий применение блока finally.

// Применение блока finally,
class UseFinally {
    public static void genException(int what) {
        int t;
        int nums[] = new int[2];

        System.out.println("Receiving " + what);
        try {
            switch(what) {
            case 0:
                t = 10 / what; // сгенерировать ошибку деления на нуль
                break;
            case 1:
                nums[4] = 4; // сгенерировать ошибку обращения к массиву
                break;
            case 2:
                return; // возвратиться из блока try
            }
        }
        catch (ArithmeticException exc) {
            // перехватить исключение
            System.out.println("Can1t divide by Zero!");
            return; // возвратиться из блока catch
        }
        catch (ArraylndexOutOfBoundsException exc) {
            // перехватить исключение
            System.out.println("No matching element found.");
        }
        // Этот блок выполняется независимо от того, каким
        // образом завершается блок try/catch.
        finally {
            System.out.println("Leaving try.");
        }
    }
}

class FinallyDemo {
    public static void main(String args[]) {

        for(int i=0; i < 3; i++) {
            UseFinally.genException(i);
            System.out.println() ;
        }
    }
}

В результате выполнения данной программы получается следующий результат:

Receiving О
Can't divide by Zero!
Leaving try.

Receiving 1
No matching element found.
Leaving try.

Receiving 2
Leaving try.

Нетрудно заметить, что блок finally выполняется независимо от того, каким об¬
разом завершается блок try/catch.

Использование ключевого слова throws

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

возвращаемый_тип имя_метода(список_параметров) throws список_исключений {
    // Тело метода
}

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

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

Пример применения оператора throws уже был представлен ранее в этой книге.
Напомним, что при организации ввода с клавиатуры в метод main() потребовалось
включить следующее выражение:

throws java.io.IOException

Теперь вы знаете, зачем это было нужно. При вводе данных может возникнуть исключение IOException, а на тот момент вы еще не знали, как оно обрабатывается. Поэтому мы и указали, что исключение должно обрабатываться за пределами метода main(). Теперь, ознакомившись с исключениями, вы сможете без труда обработать исключение IOException самостоятельно.

Рассмотрим пример, в котором осуществляется обработка исключения IOException. В методе prompt() отображается сообщение, а затем выполняется ввод символов с клавиатуры. Такой ввод данных может привести к возникновению исключения IOException. Но это исключение не обрабатывается в методе prompt(). Вместо этого в объявлении метода указан оператор throws, т.е. обязанности по обработке данного исключению поручаются вызывающему методу. В данном случае вызывающим является метод main(), в котором и перехватывается исключение.

// Применение ключевого слова throws,
class ThrowsDemo {
    // Обратите внимание на оператор throws в объявлении метода.
    public static char prompt(String str)
        throws java.io.IOException {

        System.out.print(str + ": ");
        return (char) System.in.read() ;
    }

    public static void main(String args[]) {
        char ch;

        try {
            // В методе prompt() может быть сгенерировано исключение,
            // поэтому данный метод следует вызывать в блоке try.
            ch = prompt("Enter a letter");
        }
        catch(java.io.IOException exc) {
            System.out.println("I/O exception occurred.");
            ch = 'X';
        }
        System.out.println("You pressed " + ch);
    }
}

Обратите внимание на одну особенность приведенного выше примера. Класс IOException относится к пакету java. io. Как будет разъяснено в главе 10, в этом пакете содержатся многие языковые средства Java для организации ввода-вывода. Следовательно, пакет java.io можно импортировать, а в программе указать только имя класса IOException.

Новые средства обработки исключений, внедренные в версии JDK 7

С появлением версии JDK 7 механизм обработки исключений в Java был значительно усовершенствован благодаря внедрению трех новых средств. Первое из них поддерживает автоматическое управление ресурсами, позволяющее автоматизировать процесс освобождения таких ресурсов, как файлы, когда они больше не нужны. В основу этого средства положена расширенная форма оператора try, называемая оператором try с ресурсами и описываемая в главе 10 при рассмотрении файлов. Второе новое средство называется многократным перехватом, а третье — окончательным или более точным повторным генерированием исключений. Два последних средства рассматриваются ниже.

Многократный перехват позволяет перехватывать два или более исключения одним оператором catch. Как пояснялось ранее, после оператора try можно (и даже принято) указывать два или более оператора catch. И хотя каждый блок оператора catch, как правило, содержит свою особую кодовую последовательность, нередко в двух или более блоках оператора catch выполняется одна и та же кодовая последовательность, несмотря на то, что в них перехватываются разные исключения. Вместо того чтобы перехватывать каждый тип исключения в отдельности, теперь можно воспользоваться единым блоком оператора catch для обработки исключений, не дублируя код.

Для организации многократного перехвата следует указать список исключений в одном операторе catch, разделив их типы оператором поразрядного ИЛИ. Каждый параметр многократного перехвата неявно указывается как final. (По желанию модификатор доступа final можно указать и явным образом, но это совсем не обязательно.) А поскольку каждый параметр многократного перехвата неявно указывается как final, то ему нельзя присвоить новое значение.

В приведенной ниже строке кода показывается, каким образом многократный перехват исключений ArithmeticException и ArraylndexOutOfBoundsException указывается в одном операторе catch.

catch(final ArithmeticException | ArraylndexOutOfBoundsException e) {

Ниже приведен краткий пример программы, демонстрирующий применение многократного перехвата исключений.

// Применение средства многократного перехвата исключений.
// Примечание: для компиляции этого кода требуется JDK 7
// или более поздняя версия данного комплекта,
class MultiCatch {
    public static void main(String args[]) {
        int a=88, b=0;
        int result;
        char chrs[] = { 'А', 'В', 'C' };

        for(int i=0; i < 2; i++)    {
            try {
                if (i == 0)
                    // сгенерировать исключение ArithmeticException
                    result = а / b;
                else
                    // сгенерировать исключение ArraylndexOutOfBoundsException
                    chrs[5] = 'X';
            }
            // В этом операторе catch организуется перехват обоих исключений,
            catch(ArithmeticException | ArraylndexOutOfBoundsException е) {
                System.out.println("Exception caught: " + e);
            }
        }

        System.out.println("After multi-catch.");
    }
}

В данном примере программы исключение ArithmeticException генерируется при попытке деления на нуль, а исключение ArraylndexOutOfBoundsException — при попытке обращения за границы массива chrs. Оба исключения перехватываются одним оператором catch.

Средство более точного повторного генерирования исключений ограничивает этот процесс лишь теми проверяемыми типами исключений, которые генерируются в соответствующем блоке try и не обрабатываются в предыдущем блоке оператора catch, а также относятся к подтипу или супертипу указываемого параметра. И хотя такая возможность требуется нечасто, ничто не мешает теперь воспользоваться ею в полной мере. А для организации окончательного повторного генерирования исключений параметр оператора catch должен быть, по существу, указан как final. Это означает, что ему нельзя присвоить новое значение в блоке catch. Он может быть указан как final явным образом, хотя это и не обязательно.

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

В стандартном пакете java. lang определены некоторые классы, представляющие стандартные исключения Java. Часть из них использовалась в предыдущих примерах программ. Наиболее часто встречаются исключения из подклассов стандартного класса RuntimeException. А поскольку пакет java. lang импортируется по умолчанию во все программы на Java, то исключения, производные от класса RuntimeException, становятся доступными автоматически. Их даже обязательно включать в список оператора throws. В терминологии языка Java такие исключения называют непроверяемыми, поскольку компилятор не проверяет, обрабатываются или генерируются подобные исключения в методе. Непроверяемые исключения, определенные в пакете java.lang, приведены в табл. 9.2, тогда как в табл. 9.3 — те исключения из пакета j ava. lang, которые следует непременно включать в список оператора throws при объявлении метода, если, конечно, в методе содержатся операторы, способные генерировать эти исключения, а их обработка не предусмотрена в теле метода. Такие исключения принято называть проверяемыми. В Java предусмотрен также ряд других исключений, определения которых содержатся в различных библиотеках классов. К их числу можно отнести упоминавшееся ранее исключение IOException.

Таблица 9.2. Непроверяемые исключения, определенные в пакете java.lang

Исключение Описание
ArithmeticException Арифметическая ошибка, например попытка деления на нуль
ArraylndexOutOfBoundsException Попытка обращения за границы массива
ArrayStoreException Попытка ввести в массив элемент, несовместимый с ним по типу
ClassCastException Недопустимое приведение типов
EnumConstNotPresentException Попытка использования нумерованного значения, которое не было определено ранее
IllegalArgumentException Недопустимый параметр при вызове метода
IllegalMonitorStateException Недопустимая операция контроля, например, ожидание разблокировки потока
IllegalStateException Недопустимое состояние среды выполнения или приложения
IllegalThreadStateException Запрашиваемая операция несовместима с текущим состоянием потока
IndexOutOfBoundsException Недопустимое значение индекса
NegativeArraySizeException Создание массива отрицательного размера
NullPointerException Недопустимое использование пустой ссылки
NumberFormatException Неверное преобразование символьной строки в число
SecurityException Попытка нарушить систему защиты
StringlndexOutOfBounds Попытка обращения к символьной строке за ее границами
TypeNotPresentException Неизвестный тип
UnsupportedOperationException Неподдерживаемая операция

Таблица 9.3. Проверяемые исключения, определенные в пакете java.lang

Исключение Описание
ClassNotFoundException Класс не найден
CloneNotSupportedException Попытка клонирования объекта, не реализующего интерфейс Cloneable
IllegalAccessException Доступ к классу запрещен
InstantiationException Попытка создания объекта абстрактного класса или интер¬фейса
InterruptedException Прерывание одного потока другим
NoSuchFieldException Требуемое поле не существует
NoSuchMethodException Требуемый метод не существует
ReflectiveOperationException Суперкласс исключений, связанных с рефлексией (добавлен в версии JDK 7)

Создание подклассов, производных от класса Exception

Несмотря на то что встроенные в Java исключения позволяют обрабатывать большинство ошибок, механизм обработки исключений не ограничивается только этими ошибками. В частности, можно создавать исключения для обработки потенциальных ошибок в прикладной программе. Создать исключение несложно. Для этого достаточно определить подкласс, производный от класса Exception, который, в свою очередь, является подклассом, порожденным классом Throwable. В создаваемый подкласс не обязательно включать реализацию каких-то методов. Сам факт существования такого подкласса позволяет использовать его в качестве исключения.

В классе Exception не определены новые методы. Он лишь наследует методы, предоставляемые классом Throwable. Таким образом, все исключения, включая и создаваемые вами, содержат методы класса Throwable. Конечно же, вы вольны переопределить в создаваемом вами классе один или несколько методов.

Ниже приведен пример, в котором создается исключение NonlntResultException. Оно генерируется в том случае, если результатом деления двух целых чисел является дробное число. В классе NonlntResultException содержатся два поля, предназначенные для хранения целых чисел, а также конструктор. В нем также переопределен метод toString(), что дает возможность выводить описание исключения с помощью метода println().

// Применение специально создаваемого исключения.
// создать исключение
class NonlntResultException extends Exception {
    int n;
    int d;

    NonlntResultException(int i, int j) {
        n = i;
        d = j;
    }

    public String toString()    {
        return "Result of " + n + " / " + d +
                " is non-integer.";
    }
}

class CustomExceptDemo {
    public static void main(String args[]) {

        // В массиве numer содержатся нечетные числа,
        int numer[] = { 4, 8, 15, 32, 64, 127, 256, 512 };
        int denom[] = { 2, 0, 4, 4, 0, 8 };

        for(int i=0; i<numer.length; i++)   {
            try {
                if((numer[i]%2) != 0)
                    throw new
                        NonlntResultException(numer[i], denom[i]);
                System.out.println(numer[i] + " / " +
                                   denom[i] + 11 is " +
                                   numer[i]/denom[i]);
            }
            catch (ArithmeticException exc) {
                // перехватить исключение
                System.out.println("Can11 divide by Zero!");
            }
            catch (ArraylndexOutOfBoundsException exc) {
                // перехватить исключение
                System.out.println("No matching element found.");
            }
            catch (NonlntResultException exc) {
                System.out.println(exc) ;
            }
        }
    }
}

Результат выполнения данной программы выглядит следующим образом:

4 / 2 is 2
Can't divide by Zero!
Result of 15 / 4 is non-integer.
32 / 4 is 8
Can't divide by Zero!
Result of 127 / 8 is non-integer.
No matching element found.
No matching element found.

Пример для опробования 9.1.
Добавление исключений в класс очереди

В этом проекте предстоит создать два класса исключении, которые будут использоваться классом очереди, разработанным в примере для опробования 8.1. Эти исключения должны указывать на переполнение и опустошение очереди, а генерировать их будут методы put() и get() соответственно. Ради простоты эти исключения добавляются в класс FixedQueue, но вы можете без труда внедрить их в любые другие классы очереди, разработанные в примере для опробования 8.1.

Последовательность действий

  1. Создайте файл QExcDemo.java.
  2. Определите следующие исключения в файле QExcDemo.java:
    /*
    Пример для опробования 9.1.
    Добавление обработчиков исключений в класс очереди.
    */
    // Исключение, указывающее на переполнение очереди,
    class QueueFullException extends Exception {
        int size;
    
        QueueFullException(int s) { size = s; }
    
        public String toString()    {
            return "nQueue is full. Maximum size is " + size;
        }
    }
    
    // Исключение, указывающее на опустошение очереди,
    class QueueEmptyException extends Exception {
        public String toString()    {
            return "nQueue is empty.";
        }
    }
    

    Исключение QueueFullException генерируется при попытке поместить элемент в уже заполненную очередь, а исключение QueueEmptyException — в ответ на попытку извлечь элемент из пустой очереди.

  3. Измените класс FixedQueue таким образом, чтобы при возникновении ошибки он генерировал исключение. Соответствующий код приведен ниже. Введите этот код в файл QExcDemo.java.
    // Класс, реализующий очередь фиксированного размера
    // для хранения символов.
    class FixedQueue implements ICharQ {
        private char q[]; // Массив для хранения элементов очереди,
        private int putloc, getloc; // Индексы размещения и извлечения
    
        // элементов очереди.
        // создать пустую очередь заданного размера
        public FixedQueue(int size) {
            q = new char[size+1]; // выделить память для очереди
            putloc = getloc = 0;
        }
    
        // поместить символ в очередь
        public void put(char ch)
        throws QueueFullException {
    
            if(putloc==q.length-1)
                throw new QueueFullException(q.length-1);
    
            putloc++;
            q[putloc] = ch;
        }
    
        // извлечь символ из очереди
        public char get()
        throws QueueEmptyException {
    
            if(getloc == putloc)
                throw new QueueEmptyException();
    
            getloc++;
            return q[getloc];
        }
    }
    

    Добавление исключений в класс FixedQueue выполняется в два этапа. Сначала в определении методов get() и put() указывается оператор throws с типом генерируемого исключения. А затем в этих методах организуется генерирование исключений при возникновении ошибок. Используя исключения, можно организовать обработку ошибок в вызывающей части программы наиболее рациональным способом. Как вы помните, в предыдущих версиях рассматриваемой здесь программы выводились только сообщения об ошибках. А генерирование исключений является более профессиональным подходом к разработке данной программы.

  4. Для опробования усовершенствованного класса FixedQueue введите в файл QExcDemo.java приведенный ниже исходный код класса QExcDemo.
    // Демонстрация исключений при обращении с очередью,
    class QExcDemo {
        public static void main(String args[])  {
            FixedQueue q = new FixedQueue(10);
            char ch;
            int i;
    
            try {
                // Переполнение очереди.
                for(i=0; i < 11; i++)   {
                    System.out.print("Attempting to store : " +
                                     (char) ('A' + i));
                    q.put((char) (fA' + i));
                    System.out.println(" - OK");
                }
                System.out.println();
            }
            catch (QueueFullException exc) {
                System.out.println(exc);
            }
            System.out.println();
    
            try {
                // Попытка извлечь символ из пустой очереди.
                for(i=0; i < 11; i++) {
                System.out.print("Getting next char: ");
                ch = q.get();
                System.out.println(ch);
                }
            }
            catch (QueueEmptyException exc) {
                System.out.println(exc);
            }
        }
    }
    
  5. Класс FixedQueue реализует интерфейс ICharQ, в котором определены методы get() и put(), и поэтому интерфейс ICharQ необходимо изменить таким образом, чтобы в нем отражалось наличие операторов throws. Ниже приведен видоизмененный соответственно код интерфейса ICharQ. Не забывайте о том, что он должен храниться в файле ICharQjava.
    // Интерфейс очереди для хранения символов с генерированием исключений,
    public interface ICharQ {
        // поместить символ в очередь
        void put(char ch) throws QueueFullException;
        // извлечь символ из очереди
        char get() throws QueueEmptyException;
    }
    
  6. Скомпилируйте сначала новую версию исходного файла IQChar. j ava, а затем исходный файл QExcDemo. java и запустите программу QExcDemo на выполнение. В итоге вы получите следующий результат ее выполнения:
    Attempting to store A - OK
    Attempting to store В - OK
    Attempting to store С - OK
    Attempting to store D - OK
    Attempting to store E - OK
    Attempting to store F - OK
    Attempting to store G - OK
    Attempting to store H - OK
    Attempting to store I - OK
    Attempting to store J - OK
    Attempting to store К
    Queue is full. Maximum size is 10
    
    Getting next char: A
    Getting next char: В
    Getting next char: С
    Getting next char: D
    Getting next char: E
    Getting next char: F
    Getting next char: G
    Getting next char: H
    Getting next char: I
    Getting next char: J
    Getting next char:
    Queue is empty.
    

Упражнение для самопроверки по материалу главы 9

  1. Какой класс находится на вершине иерархии исключений?
  2. Объясните вкратце, как пользоваться ключевыми словами try и catch?
  3. Какая ошибка допущена в приведенном ниже фрагменте кода?
    // ...
    vals[18] = 10;
    catch (ArraylndexOutOfBoundsException exc) {
        // обработать ошибку
    }
    
  4. Что произойдет, если исключение не будет перехвачено?
  5. Какая ошибка допущена в приведенном ниже фрагменте кода?
    class A extends Exception { ...
    class В extends А { ...
        // ...
    try {
        // ...
    }
    catch (A exc) { ... }
    catch (В exc) { ... }
    
  6. Может ли внутренний блок catch повторно генерировать исключение, которое будет обработано во внешнем блоке catch?
  7. Блок finally — последний фрагмент кода, выполняемый перед завершением программы. Верно или неверно? Обоснуйте свой ответ.
  8. Исключения какого типа необходимо явно объявлять с помощью оператора throws, включаемого в объявление метода?
  9. Какая ошибка допущена в приведенном ниже фрагменте кода?
    class MyClass { // ... }
    // ...
    throw new MyClass();
    
  10. Отвечая на вопрос 3 упражнения для самопроверки по материалу главы 6, вы создали класс Stack. Добавьте в него специальные исключения для реагирования на попытку поместить элемент в переполненный стек и извлечь элемент из пустого стека.
  11. Какими тремя способами можно сгенерировать исключение?
  12. Назовите два подкласса, производных непосредственно от класса Throwable.
  13. Что такое многократный перехват?
  14. Следует ли перехватывать в программе исключения типа Error?

Язык Javaпозволяет программисту описывать
алгоритм обработки ошибок как обработку
«бросаемых» исключений. Исключение
— это попросту название некоторой ошибки.
При возникновении ошибки происходит
бросок исключения и возникает прерывание.JVMпроизводит поиск метода,
обрабатывающего прерывание. Если
обработка прерывания не указана, то
происходит ошибка времени выполнения.

Для генерации
исключения в языке Javaиспользуется операторthrow.
Операторthrowпрерывает
для указанного исключения выполнение
программы. Управление передается
соответствующему обработчику исключений.

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

try
{

if
(i==0) { throw new Exeptioni;} //Бросок исключения

}

catch(Exeptioni
e){

System.out.printin(«Перехвачено
исключение
Exeptioni»);

}

catch
(Exeption2 e) {

}

catch
(Exeption3 e) {

}

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

Любое исключение
— это объект, являющийся экземпляром
класса Exeption. Для определения
своего собственного исключения следует
только объявить класс для этого
исключения.

class
Exeptioni extends Exeption {…} // Объявление.исключения

public
void MeMetod () throws Exeptioni, Exeption2 { …}

// Исключение

// объявляется, но
не перехватывается

Любое исключение,
возникающее при выполнении метода,
должно быть объявлено или перехвачено.
Если исключение перехватывается, то
управление передается соответствующему
блоку catchвнутри метода.
Если исключение объявляется, то управление
будет передано «вниз» по иерархии
вызовов методов методу, обрабатывающему
это исключение.

3. Ввод/вывод данных в консольном режиме.

Для вывода данных
в консольном режиме используется функция
из пакета System. Этот пакет
автоматически подключается любым
компиляторомJava.

  • System.out.print(объект)-
    выводит строку текста на экран, курсор
    остается на текущей строке;

  • System.out.println(объект)
    – выводит строку теста, курсор переходит
    на следующую строку;

С вводом информации
с клавиатуры дело обстоит сложнее,
поскольку системный пакет имеет только
объект связанный с потоком ввода. Для
того, чтобы ввести информацию с клавиатуры
нам необходимо связать этот объект с
объектом выполняющим чтение потока.
Для этого необходимо подключить класс
java.io.InputStreamReader,
который обеспечивает преобразование
данных потока в текстовую информацию.
Вся работа с файлами и потоками ввода
вывода в случае ошибки генерирует так
называемые исключительные ситуации
для обработки которых необходимо
подключить класс обработчик исключений
ввода-выводаjava.io.IOException.

Наиболее простым
в примером ввода информации с клавиатуры
является ввод строки текста. Пример
программного кода имеет следующий вид:

//
Объявляем переменную в которой будет
помещен

//
результат ввода информации

String
a;

//
Определяем блок обработки исключительных
ситуаций

try
{

//
Объявляем и создаем объект преобразования
данных потока

//
В
текстовой
вид

InputStreamReader
is=new InputStreamReader(System.in);

//
Объявляем текстовой буфер, длина которого
соответствует

//
максимальной длине строки.

char
buf[]
= new
char[128];

//
После этого в этот буфер производим
чтение

is.read(buf);

//
Этот буфер мы преобразовываем к строке

a=String.valueOf(buf);

//
После этого закрываем блок обработки
исключительных ситуаций.

}

//
И описываем поведение системы в случае
возникновения

//
ошибки при чтении данных

catch
(IO
Exception
e)
{

System.out.println(e.getMessage());

a
= null;

}

Для ввода числовой
информации также необходимо сначала
получить строку текста, после чего с
помощью класса преобразователя
преобразуется к значению числового
типа. Для исполнения данной операции
необходимо подключить библиотеку
обработки ошибок преобразования чисел
java.lang.NumbericFormatExceptionи класс – оболочку типа данных, к которой
необходимо преобразоватьjava.lang.Double;

//
Объявляем переменную в которой будет
помещен

//
результат ввода информации

double
a;

//
Определяем блок обработки исключительных
ситуаций

try{

//
Объявляем и создаем объект преобразования
данных потока

//
В
текстовой
вид

InputStreamReader
is = new InputStreamReader(System.in);

//
Объявляем текстовой буфер, длина которого
соответствует

//
максимальной длине строки.

char
buf[]
= new
char[128];

//
После этого в этот буфер производим
чтение

is.read
(buf);

//преобразовываем
к вещественному типу

a=Double.valueOf(String.valueOf(buf).trim()).double
Value();

//
После этого закрываем блок обработки
исключительных ситуаций.

}

//
описываем поведение системы в случае
возникновения

//
ошибки при чтении данных

catch
(IOException
e){

System.out.println(e.getMessage());

a
= 0;

}

//
описываем поведение системы в случае
возникновения

//
ошибки при преобразовании текстовых
данных в

//
вещественное
число

catch
(NumbericFormatException e1) {

System.out.println
(e1.getMessage());

a=0;

}

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

import
java.io.IOException;

import
java.io.InputStreamReader;

import
java.lang.NumbericFormatReader;

import
java.lang.Double;

//
объявляем класс

Class
m_cls
{

//
создаем функцию чтения строки

public
String
read_str()
{

//
Объявляем переменную в которой будет
помещен

//
результат ввода информации

String
a;

//
Определяем блок обработки исключительных
ситуаций

try
{

//
Объявляем и создаем объект преобразования
данных потока

//
В
текстовой
вид

InputStreamReader
is=new InputStreamReader(System.in);

//
Объявляем текстовой буфер, длина которого
соответствует

//
максимальной длине строки.

char
buf[]
= new
char[128];

//
После этого в этот буфер производим
чтение

is.read(buf);

//
Этот буфер мы преобразовываем к строке

a=String.valueOf(buf);

//
После этого закрываем блок обработки
исключительных ситуаций.

}

//
И описываем поведение системы в случае
возникновения

//
ошибки при чтении данных

catch
(IO
Exception
e)
{

System.out.println(e.getMessage());

a
= null;

}

if
(a = = null) return “”;

return
a.trim();

}

//
и функцию чтения вещественного числа

public
double
read_double()
{

//
Объявляем переменную в которой будет
помещен

//
результат ввода информации

double
a
= 0;

//
Определяем блок обработки исключительных
ситуаций

try
{

a=Double.valueOf(read_str()).doubleValue();

}

//
И описываем поведение системы в случае
возникновения

//
ошибки при чтении данных

catch
(NumbericFormatException
e){

System.out.println(e.getMessage());

a=0;

}

return
a;

}

}

Соседние файлы в папке СПО

  • #
  • #
  • #

Дополнительные функции работы с файлами

Модуль
System

ChDir

Выполняет
смену текущего каталога

MkDir

Создает
подкаталог

RmDir

Удаляет
подкаталог (пустой)

GetDir

Получить
текущий каталог на заданном диске

Модуль
DOS

DiskFree

Число
свободных байтов на диске

DiskSize

Размер
диска в байтах

GetFAttr

Получение
атрибутов неоткрытого файла

SetFAttr

Задание
атрибутов неоткрытого файла

FSplit

Получение
составных частей имени файла

FExpand

Формирование
полного имени файла

FSearch

Поиск
файла в списке католога

FindFirst

Поиск
в указанном или текущем каталоге
первого файла, соответствующего
заданному шаблону и атрибутам

FindNext

Поиск
следующего файла, соответствующего
шаблону и атрибутам, заданным в
последнем обращении к FindFirst

Прототипы
процедур FindFirst
и FindNext
имеют вид:

FindFirst(Path:
String; Attrib: Word; Var SR: SearchRec);

FindNext(Var
SR:
SearchRec);

Для
работы с этими подпрограммами требуются
следующие предопределенные описания:

  1. Константы

Const

ReadOnly
= $01; только
для чтения

Hidden = $02; скрытый

SysFile =
$04; системный (непереносимый)

Volume ID = $08; метка
диска

Directory =
$10; подкаталог

Archive = $20; архивный

AnyFile = $3F; сумма
всех предыдущих

Эти атрибуты можно
складывать или вычитать (из anyfile).

  1. Переменная
    DOSError.
    Она используется для анализа ошибок
    MS
    DOS.
    Значение этой переменной, равное нулю,
    соответствует отсутствию ошибки. Смысл
    некоторых ненулевых кодов следующий:

2
– файл не найден

3
– маршрут не найден

5
– доступ к файлу запрещен

6
– неправильная обработка

8
– недостаточно памяти

10
– неверные установки значений параметров
среды

11
– неправильный формат

18
– файлов нет

При
работе процедуры FindFirst
возможны ошибки с номерами 2 и 18, а при
работе FindNext
– только 18.

3)
Тип

Type

SearchRec
= record

Fill: array[1..21] of byte; {системное поле}

attr: byte; {байт атрибутов}

time: longint; {время
создания}

size: longint; {размер
файла}

name: string[12]; {имя файла}

end;

Поля
переменной этого типа содержат информацию
о последнем файле, найденном с помощью
FindFirst
и FindNext.

Процедура
FindFirst
при заданных имени файла и атрибутах
должна вызываться лишь один раз. Она
записывает в поля переданной ей переменной
SR
типа SearchRec
информацию о первом найденном файле,
удовлетворяющем заданным условиям. Эта
информация в дальнейшем будет
использоваться процедурой FindNext,
которая всегда вызывается после FindFirst
и заполняет поля переменной SR
информацией о следующем найденном
файле.

Контроль
работы этих процедур ведется с помощью
переменной DOSError:
если файла нет, то DOSError<>0.

Обработка ошибок ввода-вывода

Все рассмотренные
ранее программы были написаны так, что
возможные ошибки ввода-вывода
игнорировались. Однако реально это не
так. В ТР стандартной реакцией на наличие
ошибок в вашей программе является
следующее: ваша программа аварийно
завершается и выдает сообщение:

Runtime
error
<номер> <смещение>.

Такое сообщение
буде полезно вам как программистам при
поиске места и причины ошибки в программе.
Однако показывать такое сообщение
пользователю вашей программы будет не
совсем дружественно.

Для того, чтобы
ваша программа из-за ошибки аварийно
не завершалась и для того, чтобы не
пугать пользователей непонятными им
сообщениями, вам необходимо перехватить
инициативу по обработке ошибок у системы
и писать собственные обработчики ошибок
стадии выполнения (исключений). Традиционно
обработка ошибок в программах
осуществляется с помощью установки
флагов

случае ошибки) и последующего анализа
этих флагов. Для перехвата ошибок
ввода-вывода на Паскале необходимо:

  1. Специальным
    образом оформить каждый фрагмент
    программы, которые потенциально могут
    вызвать ошибку. К таким фрагментам
    относятся — открытие файда, чтение из
    файла, запись в файл.

  2. При наличии такого
    специального оформления необходимо
    по завершению фрагмента проверить:
    была ошибка или нет. Если была, то вы
    должны выполнить ее обработку (хотя бы
    вывести свое сообщение).

Пример:

reset(f);

при попытки открытия
файла физически файл может отсутствовать
на диске. Этот оператор в программе
является потенциальным источником
ошибки.

Специальное
оформление потенциально опасных участков
программы состоит в том, что
вы должны
как бы завернуть каждый опасный участок
в специальный код: выше
и ниже опасного фрагмента программы

вы должны
расположить две директивы компилятора:

{$I-}
— указывает на то, что необходимо
отключить системный контроль ошибок

reset(f);

{$I+}
— указывает на то, что необходимо включить
системный контроль ошибок

При наличии
директивы компилятора {$I-}
даже при наличии ошибок в/в программа
аварийно не завершается и ход выполнения
программы не нарушается.

Замечание:
По умолчанию действует директива {$i+}.

Контроль ошибок
в/в производится с помощью функции
IOResult
. Если ошибка имеет место, то значение
этой функции отлично от нуля. Поэтому
типичный шаблон обработки ошибки
ввода-вывода имеет вид:

If
IOResult
<> 0 Then
{обработка ошибки};

Обычно обработка
ошибки сводится к сообщению об ошибки
и завершению вашей программы (с помощью
процедуры HALT
или EXIT).

Замечание:
Опросить функцию IOResult
можно лишь
один раз

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

Пример.

{$I-}

reset(f);

{$I+}

If
IOResult <> 0

then
begin

Writeln(‘Ошибка
открытия
файла’);

Halt;

end;

Соседние файлы в папке WORD

  • #

    15.04.2015439.06 Кб306.docx

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

Учу С++, искал решение и почти всё, что я находил мне казалось неудобным или были недочёты в плане обработки ошибок. Решил написать свой вариант. Чтобы он не пропал зря, выложу его здесь.

Приведу в качестве примера 3 функции, одна с применением шаблонов и предиката, вторая полностью повторяет первую, но без шаблонов, а третья самая упрощённая без использования шаблонов и предиката.

Функции из примера 1 запроса значений от пользователя проходят не все тест кейсы, которые были добавлены мной позже в вопрос.

Пример 1 Полный код. Проходит НЕ все тест кейсы. Если ввести число, пробел и символ, то после нового запроса значения, если такой запрос зациклен, то он будет выведен на экран 2 раза.

#include <iostream>
#include <vector>
#include <string>
#include <functional>
#include <cassert>

void pause(const std::string& message = "")
{
    using namespace std;

    if (!message.empty()) { cout << message << endl; }
    if (cin.fail()) { cin.clear(); }
    cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n').get();
}

// Если ввести число, пробел и символ(ы), то будет следующий запрос значений выполнен 2 раза, 
// если он зациклен, как в этой функции. Самый ненадёжный вариант.
template <
    typename NumericType,
    typename TParam,
    typename = typename std::enable_if<std::is_arithmetic<NumericType>::value, NumericType>::type
>
NumericType getUserInputEx(const std::string& message = "",
    std::function<bool(TParam)> const& predicate = [](const TParam) { return true; })
{
    using namespace std;

    NumericType number = 0;
    while (true) // Бесконечный цикл, который внутри прерывается по условию
    {
        cout << message; // Вывод сообщения для пользователя
        cin >> number;   // Ввод значения пользователем

        if (!cin.fail()) // Если ввели число, а значит поток не в состоянии ошибки, то завершаем цикл
        {
            // Вызываем переданное лямбда-выражение. 
            // Если условие пройдено, то не переходим к новой итерации.
            if (!predicate(number))
            {
                continue; // Переходим к новой итерации цикла пропуская код ниже
            }

            break; // Прерывает цикл
        }
        cin.clear(); // Сбрасываем состояние ошибки
        cin.ignore(numeric_limits<streamsize>::max(), 'n');
        // На следующей итерации цикла снова запросим ввод, так как была ошибка
    }
    return number; // Возвращаем из функции результат в место вызова функции
}

uint32_t getUserInputAsUInt32(const std::string& message = "")
{
    using namespace std;

    uint32_t number = 0;
    while (true) // Бесконечный цикл, который внутри прерывается по условию
    {
        cout << message; // Вывод сообщения для пользователя
        cin >> number;   // Ввод значения пользователем
        if (!cin.fail()) // Если ввели число, а значит поток не в состоянии ошибки, то завершаем цикл
        {
            break; // Прерывает цикл
        }
        cin.clear(); // Сбрасываем состояние ошибки
        cin.ignore(numeric_limits<std::streamsize>::max(), 'n');
        // На следующей итерации цикла снова запросим ввод, так как была ошибка
    }
    return number; // Возвращаем из функции результат в место вызова функции
}

int main()
{
    using namespace std;

    setlocale(LC_ALL, "Rus");

    const uint32_t MIN_ARRAY_SIZE = 1u;
    const uint32_t MAX_ARRAY_SIZE = 50u;
    uint32_t sum = 0u, composition = 1u;

    // Формируем сообщение для пользователя
    const string message = "Введите кол-во элементов в массиве в диапазоне от " +
        to_string(MIN_ARRAY_SIZE) + " до " + to_string(MAX_ARRAY_SIZE) + ": ";

    // Это называется лямбда-выражение. Здесь используется для того, чтобы можно было написать
    // одну универсальную функцию (getUserInputEx<T1, T2>), которая может не прекращать запрашивать 
    // ввод у пользователя, пока не будет удовлетворено условие предиката isNumberValid. То 
    // есть для разных случаев использования можно задавать своё новое условие.
    // Вызывается на каждой итерации цикла внутри функции getUserInputAsInt
    const function<bool(uint32_t)> isNumberValid = [&](const uint32_t number)
    {
        // В number будет последнее введённое пользователем число. Здесь мы решаем прошло ли оно
        // наше условие, в котором мы определям согласны ли мы на такое значение от пользователя.
        return number >= MIN_ARRAY_SIZE && number <= MAX_ARRAY_SIZE;
    };

    const uint32_t numberOfValues = getUserInputEx<uint32_t, uint32_t>(message, isNumberValid);

    uint32_t array[MAX_ARRAY_SIZE] = { 0 };
    for (uint32_t i = 0; i < numberOfValues; i++)
    {
        array[i] = getUserInputAsUInt32("Ввод элемента array[" + to_string(i) + "] = ");
    }

    for (uint32_t i = 0; i < numberOfValues; i++)
    {
        cout << "[" << i << "] = " << array[i] << endl;
    }

    for (uint32_t i = 0; i < numberOfValues; i++)
    {
        sum += array[i];
    }

    cout << "Среднее арифметическое = " << sum / numberOfValues << endl;

    for (uint32_t i = 0; i < numberOfValues; ++i)
    {
        composition *= array[i];
    }

    cout << "Произведение = " << composition << endl;

    pause("Нажмите Enter, чтобы продолжить...");
    return 0;
}

Ещё такой вариант, который будет запрашивать до тех пор, пока не будет введено только число без лишних символов, пробелов и т.д.

Пример 2 Проходит все тест кейсы. Самый надёжный вариант, который возвращает результат, только если ввели значение строго из запрашиваемого диапазона без лишних символов и пробелов

uint32_t getUserInputAsUInt32(const std::string& message, uint32_t min, uint32_t max)
{
    using namespace std;
    assert(min < max);

    uint32_t number = 0u;
    while (true)
    {
        cout << message;

        string input;
        getline(cin, input);

        if (input.length() == 0)
        {
            continue;
        }

        const int64_t digitsCount = count_if(input.begin(), input.end(),
            [](unsigned char ch) { return isdigit(ch); });

        if (static_cast<size_t>(digitsCount) == input.length())
        {
            const uint64_t temp = stoull(input);
            if (temp <= numeric_limits<uint32_t>::max())
            {
                number = static_cast<uint32_t>(temp);
                if (number >= min && number <= max)
                {
                    break;
                }
            }
        }
    }
    return number;
}

Пример 3 Проходит НЕ все тест кейсы — можно ввести число и после него пробел и далее что угодно, в итоге получим число. Что, кстати, тоже как вариант, если нет строго требования, чтобы пользователь осознаннов ввёл всё правильно и строго именно число без лишних символов после него

uint32_t getUserInputAsUInt32Ex(const std::string& message = "",
    std::function<bool(uint32_t)> const& predicate = [](const uint32_t) { return true; })
{
    using namespace std;

    uint32_t number = 0;
    while (true)
    {
        cout << message;

        string input;
        getline(cin, input);

        if (input.length() == 0)
        {
            continue;
        }

        const uint64_t temp = strtoll(input.c_str(), nullptr, 10);
        if (temp <= numeric_limits<uint32_t>::max())
        {
            number = static_cast<uint32_t>(temp);
            if (predicate(number))
            {
                break;
            }
        }
    }
    return number;
}

Понравилась статья? Поделить с друзьями:
  • Обработка ошибок wpf
  • Обработка ошибок node js express
  • Обработка ошибок windows forms
  • Обработка ошибок mysql php
  • Обработка ошибок visual basic