Справочник от Автор24
Поделись лекцией за скидку на Автор24

Обработка исключений. Программирование на языке Java

  • ⌛ 2021 год
  • 👀 486 просмотров
  • 📌 451 загрузка
  • 🏢️ ВШЭ БИ
Выбери формат для чтения
Загружаем конспект в формате pdf
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Конспект лекции по дисциплине «Обработка исключений. Программирование на языке Java» pdf
Лекция 9 Обработка исключений Программирование на языке Java Роман Гуров ВШЭ БИ 2021 Пример иерархии Рассмотрим пример иерархии классов, реализующей геометрические фигуры public class Point { public Point(double x, double y) { this.x = x; this.y = y; } public double getX() { return x; } public double getY() { return y; } @Override public String toString() { return "(" + x + ", " + y + ")"; } private final double x; private final double y; } Пример иерархии public enum Color { BLACK, WHITE, RED, GREEN, BLUE } public class Triangle extends Shape { private final Point a; private final Point b; private final Point c; public Triangle(Point a, Point b, Point c, Color color) { super(color); this.a = a; this.b = b; this.c = c; } public abstract class Shape { public Shape(Color color) { this.color = color; } @Override public double getArea() { return Math.abs((a.getX() - c.getX()) * (b.getY() - c.getY()) - (b.getX() - c.getX()) * (a.getY() - c.getY())) / 2; } public Color getColor() { return color; } public abstract double getArea(); private final Color color; } } public class Circle extends Shape { private final Point center; private final double radius; } public class Square extends Shape { private final Point center; private final double size; public Circle(Point center, double radius, Color color) { super(color); this.center = center; this.radius = radius; } public Square(Point corner, double size, Color color) { super(color); this.center = corner; this.size = size; } public Point getCenter() { return center; } public Point getCenter() { return center;} public double getRadius() { return radius; } public double getSize() { return size; } @Override public double getArea() { return radius * radius * Math.PI; } @Override public double getArea() { return size * size; } } Пример иерархии public static void main(String[] args) { Circle circle = new Circle( new Point(0, 0), 1, Color.BLACK); Triangle triangle = new Triangle( new Point(0, 0), new Point(1, 0), new Point(0, 1), Color.RED); Square square = new Square( new Point(5, 5), 2, Color.BLUE); Shape shape = triangle; Object object = triangle; triangle = (Triangle) object; Shape[] shapes = {circle, triangle, square}; Shape maxShape = findShapeWithMaxArea(shapes); System.out.println("Shape with max area: " + maxShape); } private static Shape findShapeWithMaxArea(Shape[] shapes) { Shape maxShape = null; double maxArea = Double.NEGATIVE_INFINITY; for (Shape shape: shapes) { double area = shape.getArea(); if (area > maxArea) { maxArea = area; maxShape = shape; } } return maxShape; } Как обрабатывать ошибки? Рассмотрим пример: нужно прочитать текст из файла и вывести его на экран Условно, решение может выглядеть вот так: public static void main(String[] args) { String file_text = readFile("texts/testfile.txt"); System.out.println(file_text); } А что делать, если файла с таким именем не существует? Что в таком случае должна делать функция readFile? public static String readFile(String file_path) { if (!fileExists(file_path)) { // Ошибка! А что делать то?! } // ... return file.read(); } Как обрабатывать ошибки? Попытаемся разрулить ситуацию самостоятельно Давайте возвращать пустой результат при ошибке: public static String readFile(String file_path) { if (!fileExists(file_path)) { return ""; } // ... return file.read(); } Снаружи будет невозможно отличить, ошибка это, или просто пустой файл Тогда, воспользуемся особенностью ссылочных переменных и вернём null Уже лучше, но что если файл существует и мы столкнулись с ошибкой доступа? Если тоже вернём null, то снаружи будет невозможно определить причину ошибки Как обрабатывать ошибки? Поступим как в языке C, возвращая из функции код ошибки, а результат будем принимать с помощью аргументов public static int readFile(String file_path, StringBuilder result) { if (!fileExists(file_path)) { return 1; } // ...??? result.append(file.read()); return 0; } Использование функции теперь выглядит как-то так: public static void main(String[] args) { StringBuilder file_text = new StringBuilder(); int error_code = readFile1("texts/testfile.txt", file_text); if (error_code == 1) { System.out.println("File not found"); } System.out.println(file_text); } А если в аргумент result передали null? Как обрабатывать ошибки? А если в аргумент result передали null? Наш придуманный механизм обработки ошибок теперь тоже требует обработки ошибок: public static int readFile(String file_path, StringBuilder result) { if (!fileExists(file_path)) { return 1; } if (result == null) { return 2; } // ...??? result.append(file.read()); return 0; } public static void main(String[] args) { StringBuilder file_text = new StringBuilder(); int error_code = readFile1("texts/testfile.txt", file_text); if (error_code == 1) { System.out.println("File not found"); } else if (error_code == 2) { System.out.println("null pointer error"); // ??? } else if (error_code != 0) { System.out.println("Some unknown error with code " + error_code); } System.out.println(file_text); } Как обрабатывать ошибки? Можем ограничить и конкретизировать виды ошибок с помощью enum: enum ReadFileStatus { Good, FileNotFoundError, NullPointerError } public static ReadFileStatus readFile(String file_path, StringBuilder result) { if (!fileExists(file_path)) { return ReadFileStatus.FileNotFoundError; } if (result == null) { return ReadFileStatus.NullPointerError; } // ...??? result.append(file.read()); return ReadFileStatus.Good; } public static void main(String[] args) { StringBuilder file_text = new StringBuilder(); ReadFileStatus error_code = readFile1("texts/testfile.txt", file_text); if (error_code == ReadFileStatus.FileNotFoundError) { System.out.println("File not found"); } else if (error_code == ReadFileStatus.NullPointerError) { System.out.println("null pointer error"); // ??? } System.out.println(file_text); } Но это всё ужасно и неудобно. Как правильно обрабатывать ошибки? Изобретательство велосипедов не привело к хорошему результату Никак не обойтись без нового волшебного механизма от самого языка программирования В Java для этого существует механизм исключений (англ. exception) public static void main(String[] args) { StringBuilder builder = null; builder.append("data"); } Исключение – событие, прерывающее стандартный ход исполнения программы Его можно обработать и вернуть программу обратно в штатный режим исполнения Инициация такого события называется бросанием исключения Если исключение никак не обработано, то, по-умолчанию, его текст будет выведен в System.out, а сама программа экстренно завершит исполнение: Примеры исключений, встроенных в Java NullPointerException – исключение, возникающее, когда значение null оказалось в неположенном месте Например, при обращении к методу или полю объекта public static void main(String[] args) { StringBuilder builder = null; builder.append("data"); } Примеры исключений, встроенных в Java ArrayIndexOutOfBoundsException – обращение за пределы границ массива public static void main(String[] args) { int[] array = new int[100]; array[1000] = 30; } public static void main(String[] args) { ArrayList arr = new ArrayList<>(); arr.get(0); } Примеры исключений, встроенных в Java StringIndexOutOfBoundsException – обращение за пределы границ строки public static void main(String[] args) { String str = "Short"; str.charAt(-100); } Вместе с исключением также выводится и полный стек вызовов до места его появления Примеры исключений, встроенных в Java NoSuchFileException – указанный файл не найден public static void main(String[] args) throws java.io.IOException { Path file_path = Paths.get("texts/testfile.txt"); byte[] file_text_bytes = Files.readAllBytes(file_path); String file_text = new String(file_text_bytes); System.out.println(file_text); } Примеры исключений, встроенных в Java Исключения существуют и для ошибок, произошедших в JVM OutOfMemoryError – виртуальная машина Java израсходовала всю выделенную её память public static void main(String[] args) { ArrayList arr = new ArrayList<>(); while (true) { arr.add(3.14); } } Примеры исключений, встроенных в Java Исключения существуют и для ошибок, связанных с JVM NoClassDefFoundError – виртуальная машина Java не смогла найти запрашиваемый класс Вспомним старый пример с библиотекой и забудем передать её при запуске: C:\Users\user\IdeaProjects\external>javac -classpath commons-lang3-3.12.0.jar Main.java C:\Users\user\IdeaProjects\external>java Main Hello, world! Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/lang3/StringUtils at Main.main(Main.java:6) Caused by: java.lang.ClassNotFoundException: org.apache.commons.lang3.StringUtils at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) ... 1 more java.lang.Throwable Как и почти всё в языке Java, брошенные исключения являются объектами соответствующих классов Все исключения обязательно наследуются от класса Throwable Ключевое свойство экземпляров Throwable и его подклассов – возможность быть брошенными Бросить исключение можно при помощи ключевого слова throw public static void main(String[] args) { throw new RuntimeException( "This program is supposed to die." ); } Важно заметить, что стек вызовов запоминается в момент создания объекта исключения, а не в момент его бросания java.lang.Throwable Класс Throwable обладает множеством полезных методов: package java.lang; public class Throwable { public String getMessage() { /* ... */ } public void printStackTrace() { /* ... */ } public StackTraceElement[] getStackTrace() { /* ... */ } public Throwable getCause() { /* ... */ } public Throwable[] getSuppressed() { /* ... */ } // ... } • getMessage() – возвращает то самое сообщение, которое было передано в конструктор исключения и используется для описания подробностей возникшей проблемы • printStackTrace() – печатает на экран отформатированный стек вызовов для этого исключения • getStackTrace() – возвращает стек вызовов в формате массива элементов, по которым можно проитерироваться и вручную обработать в коде java.lang.Throwable public Throwable getCause() { /* ... */ } public Throwable[] getSuppressed() { /* ... */ } • getCause() – возвращает исключение, являющееся реальной причиной создания текущего исключения Обычно причина задаётся при преобразовании одного исключения в другое, более собирательное Например, для баз данных в Java принято бросать свой специальный подтип исключений – SQLException Если база данных использует локальные файлы и словила при работе с ними IOException, то она все равно бросит наружу SQLException, указав объект реального исключения как cause • getSuppressed() – возвращает массив исключений, которые были заглушены при броске данного Допустим, мы открыли файл на флешке, чтобы записать в него данные, но во время записи флешку вытащили Метод write() бросил IOException, в обработчике этого исключения мы пытаемся закрыть файл, чтобы благополучно завершить работу с ним Но объекту открытого файла настолько плохо, что при попытке закрытия он бросает ещё одно исключение В такой ситуации логично было бы считать первородное исключение главным, а остальные добавлять в его массив suppressed как заглушённые им Виды исключений Есть три основных группы исключений в Java: • java.lang.Error – исключительные ситуации в JVM public class Error extends Throwable { Пытаться как-то обработать эти исключения обычно бесполезно, после них программа вряд ли способна нормально продолжать работу • Исключительные ситуации в пользовательском коде: • java.lang.Exception – проверяемые (checked) • java.lang.RuntimeException – непроверяемые (unchecked) public class Exception extends Throwable { public class RuntimeException extends Exception { Обработка этих исключений уже, чаще всего, осмысленна Виды исключений Проверяемые исключения Исключения-наследники RuntimeException являются непроверяемыми – их можно спокойно бросать отовсюду Все остальные наследники Exception являются проверяемыми – компилятор внимательно следит за тем, чтобы эти исключения не остались незамеченными public static void someFunction() { throw new FileNotFoundException("I throw eheheheh!"); } При попытке бросить такое исключение, мы столкнёмся с ошибкой компиляции: Проверяемое исключение обязательно нужно либо обработать, либо явно указать ключевым словом throws, что наша функция умеет его бросать public static void someFunction() throws IOException { throw new FileNotFoundException("I throw eheheheh!"); } Точно такое же правило относится и к функциям, внутри которых вызываются бросающие функции: public static void main(String[] args) throws IOException { someFunction(); } Собственные исключения Научимся создавать свои типы исключений class Cat { private float weight; public static final float MAX_WEIGHT = 10.f; public Cat(float weight) { this.weight = weight; } } При создании кота может возникнуть ошибка Обычно, в такой ситуации мы могли бы обойтись стандартными исключениями из Java, но создание своего собственного позволит явно отличать его от других class CatCreationException extends RuntimeException { public CatCreationException(String message) { super(message); } public CatCreationException(String message, Throwable cause) { super(message, cause); } } public Cat(float weight) { if (weight > MAX_WEIGHT) { throw new CatCreationException("The cat is too fat with weight: " + weight + "!"); } else if (weight < 0) { throw new CatCreationException("The cat has negative weight!"); } this.weight = weight; } Обработка исключений Как же всё-таки не крашить программу при бросании исключения? Для обработки исключения в языке есть конструкция try-catch public static void dangerousFunction() { throw new IllegalArgumentException("I'll crash your program!"); } public static void main(String[] args) { try { dangerousFunction(); } catch (RuntimeException ex) { System.out.println("Dangerous function thrown an exception with text: " + ex.getMessage()); } } В блоке try пишется код, способный бросить исключение При броске, исключение летит наружу по стеку вызовов (раскручивает стек вызовов) Если его никто не поймает, то оно вылетит за пределы точки входа и вызовет экстренное завершение Чтобы поймать исключение, к блоку try добавляется блок catch, в котором указывается ловимый тип (или общий предок) Внутри блока catch реализуется обработка пойманного исключения, также тут доступен его объект Обработка исключений Обработчиков может быть несколько: public static void main(String[] args) { try { dangerousFunction(); } catch (IllegalArgumentException ex) { System.out.println("I knew it! The function threw IllegalArgumentException"); } catch (RuntimeException ex) { System.out.println("That's weird, it threw some other kind of RuntimeException"); } catch (Exception ex) { System.out.println("That shouldn't happen, dangerousFunction doesn't specify checked exceptions using throws keyword"); } catch (Throwable ex) { System.out.println("What is going on inside that function?! Does it try to break the JVM somehow?"); } System.out.println("Congratulations! We survived the exception attack! But at what cost?"); } В таком случае будет вызван только первый подходящий Если исключение поймано, то исполнение кода продолжается после конструкции try-catch Несколько типов в одном catch Иногда для разных типов исключений нужна одинаковая обработка try { dangerousFunction(); } catch (IllegalArgumentException ex) { System.out.println("Exception caught: " + ex.getMessage()); } catch (IllegalStateException ex) { System.out.println("Exception caught: " + ex.getMessage()); } Вместо того, чтобы ловить общего предка вместе со всеми другими наследниками, можно просто перечислить оба типа в одном блоке catch try { dangerousFunction(); } catch (IllegalArgumentException | IllegalStateException ex) { System.out.println("Exception caught: " + ex.getMessage()); } Тогда будут пойманы только они, а переменная ex будет иметь тип их ближайшего общего предка Повторное бросание Из блока catch можно бросить новое исключение try { try { throw new NullPointerException("inner"); } catch (NullPointerException ex) { throw new IllegalStateException("outer", ex); } } catch (RuntimeException ex) { ex.printStackTrace(); } Также, можно повторно бросить и само пойманное исключение try { try { throw new NullPointerException("inner"); } catch (NullPointerException ex) { System.out.println("NullPointerException caught but rethrown"); throw ex; } } catch (RuntimeException ex) { ex.printStackTrace(); } finally Существует ещё один блок – finally InputStream is = new FileInputStream("text.txt"); try { readTextFromStream(is); } finally { is.close(); } Он будет вызван последним всегда, несмотря на то, было исключение или нет, поймано оно или не поймано Используется он для того, чтобы освободить взятый ресурс: открытый файл, интернет-соединение Сам же блок не ловит исключений, необработанные исключения после выполнения этого блока полетят дальше Проблема с finally InputStream is = new FileInputStream("text.txt"); try { readTextFromStream(is); } finally { is.close(); } А если close сам бросит исключение? Тогда изначальное исключение “потеряется” Можно попытаться поймать и заигнорить все побочные исключения InputStream is = new FileInputStream("text.txt"); try { readTextFromStream(is); } finally { try { is.close(); } catch (IOException ex) { // do nothing } } Терять информацию – неправильно try-with-resources Для правильного закрытия ресурсов есть специальная конструкция – try-with-resources try (InputStream is = new FileInputStream("text.txt")) { readTextFromStream(is); } catch (IOException ex) { } После слова try в скобках перечисляются через точку с запятой все ресурсы, которые используются в этом блоке Гарантируется, что в после выполнения блока все ресурсы будут освобождены вызовом их метода close() Если при брошенном исключении при освобождении ресурсов вылетело ещё одно, то оно будет проигнорировано и добавлено в массив suppressed у изначального AutoCloseable Для использования ресурса с конструкцией try-with-resources, он должен реализовывать интерфейс AutoClosable: public interface AutoCloseable { void close() throws Exception; } Например: static class Resource implements AutoCloseable { public void die() throws FileNotFoundException { throw new FileNotFoundException(); } @Override public void close() throws IOException { throw new IOException("closing failed"); } } public static void main(String[] args) throws IOException { try (Resource res = new Resource()) { res.die(); } catch (FileNotFoundException ex) { ex.printStackTrace(); } } Гарантии исключений При реализации метода класса стоит задуматься о том, какие гарантии безопасности исключений он даёт Сильные гарантии – при выбросе исключения из метода, объект останется в том же корректном состоянии, как если бы этот метод не был вызван вообще Слабые гарантии – при выбросе исключения из метода, объект остаться в изменённом, но обязательно корректном состоянии Если таких гарантий дать не удаётся и объект всегда становится невалидным при бросании исключения, то можно гарантировать хотя бы отсутствие утечек ресурсов В худшем случае, может не быть вообще никаких гарантий
«Обработка исключений. Программирование на языке Java» 👇
Готовые курсовые работы и рефераты
Купить от 250 ₽
Решение задач от ИИ за 2 минуты
Решить задачу
Помощь с рефератом от нейросети
Написать ИИ

Тебе могут подойти лекции

Смотреть все 588 лекций
Все самое важное и интересное в Telegram

Все сервисы Справочника в твоем телефоне! Просто напиши Боту, что ты ищешь и он быстро найдет нужную статью, лекцию или пособие для тебя!

Перейти в Telegram Bot