\documentclass[../j-spec.tex]{subfiles} \begin{document} \section{Специализация: ООП} \begin{longtable}{|p{35mm}|p{135mm}|} \hline Экран & Слова \\ \hline \endhead Титул & Перейдём к интересному: что можно хранить в джаве, как оно там хранится, и как этим манипулировать \\ \hline На прошлом уроке & На прошлом уроке мы рассмотрели базовый функционал языка, то есть основную встроенную функциональность, такую как математические операторы, условия, циклы, бинарные операторы. Также разобрали способы хранения и представления данных в Java, и в конце поговорили о способах манипуляции данными, то есть о функциях (в терминах языка называющиеся методами) \\ \hline На этой лекции & После разбора типов данных попробуем с помощью примеров разобраться, что такое классы и объекты, а также с тем, как применять на практике основные принципы ООП: наследование, полиморфизм и инкапсуляцию. Дополнительно поговорим об устройстве памяти в джава. \\ \hline Наследование — это передача всех свойств и поведения от одного класса другому, более конкретному. У карася и ерша, как и у всех рыб, есть плавники, хвосты, жабры и чешуя, они живут в воде и плавают; Инкапсуляция — это размещение данных и методов для их обработки в одном объекте, а также сокрытие деталей его реализации. Мы знаем, как включать и выключать телевизор, переключать программы и регулировать громкость. Для этого не обязательно знать, как он устроен; Полиморфизм — это проявление одного поведения разными способами. Животные могут издавать звуки, при этом кошка мяукает, а собака лает. Чуть позже мы рассмотрим эти принципы подробнее. А сейчас разберём некоторые основы, которые мы будем использовать часто в данных принципах. Начнём мы с понятия "Класс". Что такое класс? Класс определяет форму и сущность объекта и является логической конструкцией, на основе которой построен весь язык Java. Наиболее важная особенность класса состоит в том, что он определяет новый тип данных, которым можно воспользоваться для создания объектов этого типа, т.е. класс — это шаблон (чертеж), по которому создаются объекты (экземпляры класса). Для определения формы и сущности класса указываются данные, которые он должен содержать, а также код, воздействующий на эти данные. Если мы хотим работать в нашем приложении с документами, то необходимо для начала объяснить что такое документ, описать его в виде класса (чертежа) Document. Рассказать какие у него должны быть свойства: название, содержание, количество страниц, информация о том, кем он подписан и т.д. В этом же классе мы описываем что можно делать с документами: печатать в консоль, подписывать, изменять содержание, название и т.д. Результатом такого описания и будет наш класс Document. Однако это по-прежнему всего лишь чертеж. Если нам нужны конкретные документы, то необходимо создавать объекты: документ №1, документ №2, документ №3. Все эти документы будут иметь одну и ту же структуру (название, содержание, …), с ними можно выполнять одни и те же действия, НО наполнение будет разным (например, в первом документе содержится приказ о назначении работника на должность, во втором, о выдаче премии отделу разработки и т.д.). Начнём с малого, напишем свой первый класс. Представим, что нам необходимо работать в нашем приложении с котами. Java ничего не знает о том, что такое коты, поэтому нам необходимо создать новый класс (тип данных), и объяснить что же такое кот. Создадим проект, его структура нам не в новизну, и будет иметь следующий вид: Disk:. ├───out └───src └───ru └───gb └───jcore Cat.java Main.java Теперь начнем потихоньку прописывать класс Cat. Пусть у котов есть три свойства: name (кличка), color (цвет) и age (возраст); и они пока ничего не умеют делать. Класс Cat имеет следующий вид, и как мы все прекрасно помним, имя класса должно совпадать с именем файла, в котором он объявлен, т.е. класс Cat должен находиться в файле Cat.java: package ru.gb.jcore; public class Cat { String name; String color; int age; } Итак, мы рассказали Java что такое коты, теперь если мы хотим создать в нашем приложении кота, следует воспользоваться следующим оператором: Cat cat1 = new Cat(); Подробный разбор того, что происходит в этой строке, будет проведен в следующем пункте. Пока же нам достаточно знать, что мы создали объект типа Cat (экземпляр класса Cat), и для того чтобы с ним работать, положили его в переменную, которой присвоили имя cat1. На самом деле, в переменной не лежит весь объект, а только ссылка где его искать в памяти, но об этом позже. Объект cat1 создан по чертежу Cat, и значит у него есть поля name, color, age, с которыми можно работать (получать или изменять их значения). Для доступа к полям объекта служит операция-точка, которая связывает имя объекта с именем поля. Например, чтобы присвоить полю color объекта cat1 значение "Белый", нужно выполнить следующий оператор: cat1.color = "Белый"; Операция-точка служит для доступа к полям и методам объекта по его имени. Рассмотрим пример консольного приложения, работающего с объектами класса Cat. Перейдём в главный класс Main и напишем следующий код: package ru.gb.jcore; public class Main{ public static void main(String[] args) { Cat cat1 = new Cat(); Cat cat2 = new Cat(); cat1.name = "Барсик"; cat1.color = "Белый"; cat1.age = 4; cat2.name = "Мурзик"; cat2.color = "Черный"; cat2.age = 6; System.out.println("Кот 1 имя: " + cat1.name + " цвет: " + cat1.color + " возраст: " + cat1.age); System.out.println("Кот 2 имя: " + cat2.name + " цвет: " + cat2.color + " возраст: " + cat2.age); } } Выполним уже известные нам команды для компиляции и запуска приложения: javac -sourcepath ./src -d out .\src\ru\gb\jcore\Main.java java -classpath ./out ru.gb.jcore.Main Выведется: Кот 1 имя: Барсик цвет: Белый возраст: 4 Кот 2 имя: Мурзик цвет: Черный возраст: 6 Вначале мы создали два объекта типа Cat: cat1 и cat2, соответственно они имеют одинаковый набор полей (name, color, age), однако каждому из них мы в эти поля записали разные значения. Как видно из результата печати в консоле, изменение значения полей одного объекта, никак не влияет на значения полей другого объекта. Данные объектов cat1 и cat2 изолированы друг от друга. ЧТО ТАКОЕ ОБЪЕКТ Как создавать новые типы данных (классы) мы разобрались, мельком посмотрели и как создаются объекты наших классов. Давайте теперь поподробнее разберем как создавать объекты, и что при этом происходит Создание объекта проходит в два этапа. Сначала создается переменная, имеющая интересующий нас тип (в данном случае Cat), в нее мы сможем записать ссылку на будущий объект (поэтому при работе с классами и объектами мы говорим о ссылочных типах данных). Затем необходимо выделить память под наш объект, создать и положить объект в выделенную часть памяти, и сохранить ссылку на этот объект в памяти в нашу переменную. Для непосредственного создания объекта применяется оператор new, который динамически резервирует память под объект и возвращает ссылку на него, в общих чертах эта ссылка представляет собой адрес объекта в памяти, зарезервированной оператором new. public static void main(String[] args) { Cat cat1; cat1 = new Cat(); } В первой строке кода переменная cat1 объявляется как ссылка на объект типа Cat и пока ещё не ссылается на конкретный объект (первоначально значение переменной cat1 равно null). В следующей строке выделяется память для объекта типа Cat, и в переменную cat1 сохраняется ссылка на него. После выполнения второй строки кода переменную cat1 можно использовать так, как если бы она была объектом типа Cat. Обычно новый объект создается в одну строку (Cat cat1 = new Cat()). Теперь немного подробнее рассмотрим оператор new. Оператор new динамически выделяет память для нового объекта, общая форма применения этого оператора имеет следующий вид: Имя_класса имя_переменной = new Имя_класса() //на самом деле не имя класса, а название конструктора; Имя_класса() в правой части выполняет вызов конструктора данного класса, который позволяет подготовить наш объект к работе. Возможна ситуация, когда две переменные указывают на один и тот же объект в памяти: public static void main(String[] args) { Cat cat1 = new Cat(); Cat cat2 = cat1; cat1.name = "Барсик"; cat1.color = "Белый"; cat1.age = 4; cat2.age = 5; System.out.println("Кот 1 имя: " + cat1.name + " цвет: " + cat1.color + " возраст: " + cat1.age); System.out.println("Кот 2 имя: " + cat2.name + " цвет: " + cat2.color + " возраст: " + cat2.age); } На первый взгляд может показаться, что переменной cat2 присваивается ссылка на копию объекта cat1, т.е. переменные cat1 и cat2 будут ссылаться на разные объекты в памяти. Но это не так. На самом деле cat1 и cat2 будут ссылаться на один и тот же объект. Присваивание переменной cat1 значения переменной cat2 не привело к выделению области памяти или копированию объекта, лишь к тому, что переменная cat2 ссылается на тот же объект, что и переменная cat1. Таким образом, любые изменения, внесённые в объекте по ссылке cat2, окажут влияние на объект, на который ссылается переменная cat1, поскольку это один и тот же объект в памяти, как и в примере выше, где мы указали возраст второго кота 5 лет, а при выводе, возраст 5 лет оказался и у первого кота. STATIC Теперь мы знаем что такое класс и объект. На этом моменте хотелось бы остановиться на специальном модификаторе - static - с англ. "статичный", "постоянный" - делает переменную или метод "независимыми" от объекта. Ещё чуть подробнее, то: Static — модификатор, применяемый к полю, блоку, методу или внутреннему классу. Данный модификатор указывает на привязку субъекта к текущему классу. В таком случае можно воспользоваться ключевым словом static, то есть объявить членов класса статическими. В Java большинство членов служебного класса являются статическими. Вот несколько примеров. java.util.Objects содержит статические служебные операции для метода объекта; java.util.Collections состоит исключительно из статических методов, которые работают с коллекциями или возвращают их. Итак, где же можно употреблять данное ключевое слово? Мы можем использовать это ключевое слово в четырех контекстах: статические методы; статические переменные; статические вложенные классы; статические блоки. Рассмотрим подробнее каждый из перечисленных пунктов. Статические методы Статические методы также называются методами класса, потому что статический метод принадлежит классу, а не его объекту. Кроме того, статические методы можно вызывать напрямую через имя класса. public class CalcExample { public static void printSum(int a, int b) { System.out.println(a + b); } public void printDifference(int a, int b) { System.out.println(a - b); } public static void main(String[] args) { /** Вызов статического метода **/ CalcExample.printSum(10, 2); /** Вызов не-статического метода**/ CalcExample calcExample = new CalcExample(); CalcExample.printDifference(10, 2); } } В приведенном выше примере метод printSum — статический, поэтому его можно вызывать напрямую с именем класса. Нет необходимости создавать новый экземпляр класса CalcExample. Но метод printDifference не является статическим. Таким образом, для нестатического метода необходимо создать новый экземпляр класса CalcExample. Статические поля При обозначении переменной уровня класса мы указываем на то, что это значение относится к классу. Если этого не делать, то значение переменной будет привязываться к объекту, созданному по этому классу. Это значит, что если переменная не статическая, то у каждого нового объекта данного класса будет своё значение этой переменной, меняя которое мы меняем его исключительно в одном объекте: Например, у нас есть класс котика с нестатической переменной: public class Cat { String name; } Тогда в мейн класс: Cat cat1 = new Cat(); cat1.name = "Murka"; Cat cat2 = new Cat(); cat2.name = "Vasya"; System.out.println("First cat - " + cat1.name); System.out.println("Second cat - " + cat2.name); Вывод будет следующим: First cat - Murka Second cat - Vasya Как видим, у каждого объекта своя переменная, изменение которой происходит только для этого объекта. Ну а если у нас переменная статическая, то это глобальное значение — одно для всех: Теперь мы имеем Cat со статической переменной: public class Cat { static String name; } По факту переменная у нас одна на всех, и каждый раз мы меняем именно ее. К статическим переменным, как правило, обращаются не по ссылке на объект — cat1.name, а по имени класса — Cat.name Соответственно, код из мейн класса после выполнения выведет следующее сообщение: First cat - Vasya Second cat - Vasya К слову, статические переменные — редкость в Java. Вместо них применяют статические константы. Они определяются ключевым словом static final и представлены в верхнем регистре. Вот почему некоторые предпочитают использовать верхний регистр и для статических переменных. Статические блоки Статические блоки применяют для инициализации статических переменных. Статический блок выполняется только один раз, когда класс загружается в память. Это происходит, если в коде запрашивается либо объект класса, либо статические члены этого класса. Ниже пример выведет имя котика: public class Cat { public static String name = null; static { name = "Murka"; } public static void main(String[] args) { System.out.println(name); } } Класс может содержать несколько статических блоков, а каждый из них выполняется в той же последовательности, в которой они написаны в коде. Последним контекстом использования ключевого слова static - это в части статических вложенных классов, которые мы рассмотрим чуть позднее. \end{longtable} \end{document}