arrow-left

All pages
gitbookPowered by GitBook
1 of 19

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

11. Принцип абстракции. Дополнительные принципы ООП

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

Принцип минимальных обязательств - интерфейс объекта должен описать только существенные аспекты его поведения;

Принцип наименьшего удивления - абстракция должна описывать только поведение объекта, ни больше, ни меньше.

Виды абстракций:

  • абстракция сущности - объект представляет собой полезную модель некоторой сущности в предметной области ("Студент", "Преподаватель", "Аудитория");

  • абстракция поведения - объект состоит из обобщенного множества операций ("Менеджер соединения с базой данных");

  • абстракция виртуальной машины - объект группирует операции, которые вместе используются более высоким уровнем управления;

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

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

Для описания грузоперевозок важной характеристикой будет грузоподъемность автомобиля, а для описания автомобильных гонок она не существенна. Но для моделирования гонок обязательно надо описать метод набора скорости данным автомобилем, а для грузоперевозок это не столь важно.

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

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

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

Как это сделать? Можно воспользоваться следующей методикой: опишите процесс словами и проанализируйте получившиеся фразы. "Завод выпускает автомобили" - здесь два объекта: завод и автомобиль. Производственно-технические характеристики завода составят набор полей объекта "Завод", а процесс выпуска автомобиля будет описан в виде набора методов объекта "Завод".

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

Полями объекта "Учебный курс" будут его название, программа, количество часов, перечень учебных пособий. Будет ли объект "Учебный курс" обладать какими-то методами или в этом объекта будут только поля? Какие действия выполняет "Учебный курс"? По-видимому, единственным действием объекта "Учебный курс" будет предоставление своих полей другим объектам, значит, нужны методы доступа к полям объекта.

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

hashtag
Дополнительные принципы ООП

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

hashtag
Модульность

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

hashtag
Иерархия

Иерархия - ранжированная или упорядоченная система абстракций. Принцип иерархичности предполагает использование иерархий при разработке программных систем. В ООП используется два вида иерархии:

  • иерархия "целое/часть" - показывает, что некоторые абстракции являются частями других абстракций. Например, лампа состоит из цоколя, нити накаливания и колбы;

  • иерархия "общее/частное" - показывает, что некоторая абстракция является частным случаем другой абстракции. Например, "обеденный стол" - конкретный вид стола, а "стол" - конкретный вид мебели. Такая иерархия используется при разработке структуры классов, когда сложные классы строятся на базе более простых классов путем добавления к ним новых характеристик и, возможно, уточнения имеющихся. Реализуется с помощью иерархии наследования.

hashtag
Типизация

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

hashtag
Устойчивость

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

  • временные объекты - хранят промежуточные результаты некоторых действий, например, вычислений;

  • локальные объекты - существуют внутри методов, объект уничтожается после окончания работы метода;

  • глобальные объекты - существуют, пока программа загружена в память;

сохраняемые объекты - хранятся в файлах внешней памяти между сеансами работы программы.

15. Исключения в Java. Обработка исключений

Исключение (exception) - это ненормальная ситуация (термин "исключение" здесь следует понимать как "исключительная ситуация"), возникающая во время выполнения программного кода. Иными словами, исключение - это ошибка, возникающая во время выполнения программы (в runtime).

Исключение - это способ системы Java (в частности, JVM - виртуальной машины Java) сообщить вашей программе, что в коде произошла ошибка. К примеру, это может быть деление на ноль, попытка обратиться к массиву по несуществующему индексу, очень распространенная ошибка нулевого указателя (NullPointerException) - когда вы обращаетесь к ссылочной переменной, у которой значение равно null и так далее.

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

Обработка исключений (exception handling) - название объектно-ориентированной техники, которая пытается разрешить эти ошибки.

Программа в Java может сгенерировать различные исключения, например:

  • программа может пытаться прочитать файл из диска, но файл не существует;

  • программа может попытаться записать файл на диск, но диск заполнен или не отформатирован;

  • программа может попросить пользователя ввести данные, но пользователь ввел данные неверного типа;

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

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

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

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

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

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

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

Так как в Java ВСЁ ЯВЛЯЕТСЯ ОБЪЕКТОМ, то исключение тоже является объектом некоторого класса, который описывает исключительную ситуацию, возникающую в определенной части программного кода.

