gb-java-devel/jtc3-03-abstract.tex

1213 lines
109 KiB
TeX
Raw Normal View History

2022-12-14 14:43:40 +03:00
\documentclass[j-spec.tex]{subfiles}
2023-05-09 23:34:17 +03:00
\begin{document} \sloppy
2022-12-14 14:43:40 +03:00
\setcounter{section}{2}
\setlength{\columnsep}{22pt}
\pagestyle{plain}
2023-01-20 00:56:30 +03:00
\tableofcontents
2022-12-14 14:43:40 +03:00
\section{Специализация: ООП}
2023-01-20 00:56:30 +03:00
\subsection*{В предыдущем разделе}
2022-12-14 14:43:40 +03:00
Будет рассмотрен базовый функционал языка, то есть основная встроенная функциональность, такая как математические операторы, условия, циклы, бинарные операторы. Далее способы хранения и представления данных в Java, и в конце способы манипуляции данными, то есть функции (в терминах языка называющиеся методами).
2023-01-20 00:56:30 +03:00
\subsection*{В этом разделе}
2022-12-14 14:43:40 +03:00
Разберём такие основополагающих в Java вещи, как классы и объекты, а также с тем, как применять на практике основные принципы ООП: наследование, полиморфизм и инкапсуляцию. Дополнительно рассмотрим устройство памяти в джава.
\begin{itemize}
\item \nom{Класс}{определяет форму и сущность объекта и является логической конструкцией, на основе которой построен весь язык Java. Определяет новый тип данных};
\item \nom{Объект}{конкретный экземпляр класса, созданный в программе};
\item \nom{Статика}{(статический контекст) static - (от греч. неподвижный) — раздел механики, в котором изучаются условия равновесия механических систем под действием приложенных к ним сил и возникших моментов. В языке программирования Java - принадлежность поля и его значения не объекту, а классу, и, как следствие, доступность такого поля и его значения в единственном экземпляре всем объектам класса.};
2022-12-14 14:43:40 +03:00
\item \nom{Стек}{структура данных, работающая по принципу LIFO (last in, first out) или FILO (first in, last out). Стековая память отвечает за хранение ссылок на объекты кучи и за хранение типов значений (также известных в Java как примитивные типы), которые содержат само значение, а не ссылку на объект из кучи.};
\item \nom{Куча}{адресуемое пространство оперативной памяти компьютера. Куча содержит все объекты, созданные в вашем приложении, независимо от того, какой поток создал объект.};
\item \nom{Сборщик мусора}{специализированная подпрограмма, очищающая память от неиспользуемых объектов.};
\item \nom{Конструктор}{это частный случай метода, предназначенный для инициализации объектов при создании в Java.};
\item \nom{Инкапсуляция}{(англ. encapsulation, от лат. in capsula) — в информатике, процесс разделения элементов абстракций, определяющих ее структуру (данные) и поведение (методы); инкапсуляция предназначена для изоляции контрактных обязательств абстракции (протокол/интерфейс) от их реализации.};
\item \nom{Наследование}{(англ. inheritance) — концепция объектно-ориентированного программирования, согласно которой абстрактный тип данных может наследовать данные и функциональность некоторого существующего типа, способствуя повторному использованию компонентов программного обеспечения.};
\item \nom{Полиморфизм }{это возможность объектов с одинаковой спецификацией иметь различную реализацию};
2022-12-14 14:43:40 +03:00
\end{itemize}
\subsection{Классы и объекты, поля и методы, статика}
\subsubsection{Классы}
Что такое класс? С точки зрения ООП, \textbf{класс} определяет форму и сущность объекта и является логической конструкцией, на основе которой построен весь язык Java.
\begin{frm}\info
Наиболее важная особенность класса состоит в том, что он определяет новый тип данных, которым можно воспользоваться для создания объектов этого типа
\end{frm}
То есть класс — это шаблон (чертёж), по которому создаются объекты (экземпляры класса). Для определения формы и сущности класса указываются данные, которые он должен содержать, а также код, воздействующий на эти данные. Создаем мы свои классы, когда у нас не хватает уже созданных.
Например, если мы хотим работать в нашем приложении с документами, то необходимо для начала объяснить приложению, что такое документ, описать его в виде класса (чертежа) \code{Document}. Указать, какие у него должны быть свойства: название, содержание, количество страниц, информация о том, кем он подписан и т.п. В этом же классе мы обычно описываем, что можно делать с документами: печатать в консоль, подписывать, изменять содержание, название и т.д. Результатом такого описания и будет класс \code{Document}. Однако, это по-прежнему всего лишь чертеж хранимых данных (состояний) и способы взаимодействия с этими данными.
Если нам нужны конкретные документы, а нам они обязательно нужны, то необходимо создавать \textbf{объекты}: документ №1, документ №2, документ №3. Все эти документы будут иметь одну и ту же структуру (описанные нами название, содержание, ...), с ними можно выполнять одни и те же описанные нами действия (печатать, подписать, ...), но наполнение будет разным, например, в первом документе содержится приказ о назначении работника на должность, во втором, о выдаче премии отделу разработки и т.д.
Начнём с малого, напишем свой первый класс. Представим, что необходимо работать в приложении с котами. Java ничего не знает о том, что такое коты, поэтому необходимо создать новый класс (тип данных), и объяснить что такое кот. Создадим новый файл, для простоты в том же пакете, что и главный класс программы.
\begin{figure}[H]
\begin{forest}
for tree={
font=\ttfamily, grow'=0, child anchor=west,
parent anchor=south, anchor=west, calign=first,
edge path={
\noexpand\path [draw, \forestoption{edge}]
(!u.south west) +(7.5pt,0) |- node[fill,inner sep=1.5pt]
{} (.child anchor)\forestoption{edge label};
}, before typesetting nodes={
if n=1 {insert before={[,phantom]}} {} },
fit=band, before computing xy={l=20pt},
}
[Sample
[src
[ru
[gb
[jcore
[sample
[Main.java]
[Cat.java]
]
]
]
]
]
[out
]
[README.md]
]
\end{forest}
\caption{Структура проекта}
\label{pic:simple-tree}
\end{figure}
\subsubsection{Поля класса}
Начнем описывать в классе \code{Cat} так называемый API кота. Как изестно, имя класса должно совпадать с именем файла, в котором он объявлен, т.е. класс \code{Cat} должен находиться в файле \code{Cat.java}. Пусть у котов есть три свойства: \code{name} (кличка), \code{color} (цвет) и \code{age} (возраст); совокупность этих свойств называется состоянием, и коты пока ничего не умеют делать. Класс \code{Cat} будет иметь вид, представленный в листинге \hrf{lst:class-simple}. Свойства класса, записанные таким образом, в виде переменных, называются \textbf{полями}.
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Структура кота в программе},label={lst:class-simple}]
package ru.gb.jcore;
public class Cat {
String name;
String color;
int age;
}
\end{lstlisting}
\begin{frm} \excl
Для новичка важно не запутаться, класс кота мы описали в отдельном файле, а создавать объекты и совершать манипуляции следует в основном классе программы, не может же кот назначить имя сам себе.
\end{frm}
Мы рассказали программе, что такое коты, теперь если мы хотим создать в нашем приложении конкретного кота, следует воспользоваться оператором \code{new Cat();} в основном классе программы. Более подробно разберём, что происходит в этой строке, чуть позже, пока же нам достаточно знать, что мы создали объект типа \code{Cat} (экземпляр класса \code{Cat}), и запомнить эту конструкцию. Для того чтобы с ним (экземпляром) работать, можем положить его в переменную, которой дать идентификатор \code{cat1}. При создании объекта полям присваиваются значения по умолчанию (нули для числовых переменных и \code{false} для булевых).
\begin{lstlisting}[language=Java,style=JCodeStyle]
Cat cat0; // cat0 = null;
cat0 = new Cat();
Cat cat1 = new Cat();
\end{lstlisting}
В листинге выше можно увидеть все три операции (объявление, присваивание и инициализацию) и становится понятно, как можно создавать объекты. Также известно, что в переменной не лежит сам объект, а только ссылка на него. Объект \code{cat1} создан по чертежу \code{Cat}, это значит, что у него есть поля \code{name}, \code{color}, \code{age}, с которыми можно работать: получать или изменять их значения.
\begin{frm} \info
Для доступа к полям объекта используется оператор точка, который связывает имя объекта с именем поля. Например, чтобы присвоить полю \code{color} объекта \code{cat1} значение «Белый», нужно выполнить код \code{cat1.color = "Белый"};
\end{frm}
Операция «точка» служит для доступа к полям и методам объекта по его имени. Мы уже использовали оператор «точка» для доступа к полю с длиной массива, например. Рассмотрим пример консольного приложения, работающего с объектами класса \code{Cat}. Создадим двух котов, один будет белым Барсиком 4х лет, второй чёрным Мурзиком шести лет, и просто выведем информацию о них в терминал.
\begin{lstlisting}[language=Java,style=JCodeStyle]
package ru.gb.jcore;
public class Main {
public static void main(String[] args) {
Cat cat1 = new Cat();
Cat cat2 = new Cat();
cat1.name = "Barsik";
cat1.color = "White";
cat1.age = 4;
cat2.name = "Murzik";
cat2.color = "Black";
cat2.age = 6;
System.out.println("Cat1 named: " + cat1.name +
" is " + cat1.color +
" has age: " + cat1.age);
System.out.println("Cat2 named: " + cat2.name +
" is " + cat2.color +
" has age: " + cat2.age);
}
}
\end{lstlisting}
в результате работы программы в консоли появятся следующие строки:
\begin{verbatim}
Cat1 named: Barsik is White has age: 4
Cat2 named: Murzik is Black has age: 6
\end{verbatim}
Вначале мы создали два объекта типа \code{Cat}: \code{cat1} и \code{cat2}, соответственно, они имеют одинаковый набор полей \code{name}, \code{color}, \code{age}. Почему? Потому что они принадлежат одному классу, созданы по одному шаблону. Объекты всегда «знают», какого они класса. Однако каждому из них в эти поля записаны разные значения. Как видно из результата печати в консоли, изменение значения полей одного объекта, никак не влияет на значения полей другого объекта. Данные объектов \code{cat1} и \code{cat2} изолированы друг от друга. А значит мы делаем вывод о том, поля хранятся в классе, а значения полей хранятся в объектах. Логическая структура, демонстрирующая отношения объектов и классов, в том числе в части хранения полей и их значений показана на рис. \hrf{pic:class-obj-fields}.
\begin{figure}[H]
\centering
\fontsize{12}{1}\selectfont
\includesvg[scale=1.01]{pics/jc-03-class-obj-fields.svg}
\caption{Логическая структура отношения класс-объект}
\label{pic:class-obj-fields}
\end{figure}
\subsubsection{Объекты}
Разобравшись с тем, как создавать новые типы данных (классы) и мельком посмотрев, как создаются объекты, нужно подробнее разобрать, как создавать объекты, и что при этом происходит. Создание объекта как любого ссылочного типа данных проходит в два этапа. Как и в случае с уже известными нам массивами.
\begin{itemize}
\item Сначала создается переменная, имеющая интересующий нас тип, в неё возможно записать ссылку на объект;
\item затем необходимо выделить память под объект;
\item создать и положить объект в выделенную часть памяти;
\item и сохранить ссылку на этот объект в памяти - в нашу переменную.
2022-12-14 14:43:40 +03:00
\end{itemize}
Для непосредственного создания объекта применяется оператор \code{new}, который динамически резервирует память под объект и возвращает ссылку на него, в общих чертах эта ссылка представляет собой адрес объекта в памяти, зарезервированной оператором \code{new}.
\begin{lstlisting}[language=Java,style=JCodeStyle]
Cat cat1; // cat1 = null;
cat1 = new Cat();
Cat cat2 = new Cat();
\end{lstlisting}
В первой строке кода переменная \code{cat1} объявляется как ссылка на объект типа \code{Cat} и пока ещё не ссылается на конкретный объект (первоначально значение переменной \code{cat1} равно \code{null}). В следующей строке выделяется память для объекта типа \code{Cat}, и в переменную \code{cat1} сохраняется ссылка на него. После выполнения второй строки кода переменную \code{cat1} можно использовать так, как если бы она была объектом типа \code{Cat}. Обычно новый объект создается в одну строку, то есть инициализируется.
\subsubsection{Оператор \code{new}}
\begin{frm}\info
[квалификаторы] ИмяКласса имяПеременной = \textbf{\code{new}} ИмяКласса();
\end{frm}
Оператор \code{new} динамически выделяет память для нового объекта, общая форма применения этого оператора имеет вид как на врезке выше, но на самом деле справа - не имя класса, конструкция ИмяКласса() в правой части выполняет вызов конструктора данного класса, который подготавливает вновь создаваемый объект к работе.
2022-12-14 14:43:40 +03:00
Именно от количества применений оператора \code{new} будет зависеть, сколько именно объектов будет создано в программе.
\begin{lstlisting}[language=Java,style=JCodeStyle]
Cat cat1 = new Cat();
Cat cat2 = cat1;
cat1.name = "Barsik";
cat1.color = "White";
cat1.age = 4;
cat2.name = "Murzik";
cat2.color = "Black";
cat2.age = 6;
System.out.println("Cat1 named: " + cat1.name +
" is " + cat1.color +
" has age: " + cat1.age);
System.out.println("Cat2 named: " + cat2.name +
" is " + cat2.color +
" has age: " + cat2.age);
\end{lstlisting}
На первый взгляд может показаться, что переменной \code{cat2} присваивается ссылка на копию объекта \code{cat1}, т.е. переменные \code{cat1} и \code{cat2} будут ссылаться на разные объекты в памяти. Но это не так. На самом деле \code{cat1} и \code{cat2} будут ссылаться на один и тот же объект. Присваивание переменной \code{cat1} значения переменной \code{cat2} не привело к выделению области памяти или копированию объекта, лишь к тому, что переменная \code{cat2} содержит ссылку на тот же объект, что и переменная \code{cat1}. Это явление дополнительно подчёркивает ссылочную природу данных в языке Java.
Таким образом, любые изменения, внесённые в объект по ссылке \code{cat2}, окажут влияние на объект, на который ссылается переменная \code{cat1}, поскольку \textit{это один и тот же объект в памяти}. Поэтому результатом выполнения кода, где мы как будто бы указали возраст второго кота, равный шести годам, станут строки, показывающие, что по обеим ссылкам оказался кот возраста шесть лет с именем Мурзика.
\begin{verbatim}
Cat1 named: Murzik is Black has age: 6
Cat2 named: Murzik is Black has age: 6
\end{verbatim}
\begin{frm}\info
Множественные ссылки на один и тот же объект в памяти довольно легко себе представить как ярлыки для запуска одной и той же программы на рабочем столе и в меню быстрого запуска. Или если на один и тот же шкафчик в раздевалке наклеить два номера - сам шкафчик можно будет найти по двум ссылкам на него.
2022-12-14 14:43:40 +03:00
\end{frm}
Важно всегда перепроверять, какие объекты созданы, а какие имеют множественные ссылки.
\subsubsection{Методы}
Ранее было сказано о том, что в языке Java любая программа состоит из классов и функций, которые могут описываться только внутри них. Именно поэтому все функции в языке Java являются методами. А метод - это функция, являющаяся частью некоторого класса, которая может выполнять операции над данными этого класса.
2022-12-14 14:43:40 +03:00
\begin{frm} \info
Метод - это функция, принадлежащая классу
2022-12-14 14:43:40 +03:00
\end{frm}
Метод для своей работы может использовать поля объекта и/или класса, в котором определен, напрямую, без необходимости передавать их во входных параметрах. Это похоже на использование глобальных переменных в функциях, но в отличие от глобальных переменных, метод может получать прямой доступ только к членам класса. Самые простые методы работают с данными объектов. Методы чаще всего формируют API классов, то есть способ взаимодействия с классами, интерфейс. Место методов во взаимодействии классов и объектов показано на рис. \hrf{pic:class-obj-nostatic}.
\begin{figure}[H]
\centering
\fontsize{12}{1}\selectfont
\includesvg[scale=1.01]{pics/jc-03-class-obj-nostatic.svg}
\caption{Логическая структура отношения класс-объект}
\label{pic:class-obj-nostatic}
\end{figure}
Вернёмся к примеру с котиками. Широко известно, что котики умеют урчать, мяукать и смешно прыгать. В целях демонстрации в описании этих действий просто будем делать разные выводы в консоль, хотя возможно и научить котика в программе выбирать минимальное значение из массива, но это было бы, как минимум, неожиданно. Итак опишем метод например подать голос и прыгать.
\begin{lstlisting}[language=Java,style=JCodeStyle]
public class Cat {
String name;
String color;
int age;
void voice() {
System.out.println(name + " meows");
}
void jump() {
if (this.age < 5) System.out.println(name + " jumps");
}
}
\end{lstlisting}
Обращение к методам выглядит очень похожим на стандартный способом, через точку, как к полям. Теперь когда появляется необходимость позвать котика, он скажет: «мяу, я имя котика», а если в программе пришло время котику прыгнуть, он решит, прилично ли это -- прыгать в его возрасте.
\begin{lstlisting}[language=Java,style=JCodeStyle]
package ru.gb.jcore;
public class Main {
public static void main(String[] args) {
Cat cat1 = new Cat();
Cat cat2 = new Cat();
cat1.name = "Barsik";
cat1.color = "White";
cat1.age = 4;
cat2.name = "Murzik";
cat2.color = "Black";
cat2.age = 6;
cat1.voice();
cat2.voice();
cat1.jump();
cat2.jump();
}
}
\end{lstlisting}
\begin{verbatim}
Barsik meows
Murzik meows
Barsik jumps
\end{verbatim}
Как видно, Барсик замечательно прыгает, а Мурзик от прыжков воздержался, хотя попрыгать программа попросила их обоих.
\subsubsection{Ключевое слово \code{static}}
В завершение базовой информации о классах и объектах, остановимся на специальном модификаторе \code{static}, делающем переменную или метод «независимыми» от объекта.
\begin{frm}\info
\textbf{\code{static}} — модификатор, применяемый к полю, блоку, методу или внутреннему классу, он указывает на привязку субъекта к текущему классу.
\end{frm}
Для использования таких полей и методов, соответственно, объект создавать не нужно. В Java большинство членов служебных классов являются статическими. Возможно использовать это ключевое слово в четырех контекстах:
\begin{itemize}
\item статические методы;
\item статические переменные;
\item статические вложенные классы;
\item статические блоки.
\end{itemize}
В этом разделе рассмотрим подробнее только первые два пункта, третий опишем чуть позже, а четвёртый потребует от нас знаний, выходящих не только за этот урок, но и за десяток следующих.
\textbf{Статические методы} также называются методами класса, потому что статический метод принадлежит классу, а не его объекту. Нестатические называются методами объекта. Статические методы можно вызывать напрямую через имя класса, не обращаясь к объекту и вообще объект не создавая. Что это и зачем нужно? Например, умение кота мяукать можно вывести в статическое поле, потому что, например, весной можно открыть окно, не увидеть ни одного экземпляра котов, но зато услышать их, и точно знать, что мяукают не дома и не машины, а именно коты.
\begin{figure}[H]
\centering
\fontsize{12}{1}\selectfont
\includesvg[scale=1.01]{pics/jc-03-class-obj-full.svg}
\caption{Логическая структура отношения класс-объект}
\label{pic:class-obj-full}
\end{figure}
Аналогично статическим методам, \textbf{статические поля} принадлежат классу и совершенно ничего «не знают» об объектах.
\begin{frm}\excl
Важной отличительной чертой статических полей является то, что их значения также хранятся в классе, в отличие от обычных полей, чьи значения хранятся в объектах.
\end{frm}
Рисунок \hrf{pic:class-obj-full} именно в этом виде автор настоятельно рекомендует если не заучить, то хотя бы хорошо запомнить, он ещё пригодится в дальнейшем обучении и работе. Из этого же изображения можно сделать несколько выводов.
\begin{lstlisting}[language=Java,style=JCodeStyle]
public class Cat {
static int pawsCount = 4;
String name;
String color;
int age;
// ...
}
\end{lstlisting}
2023-02-02 21:30:53 +03:00
Помимо того, что статические поля -- это полезный инструмент создания общих свойств, это ещё и опасный инструмент создания общих свойств. Так, например, мы знаем, что у котов четыре лапы, а не шесть и не восемь. Не создавая никакого Барсика будет понятно, что у кота -- четыре лапы. Это полезное поведение любого класса и его объектов.
2023-01-20 00:56:30 +03:00
\begin{lstlisting}[language=Java,style=JCodeStyle]
public class Cat {
static int pawsCount = 4;
2022-12-14 14:43:40 +03:00
2023-01-20 00:56:30 +03:00
static String name;
String color;
int age;
// ...
}
\end{lstlisting}
2022-12-14 14:43:40 +03:00
2023-01-20 00:56:30 +03:00
У каждого кота есть имя, и коты хранят значение своего имени каждый сам у себя. А знают экземпляры о названии поля потому что знают, какого класса они экземпляры. В чём опасность? Что если по невнимательности добавить свойство статичности к имени кота?
2022-12-14 14:43:40 +03:00
2023-01-20 00:56:30 +03:00
Создав тех же котов, такой класс вернёт двух Мурзиков и ни одного Барсика. Почему это произошло? По факту переменная одна на всех, и значение тоже одно, а значит при каждом вызове конструктора меняется именно оно, а все остальные коты, ничего не подозревая, смотрят на значение общей переменной и возвращают его. Поэтому, к статическим переменным, как правило, обращаются не по ссылке на объект -- \code{cat1.name}, а по имени класса -- \code{Cat.name}.
\begin{frm} \info Статические переменные -- редкость в Java. Вместо них применяют статические константы. Они определяются ключевыми словами \code{static final} и по соглашению о внешнем виде кода пишутся в верхнем регистре, разделяя слова символом нижнего подчёркивания, так называемым \code{UPPER_SNAKE_CASE}.
\end{frm}
2022-12-14 14:43:40 +03:00
\subsubsection{Задание для самопроверки}
\begin{enumerate}
\item Что такое класс?
\item Что такое поле класса?
\item На какие три этапа делится создание объекта?
\item Какое свойство добавляет ключевое слово static полю или методу?
\begin{enumerate}
\item неизменяемость;
\item принадлежность классу;
\item принадлежность приложению.
\end{enumerate}
\item Может ли статический метод получить доступ к полям объекта?
\begin{enumerate}
\item не может;
\item может только к константным;
\item может только к неинициализированным.
\end{enumerate}
\end{enumerate}
\subsection{Устройство памяти. Стек, куча и сборка мусора}
Это погружение в управление памятью Java позволит расширить ваши знания о том, как работает куча, ссылочные типы и сборка мусора, понять глубинные процессы и, как следствие, писать более хорошие программы. Для оптимальной работы приложения JVM делит память на область стека (stack) и область кучи (heap). Всякий раз, когда объявляются новые переменные, создаются объекты или вызывается новый метод, JVM выделяет память для этих операций в стеке или в куче. На рисунке \hrf{mem:items} представлена общая модель организации памяти в Java.
\begin{figure}[H]
\centering
\includesvg[scale=1]{pics/jc-03-memory.svg}
\caption{Устройство памяти}
\label{mem:items}
\end{figure}
\textbf{Стековая память} отвечает за хранение ссылок на объекты кучи и за хранение типов значений (также известных в Java как примитивные типы), которые содержат само значение, а не ссылку на объект из кучи. Данная память в Java работает по схеме LIFO (last in, first out, последний зашел, первый вышел).
Немного забегая вперёд можно сказать, что все потоки, работающие в JVM, имеют свой стек. Пока достаточно отождествлять поток и собственно исполнение программы. Стек в свою очередь держит информацию о том, какие методы вызвал поток. Назовём это «стеком вызовов». Стек вызовов возобновляется, как только поток завершает выполнение своего кода. Каждый слой стека вызовов содержит все локальные переменные для вызываемого метода и потока. Все локальные переменные примитивных типов полностью хранятся в стеке потоков и не видны другим потокам.
Особенности стека:
\begin{itemize}
\item Он заполняется и освобождается по мере вызова и завершения новых методов;
\item Переменные на стеке существуют до тех пор, пока выполняется метод в котором они были созданы;
\item Если память стека будет заполнена, Java бросит исключение \code{java.lang.StackOverflowError};
\item Доступ к этой области памяти осуществляется быстрее, чем к куче;
\item Является потокобезопасным, поскольку для каждого потока создается свой отдельный стек.
\end{itemize}
\textbf{Куча} содержит все объекты, созданные в приложении, независимо от того, какой поток создал объект. Неважно, был ли объект создан и присвоен локальной переменной или создан как переменная-член другого объекта, он хранится в куче.
Локальная переменная может быть примитивной, но также может быть ссылкой на объект. В этом случае ссылка (локальная переменная) хранится на стеке, но сам объект хранится в куче. Объект использует методы, эти методы содержат локальные переменные. Эти локальные переменные (то есть в момент выполнения метода) также хранятся на стеке, несмотря на то, что объект, который использует метод, хранится в куче. Переменные-члены класса хранятся в куче вместе с самим классом. Это верно как в случае, когда переменная-член имеет примитивный тип, так и в том случае, если она является ссылкой на объект. Статические переменные класса также хранятся в куче вместе с определением класса.
\begin{frm} \info В общем случае, эти объекты имеют глобальный доступ и могут быть получены из любого места программы.
\end{frm}
Куча разбита на несколько более мелких частей, называемых поколениями:
\begin{itemize}
\item Young Generation — область где размещаются недавно созданные объекты. Когда она заполняется, происходит быстрая сборка мусора;
\item Old (Tenured) Generation — здесь хранятся долгоживущие объекты. Когда объекты из Young Generation достигают определенного порога «возраста», они перемещаются в Old Generation;
\item Permanent Generation — эта область содержит метаинформацию о классах и методах приложения, но начиная с Java 8 данная область памяти была упразднена. В Java 8 Permanent Generation заменён на Metaspace - его динамически изменяемый по размеру аналог. Именно здесь находятся статические поля.
2022-12-14 14:43:40 +03:00
\end{itemize}
Особенности кучи:
\begin{itemize}
\item В общем случае, размеры кучи на порядок больше размеров стека
\item Когда эта область памяти полностью заполняется, Java бросает \code{java.lang.OutOfMemoryError};
\item Доступ к ней медленнее, чем к стеку;
\item Эта память, в отличие от стека, автоматически не освобождается. Для сбора неиспользуемых объектов используется сборщик мусора;
\item В отличие от стека, который создаётся для каждого потока свой, куча не является потокобезопасной, поскольку для всех одна, и ее необходимо контролировать, правильно синхронизируя код.
\end{itemize}
\begin{frm} \info Также, хотелось бы отметить, что мы можем использовать -Xms и -Xmx опции JVM, чтобы определить начальный и максимальный размер памяти в куче. Для стека определить размер памяти можно с помощью опции -Xss;
\end{frm}
\textbf{Управление неиспользуемыми объектами}
\begin{itemize}
\item запускается автоматически Java, и Java решает, запускать или нет этот процесс;
\item это дорогостоящий процесс. При запуске сборщика мусора все потоки в приложении приостанавливаются (в зависимости от типа GC);
\item гораздо более сложный процесс, чем просто сбор мусора и освобождение памяти.
\end{itemize}
\begin{frm}\excl Программисту доступен метод класса System, который мы можем явно вызвать и ожидать, что сборщик мусора будет запускаться при выполнении этой строки кода. Это ошибочное предположение. Java решать, делать это или нет. Явно вызывать \code{System.gc()} не рекомендуется.
\end{frm}
Поскольку это довольно сложный процесс и может повлиять на производительность всего приложения, он реализован весьма разумно. Для этого используется так называемый процесс «Mark and Sweep» (отмечай и подметай). Java анализирует переменные из стека и «отмечает» все объекты, которые необходимо поддерживать в рабочем состоянии. Затем все неиспользуемые объекты очищаются. Фактически, чем больше мусора и чем меньше объектов помечены как живые, тем быстрее идет процесс. Чтобы сделать это еще более оптимизированным, память кучи состоит из нескольких частей.
\begin{enumerate}
\item Молодое поколение -- Все новые объекты начинаются с молодого поколения. Как только они выделены в коде Java, они попадают в этот подраздел, называемый \textbf{eden space}. В конце концов пространство эдема заполняется объектами. На этом этапе происходит незначительная сборка мусора, так называемая minor collection. Некоторые объекты (те, на которые есть ссылки) помечаются, а некоторые (те, на которые нет ссылок) - нет. Те, которые были отмечены, затем переходят в другой подраздел молодого поколения под названием пространство выживших (само пространство выживших разделено на две части). Те, которые остались немаркированными, удаляются автоматической сборкой мусора.
2022-12-14 14:43:40 +03:00
\item Выжившее поколение -- Так будет продолжаться до тех пор, пока пространство eden снова не заполнится; на этом этапе начинается новый цикл. События minor collection повторяются, но в этом цикле все отмеченные объекты, которые выживают как из пространства eden, так и из S0, фактически попадают во вторую часть пространства survivor, называемую S1.
\item Третье поколение -- Любые объекты, попадающие в пространство выживших, помечаются счетчиком возраста. Алгоритм проверяет этот счётчик, чтобы увидеть, соответствует ли он пороговому значению для перехода в старое поколение. Главная мысль в том, что объекты не обязательно переходят из S0 в S1 пространства выживших. На самом деле, они просто чередуются с тем, куда они переключаются при каждой minor сборке мусора.
Если эти процессы обобщить, то все новые объекты начинаются в пространстве eden, а затем в конечном итоге попадают в пространство survivor, поскольку они переживают несколько циклов сборки мусора.
\item Старое поколение можно рассматривать как место, где лежат долгоживущие объекты. Когда объекты собирают мусор из старого поколения, происходит крупное событие сборки мусора. Старое поколение состоит только из одной секции, называемой постоянным поколением.
\item Постоянное поколение -- Постоянное поколение не заполняется, когда объекты старого поколения достигают определенного порога, а затем перемещаются (повышаются) в постоянное поколение. Скорее, постоянное поколение немедленно заполняется JVM метаданными, которые представляют классы и методы приложений во время выполнения. JVM иногда может следовать определенным правилам для очистки постоянного поколения, и когда это происходит, это называется полной сборкой мусора major collection.
\end{enumerate}
\begin{frm} \info Также, хотелось бы ещё раз упомянуть событие под названием остановить мир. Когда происходит небольшая сборка мусора (для молодого поколения) или крупная сборка мусора (для старого поколения), мир останавливается; другими словами, все потоки приложений полностью останавливаются и должны ждать завершения события сборки мусора.
\end{frm}
\textbf{Сборщик мусора. Реализации}
\begin{enumerate}
\item Последовательный сборщик мусора. Это самая простая реализация GC, поскольку она в основном работает с одним потоком. В результате эта реализация GC замораживает все потоки приложения при запуске. Поэтому не рекомендуется использовать его в многопоточных приложениях, таких как серверные среды;
\item Параллельный сборщик мусора. Это GC по умолчанию для JVM, который иногда называют сборщиками пропускной способности. В отличие от последовательного сборщика мусора, он использует несколько потоков для управления пространством кучи, но также замораживает другие потоки приложений во время выполнения GC. Если мы используем этот GC, мы можем указать максимальные потоки сборки мусора и время паузы, пропускную способность и занимаемую площадь (размер кучи);
\item Сборщик мусора CMS. Реализация Concurrent Mark Sweep (CMS) использует несколько потоков сборщика мусора для сбора мусора. Он предназначен для приложений, которые требуют более коротких пауз при сборке мусора и могут позволить себе совместно использовать ресурсы процессора со сборщиком мусора во время работы приложения. Проще говоря, приложения, использующие этот тип GC, в среднем работают медленнее, но не перестают отвечать, чтобы выполнить сборку мусора.
\begin{frm}\info Следует отметить, что, поскольку этот GC является параллельным, вызов явной сборки мусора, такой как использование System.gc() во время работы параллельного процесса, приведет к сбою или прерыванию параллельного режима;
\end{frm}
\item Сборщик мусора G1. Сборщик мусора G1 (Garbage First) предназначен для приложений, работающих на многопроцессорных компьютерах с большим объемом памяти. Он доступен с обновления 4 JDK7 и в более поздних версиях. Сборщик G1 заменит сборщик CMS, поскольку он более эффективен;
\item Z сборщик мусора. ZGC (Z Garbage Collector) - это масштабируемый сборщик мусора с низкой задержкой, который дебютировал в Java 11 в качестве экспериментального варианта для Linux. JDK 14 представил ZGC под операционными системами Windows и macOS. ZGC получил статус production начиная с Java 15.
2022-12-14 14:43:40 +03:00
\end{enumerate}
Итоги рассмотрения устройства памяти
\begin{itemize}
\item куча доступна везде, объекты доступны отовсюду
\item все объекты хранятся в куче, все локальные переменные хранятся на стеке
\item стек недолговечен
\item и стек и куча могут быть переполнены
\item куча много больше стека, но стек гораздо быстрее
\end{itemize}
\subsubsection{Задания для самопроверки}
\begin{enumerate}
\item По какому принципу работает стек?
\item Что быстрее, стек или куча?
\item Что больше, стек или куча?
\end{enumerate}
\subsection{Конструкторы}
\subsubsection{Контроль над созданием объекта}
Чтобы создать объект мы тратим одну строку кода \code{Cat cat1 = new Cat();} поля этого объекта заполнятся автоматически значениями по-умолчанию (числовые - 0, логические - \code{false}, ссылочные - \code{null}). Часто нужно при создании дать коту какое-то имя, указать его возраст и цвет, поэтому пишем ещё три строки кода.
2022-12-14 14:43:40 +03:00
\begin{frm} \excl В таком подходе есть несколько недостатков:
\begin{enumerate}
\item прямое обращение к полям объекта,
\item если полей у класса будет намного больше, то для создания всего лишь одного объекта будет уходить 5-10-15 строк кода, что очень громоздко и утомительно.
\end{enumerate}
\end{frm}
Было бы неплохо иметь возможность сразу, при создании объекта указывать значения его полей. Для инициализации объектов при создании в Java предназначены конструкторы.
\begin{frm} \info Конструктор - это частный случай метода в том смысле, что он тоже выполняет какие-то действия. Имя конструктора обязательно должно совпадать с именем класса, возвращаемое значение не пишется.
2022-12-14 14:43:40 +03:00
\end{frm}
Если создать конструктор класса Cat, как показано в листинге \hrf{lst:construct-bad}, он автоматически будет вызываться при создании объекта. Теперь, при создании объектов класса Cat, все коты будут иметь одинаковые имена, цвет и возраст (это будут белые двухлетние Барсики).
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Не самый лучший конструктор},label={lst:construct-bad}]
public class Cat {
String name;
String color;
int age;
public Cat() {
name = "Barsik";
color = "White";
age = 2;
}
// ...
}
\end{lstlisting}
При использовании такого конструктора, все созданные коты будут одинаковыми, пока мы вручную не поменяем значения их полей. Чтобы можно было указывать начальные значения полей объектов необходимо создать параметризированный конструктор.
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:constr-param},caption={Параметризированный конструктор}]
public class Cat {
String name;
String color;
int age;
Cat(String n, String c, int a) {
name = n;
color = c;
age = a;
}
// ...
}
\end{lstlisting}
В приведенном примере, в параметрах конструктора используется первая буква от названия поля, это сделано для упрощения понимания логики заполнения полей объекта, и будет заменено на более корректное использование ключевого слова \code{this}. При такой форме конструктора, когда появится необходимость создавать в программе кота, необходимо будет обязательно указать его имя, цвет и возраст. Набор полей, которые будут заполнены через конструктор, определяется разработчиком класса сами, то есть язык не обязывает заполнять все поля, которые есть в классе записывать в параметры конструктора, но при вызове обязательно заполнить аргументами все, что есть в параметрах, как при вызове метода.
\begin{lstlisting}[language=Java,style=JCodeStyle]
Cat cat1 = new Cat("Barsik", "White", 4);
Cat cat2 = new Cat("Murzik", "Black", 6);
\end{lstlisting}
Наборы значений имён, цветов и возрастов будут переданы в качестве аргументов конструктора (\code{n}, \code{c}, \code{a}), а конструктор уже перезапишет полученные значения в поля объект (\code{name}, \code{color}, \code{age}). То есть начальные значения полей каждого из объектов будут определяться тем, что мы передадим ему в конструкторе.
Язык позволяет как не объявлять ни одного конструктора, так и объявить их несколько. Также как и при перегрузке методов, имеет значение набор аргументов, не может быть нескольких конструкторов с одинаковым набором аргументов. Соответствующий перегружаемый конструктор вызывается в зависимости от аргументов, указываемых при выполнении оператора new.
Как только в программе в классе создана своя реализация конструктора, пустой конструктор, называемый также \textbf{конструктором по-умолчанию} автоматически создаваться не будет. И если понадобится такая форма конструктора, необходимо будет создать его вручную, \code{public Cat() {}}.
\begin{frm}\info То есть, компилятор как-бы думает, что если вы не описали конструкторы, значит вам не важно, как будет создаваться объект, а значит, хватит пустого конструктора, ну а если конструктор написан, значит выкручивайтесь сами.\end{frm}
\subsubsection{Ключевое слово \code{this}}
В контексте конструкторов, применять this нужно в двух случаях:
\begin{enumerate}
\item Когда у переменной экземпляра класса и переменной метода/конструктора одинаковые имена;
\item Когда нужно вызвать конструктор одного типа (например, конструктор по умолчанию или параметризированный) из другого. Это еще называется явным вызовом конструктора.
\end{enumerate}
Внимательно посмотрев на параметризированный конструктор (листинг \hrf{lst:constr-param}), видим, что переменные в параметрах называются не также, как поля класса.
\begin{frm} \excl Нельзя просто сделать названия параметров идентичными названиям полей, в этом случае возникает проблема. Для примера возьмём имя кота, поле \code{String name}. Один \code{String name} принадлежит классу \code{Cat}, а другой \code{String name} находится в локальной видимости конструктора. JVM, как и любой другой электрический прибор всегда идёт по пути наименьшего сопротивления, когда есть неопределённость. То есть, когда написана строка \code{name = name;} Java берёт самую близкую \code{name} из конструктора и для левой и для правой части оператора присваивания, что не имеет никакого смысла.
\end{frm}
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:constr-this},caption={Использование ключевого слова \code{this} для параметров}]
public class Cat {
String name;
String color;
int age;
public Cat(String name, String color, int age) {
this.name = name;
this.color = color;
this.age = age;
}
// ...
}
\end{lstlisting}
Ключевое слово \code{this} сошлётся на вызвавший объект, в результате чего (в листинге \hrf{lst:constr-this})имя котика через конструктор будет установлено создаваемому объекту. Таким образом, здесь \code{this} позволяет не вводить новые переменные для обозначения одного и того же, что позволяет сделать код менее перегруженным дополнительными переменными.
Второй случай частого использования \code{this} с конструкторами - вызов одного конструктора из другого. это может пригодиться когда в классе описано несколько конструкторов и не хочется в новом конструкторе переписывать код инициализации, приведенный в конструкторе ранее\footnote{один из базовых принципов программирования - DRY (от англ dry - чистый, сухой, акроним don't repeat yourself) - не повторяйся. Его антагонист WET (от англ wet - влажный, акроним write everything twice) - пиши всё дважды.}. В листинге \hrf{lst:constr-this} вызывается обычный конструктор с тремя параметрами, который принимает имя цвет и возраст, но, допустим, когда котята рождаются возраст им задавать смысла нет, поэтому, может пригодиться и конструктор просто с именем и цветом, а зачем писать присваивание имени и цвета несколько раз, если можно вызвать соответствующий конструктор?
2022-12-14 14:43:40 +03:00
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:constr-this-constr},caption={Использование ключевого слова \code{this} для конструктора}]
2022-12-14 14:43:40 +03:00
public class Cat {
String name;
String color;
int age;
public Cat(String name, String color) {
this.name = name;
this.color = color;
}
public Cat(String name, String color, int age) {
this(name, color);
this.age = age;
}
// ...
}
\end{lstlisting}
На такой вызов есть ограничение, конструктор из конструктора можно вызвать только один раз и только на первой строке конструктора.
\begin{frm} \info Ключевое слово \code{this} в Java используется только в составе экземпляра класса. Но неявно ключевое слово \code{this} передается во все методы, кроме статических (поэтому \code{this} часто называют неявным параметром) и может быть использовано для обращения к объекту, вызвавшему метод.
\end{frm}
Существует ещё один вид конструктора - это \textbf{конструктор копирования}. Чтобы создать конструктор копирования, возможно объявить конструктор, который принимает объект того же типа, в нашем случае котика, в качестве параметра, а в самом конструкторе аналогично конструктору, заполняющему все параметры, заполнить каждое поле входного объекта в новый экземпляр.
2022-12-14 14:43:40 +03:00
\begin{lstlisting}[language=Java,style=JCodeStyle]
public Cat (Cat cat) {
this(cat.name, cat.color, cat.age);
}
\end{lstlisting}
Благодаря имеющемуся конструктору со всеми нужными параметрами, с помощью ключевого слова \code{this} явно вызывается конструктор заполняющий все поля создаваемого кота, значениями из переданного объекта, фактически, его копирующий. То, что мы имеем здесь, это неглубокая копия. Если класс имеет изменяемые поля, например, массивы, то мы можем вместо простой сделать глубокую копию внутри его конструктора копирования. При глубокой копии вновь созданный объект не должен зависеть от исходного, а значит просто скопировать ссылку на массив будет недостаточно
\subsubsection{Задания для самопроверки}
\begin{enumerate}
\item Для инициализации нового объекта абсолютно идентичными значениями свойств переданного объекта используется
\begin{enumerate}
\item пустой конструктор
\item конструктор по-умолчанию
\item конструктор копирования
\end{enumerate}
\item Что означает ключевое слово this?
\end{enumerate}
\subsection{Инкапсуляция}
Инкапсуляция связывает данные с манипулирующим ими кодом и позволяет управлять доступом к членам класса из отдельных частей программы, предоставляя доступ только с помощью определенного ряда методов, что позволяет предотвратить злоупотребление этими данными. То есть класс должен представлять собой «черный ящик», которым возможно пользоваться, но его внутренний механизм защищен от повреждений.
\begin{frm}\info
\textbf{Инкапсуляция} - (англ. encapsulation, от лат. in capsula) — в информатике, процесс разделения элементов абстракций, определяющих ее структуру (данные) и поведение (методы); инкапсуляция предназначена для изоляции контрактных обязательств абстракции (протокол/интерфейс) от их реализации.
2022-12-14 14:43:40 +03:00
\end{frm}
В Java в роли чёрного ящика выступает класс. Класс содержит в себе и данные (поля класса), и действия (методы класса) для работы с этими данными. Все члены класса в языке Java - поля и методы - имеют модификаторы доступа. Ранее уже было описан модификатор \code{public}, означающий доступность отовсюду, обычно используется для методов.
2022-12-14 14:43:40 +03:00
\begin{frm} \info Модификаторы доступа позволяют задать допустимую область видимости для членов класса, то есть контекст, в котором можно употреблять данную переменную или метод.
\end{frm}
Есть два модификатора, которые уже известны и активно используются, это \code{public} и package-private, также известный как default, пакетный или отсутствующий модификатор. Что это значит? Это значит, что вообще всё что пишется в Java имеет уровень доступа, и если этот уровень не определён явно, то Java отнесёт данные к уровню доступности внутри пакета.
\begin{figure}[H]
\centering
\includesvg[scale=1]{pics/jc-03-modifiers.svg}
\caption{Модификаторы доступа и их относительная область видимости}
\label{mod:items-no-protect}
2022-12-14 14:43:40 +03:00
\end{figure}
Модификатор \code{private} определяет доступность только внутри класса, и предпочтительнее всех.
Внимательно рассмотрим класс котика из листинга \hrf{lst:constr-this}. Например, кто-то может создать хорошего кота, а потом его переименовать, перекрасить в зелёный цвет или сделать ему отрицательный возраст, в результате в программе находятся объекты с некорректным состоянием. Все поля находятся в пакетном доступе. К ним можно обратиться в любом месте пакета: достаточно просто создать объект.
\code{private} — самый строгий модификатор доступа в Java. Если его использовать, поля класса не будут доступны за его пределами. Решая проблему несанкционированного доступа была получена проблема штатного функционирования, доступ к полям закрыт, в программе нельзя даже получить вес существующей кошки, если это понадобится.
Необходимо решить вопросы с получением и изменением значений полей. На помощь приходят «геттеры» и «сеттеры». Название происходит от английского «get» — «получать» (т.е. «метод для получения значения поля») и «set» — «устанавливать».
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:getset},caption={Геттеры и сеттеры для всех полей}]
public class Cat {
private String name;
private String color;
private int age;
// ...
public String getName() {
return name;
}
public String getColor() {
return color;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setColor(String color) {
this.color = color;
}
public void setAge(int age) {
this.age = age;
}
}
\end{lstlisting}
В листинге \hrf{lst:getset} показан пример написания геттеров и сеттеров для всех полей класса, что даёт возможность получать данные и устанавливать их. Например, с помощью \code{getColor} возможно получить текущее значение окраса котика.
Важно, что создавая для класса геттеры и сеттеры не только появляется возможность дополнять установке и возвращению значений полей дополнительную логику, но и возможность регулировать доступ к полям. Например, если в программе нужно запретить менять котикам окрас, то для класса просто не пишется соответствующий сеттер.
Внимательно осмотрев класс кота возможно прийти к выводу, что хранить возраст котов очень неудобно, потому что каждый год нужно будет обновлять это значение для каждого объекта кота в программе, а это может оказаться утомительно. Выходом может оказаться хранение не возраста, а неизменяемого параметра - даты рождения и подсчёт возраста каждый раз, когда его запрашивают, ведь человеку, который запрашивает возраст кота, не интересно, каким образом получено значение, прочитано из поля или вычислено, ему важен конечный результат. Это и есть инкапсуляция, сокрытие реализации.
2022-12-14 14:43:40 +03:00
\subsubsection{Задания для самопроверки}
\begin{enumerate}
\item Перечислите модификаторы доступа
\item Инкапсуляция - это
2022-12-14 14:43:40 +03:00
\begin{enumerate}
\item архивирование проекта
\item сокрытие информации о классе
\item создание микросервисной архитектуры
\end{enumerate}
\end{enumerate}
\subsection{Наследование}
\subsubsection{Проблема}
Второй кит ООП после инкапсуляции - наследование.
2022-12-14 14:43:40 +03:00
Представим, что есть необходимость создать помимо класса котиков, класс собачек. Данный класс будет выглядеть очень похожим образом, только он будет не мяукать, а гавкать, и заменим обоим животным прыжок на простое перемещение на лапках.
2023-01-20 00:56:30 +03:00
\begin{figure}[H]
\begin{multicols}{2}
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:cmpcat},caption={Класс кота}]
2022-12-14 14:43:40 +03:00
public class Cat {
private String name;
private String color;
private int age;
public Cat(String name, String color, int age) {
this.name = name;
this.color = color;
this.age = age;
}
2023-01-20 00:56:30 +03:00
2022-12-14 14:43:40 +03:00
public String getName() {
2023-01-20 00:56:30 +03:00
return name;
2022-12-14 14:43:40 +03:00
}
public String getColor() {
2023-01-20 00:56:30 +03:00
return color;
2022-12-14 14:43:40 +03:00
}
public int getAge() {
2023-01-20 00:56:30 +03:00
return age;
2022-12-14 14:43:40 +03:00
}
public void setName(String name) {
2023-01-20 00:56:30 +03:00
this.name = name;
2022-12-14 14:43:40 +03:00
}
public void setColor(String color) {
2023-01-20 00:56:30 +03:00
this.color = color;
2022-12-14 14:43:40 +03:00
}
public void setAge(int age) {
2023-01-20 00:56:30 +03:00
this.age = age;
2022-12-14 14:43:40 +03:00
}
void voice() {
System.out.println(name + " meows");
}
void move() {
System.out.println(name + " walks on paws");
}
}
2023-01-20 00:56:30 +03:00
\end{lstlisting}
\columnbreak
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:cmpdog},caption={Класс собаки}]
2022-12-14 14:43:40 +03:00
public class Dog {
private String name;
private String color;
private int age;
public Dog(String name, String color, int age) {
this.name = name;
this.color = color;
this.age = age;
}
2023-01-20 00:56:30 +03:00
2022-12-14 14:43:40 +03:00
public String getName() {
2023-01-20 00:56:30 +03:00
return name;
2022-12-14 14:43:40 +03:00
}
public String getColor() {
2023-01-20 00:56:30 +03:00
return color;
2022-12-14 14:43:40 +03:00
}
public int getAge() {
2023-01-20 00:56:30 +03:00
return age;
2022-12-14 14:43:40 +03:00
}
public void setName(String name) {
2023-01-20 00:56:30 +03:00
this.name = name;
2022-12-14 14:43:40 +03:00
}
public void setColor(String color) {
2023-01-20 00:56:30 +03:00
this.color = color;
2022-12-14 14:43:40 +03:00
}
public void setAge(int age) {
2023-01-20 00:56:30 +03:00
this.age = age;
2022-12-14 14:43:40 +03:00
}
void voice() {
System.out.println(name + " barks");
}
void move() {
System.out.println(name + " walks on paws");
}
}
2023-01-20 00:56:30 +03:00
\end{lstlisting}
\end{multicols}
\end{figure}
2022-12-14 14:43:40 +03:00
Очевидно это не DRY и неприемлемо, если появится необходимость описать классы для целого зоопарка. В приведённых классах есть очень много абсолютно одинаковых и очень похожих полей и методов.
\begin{frm} \info Наследование (англ. inheritance) — концепция объектно-ориентированного программирования, согласно которой абстрактный тип данных может наследовать данные и функциональность некоторого существующего типа, способствуя повторному использованию компонентов программного обеспечения.
\end{frm}
Наследование в Java реализуется ключевым словом \code{extends} (англ. - расширять). И кот и пёс являются животными, у всех описываемых в программе животных есть имя, возраст, окрас, все описываемые животные могут бегать, прыгать, и откликаться на имя. Создав так называемый \textbf{родительский класс}, или суперкласс (листинг \hrf{lst:animal-fields}), и поместив в него поля, геттеры и сеттеры, стало возможным убрать поля, геттеры и сеттеры из кота и пса. Если полей много, лаконичность описания родственных классов может быть весьма ощутимой.
2022-12-14 14:43:40 +03:00
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:animal-fields},caption={Класс животного}]
public class Animal {
private String name;
private String color;
private int age;
public String getName() {
return name;
}
public String getColor() {
return color;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setColor(String color) {
this.color = color;
}
public void setAge(int age) {
this.age = age;
}
}
\end{lstlisting}
Чтобы унаследовать один класс от другого, нужно после объявления указать ключевое слово \code{extends} и написать имя родительского класса как это показано в листингах \hrf{lst:cmpdog1} и \hrf{lst:cmpcat1}. Если перенос геттеров возможен, то значит достаточно безболезненно можно перенести и одинаковые методы.
Простой перенос кода в родительский класс показал наличие проблемы. Модификатор \code{private} определяет область видимости только внутри класса, а если нужно чтобы переменную было видно ещё и в классах-наследниках, нужен хотя бы модификатор доступа по умолчанию. Если же класс наследник создаётся в каком-то другом пакете, то и default не подойдёт.
2023-01-20 00:56:30 +03:00
\begin{figure}[H]
2022-12-14 14:43:40 +03:00
\begin{multicols}{2}
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:cmpdog1},caption={Класс собаки}]
public class Dog extends Animal {
public Dog(String name, String color, int age) {
this.name = name;
this.color = color;
this.age = age;
}
void voice() {
System.out.println(name + " barks");
}
void move() {
System.out.println(name + " walks on paws");
}
}
\end{lstlisting}
\columnbreak
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:cmpcat1},caption={Класс кота}]
public class Cat extends Animal {
public Cat(String name, String color, int age) {
this.name = name;
this.color = color;
this.age = age;
}
void voice() {
System.out.println(name + " meows");
}
void move() {
System.out.println(name + " walks on paws");
}
}
\end{lstlisting}
\end{multicols}
2023-01-20 00:56:30 +03:00
\end{figure}
2022-12-14 14:43:40 +03:00
То есть, к членам данных и методам класса можно применять следующие модификаторы доступа
\begin{itemize}
\item \code{private} - содержимое класса доступно только из методов данного класса;
\item \code{public} - есть доступ фактически отовсюду;
\item default (по-умолчанию) - содержимое класса доступно из любого места пакета, в котором этот класс находится;
2022-12-14 14:43:40 +03:00
\item \code{protected} (защищенный доступ) содержимое доступно также как с модификатором по-умолчанию, но ещё и для классов-наследников.
\end{itemize}
\begin{figure}[H]
\centering
\includesvg[scale=1]{pics/jc-04-modifiers-full.svg}
\caption{Модификаторы доступа и их относительная область видимости}
\label{mod:items}
\end{figure}
То есть верным вариантом в листинге \hrf{lst:animal-fields} будет применение модификатора \code{protected}.
\subsubsection{Конструкторы в наследовании}
\begin{frm} \excl Несмотря на то, что конструктор - это частный случай метода, если перенести одинаковые конструкторы кота и пса в общий класс животного, программа снова перестанет работать, потому что важно учитывать механику вызова конструкторов при наследовании.
2022-12-14 14:43:40 +03:00
\end{frm}
Важно запомнить, что при создании любого объекта в первую очередь вызывается конструктор его базового (родительского) класса, а только потом — конструктор самого класса, объект которого мы создаем. То есть при создании объекта \code{Cat} сначала отработает конструктор класса \code{Animal}, а только потом конструктор \code{Cat}. Но, поскольку конструктор по-умолчанию в нашем случае перестал создаваться, а других может быть бесконечно много, это создало неопределённость, которую программа разрешить не может.
При описании класса, можно явно вызвать конструктор базового класса в конструкторе класса-потомка. Базовый класс еще называют «суперклассом», поэтому в Java для его обозначения используется ключевое слово \code{super}. Здесь такое же ограничение, как и при вызове конструкторов данного класса (через \code{this}) - вызов такого конструктора может быть только один и быть только первой строкой. Таким образом, код, для всех животных в программе будет выглядеть следующим образом:
2022-12-14 14:43:40 +03:00
2023-01-20 00:56:30 +03:00
\begin{figure}[H]
2022-12-14 14:43:40 +03:00
\begin{multicols}{2}
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:animal-full},caption={Класс животного}]
public class Animal {
private String name;
private String color;
private int age;
public Animal(String name, String color, int age) {
this.name = name;
this.color = color;
this.age = age;
}
public String getName() {
return name;
}
public String getColor() {
return color;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setColor(String color) {
this.color = color;
}
public void setAge(int age) {
this.age = age;
}
void move() {
System.out.println(name + " walks on paws");
}
}
\end{lstlisting}
\columnbreak
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:cmpcat3},caption={Класс кота}]
public class Cat extends Animal {
public Cat(String name, String color, int age) {
super(name, color, age);
}
void voice() {
System.out.println(name + " meows");
}
}
\end{lstlisting}
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:cmpdog3},caption={Класс собаки}]
public class Dog extends Animal {
public Dog(String name, String color, int age) {
super(name, color, age);
}
void voice() {
System.out.println(name + " barks");
}
}
\end{lstlisting}
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:cmpbird3},caption={Класс птицы}]
public class Bird extends Animal {
private int flyHeight;
public Bird(String name, String color, int age, int flyHeight) {
super(name, color, age);
this.flyHeight = flyHeight;
}
void voice() {
System.out.println(name + " tweets");
}
void fly() {
System.out.println(name + " flies at " + flyHeight + " m");
}
}
\end{lstlisting}
\end{multicols}
2023-01-20 00:56:30 +03:00
\end{figure}
2022-12-14 14:43:40 +03:00
Для примера, создали ещё один класс, наследника животного, чтобы использовать наследование по назначению. Наследование реализуется через ключевое слово \code{extends}, расширять. Важно, что класс-родитель расширяется функциональностью или свойствами класса-наследника. Это позволило, например, добавить в птичку такое свойство как высота полёта и такой метод как летать, в дополнение к тому, что умеют все животные.
\subsubsection{\code{Object} и каскадное наследование}
\begin{frm} \excl Множественное наследование запрещено! Для каждого создаваемого подкласса можно указать только один суперкласс. В Java не поддерживается множественное наследование, то есть наследование одного класса от нескольких суперклассов. Зато возможно каскадное наследование, то есть класс-наследник вполне может быть чьим-то родителем.
\end{frm}
Если класс-родитель не указан, таковым считается класс \code{Object}. Таким образом можно сделать вывод о том, что любой класс в джава так или иначе - наследник \code{Object} и, соответственно, всех его свойств и методов. Объект подкласса представляет объект суперкласса, выражаясь проще, возможно ко всем котикам обращаться через общее название Животное, и ко всем объектам в программе возможно обратиться через класс \code{Object}. Поэтому в программе не будет ошибкой написать подобный код:
2022-12-14 14:43:40 +03:00
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:obj},caption={}]
Object animal = new Animal("Cat", "Black", 3);
Object cat = new Cat("Murka", "Black", 4);
Object dog = new Dog("Bobik", "White", 2);
Animal dogAnimal = new Bird("Chijik", "Grey", 3, 10);
Animal catAnimal = new Cat("Marusya", "Orange", 1);
\end{lstlisting}
Это так называемое восходящее преобразование (от подкласса внизу к суперклассу вверху иерархии) или \textbf{upcasting}. Такое преобразование осуществляется автоматически. Обратное не всегда верно. Например, объект \code{Animal} не всегда является объектом \code{Cat} или \code{Dog}. Поэтому нисходящее преобразование или \code{downcasting} от суперкласса к подклассу автоматически не выполняется. В этом случае необходимо использовать операцию преобразования типов.
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:downcast},caption={}]
Object animal = new Cat("Murka", "Black", 4);
Cat cat = (Cat)animal;
cat.move();
\end{lstlisting}
Обратите внимание, что в данном случае переменная \code{animal} приводится к типу \code{Cat}. И затем через объект \code{cat} становится возможным обратиться к функционалу кота. Важно при этом, что изначально оператором \code{new} был создан объект кота, а не \code{Object} или \code{Animal}/
\subsubsection{Оператор \code{instanceof} и ключевое слово \code{final}}
\textbf{Оператор \code{instanceof}} возвращает истину, если объект принадлежит классу или его суперклассам и ложь в противном случае. Нередко данные приходят извне, и невозможно точно знать, какой именно объект эти данные представляют. Возникает большая вероятность столкнуться с ошибкой преобразования типов. И перед тем, как провести преобразование типов, необходимо проверить возможность выполнить приведение с помощью оператора \code{instanceof}.
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:instanceof},caption={}]
Object cat = new Cat("Murka", "Black", 4);
if (cat instanceof Dog) {
Dog dogIsCat = (Dog) cat;
dogIsCat.voice();
} else {
System.out.println("Conversion is invalid");
}
\end{lstlisting}
Выражение \code{cat instanceof Dog} проверяет, является ли переменная \code{cat} объектом типа \code{Dog}. Так как в данном случае явно кот не является собакой, то такая проверка вернет значение \code{false}, и преобразование не сработает. А выражение \code{cat instanceof Animal} выдало бы истинный результат.
\begin{frm} \info \textbf{Ключевое слово \code{final}}. Класс с конечной реализацией. \end{frm}
Ключевое слово final может применяться к классам, методам, переменным (в том числе аргументам методов). Применительно к классам, это возможность запретить наследование. То есть, если пометить этим ключевым словом, например, птичку, тогда, если в коде начать писать класс например, попугайчика, и указать, наследование от птицы, то выведется \code{Cannot inherit from final}.
\begin{figure}[H]
\centering
\includegraphics[width=\textwidth]{jc-03-finalclass.png}
\caption{Ошибка наследования от final класса}
\end{figure}
\subsubsection{Абстракция}
Иногда абстракцию выделяют, как четвёртый принцип ООП.
Абстрактный класс — это написанная максимально широкими мазками, приблизительная «заготовка» для группы будущих классов. Эту заготовку нельзя использовать в чистом виде — слишком «сырая». Но она описывает некое общее состояние и поведение, которым будут обладать будущие классы — наследники абстрактного класса.
Абстрактными могут быть не только классы, но и методы. \textbf{Абстрактный метод} - это метод без реализации. Все животные в примерах выше умеют издавать свой звук. Известно, на этапе проектирования животного, что все животные должны издавать звук, но невозможно сказать, какой именно. Поэтому, определяется, что у животного есть метод издать звук, но реализация этого метода в животном не пишется, слишком мало сведений. Поэтому, метод помечается как абстрактный.
2022-12-14 14:43:40 +03:00
Что будет, если программа попытается вызвать метод \code{voice()} у животного?
Класс животного максимально абстрактно описывает нужную нам сущность — животное. Но в мире не существует «просто животных». Есть губки, иглокожие, хордовые и т.д. Данный класс теперь слишком абстрактный, чтобы программа могла с ним нормально взаимодействовать, а значит просто является чертежом по которому будут создаваться дальнейшие классы животных. Отметим этот факт явно, написав ключевое слово \code{abstract} у класса.
\begin{frm} \info
\begin{itemize}
\item Абстрактный метод - это метод не содержащий реализации (объявление метода).
\item Абстрактный класс - класс содержащий хотя бы один абстрактный метод.
2022-12-14 14:43:40 +03:00
\item Абстрактный класс нельзя инстанциировать (создать экземпляр).
\end{itemize}
\end{frm}
Очевидно, что абстрагирование метода вынуждает абстрагировать класс, но не наоборот, абстрактный класс необязательно должен содержать абстрактные методы, фактически, это просто запрещение создания экземпляров.
\subsubsection{Задания для самопроверки}
\begin{enumerate}
\item Какое ключевое слово используется при наследовании?
\begin{enumerate}
\item parent
\item extends
\item как в С++, используется двоеточие
\end{enumerate}
\item super - это
2022-12-14 14:43:40 +03:00
\begin{enumerate}
\item ссылка на улучшенный класс
\item ссылка на расширенный класс
\item ссылка на родительский класс
\end{enumerate}
\item Не наследуются от Object
\begin{enumerate}
\item строки
\item потоки ввода-вывода
\item ни то ни другое
\end{enumerate}
\end{enumerate}
\subsection{Полиморфизм}
\textbf{Полиморфизм} это возможность объектов с одинаковой спецификацией иметь различную реализацию (Overriding). Полиморфизм выражается возможностью переопределения поведения суперкласса (часто можно встретить утверждение, что при помощи перегрузки. Основная суть в том, что в классе-родителе имеется некоторый метод, но реализация этого метода разная у каждого класса-наследника, фактически это и есть полиморфизм.
Вынесем последний оставшийся в котиках и птичках метод в общий класс животного. В классах-потомках определим такие же методы, как и объявленный метод класса родителя, который хотим изменить.
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:cmpcat4},caption={Класс кота}]
2022-12-14 14:43:40 +03:00
public abstract class Animal {
// ...
void voice();
// ...
}
public class Cat extends Animal {
public Cat(String name, String color, int age) {
super(name, color, age);
}
@Override
void voice() {
System.out.println(name + " meows");
}
}
\end{lstlisting}
Получается, при наследовании от \code{Animal}, у которого есть метод \code{voice()}, класс котика сам определяет, какой он издаёт звук.
\begin{frm} \info Аннотации реализуют вспомогательные интерфейсы. \end{frm}
Аннотация \code{@Override} проверяет, действительно ли метод переопределяется, а не перегружается. Если существует ошибка в сигнатуре метода, то компилятор сразу об этом скажет.
Полиморфизм чаще всего используется когда нужно описать поведение абстрактного класса или назначить разным наследникам разное поведение, одинаково названное в классе родителе. Но есть и ситуации, когда все классы делают что-то одинаково, а один делает это как-то иначе. Продемонстрируем на примере класса \code{Snake}, змея.
По очевидным причинам змейка не может ходить на лапках. Поэтому это поведение у змейки будет переопределено.
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:snake},caption={Класс кота}]
public class Snake extends Animal {
public Snake(String name, String color, int age) {
super(name, color, age);
}
@Override
void move() {
System.out.println(name + " crawls");
}
@Override
void voice() {
System.out.println(name + " hisses");
}
}
\end{lstlisting}
Также, например, можно было создать черепаху, которая не умеет бегать, рыбу, которая не издаёт звуков, слона, который не умеет прыгать, в отличие от остальных, практикующих «среднее» поведение.
Стоит помнить, что переопределять можно только нестатические методы. Статические методы не наследуются в привычном смысле и, следовательно, не переопределяются. Создав в животном и коте статический метод с одинаковой сигнатурой мы сможем наблюдать то, что называется хайдингом, иначе сокрытием или перекрытием. Также, обратите внимание, что попытка написать у этих методов аннотацию \code{@Override} вызовет ошибку компиляции.
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:hide},caption={}]
public class Animal {
// ...
public static void self() { ... }
}
public class Cat extends Animal {
// ...
public static void self() { ... }
}
\end{lstlisting}
По коду видно, что класс унаследовался и метод должен переопределиться, внешне если создать \code{Animal a} и \code{Cat c}, будет похоже, что так и произошло, но если сделать обе переменные типа \code{Animal}, очевидно, что поведение изменилось. Статические члены класса относятся к классу, т.е. к типу переменной. Поэтому, логично, что если \code{Cat} имеет тип \code{Animal}, то и метод будет вызван у \code{Animal}, а не у \code{Cat}.
\begin{frm} \info Полиморфизм в языках программирования и теории типов — способность функции обрабатывать данные разных типов. Выделяют параметрический полиморфизм и ad-hoc-полиморфизм.
\end{frm}
Широко распространено определение полиморфизма, приписываемое Бьёрну Страуструпу: «один интерфейс — много реализаций». Полиморфизм - это гораздо более широкое понятие, чем просто переопределение методов, в эту тему завязаны разные интересные теории типов и информации, множество парадигм программирования и другое. С утилитарной точки зрения, остался ещё один вариант, который, тем не менее, не дотягивает до истинного полиморфизма.
2022-12-14 14:43:40 +03:00
\begin{frm} \info К полиморфизму также относится перегрузка методов (Overloading) - использование более одного метода с одним и тем же именем, но с разными параметрами в одном и том же классе или между суперклассом и подклассами. \end{frm}
2022-12-14 14:43:40 +03:00
Перегрузка работает также, как работала без явной привязки кода к парадигме ООП, ничего нового, но для порядка следует создать возможность животным перемещаться не только абстрактно, но и на какое-то конкретное место или на какое-то конкретное количество шагов.
\begin{lstlisting}[language=Java,style=JCodeStyle,label={lst:override},caption={}]
void move() {
System.out.println(name + " walks on paws");
}
void move(String to) {
System.out.println(name + " moves to " + to);
}
void move(int steps) {
System.out.println(name + " moves " + steps + " steps away");
}
\end{lstlisting}
Как видно, методы имеют одинаковые названия, но отличаются по количеству параметров и их типу.
Чтобы стиль вашей программы соответствовал концепции ООП и принципам ООП в Java следуйте следующим советам:
\begin{itemize}
\item выделяйте главные характеристики объекта;
\item выделяйте общие свойства и поведение и используйте наследование при создании объектов;
\item используйте абстрактные типы для описания объектов;
\item старайтесь всегда скрывать методы и поля, относящиеся к внутренней реализации класса.
\end{itemize}
\subsubsection{Задания для самопроверки}
\begin{enumerate}
\item Является ли перегрузка полиморфизмом
\begin{enumerate}
\item да, это истинный полиморфизм
\item да, это часть истинного полиморфизма
\item нет, это не полиморфизм
\end{enumerate}
\item Что обязательно для переопределения?
\begin{enumerate}
\item полное повторение сигнатуры метода
\item полное повторение тела метода
\item аннотация Override
\end{enumerate}
\end{enumerate}
\subsection*{Практическое задание}
\begin{enumerate}
\item Написать класс кота так, чтобы каждому объекту кота присваивался личный порядковый целочисленный номер.
\item Написать классы кота, собаки, птицы, наследники животного. У всех есть три действия: бежать, плыть, прыгать. Действия принимают размер препятствия и возвращают булев результат. Три ограничения: высота прыжка, расстояние, которое животное может пробежать, расстояние, которое животное может проплыть. Следует учесть, что коты не любят воду.
\item * Добавить механизм, создающий 25\% разброс значений каждого ограничения для каждого объекта.
\end{enumerate}
2023-01-20 00:56:30 +03:00
\newpage
\printnomenclature[40mm]
2022-12-14 14:43:40 +03:00
\end{document}