Обработка исключений
Как предусмотреть возможность появления исключения в программе? Вернёмся к задаче о преобразовании времени в формате «ЧЧ:ММ:СС» в число секунд, прошедшее с начала дня. В этой задаче нам известно, что число часов, минут и секунд неотрицательно, поэтому мы могли бы возвращать результат -1 в случае, когда исходная строка некорректна. В отличие от функции toInt(), в нашем случае -1 секунда не может получиться из любой корректной строки. Но как вернуть результат -1, если произошло исключение? Для этого исключение необходимо поймать (catch).
fun timeStrToSeconds(str: String): Int {
val parts = str.split(":")
var result = 0
try {
for (part in parts) {
val number = part.toInt()
result = result * 60 + number
}
return result
}
catch (e: NumberFormatException) {
return -1
}
}
Ловится исключение так. Часть функции, где может произойти исключение, оборачивается блоком try { } — сравните текст функции с её первоначальным вариантом. try с английского переводится как «попытаться» (выполнить участок программы, в котором может произойти исключение). После блока try записывается один (или несколько) блоков catch (e: ExceptionType) { } — в котором написано, что следует делать, если произошло определённое исключение. Как только в результате одного из вызовов функций внутри блока try происходит исключение типа NumberFormatException, выполнение блока try прерывается и начинает выполняться блок catch. e: ExceptionType — это параметр блока catch, ExceptionType указывает его тип — в нашем случае это NumberFormatException.
Рассмотрим порядок ловли исключения чуть более точно. Пусть в некоторой функции foo произошло определённое исключение типа SomeException. Будем считать, что функция способна обработать исключение типа SomeException, если в данный момент она находится внутри блока try, и за ним имеется блок catch для ловли исключения типа SomeException или более общего (например, Exception). Тогда программа последовательно выполнит следующие действия:
- Проверим, может ли функция
fooобработать исключение. Если да — управление передаётся её блоку catch. - В противном случае, перейдём к функции
bar, которая до этого вызвала функциюfoo. Проверим, может ли она обработать исключение. Если да — управление передаётся её блоку catch. - В противном случае, перейдём у функции
baz, которая до этого вызвала функциюbar. Проверим то же самое для неё. - И так далее. Если в итоге мы дошли до самого верхнего уровня (например, функции
main), и ни одна из функций на нашем пути не может обработать исключение — выполнение программы прерывается. В консоли при этом появится сообщение о произошедшем исключении и стек вызовов функций в момент его появления.
Выполнение блока catch после передачи управления ему происходит обычным образом. В нашем случае он содержит один оператор return -1, который формирует результат функции, и выполнение её на этом заканчивается. В общем случае содержимое блока catch может быть любым. После окончания его выполнения, начинает выполняться следующий оператор после try..catch, если такой оператор есть.
Ловля и обработка исключений — очень важный элемент программирования. Пользуясь чужими программами, вам, скорее всего, не раз приходилось говорить, что программа «упала». В современном программировании такое «падение» программы чаще всего вызывается именно исключением, которое возникло, но никем не было поймано и обработано. Такое исключение приводит к аварийной остановке работы программы, что в промышленном программировании недопустимо. Принято, что программа должна КОРРЕКТНО реагировать на любые, в том числе некорректные, действия пользователя, поэтому промышленные программы обычно включают в себя механизмы обработки исключений.
Форматирование строк
Не менее важной задачей является представление определённой информации пользователю. Здесь мы касаемся лишь маленького кусочка этой задачи — правильного форматирования строк. Вспомним ещё раз нашу задачу о преобразовании времени в число секунд и рассмотрим обратную ей. Пусть дано время в секундах, прошедшее с начала дня, и необходимо сформировать строку в формате «ЧЧ:ММ:СС», соответствующую данному времени.
Представим себе, что мы дали на эту задачу ответ вроде "13:8:1" вместо ожидаемого "13:08:01". С одной стороны, человек должен быть в состоянии понять и наш ответ, но с другой стороны, привычным для человека является всё-таки формат "13:08:01" и, увидев наш ответ без нулей, он на мгновение придёт в ступор и задумается, а что же это вообще такое — время или же просто последовательность чисел. Именно поэтому важно всё-таки соблюдать ожидаемый формат.
Для решения задачи мы могли бы воспользоваться функцией вроде этой:
fun twoDigitStr(n: Int) = if (n in 0..9) "0$n" else "$n"
которая для однозначных чисел формирует строку с нулём впереди, а для остальных всё оставляет как есть. Решение с помощью функции twoDigitStr выглядело бы так:
fun timeSecondsToStr(seconds: Int): String {
val hour = seconds / 3600
val minute = (seconds % 3600) / 60
val second = seconds % 60
return "${twoDigitStr(hour)}:${twoDigitStr(minute)}:${twoDigitStr(second)}"
}
В первых трёх операторах мы рассчитываем текущий час, минуту и секунду путём деления на 60. В последнем мы формируем требуемую строку, и данная функция работает верно. Есть только два «но»: выглядит последний оператор довольно уродливо, а кроме того, при форматировании строк может возникать много похожих задач и, казалось бы, для них должно существовать общее решение.
Таким решением является готовая функция String.format(). В данном случае она может использоваться так:
fun timeSecondsToStr(seconds: Int): String {
val hour = seconds / 3600
val minute = (seconds % 3600) / 60
val second = seconds % 60
return String.format("%02d:%02d:%02d", hour, minute, second)
}
Первым аргументом функции является форматная строка. Это обычный строковый литерал (константа), в которой, однако, особый смысл несёт символ процента %. Этот символ вместе с несколькими последующими образует модификатор формата, который функцией String.format будет заменён на её следующий аргумент (hour для первого процента, minute для второго и second для третьего). В этом смысле модификаторы формата напоминают строковые шаблоны "$name", но они имеют большую мощность, так как позволяют выбрать ещё и форматподстановки аргумента в строку.
Конкретно %02d означает «подставить в строку целое число, заняв НЕ МЕНЬШЕ двух (2) символов и заполнив НЕДОСТАЮЩИЕ символы (если число однозначное) нулём (0). Перечислим другие распространённые модификаторы формата:
%d— подставить число типаInt;%3d— подставить число типаInt, заняв не меньше трёх позиций (пустые заполняются по умолчанию пробелами);%c— подставить символ;%s— подставить строку;%20s— подставить строку, заняв не меньше 20 позиций;%lf— подставить число типаDoubleв обычном формате;%le— подставить число типаDoubleв экспоненциальном формате вида 1.3e+4;%6.2lf— подставить число типаDoubleв обычном формате, заняв не меньше шести позиций и используя ровно два знака после запятой.
Полное перечисление возможностей форматной строки выходит за рамки этого пособия. Довольно полное описание имеется в соответствующей статье Википедии, см. https://en.wikipedia.org/wiki/Printf_format_string#Syntax или её русскоязычный аналог.
Благодарю!