«Обработка исключений» работает следующим образом:

  • когда возникает исключительная ситуация, JVM генерирует (говорят, что JVM ВЫБРАСЫВАЕТ исключение, для описания этого процесса используется ключевое слово throw) объект исключения и передает его в метод, в котором произошло исключение;

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

circle-exclamation

Если вы не предусмотрите обработчик исключений, то исключение будет перехвачено стандартным обработчиком Java. Стандартный обработчик прекратит выполнение программы и выведет сообщение об ошибке.

Рассмотрим пример исключения и реакцию стандартного обработчика Java.

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

Строка

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

Далее сообщается

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

называются «трассировкой стека» (stack tracing). О каком стеке идет речь? Речь идет о стеке вызовов (call stack). Соответственно, эти строки означают последовательность вызванных методов, начиная от метода, в котором произошло исключение, заканчивая самым первым вызванным методом.

circle-info

Для вызова методов в программе используется инструкция «call». Когда вы вызываете метод в программе, важно сохранить адрес следующей инструкции, чтобы, когда вызванный метод отработал, программа продолжила работу со следующей инструкции. Этот адрес нужно где-то хранить в памяти. Также перед вызовом необходимо сохранить аргументы функции, которые тоже необходимо где-то хранить.

Вся эта информация хранится в специальной структуре – стеке вызовов. Каждая запись в стеке вызовов называется кадром или фреймом (stack frame).

Подробнее: , , ,

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

Классы исключений

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

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

На самом верхнем уровне расположен класс Throwable, который является базовым для всех исключений (как мы помним, JVM «выбрасывает» исключение», поэтому класс Throwable означает – то, что может «выбросить» JVM).

От класса Throwable наследуются классы Error и Exception. Среди подклассов Exception отдельно выделен класс RuntimeException, который играет важную роль в иерархии исключений.

hashtag
Виды исключений

В Java существует некоторая неопределенность насчет того – существует ли два или три вида исключений.

Если делить исключения на два вида, то это:

  1. контролируемые исключения (checked exceptions) – подклассы класса Exception, кроме подкласса RuntimeException и его производных;

  2. неконтролируемые исключения (unchecked exceptions) – класс Error с подклассами, а также класс RuntimeException и его производные;

circle-info

В некоторых источниках класс Error и его подклассы выделяют в отдельный вид исключений - ошибки (errors).

Класс Error

Далее мы видим класс Error. Классы этой ветки составляют вид исключений, который можно обозначить как «ошибки» (errors). Ошибки представляют собой серьезные проблемы, которые не следует пытаться обработать в собственной программе, поскольку они связаны с проблемами уровня JVM.

circle-info

На самом деле, вы конечно можете предпринять некоторые действия при возникновении ошибок, например, вывести сообщение для пользователя в удобном формате, выслать трассировку стека себе на почту, чтобы понять – что вообще произошло.

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

В качестве примеров «ошибок» можно привести: переполнение стека вызова (класс StackOverflowError); нехватка памяти в куче (класс OutOfMemoryError), вследствие чего JVM не может выделить память под новый объект и сборщик мусора не помогает; ошибка виртуальной машины, вследствие которой она не может работать дальше (класс VirtualMachineError) и так далее.

Несмотря на то, что в нашей программе мы никак не можем помочь этой проблеме, и приложение не может работать дальше (ну как может работать приложение, если стек вызовов переполнен или JVM не может дальше выполнять код?!); знание природы этих ошибок поможет вам предпринять некоторые действия, чтобы избежать этих ошибок в дальнейшем. Например, ошибки типа StackOverflowError и OutOfMemoryError могут быть следствием вашего некорректного кода.

Например, попробуем спровоцировать ошибку StackOverflowError

Получим такое сообщение об ошибке

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

Ошибка VirtualMachineError может означать, что следует переустановить библиотеки Java.

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

circle-info

Справочник по исключениям типа Error смотрите здесь –

Класс Exception

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

В классе Exception описаны исключения двух видов: контролируемые исключения (checked exceptions) и неконтролируемые исключения (unchecked exceptions).

Неконтролируемые исключения содержатся в подклассе RuntimeException и его наследниках. Контролируемые исключения содержатся в остальных подклассах Exception.

В чем разница между контролируемыми и неконтролируемыми исключениями, мы узнаем позже, а теперь рассмотрим вопрос – а как же именно нам обрабатывать исключения?

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

Обработка исключений в методе может выполняться двумя способами:

  1. с помощью связки try-catch;

  2. с помощью ключевого слова throws в сигнатуре метода.

