\documentclass[../j-spec.tex]{subfiles} \begin{document} \section{Специализация: ООП и исключения} \begin{longtable}{|p{35mm}|p{135mm}|} \hline Экран & Слова \\ \hline \endhead Титул & Здравствуйте, продолжим беседу об ООП \\ \hline Отбивка & и дополнительно затронем вопрос исключений. \\ \hline На прошлом уроке & мы поговорили о достаточно большой теме - реализации ООП в джава. рассмотрели классы и объекты, а также наследование, полиморфизм и инкапсуляцию. Дополнительно немного поговорили об устройстве памяти. На следующей лекции рассмотрим внутренние и вложенные классы, перечисления и исключения, нас ждут очень интересные темы, не переключайтесь \\ \hline На этом уроке & На этой лекции в дополнение к предыдущей, разберём такие понятия как внутренние и вложенные классы; процессы создания, использования и расширения перечислений. Детально разберём уже знакомое вам понятие исключений и их тесную связь с многопоточностью в джава. Посмотрим на исключения с точки зрения ООП, обработаем немного исключений, а также раз и навсегда разделим понятия штатных и нештатных ситуаций.\\ \hline отбивка Перечисления & Начнём с небольшой темы, с перечислений \\ \hline Перечисление - это упоминание объектов, объединённых по какому-либо признаку & Кроме восьми примитивных типов данных и классов в Java есть специальный тип, выведенный на уровень синтаксиса языка - enum или перечисление. Перечисления представляют набор логически связанных констант. Объявление перечисления происходит с помощью оператора enum, после которого идет название перечисления. Затем идет список элементов перечисления через запятую. \\ \hline лайвкод 04-сезоны & Если копнуть немного глубже, перечисления - это такие специальные классы, содержащие внутри себя собственные статические экземпляры. Сложноватая мысль, если нужно, повторите её про себя несколько раз. а я пока напишу перечисление времён года enum Season { WINTER, SPRING, SUMMER, AUTUMN }. Когда мы доберёмся до рассмотрения внутренних и вложенных классов, в том числе статических, дополнительно это проговорим. \\ \hline лайвкод 04-один-сезон & Перечисление фактически представляет новый тип данных, поэтому мы можем определить переменную данного типа и использовать её. Переменная типа перечисления может хранить любой объект этого исключения. Season current = Season.SPRING; System.out.println(current); Интересно также то, что вывод в терминал и запись в коде у исключений полностью совпадают, поэтому, в терминале мы видим .... \\ \hline лайвкод 04-перечислить & Каждое перечисление имеет статический метод values(). Он возвращает массив всех констант перечисления, далее мы можем этим массивом манипулировать как нам нужно, например, вывести на экран все его элементы. Season[] seasons = Season.values(); for (Season s : seasons) { System.out.printf("s ", s); } Именно в этом примере, я использую цикл foreach для прохода по массиву, для лаконичности записи. Чуть подробнее о его особенностях мы поговорим на одной из следующих лекций. Если коротко, данный цикл возьмёт последовательно каждый элемент перечисления, присвоит ему имя s точно также, как мы это делали в примере на две строки выше, и сделает эту переменную С доступной в теле цикла в рамках одной итерации, на следующей итерации будет взят следующий элемент, и так далее \\ \hline % +++ Комментатор_Ильнар. Строго говоря не очень корректная фраза "потому что у перечислений нет индексов". seasons - это уже обычный массив и можно его обойти обычным способом с индексами, скорее мы просто тут показали foreach цикл. лайвкод 04-порядковый номер & Также в перечисления встроен метод ordinal() возвращающий порядковый номер определенной константы (нумерация начинается с 0). System.out.println(current.ordinal()) Обратите внимание на синтаксис, метод можно вызвать только у конкретного экземпляра перечисления, а при попытке вызова у самого класса перечисления System.out.println(Seasons.ordinal()) мы ожидаемо получаем ошибку невозможности вызова нестатического метода из статического контекста.\\ \hline 03-статические-поля & как мы с вами помним из пояснения связи классов и объектов, такое поведение возможно только если номер элемента как-то хранится в самом объекте. Мы видим в перечислениях очень примечательный пример инкапсуляции - мы не знаем, хранятся ли на самом деле объекты перечисления в виде массива, но можем вызвать метод вельюс. Мы не знаем, хранится ли в каждом объекте перечисления его номер, но можем вызвать его метод ординал. А раз перечисление - это класс, мы можем определять в нём поля, методы, конструкторы и прочее. \\ \hline лайвкод 04-перечисление-цвет & Перечисление Color определяет приватное поле code для хранения кода цвета, а с помощью метода getCode оно возвращается. enum Color { RED("\#FF0000"), GREEN("\#00FF00"), BLUE("\#0000FF"); String code; Color (String code) { this.code = code; } String getCode() { return code; } } Через конструктор передается для него значение. Следует отметить, что конструктор по умолчанию приватный, то есть имеет модификатор private. Любой другой модификатор будет считаться ошибкой. Поэтому создать константы перечисления с помощью конструктора мы можем только внутри перечисления. И что косвенно намекает нам на то что объекты перечисления это статические объекты внутри самого класса перечисления. Также важно, что механизм описания конструкторов класса работает по той же логике, что и обычные конструкторы, то есть создав собственный конструктор мы уничтожили конструктор по-умолчанию, впрочем, мы его можем создать, если это будет иметь смысл для решаемой задачи. \\ \hline лайвкод 04-перечисление-с-полем & Исходя из сказанного ранее можно сделать вывод, что с объектами перечисления можно работать точно также, как с обычными объектами, что мы и сделаем, например, выведя информацию о них в консоль for (Color c : Color.values()) { System.out.printf("s(s) ", c, c.getCode()); } \\ \hline Вопросы для самопроверки & \begin{enumerate} \item Перечисления нужны, чтобы: 3 \begin{enumerate} \item вести учёт созданных в программе объектов; \item вести учёт классов в программе; \item вести учёт схожих по смыслу явлений в программе; \end{enumerate} \item Перечисление - это: 2 \begin{enumerate} \item массив \item класс \item объект \end{enumerate} \item каждый объект в перечислении - это: 3 \begin{enumerate} \item статическое поле \item статический метод \item статический объект \end{enumerate} \end{enumerate} \\ \hline отбивка Вложенные и внутренние (Nested) классы & Взглянем на чуть более, скажем так, комплексный момент - вложенные классы. На самом деле мы очень хорошо подготовились к этой теме и сейчас должно быть не так уж и сложно. \\ \hline 03-вложенные-классы & В Java есть возможность создавать классы внутри других классов и их разделяют на два вида: Non-static nested classes — нестатические вложенные классы. По-другому их еще называют inner classes — внутренние классы; Static nested classes — статические вложенные классы. Непосредственно внутренние классы подразделяются ещё на два подвида. Помимо того, что внутренний класс может быть просто внутренним классом, он еще бывает: локальным классом (local class); анонимным классом (anonymous class). Анонимные классы мы пока что не будем рассматривать, отложим на несколько лекций. \\ \hline лайвкод 04-класс-апельсин & Разберём всё по порядку и начнём мы с внутренних классов. Почему их так называют? public class Orange public void squeezeJuice() System.out.println("Squeeze juice ..."); class Juice public void flow() System.out.println("Juice dripped ..."); Всё просто, их создают внутри другого класса. Рассмотрим на примере апельсина с реализацией, как это предлагает официальная документация оракла.\\ \hline 04-использование-апельсина & В основной программе мы должны будем создать отдельно апельсин, отдельно его сок через вот такую интересную форму вызова конструктора и можем отдельно работать как с апельсином, так и его соком. Orange orange = new Orange(); Orange.Juice juice = orange.new Juice(); orange.squeezeJuice(); juice.flow(); Здесь всё прозрачно и последовательно, но не очень хорошо соответствует жизни. \\ \hline лайвкод 04-технологичный-апельсин & Важно, что мы же программисты, разработчики. Ещё немного и инженеры, уж мы то понимаем, что когда мы сдавливаем апельсин из него сам по себе течёт сок, а когда апельсин попадает к нам в программу он сразу снабжается соком. Поэтому мы можем слегка модифицировать наш код public class Orange private Juice juice; public Orange() this.juice = new Juice(); public void squeezeJuice() System.out.println("Squeeze juice ..."); juice.flow(); private class Juice public void flow() System.out.println("Juice dripped ..."); Что мы сделали? Мы создали объект апельсина. Создали один его, если можно так выразиться, «подобъект» — сок. Далее, мы описали потенциальное наличие у апельсина сока, как его части, поэтому создали внутри класса апельсин класс сока. При создании апельсина создали сок, то есть можно сказать что описали некоторое созревание, Решив выдавить сок у апельсина - объект сока сообщил о том, что начал течь \\ \hline лайвкод 04-апельсиновый сок & И в основной программе осталось выполнить довольно привычное нам создание апельсина Orange orange = new Orange(); orange.squeezeJuice(); После чего произойдёт достаточно логичное для сдавливания апельсина действие - вытекание сока \\ \hline 04-схема-работы-внутреннего-класса & Таким образом очевидно что мы создаем апельсин и внутри него создается сок при создании каждого объекта апельсина то есть у каждого апельсина будет свой собственный сок который мы можем выжать сдавив апельсин. в этом смысл внутренних классов не статического типа. Нужные нам методы вызываются у нужных объектов. Все просто и удобно. И кстати вполне возможно что в будущем нам это пригодится такая связь объектов и классов называется композиции есть ещё ассоциация и агрегация а именно эта композиция. \\ \hline На одном слайде 04-апельсин и 04-технологичный-апельсин & Если класс полезен только для одного другого класса, то вполне логично встроить его в этот класс и хранить их вместе. Использование внутренних классов увеличивает инкапсуляцию. Оба примера достаточно отличаются реализацией. Мой пример подразумевает "более сильную" инкапсуляцию, так как извне ко внутреннему классу доступ получить нельзя, поэтому создание объекта внутреннего класса происходит в конструкторе основного класса - в апельсине. Вы можете создавать объект сока где вам это нужно, не обязательно в конструкторе. С другой стороны, у примера из документации есть доступ извне ко внутреннему классу сок но всё равно только через основной класс апельсина. Как и собственно создать объект сока можно только через объект апельсина. \\ \hline Особенности внутренних классов (последовательное появление элементов перечисления) \begin{itemize} \item внутренний объект не существует без внешнего; \item внутренний имеет доступ ко всему внешнему; \item внешний не имеет доступа ко внутреннему без создания объекта; \end{itemize} & Давайте познакомимся с важными особенностями внутренних классов: объект внутреннего класса не может существовать без объекта внешнего класса. Это логично: для того мы и сделали Juice внутренним классом, чтобы в нашей программе не появлялись то тут, то там апельсиновые соки из воздуха. код внутреннего класса имеет доступ ко всем полям и методам экземпляра (так же как и к статическим членам) окружающего класса, включая все члены, даже объявленные как private, на самом деле, от нас это скрыто, но объект внутреннего класса получает неявную ссылку на внешний объект, который его создал, и поэтому может обращаться к членам внешнего объекта без дополнительных уточнений; экземпляр внешнего класса не имеет доступа ни к каким членам экземпляра внутреннего класса на прямую, то есть без создания экземпляра внутреннего класса внутри своих методов (И это логично, так как экземпляров внутреннего класса может быть создано сколько угодно много, и к какому же из них тогда обращаться?);\\ \hline \begin{itemize} \item у внутренних классов есть модификаторы доступа; \item внутренний класс не может называться как внешний; \item во внутреннем классе нельзя иметь не-final статические поля; \item Объект внутреннего класса нельзя создать в статическом методе «внешнего» класса \item Со внутренними классами работает наследование и полиморфизм. \end{itemize} & у внутреннего класса, как и у любого члена класса, может быть установлен один из трех уровней видимости: public, protected или private. Если ни один из этих модификаторов не указан, то по умолчанию применяется пакетная видимость. Это влияет на то, где в нашей программе мы сможем создавать экземпляры внутреннего класса. Единственное сохраняющееся требование — объект внешнего класса тоже обязательно должен существовать и быть видимым; внутренний класс не может иметь имя, совпадающее с именем окружающего класса или пакета. Это важно помнить. Правило не распространяется ни на поля, ни на методы; внутренний класс не может иметь полей, методов или классов, объявленных как static (за исключением полей констант, объявленных как static и final). Статические поля, методы и классы являются конструкциями верхнего уровня, которые не связаны с конкретными объектами, в то время как каждый внутренний класс связан с экземпляром окружающего класса; Объект внутреннего класса нельзя создать в статическом методе «внешнего» класса. Это объясняется особенностями устройства внутренних классов. У внутреннего класса могут быть конструкторы с параметрами или только конструктор по умолчанию. Но независимо от этого, когда мы создаем объект внутреннего класса, в него незаметно передается ссылка на объект внешнего класса. Ведь наличие такого объекта — обязательное условие. Иначе мы не сможем создавать объекты внутреннего класса. Но если метод внешнего класса статический, значит, объект внешнего класса может вообще не существовать. А значит, логика работы внутреннего класса будет нарушена. В такой ситуации компилятор выбросит ошибку; Также, внутренние классы имеют право наследовать другие классы, реализовывать интерфейсы и выступать в роли объектов наследования. Это уже более сложная тема, которую вы можете рассмотреть самостоятельно при желании. \\ \hline Вопросы для самопроверки & \begin{enumerate} \item Внутренний класс: 1 \begin{enumerate} \item реализует композицию; \item это служебный класс; \item не требует объекта внешнего класса; \end{enumerate} \item Инкапсуляция с использованием внутренних классов: 2 \begin{enumerate} \item остаётся неизменной \item увеличивается \item уменьшается \end{enumerate} \item Статические поля внутренних классов: 2 \begin{enumerate} \item могут существовать \item могут существовать только константными \item не могут существовать \end{enumerate} \end{enumerate} \\ \hline 03-вложенные-классы & Как мы помним классы это новый тип данных для нашей программы поэтому стоит ли упоминать что мы можем создавать классы а также их описывать например внутри методов это довольно редко используется но синтаксически язык позволяет это сделать. Первое, что нужно вспомнить перед изучением — их место в структуре вложенных классов. Исходя из схемы мы можем понять, что локальные классы — это подвид внутренних классов. Однако, у локальных классов есть ряд важных особенностей и отличий от внутренних классов. Главное заключается в их объявлении. \\ \hline лайвкод 04-локальный-внутренний-класс & Локальный класс объявляется только в блоке кода. Чаще всего — внутри какого-то метода внешнего класса. Например, это может выглядеть так: public class Animal void performBehavior(boolean state) class Brain void sleep() if(state) System.out.println("Sleeping"); else System.out.println("Not sleeping"); Brain brain = new Brain(); brain.sleep(); некоторое животное, у которого утанавливается состояние спит оно или нет. метод performBehavior() принимает на вход булевое значение и определяет, спит ли животное. И внутри этого метода мы объявили наш локальный класс Brain \\ \hline лайвкод 04-вызов-с-локальным-классом & Соответственно, снаружи это просто вызов метода Animal animal = new Animal(); animal.performBehavior(true); мог возникнуть логичный вопрос: зачем? Зачем объявлять класс именно внутри метода? Почему не использовать обычный внутренний класс? Действительно, можно было бы просто сделать класс Brain внутренним. Другое дело, что итоговое решение зависит от структуры, сложности и предназначения программы. \\ \hline Особенности локальных классов (последовательное появление элементов перечисления) \begin{itemize} \item сохраняет доступ ко всем полям и методам внешнего класса; \item должен иметь свои внутренние копии всех локальных переменных; \item имеют ссылку на окружающий экземпляр. \end{itemize} & Соответственно, теперь рассмотрим особенности: локальный класс сохраняет доступ ко всем полям и методам внешнего класса, а также ко всем константам, объявленным в текущем блоке кода, то есть полям и аргументам метода объявленным как final. Но начиная с JDK 8 локальный класс может обращаться к любым полям и аргументам метода объявленным в текущем блоке кода, даже если они не объявлены как final, но только в том случае если их значение не изменяется после инициализации; локальный класс должен иметь свои внутренние копии всех локальных переменных, которые он использует (эти копии автоматически создаются компилятором). Единственный способ обеспечить идентичность значений локальной переменной и ее копии – объявить локальную переменную как final. Опять же напомню, что это все было справедливо до JDK 7 включительно. В JDK 8 ситуация поменялась и можно обойтись без объявления переменной как final, но не менять ее значение в коде после инициализации. Хотя по большому счету лучше, для самоконтроля, все таки объявлять переменные как final; экземпляры локальных классов, как и экземпляры внутренних классов, имеют окружающий экземпляр, ссылка на который неявно передается всем конструкторам локальных классов. В результате определение внутренних классов можно описать так: подобно полям и методам экземпляра, каждый экземпляр внутреннего класса связан с экземпляром класса, внутри которого он определен (то есть каждый экземпляр внутреннего класса связан с экземпляром его окружающего класса). Вы не можете создать экземпляр внутреннего класса без привязки к экземпляру внешнего класса. То есть сперва должен быть создан экземпляр внешнего класса, а только затем уже вы можете создать экземпляр внутреннего класса. \\ \hline % Комментатор_Ильнар. Тут у народа мозги закипят конечно, вся надежда на презу, которая это оживит и сделает более понятным) С вложенными и внутренними классами всегда есть проблема при изучении. Возможно надо будет побольше задачек им на семинары дать, которые покажут явно разницу между ними и смысл их использования отбивка Статические вложенные классы & Мы поговорили о нестатических внутренних классах (non-static nested classes) или, проще, внутренних классах. Рассмотрим статические вложенные классы (static nested classes). Чем они отличаются от остальных? \\ \hline лайвкод 04-статический-класс & При объявлении такого класса мы используем уже знакомое нам ключевое слово static. Возьмём класс нашего котика и заменим метод voice() на статический класс. Объясняется это, как вы уже слышали на прошлом уроке, что допустим, мы находимся дома и у нас отрыто окно, мы слышим разные звуки, которые доносятся из окна. Из этих звуков мы можем разобрать звук мурчания котика. И тут мы понимаем, что котика мы не видим, а при этом слышим. public class Cat private String name, color; private int age; public Cat() public Cat(String name, String color, int age) this.name = name; this.color = color; this.age = age; static class Voice private final int volume; public Voice(int volume) this.volume = volume; public void sayMur() System.out.printf( "A cat purrs with volume dn", volume); То есть, такое мурчание котика может присутствовать без видимости и понимания, что это такой за котик. Также, добавим возможность установить уровень громкости его мурчанья \\ \hline 04-отличия-статик-и-не & В чем отличие между статическим и нестатическим вложенными классами? Объект статического класса не хранит ссылку на конкретный экземпляр внешнего класса. Если помните, только что мы говорили о том, что в каждый экземпляр внутреннего класса незаметно для нас передается ссылка на объект внешнего класса. Без объекта внешнего класса объект внутреннего просто не мог существовать. Для статических вложенных классов это не так. Объект статического вложенного класса вполне может существовать сам по себе. В этом плане статические классы более независимы, чем нестатические. \\ \hline лайвкод 04-использование-статического & Довольно важный и вместе с тем довольно очевидный момент заключается в том, что при создании такого объекта нужно указывать название внешнего класса, Cat.Voice voice = new Cat.Voice(100); voice.sayMur(); примерно так. \\ \hline лайвкод 04-последнее-о-статике & И ещё одна особенность - разный доступ к переменным и методам внешнего класса. Статический вложенный класс может обращаться только к статическим полям внешнего класса. При этом неважно, какой модификатор доступа имеет статическая переменная во внешнем классе. Даже если это private, доступ из статического вложенного класса все равно будет. Все вышесказанное касается не только доступа к статическим переменным, но и к статическим методам. Слово static в объявлении внутреннего класса не означает, что можно создать всего один объект. for (int i = 0; i < 4; i++) Cat.Voice voice = new Cat.Voice(100 + i); voice.sayMur(); Не следует путать объекты с переменными. Если мы говорим о статических переменных — да, статическая переменная класса существует в единственном экземпляре. Но применительно ко вложенному классу static означает лишь то, что его объекты не содержат ссылок на объекты внешнего класса. В случае примера с котиком - мы слышим мурчание с разной громкостью и непонятно одного и того же котика или это другой. А самих объектов мы можем создать сколько угодно \\ \hline Вопросы для самопроверки & \begin{enumerate} \item Вложенный класс: 1 \begin{enumerate} \item реализует композицию; \item это локальный класс; \item всегда публичный; \end{enumerate} \item Статический вложенный классо бладает теми же свойствами, что: 2 \begin{enumerate} \item константный метод \item внутренний класс \item статическое поле \end{enumerate} \end{enumerate} \\ \hline отбивка Механизм исключительных ситуаций & Наконец-то, тема, которая почему-то вызывает у новичков отторжение недоумение и полное непонимание. \\ \hline Исключение - это отступление от общего правила, несоответствие обычному порядку вещей & Думаю, тут надо начать с такой, немного философской части. Посмотрите пока что на этот вступительный слайд, он хорошая и в нём нет ничего сложного. Мы изучаем программирование, а что такое язык программирования? Это в первую очередь набор инструментов. Смотрите, например, есть строитель или вот лучше - художник. У художника есть набор всевозможных красок, кистей, холстов, карандашей, мольберт, ластик и куча-много-чего-ещё. Это всё его инструменты, с их помощью он делает свои важные художественные штуки. Тоже самое для программиста, у программиста есть язык программирования, который предоставляет ему инструменты: циклы, условия, классы, функции, методы, ООП, фрейморки, библиотеки... Исключения - это один из инструментов. Смотрите на исключения как на ещё один инструмент для работы программиста. Работает он достаточно специфично, и является достаточно высокоуровневым, исключения представляют из себя некую подсистему языка, которая является неотъемлемой частью любого мало-мальски серьёзного проекта.\\ \hline В общем случае, возникновение исключительной ситуации, это ошибка в программе, но основным вопросом является следующий: \begin{itemize} \item ошибка в коде программы, \item ошибка в действиях пользователя \item ошибка в аппаратной части компьютера \end{itemize} & Итак бывают такие ситуации, когда в процессе выполнения программы возникают ошибки. При возникновении ошибок создаётся объект класса Исключение, и в этот объект записывается какое-то максимальное количество информации о том, какая ошибка произошла, чтобы потом прочитать и понять, где же проблема. Соответственно эти объекты можно ловить, связывать и бросать в подвал для дальнейших выяснений... Но в программировании это называется гуманным термином “обрабатывать”. То есть вы можете как-то повлиять на ход программы, когда она уже запущена, и сделать так, чтобы она не прекратила работу, увидев деление на ноль, например, а выдала пользователю сообщение, и отменила только одну последнюю операцию. Сегодня поговорим о том, как отличить штатную ситуацию от нештатной. \\ \hline 04-иерархия-исключений & Исключения все наследуются от класса Throwable и могут быть как обязательные к обработке, так и необязательные. Есть ещё подкласс Error, но он больше относится к аппаратным сбоям или серьёзным алгоритмическим или архитектурным ошибкам, и нас не интересует, потому что поймав что-то вроде OutOfMemory мы средствами Java прямо в программе ничего с ним сделать не сможем, такие ошибки надо обрабатывать и исключать в процессе разработки ПО. Или, возможно, в системном программировании, но не в прикладном. Нас интересует подкласс Throwable-Exception