Рассмотрим оба метода поподробнее:

Способ 1. Связка try-catch

Этот способ кратко можно описать следующим образом.

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

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

Первое, что нам нужно определить – и что является главным при работе с исключениями, КАКАЯ ИНСТРУКЦИЯ МОЖЕТ ПРИВЕСТИ К ВОЗНИКНОВЕНИЮ ИСКЛЮЧЕНИЯ?

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

в которых происходит преобразование ввода пользователя в целое число (метод parseInt() преобразует цифры в строке в число).

Почему здесь может возникнуть исключение? Потому что пользователь может ввести не число, а просто какой-то текст и тогда непонятно – что записывать в переменную a или b. И да, действительно, если пользователь введет некорректное значение, возникнет исключение в методе Integer.parseInt().

Итак, что мы можем сделать. «Опасный код» нужно поместить в блок try{}

Обратите внимание на синтаксис блока try. В самом простом случае это просто ключевое слово try, после которого идут парные фигурные скобки. Внутри этих скобок и заключается «опасный» код, который может вызвать исключение. Сразу после блока try должен идти блок catch().

Обратите внимание на синтаксис блока catch. После ключевого слова, в скобках описывается аргумент с именем e типа NumberFormatException.

Когда произойдет исключение, то система Java прервет выполнение инструкций в блоке try и передаст управление блоку catch и запишет в этот аргумент объект исключения, который сгенерировала Java-машина.

circle-exclamation

То есть, как только в блоке try возникнет исключение, то дальше инструкции в блоке try выполняться не будут! А сразу же начнут выполняться действия в блоке catch.

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

circle-exclamation

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

Например, если в нашем примере мы напишем код, который потенциально может выбросить исключение типа IOException, но не изменим блок catch

тогда обработчик не будет вызван и исключение будет обработано стандартным обработчиком Java.

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

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

Изменим наш пример и выделим в отдельный метод код, который будет запрашивать у пользователя число и возвращать его как результат работы метода

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

  1. обработка исключений может происходить централизованно однотипным способом (например, показ окошка с сообщением и с определенным текстом);

  2. это не входит в нашу компетенцию как программиста – обработкой исключений занимается другой программист;

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

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

Обратите внимание на расположение сигнатуру метода. Мы привыкли, что при объявлении метода сразу после скобок входных аргументов мы открываем фигурную скобку и записываем тело метода. Здесь же, после входных аргументов, мы пишем ключевое слово throws и потом указываем тип исключения, которое может быть сгенерировано в нашем методе. Если метод может выбрасывать несколько типов исключений, они записываются через запятую

Тогда, в методе main мы должны написать примерно следующее

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

hashtag
Отличия между контролируемыми и неконтролируемыми исключениями

Если вы вызываете метод, который выбрасывает checked исключение, то вы ОБЯЗАНЫ предусмотреть обработку возможного исключения, то есть связку try-catch.

Яркий пример checked исключения – класс IOException и его подклассы.

Рассмотрим пример – попробуем прочитать файл и построчно вывести его содержимое на экран консоли:

Как мы видим, компилятор не хочет компилировать наш код. Чем же он недоволен? У нас в коде происходит вызов двух методов – статического метода Files.newBufferedReader() и обычного метода BufferedReader.readLine().

Если посмотреть на сигнатуры этих методов то можно увидеть, что оба этих метода выбрасывают исключения типа IOException. Этот тип исключения относится к checked-исключению и поэтому, если вы вызываете эти методы, компилятор ТРЕБУЕТ от вас предусмотреть блок catch, либо в самом вашем методе указать throws IOException и, таким образом, передать обязанность обрабатывать исключение другому методу, который будет вызывать ваш.

Таким образом, «оборачиваем» наш код в блок try и пишем блок catch.

Еще один способ - указать в сигнатуре метода, что он выбрасывает исключение типа IOException и переложить обязанность обработать ошибку в вызывающем коде

circle-exclamation

Eсли метод выбрасывает checked-исключение, то проверка на наличие catch-блока происходит на этапе компиляции. И вы обязаны предусмотреть обработку исключения для checked-исключения.

Что касается unchecked-исключения, то обязательной обработки исключения нет – вы можете оставить подобные ситуации без обработки.

hashtag
Зачем необходимо наличие двух видов исключений?

В большинстве языков существует всего лишь один тип исключений – unchecked. Некоторые языки, например, C#, в свое время отказались от checked-исключений.

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

С другой стороны, зачем нужно делать некоторые типы исключений checked? Почему просто не сделать все исключения unchecked и оставить решения об обработке исключений целиком на совести программиста?

В официальной документации написано, что unchecked-исключения – это те исключения, от которых программа «не может восстановиться», тогда как checked-исключения позволяют откатить некоторую операцию и повторить ее снова.

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

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

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

hashtag
Дополнительно об исключениях

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

hashtag
Несколько блоков catch

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

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

Так как вы можете указать как точный класс, так и суперкласс, то если первым блоком будет блок для суперкласса – выполнится он. Например, исключение FileNotFoundException является подклассом IOException. И поэтому если вы первым поставите блок с IOException – он будет вызываться для всех подтипов исключений, в том числе и для FileNotFoundException и блок c FileNotFoundException никогда не выполнится.

hashtag
Один блок для обработки нескольких типов исключений

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

Как мы видим, один блок catch используется для обработки и типа IOException и NullPointerException и NumberFormaException.

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

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

Выбрасывание исключения с помощью ключевого слова throw

С помощью ключевого слова throw вы можете преднамеренно «выбросить» определенный тип исключения.

Блок finally

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

При любом развитии события в блоке try, код в блоке finally отработает в любом случае.

circle-info

Блок finally отработает, даже если в try-catch присутствует оператор return.

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

Блок try-with-resources

Блок try-with-resources является модификацией блока try. Данный блок позволяет автоматически закрывать ресурс после окончания работы блока try и является удобной альтернативой блоку finally.

Внутри скобок блока try объявляется один или несколько ресурсов, которые после отработки блока try-catch будут автоматически освобождены. Для этого объект ресурса должен реализовывать интерфейс java.lang.AutoCloseable.

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

Встроенные в Java исключения позволяют обрабатывать большинство распространенных ошибок. Тем не менее, вы можете создавать и обрабатывать собственные типы исключений. Для того, чтобы создать класс собственного исключения, достаточно определить как его произвольный от Exception или от RuntimeException (в зависимости от того, хотите ли вы использовать checked или unchecked – исключения).

Насчет создания рекомендуется придерживаться двух правил:

  1. определитесь, исключения какого типа вы хотите использовать для собственных исключений (checked или unchecked) и старайтесь создавать исключения только этого типа;

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

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

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

  1. Указание в блоке catch объекта исключения типа Exception. Существует очень большой соблазн при создании блока catch указать тип исключения Exception и, таким образом, перехватывать все исключения, которые относятся к этому классу (а это все исключения, кроме системных ошибок). Делать так крайне не рекомендуется, т.к. вместо того чтобы решать проблему с исключениями, мы фактически игнорируем ее и просто реализуем некоторую «заглушку», чтобы приложение продолжило работу дальше. Кроме того, каждый тип исключения должен быть обработан своим определенным образом.

  2. Помещение в блок try всего тела метода. Следующий плохой прием используется, когда программист не хочет разбираться с кодом, который вызывает исключение и просто, опять же, реализует «заглушку». Этот прием очень "хорошо" сочетается с первым приемом. В блок try

программа может попытаться осуществить деление на ноль;

  • программа может попытаться обратиться к массиву по несуществующему индексу.

  • таким образом, если возникнет ошибка, все необходимые действия по ее обработке выполнит обработчик исключений.

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

  • https://goo.gl/TpuYbSarrow-up-right
    https://goo.gl/S50lR4arrow-up-right
    https://goo.gl/CcyKCCarrow-up-right
    https://goo.gl/0EMyvuarrow-up-right
    https://goo.gl/oh7RdGarrow-up-right

    2. Базовые термины ООП

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

    Объект – структура, которая объединяет данные и методы, которые эти данные обрабатывают. Фактически, объект является основным строительным блоком объектно-ориентированных программ.

    Класс – шаблон для объектов. Каждый объект является экземпляром (instance) какого-либо класса («безклассовых» объектов не существует). В рамках класса задается общий шаблон, структура, на основании которой создаются объекты. Данные, относящиеся к классу, называются полями класса, а программный код для их обработки называется методами класса. Поля и методы иногда называют общим термином – члены класса.

    Разница между классом и объектом такая же, как между абстрактным понятием и реальным объектом.

    Объект состоит из следующих частей:

    • имя объекта;

    • состояние (переменные состояния). Данные, содержащиеся в объекте, представляют его состояние. В терминологии ООП эти данные называются атрибутами. Например, атрибутами работника могут быть: имя, фамилия, пол, дата рождения, номер телефона. В разных объектах атрибуты имеют разное значение. Фактически, в объектах определяются конкретные значения тех переменных (полей класса), которые были заявлены при описании класса;

    • методы (операции) – применяются для выполнения операций с данными, а также для совершения других действий. Методы определяют, как объект взаимодействует с окружающим миром.

    Объекты могут отправлять друг другу сообщения. Сообщение (message) - это практически то же самое, что и вызов функции в обычном программировании. В ООП обычно употребляется выражение "послать сообщение" какому-либо объекту. Понятие "сообщение" в ООП можно объяснить с точки зрения ООП: мы не можем напрямую изменить состояние объекта и должны как бы послать сообщение объекту, что мы хотим как-то изменить его состояние. Очень важно понять, что объект сам меняет свое состояние, а мы можем только попросить его об этом с помощью отсылки сообщения.

    В объектно-ориентированной программе весь код должен находиться внутри классов!

    В классе описываются, какого типа данные относятся к классу, а также то, какие методы применяются к этим данным. Затем, в программе на основе того или иного класса создается экземпляр класса (объект), в котором указываются конкретные значения полей и выполняются необходимые действия над ними.

    hashtag
    От языка С к объектно-ориентированному программированию

    Давайте попробуем понять, что такое объект и класс с помощью языка программирования C.

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

    Мы можем воспользоваться массивами и хранить данные о книгах в нескольких массивах.

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

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

    Но самое главное - мы мыслим в контексте структуры компьютера, а не решаемой задачи. Для нас книга - это некий единый объект, который имеет некоторые параметры (атрибуты): название, количество страниц и так далее. Мы же представляем атрибуты этого объекта в виде отдельных записей в разных массивах, потому что на языке C мы вынуждены мыслить в терминах имеющихся структур данных (массивов, очередей, деревьев), а не в терминах отдельных объектов и их взаимоотношений. Это затрудняет понимание решаемой задачи (управление книжной лавкой и продажей книг), увеличивает количество ошибок в программе и на некотором этапе мы вообще перестаем понимать, что происходит в программе.

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

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

    Сначала мы должны описать структуру (новый тип данных).

    Таким образом, некоторое понятие реального мира (то есть, книга вообще, как понятие) в программе описан структурой Book. Экземпляр этого понятия (какая-то конкретная книга) в программе будет представлен экземпляром структуры - переменной типа Book. Мы объявляем экземпляр структуры, после чего мы сможет заполнить поля структуры и работать с ней дальше в программе.

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

    Объект реального мира обладает не только атрибутами (автор книги, название книги, количество страниц и так далее), но и определенным поведением (книгу можно продать, купить, переместить на склад и так далее). При разработке сложных программных систем необходимо группировать не только данные, но и функции, которые работают с этими данными.

    hashtag
    Пример использования объектного подхода

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

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

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

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

    Для указания класса в Java используется ключевое слово class, после чего идет название класса, далее мы ставим фигурные скобки и всё, что будет написано внутри фигурных скобок (переменные и функции) будет относиться к этому классу.

    Какие переменные и типы данных будут моделировать координаты точек и цвет? Для моделирования цвета воспользуемся обычным целым числом (очень часто цвета моделируются обычным целочисленным значением).

    Теперь попробуем подумать, а как нам смоделировать координаты трех точек? Если бы мы программировали на языке C, мы бы написали что-то вроде

    Но давайте подумаем - координаты каждой точки логически связаны между собой и когда мы будем, например, перемещать треугольник или будем менять размер треугольника, будут одновременно меняться две переменные, которые моделируют одну точку. С точки зрения объектной модели, лучшим вариантом будет предусмотреть отдельную сущность, отдельный класс, который моделирует точку на двумерной плоскости. Таким образом, мы сгруппируем данные и код, который будет эти данные изменять (например, менять значение X и Y).

    Теперь вернемся к классу Triangle и укажем, что в качестве координат у нас будут выступать три объекта класса Point2D.

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

    Какое поведение может быть у объекта класса «точка»? Совершенно точно это будет метод «изменить координаты». То есть, наша точка как отдельный объект может вести себя следующим образом – менять свои координаты. Давайте запрограммируем этот метод.

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

    Итак, мы описали треугольник, но мы определили только понятие «треугольник», у нас нет их физически, код класса - это просто описание. Если вы создали java-проект, у вас, по умолчанию, будет присутствовать класс Mainи функция main(). Пока что мы не будем объяснять, зачем нужен класс Main, а обратим внимание на метод main(). Этот метод является «точкой входа» в программу, с вызова этого метода начинается работа программы. Когда мы дойдем и выполним последнюю инструкцию внутри метода main(), приложение завершится.

    Создадим внутри метода main() несколько объектов класса Triangle. Создание объектов мы будем рассматривать подробно в следующих лекциях, на данный момент мы просто скажем, что для создания объекта используется ключевое словоnew. После создания объекта, мы можем обращаться к объекту и вызывать методы у объектов (как мы вызывали функции в C).

    circle-info

    Важно! Вы должны понять, что у разных объектов значения переменных будут разные!

    То есть, где-то в оперативной памяти у нас создано два разных объекта. Внутри первого есть объект класса Point2D, внутри этого объекта есть две переменные типа int, у которых будут значения 140 и 180. Внутри же второго треугольника будет свой объект Point2D, внутри которого будут совершенно другие две переменные типа int, у которых будут значения 50 и 80. Это же будет касаться переменных color в двух разных объектах (рис.1.5).

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

    5. Основные принципы ООП. Инкапсуляция.

    hashtag
    Инкапсуляция

    Инкапсуляция – один из основополагающих принципов ООП. Инкапсуляция – это одна из причин, почему так широко используется ООП.

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

    Инкапсуляция

    public static void main(String[] args) {
        System.out.println(5 / 0);
    }
    "C:\Program Files\Java\jdk1.8.0_60\bin\java"...
    Exception in thread "main" java.lang.ArithmeticException: / by zero
           at ua.opu.Main.main(Main.java:6)
           at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
           at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
           at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
           at java.lang.reflect.Method.invoke(Method.java:497)
           at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
    Process finished with exit code 1
    Exception in thread "main" java.lang.ArithmeticException: / by zero
    at ua.opu.Main.main(Main.java:6)
    at ua.opu.Main.main(Main.java:6)
           at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
           at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
           at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
           at java.lang.reflect.Method.invoke(Method.java:497)
           at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
    public class Main {
    
        public static void main(String[] args) {
            methodA();
        }
    
        public static void methodA() {
            methodB();
        }
    
        private static void methodB() {
            methodA();
        }
    }
    Exception in thread "main" java.lang.StackOverflowError
    	at com.company.Main.methodB(Main.java:14)
    	at com.company.Main.methodA(Main.java:10)
    	at com.company.Main.methodB(Main.java:14)
    	at com.company.Main.methodA(Main.java:10)
    	at com.company.Main.methodB(Main.java:14)
    	at com.company.Main.methodA(Main.java:10)
    	at com.company.Main.methodB(Main.java:14)
    	at com.company.Main.methodA(Main.java:10)
    ...
    List size: 236557355
    List size: 236557356
    List size: 236557357
    List size: 236557358
    List size: 236557359
    List size: 236557360
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    	at java.base/java.util.Arrays.copyOf(Arrays.java:3511)
    	at java.base/java.util.Arrays.copyOf(Arrays.java:3480)
    	at java.base/java.util.ArrayList.grow(ArrayList.java:237)
    	at java.base/java.util.ArrayList.grow(ArrayList.java:244)
    	at java.base/java.util.ArrayList.add(ArrayList.java:454)
    	at java.base/java.util.ArrayList.add(ArrayList.java:467)
    	at com.company.Main.main(Main.java:13)
    
    Process finished with exit code 1
    public static void main(String[] args) {
    
        Scanner scanner = new Scanner(System.in);
    
        System.out.println("Введите первое число: ");
        String firstNumber = scanner.nextLine();
    
        System.out.println("Введите второе число: ");
        String secondNumber = scanner.nextLine();
    
        int a = 0;
        int b = 0;
        
        a = Integer.parseInt(firstNumber);
        b = Integer.parseInt(secondNumber);
    
        System.out.println("Результат: " + (a + b));
    }
    a = Integer.parseInt(firstNumber);
    b = Integer.parseInt(secondNumber);
    int a = 0;
    int b = 0;
    
    try {
        a = Integer.parseInt(firstNumber);
        b = Integer.parseInt(secondNumber);
    } catch (NumberFormatException e) {
        // сохранить текст ошибки в лог
        System.out.println("Одно или оба значения некорректны!");
    }
    
    System.out.println("Результат: " + (a + b));
    } catch (NumberFormatException e) {
        // сохранить текст ошибки в лог
        System.out.println("Одно или оба значения некорректны!");
    }
    public static void main(String[] args) {
    
        int a = getNumberFromConsole("Введите первое число");
        int b = getNumberFromConsole("Введите второе число");
    
        System.out.println("Результат: " + (a + b));
    }
    
    public static int getNumberFromConsole(String message) {
        Scanner scanner = new Scanner(System.in);
        System.out.print(message + ": ");
        String s = scanner.nextLine();
    
        return Integer.parseInt(s);
    }
    public static int getNumberFromConsole(String message) throws NumberFormatException {
        Scanner scanner = new Scanner(System.in);
        System.out.print(message + ": ");
        String s = scanner.nextLine();
    
        return Integer.parseInt(s);
    }
    public static void foo() throws NumberFormatException, ArithmeticException, IOException {
        
    }
    public static void main(String[] args) {
    
        int a = 0;
        int b = 0;
    
        try {
            a = getNumberFromConsole("Введите первое число");
            b = getNumberFromConsole("Введите второе число");
        } catch (NumberFormatException e) {
            // сохранить текст ошибки в лог
            System.out.println("Одно или оба значения некорректны!");
        }
    
        System.out.println("Результат: " + (a + b));
    }
    public static void main(String[] args) {
        Path p = Paths.get("c:\\temp\\file.txt");
    
        BufferedReader reader = Files.newBufferedReader(p);
    
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
    }
    public static void main(String[] args) {
        try {
            Path p = Paths.get("c:\\temp\\file.txt");
    
            BufferedReader reader = Files.newBufferedReader(p);
    
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            // запись ошибки в лог
            System.out.println("Ошибка при чтении файла!");
        }
    }
    public static void main(String[] args) {
        Path p = Paths.get("c:\\temp\\file.txt");
    
        try {
            printFile(p);
        } catch (IOException e) {
    
            // запись ошибки в лог
            System.out.println("Ошибка при чтении файла!");
        }
    }
    
    public static void printFile(Path p) throws IOException {
        BufferedReader reader = Files.newBufferedReader(p);
    
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
    }
    public static void foo() {
    
        try {
            // some action
        } catch (ArithmeticException e) {
            // обработка арифметического исключения
        } catch (IndexOutOfBoundsException e) {
            // обработка выхода за пределы коллекции
        } catch (IllegalArgumentException e) {
            // обработка некорректного аргумента
        }
    }
    public static void main(String[] args) {
        Path p = Paths.get("c:\\temp\\file.txt");
    
        try {
            printFile(p);
        } catch (IOException e) {
    
            // запись ошибки в лог
            System.out.println("Ошибка при чтении файла!");
        } catch (FileNotFoundException e) {
            // данный блок никогда не будет вызван
        }
    }
    
    public static void printFile(Path p) throws IOException {
        BufferedReader reader = Files.newBufferedReader(p);
    
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
    }
    public static void foo() {
    
        try {
            // some action
        } catch (ArithmeticException | IllegalArgumentException | IndexOutOfBoundsException e) {
            // три типа исключений обрабатываются одинаково
        }
    
    }
    public static void foo() {
        try {
            // some action
            try {
                // some action
            } catch (IllegalArgumentException e) {
                // обработка вложенного блока try
            }
        } catch (ArithmeticException e) {
            // обработка исключения
        }
    }
    public static void foo(int a) {
        if (a < 0)
            throw new IllegalArgumentException("Аргумент не может быть отрицательным!");
    }
    public static void foo(int a) {
        FileOutputStream fout = null;
        try {
            File file = new File("file.txt");
            file.createNewFile();
            fout = new FileOutputStream(file);
        } catch (IOException e) {
            // обработка исключения при записи в файл
        } finally {
            try {
                fout.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public static void foo() {
        Path p = Paths.get("c:\\temp\\file.txt");
        try (BufferedReader reader = Files.newBufferedReader(p)) {
            String line;
            while ((line = reader.readLine()) != null)
                System.out.println(line);
        } catch (IOException e) {
            // обработка исключения
        }
    }