Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Лекция Bob Martin "The Future of Programming"- https://www.youtube.com/watch?v=ecIWPzGEbFc
Мэтт Вайсфельд - страницы 23 - 36.
Шилдт - страницы 37 - 55.
Необязательно! Шилдт - страницы - 58 - 125. (Глава 2 и 3). В 2 и 3 главе дано краткое описание примитивным типам данных, ветвлениям и циклам.
Гради Буч - страницы 33 - 73, 106 - 119.
Эккель, страницы - 40 - 46, 70 - 86.
Зарегистрироваться на сайте https://codingbat.com и пройти следующие уроки:
Warmup 1, 2;
String 1, 2, 3;
Array 1, 2, 3;
Logic 1, 2.
При создании объектов с помощью оператора new
возвращается ссылка на вновь созданный объект. Однако нас никто не обязывает эту ссылку присваивать в качестве значения ссылочной переменной. В таких случаях создается анонимный объект. Другими словами, объект есть, а переменной, которая бы содержала ссылку на этот объект, нет.
С практической точки зрения, это может выглядеть бесполезным, однако, анонимные объекты требуются довольно часто - обычно в тех ситуациях, когда объект класса используется один раз. Рассмотрим пример
В данном случае, нам нужен объект класса PrinterManager только для одного действия - для распечатки файла. То есть, мы создаем объект, вызываем метод, после чего объект нам больше нужен.
В первом случае используется обычный порядок работы с объектами - создаем объект класса PrinterManager, ссылка на объект записывается в ссылочную переменную manager.
Во втором случае мы создаем анонимный объект. Инструкцию
можно условно разбить на две части. Выражениеnew PrinterManager()
создает новый объект класса PrinterManager и возвращает ссылку на объект в качестве результата выражения. Поскольку ссылка не присваивается переменной, то такой объект является анонимным. Но так как выражение new PrinterManager()
возвращает ссылку на объект, мы можем у этой ссылки вызвать метод printFile()
.
Интерфейсы и абстрактные классы улучшают структуру кода и способствуют отделению интерфейса от реализации.
Вернемся к примеру с классами фигур
Методы базового класса Shape всегда были "фиктивными". Попытка вызова метода из класса Shape привела бы к ошибке в программе. Такая логика написания кода связана с тем, что класс Shape нам нужен лишь для того, чтобы определить общий интерфейс всех классов, производных от него, а уже производные классы переопределяли эти методы и реализовывали их по-своему.
Если в программе определяется такой абстрактный базовый класс вроде Shape, создание объектов такого класса практически всегда бессмысленно. Абстрактный класс создается для работы с набором классов через общий интерфейс. А если Shape только выражает интерфейс, а создание объектов такого класса не имеет смысла, лучше всего запретить пользователю создавать такие объекты, так как попытка работать с этим классом приведет к ошибке в программе.
В языке Java для решения подобных задач применяются абстрактные методы. Абстрактный метод является незавершенным, он состоит только из объявления и не имеет тела. Приведем пример абстрактного метода:
Класс, содержащий один или более абстрактных метода, называется абстрактным классом. Такие классы также должны помечаться ключевым словом abstract (в противном случае, компилятор выдает сообщение об ошибке)
Компилятор запрещает создавать объекты абстрактного класса.
Если вы объявите не абстрактный класс, производный от абстрактного класса, то вы обязаны переопределить все абстрактные методы базового класса. Если это не будет сделано, то производный класс будет считаться абстрактным, и компилятор заставит пометить новый класс ключевым словом abstract.
Объявление класса как abstract не подразумевает, что все его методы должны быть абстрактными.
Класс можно обозначить как абстрактный даже когда в нем нет ни одного абстрактного метода. Это бывает полезно, когда необходимо просто запретить создание экземпляров этого класса.
Создавать абстрактные классы необходимо, так как они подчеркивают абстрактность класса, а также сообщают и пользователю класса и компилятору, как следует с ним обходиться. Кроме этого, абстрактные классы играют полезную роль при рефакторинге программ, потому что они позволяют легко перемещать общие методы вверх по иерархии классов.
Ключевое слово interface становится следующим шагом на пути к абстракции. Ключевое слово interface используется для создания классов, вообще не имеющих реализации. Создатель интерфейса определяет имена методов, списки аргументов и типы возвращаемых значений, но не тела методов. Интерфейс описывает форму, но не реализацию.
Ключевое слово interface фактически означает "Именно так должны выглядеть все классы, которые реализуют данный интерфейс". Поэтому любой код, использующий конкретный интерфейс, знает только то, какие методы вызываются для этого интерфейса, но не более того. Интерфейс определяет своего рода "протокол взаимодействия" между классами.
Кроме этого, в отличие от абстрактного класса, интерфейс позволяет реализовать своего рода, множественное наследование.
Чтобы создать интерфейс, используйте ключевое слово interface вместо class. Как и в случае с классами, перед ключевым словом interface указывается модификатор доступа (public, protected и так далее). Интерфейс также может содержать поля, они автоматически являются статическими (static) константами (final).
Для создания класса, реализующего определенный интерфейс (или несколько интерфейсов), используется ключевое слово implements. Фактически это означает "интерфейс определяет форму, а здесь будет показано, как это работает".
В классе, который реализует интерфейс, реализуемые методы должны быть объявлены как public.
Неважно, приводите ли вы преобразование к "обычному" классу с именем Shape, к абстрактному классу Shape или к интерфейсу Shape - действие будет одинаковым.
Когда метод работает с классом вместо интерфейса, мы ограничены использованием базового класса и его подклассами. Это исключает возможность использовать метод для класса, который не входит в эту иерархию. Интерфейс, в значительной степени, ослабляет это ограничение. В результате код становится более универсальным.
В Java допускается объявлять переменные ссылочного интерфейсного типа, то есть переменные, хранящие ссылки на объекты классов, которые реализуют определенный интерфейс.
Такая переменная может ссылаться на любой объект класса, который реализует интерфейс. При вызове метода для объекта по интерфейсной ссылке выполняется вариант этого метода, реализованный в классе данного объекта. Этот механизм аналогичен тому, когда ссылочная переменная суперкласса ссылается на объект подкласса.
Так как интерфейс по определению не имеет реализации, нет ничего, что могло бы помешать совмещению нескольких интерфейсов. При объявлении класса, который реализует несколько интерфейсов, имена интерфейсов перечисляются вслед за ключевым словом implements и разделяется запятыми.
Механизм обратного вызова (callback) является ключевым в программировании. При обратном вызове, программист задает действия, которые должны выполняться всякий раз, когда происходит некоторое событие. Например, можно задать действие, которое должно быть выполнено после нажатия на кнопку или при выборе определенного пункта меню.
Приведем небольшой пример. В стандартной библиотеке классов Java нам доступен класс javax.swing.Timer, который используется для отсчета интервалов времени.
Устанавливая таймер, мы задаем интервал времени и указываем, что должно произойти по его истечении. Как указать таймеру, что он должен делать по истечении времени?
Для этого в Java существует механизм обратного вызова. Он заключается в том, что программист должен передать объекту таймера объект некоторого типа. После этого таймер вызывает у объекта некоторый метод.
Разумеется, таймер должен знать, какой метод объекта он должен вызвать и этот метод в объекте гарантированно должен быть реализован. Для этого таймеру нужно указать объект класса, который реализует интерфейс ActionListener. Этот интерфейс входит в состав стандартной библиотеки и выглядит следующим образом
По истечении заданного интервала времени таймер вызывает метод actionPerformed() и передает ему объект класса Event (класс Event описывает событие в Java):
Как мы видим, конструктор класса Timer запрашивает задержку и объект, у которого будет вызван метод actionPerformed.
Создадим класс, который будет реализовывать интерфейс ActionListener
Как мы видим, данный класс ничего кроме реализации интерфейса не делает. То есть он нужен только для одной цели - он содержит метод, который будет вызван таймером после задержки. Такие классы и их объекты называют слушателями. Слушатель - это объект, который, как бы, "слушает" события, которые происходят с другим объектом. И когда это "слушаемое" событие происходит, вызывается указанный в интерфейсе метод этого объекта.
Создадим слушатель MessageDialogPoster и передадим этот объект таймеру
Запустим приложение и посмотрим на результат.
Обратите внимание, что метод actionPerformed принимает на вход объект класса ActionEvent. При вызове метода actionPerformed(), таймер передает в метод объект класса ActionEvent, который содержит различную информацию о событии. Таким образом, мы можем запрограммировать те или иные действия, в зависимости от параметров события.
Рассмотрим еще один пример, на этот раз будем использовать кнопку. Создадим объект окна, объект кнопки и добавим кнопку в окно.
По умолчанию, при нажатии на кнопку ничего не происходит. По аналогии с таймером, нам необходимо передать кнопке слушатель, который реализует определенный интерфейс. Кнопка гораздо более сложный объект, чем простой таймер, поэтому событий, которые привязаны к кнопке, может быть гораздо больше. Для каждого типа событий, кнопка принимает свой слушатель, который реализует свой определенный интерфейс.
С помощью такого многообразия интерфейсов, мы можем обработать самые разнообразные события, которые могут случиться с кнопкой.
В данном случае нас интересует метод addActionListener(), который принимает объект слушателя, который реализует интерфейс ActionListener
С помощью этого метода, мы передаем кнопке объект класса MouseClickHandler. Когда произойдет какое-то событие, кнопка возьмет этот переданный объект и вызовет метод actionPerformed() этого объекта.
Запустим приложение и нажмем кнопку.
Та
В рамках данной темы поднимается вопрос сложность программного обеспечения как основного вызова при разработке ПО, понятию ОО программирования, приводится пример использования объектного подхода.
"Если бы строители строили здания так же, как программисты пишут программы, первый залетевший дятел разрушил бы цивилизацию" (с) Второй закон Вейнберга
Одним из основных вызовов при разработке программного обеспечения является сложность программ. Этот вызов иногда называют «кризисом программного обеспечения».
Не всё программное обеспечение является сложным, существует большое количество «простых» программ, которые разрабатываются и поддерживаются одним человеком. Такие программы, как правило, имеют очень ограниченный функционал и используются в течение короткого периода времени. С написанием таких программ не возникает много проблем, поэтому для их написания вы можете использовать практически любые языки программирования, технологии и методы разработки программных продуктов.
Наибольшие проблемы возникают при разработке корпоративного программного обеспечения (enterprise software). Такие системы используются довольно долго (годами и десятилетиями), и от их корректной работы зависят тысячи и даже миллионы людей. Это могут быть, например, системы управления воздушным транспортом, железнодорожными перевозками, банковские системы, системы коммунальных платежей, онлайн-игры, популярные веб-сайты и веб-службы и так далее.
Важнейшей особенностью корпоративной программы является ее высокая сложность. Одному программисту не под силу решить все проблемы, связанные с проектированием такой системы. Грубо говоря, сложность промышленных программ превышает интеллектуальные возможности отдельного человека.
Некоторые специалисты считают что популярные операционные системы являются самой сложной рукотворной системой, которую когда-либо создавало человечество.
Со времени возникновения области разработки программного обеспечения, человечество накопило достаточно знаний, чтобы проектировать даже самые сложные системы программного обеспечения, но мы до сих пор сталкиваемся с огромным количеством проблем. В чем же дело?
При анализе сложных систем, мы обнаруживаем много составных частей, которые взаимодействуют друг с другом разными довольно запутанными способами, причем части и способы их взаимодействия могут быть совершенно разными. При проектировании и организации сложных систем разработчику необходимо думать сразу о многом. Например, система управления воздушным транспортом должны одновременно контролировать состояние многих самолетов, учитывая, например, их местоположение, скорость и курс. К сожалению, один человек не может охватить все эти детали одновременно.
Таким образом, у нас возникает проблема сложности – программное обеспечение становится всё более сложным, а способности справиться с этой сложностью остаются ограниченными. Как же решить эту проблему?
Одним из способов справиться со сложностью программных систем, это декомпозиция. При проектировании сложного программного обеспечения, необходимо разделять его на всё меньшие и меньшие части, каждую из которых можно обрабатывать независимо друг от друга. Таким образом, вместо работы над всей программной системой сразу, мы будем работать с ее отдельными частями.
Одним из методов декомпозиции является алгоритмическая декомпозиция (вы сталкивались с таким видом декомпозиции на первом курсе, когда изучали дисциплину «Алгоритмизация и программирование»). Алгоритмическая декомпозиция выполняется методом «сверху вниз», где каждый модуль системы выполняет один из этапов общего процесса. На рисунке 1.1 приведена часть программы, которая обновляет содержимое основного файла.
Другим видом декомпозиции называется объектно-ориентированная декомпозиция, которая вам пока неизвестна.
При использовании этого вида декомпозиции, вместо разделения системы на этапы, например, «Прочитать отформатированное обновление» и «Добавить контрольную сумму», мы определяем такие объекты, как «Основной файл» и «Контрольная сумма», которые создаются при анализе предметной области. На рисунке 1.2 приведен пример объектно-ориентированной декомпозиции для той же части программы.
В случае объектно-ориентированной декомпозиции, мир представляет собой совокупность автономных агентов, которые взаимодействуют друг с другом и обеспечивают более сложное поведение системы. Действие «Прочитать отформатированное обновление» больше не является независимым алгоритмом, это действие представляет собой операцию, связанную с объектом «Файл обновлений». В результате выполнения этой операции возникает другой объект – «Обновление карты». Таким образом, каждый объект в такой схеме реализует свое собственное поведение, и каждый из них моделирует некоторый объект реального мира.
С такой точки зрения объект является материальной сущностью, обладающей определенным поведением. Получая сообщения, объекты выполняют определенные операции. Такая композиция основана на объектах, поэтому и называется объектно-ориентированной.
Так какой же метод декомпозиции следует использовать? Использовать оба метода одновременно нельзя – сначала следует произвести декомпозицию либо по алгоритмам, либо по объектам.
Многолетний опыт разработчиков программного обеспечения явно показывает, что объектно-ориентированная декомпозиция имеет много чрезвычайно важных преимуществ над алгоритмической. Декомпозицию следует начинать с объектов, поскольку она облегчает упорядочение сложных систем, таких как программное обеспечение, компьютеры, растения, галактики и крупные общественные институты.
Преимущества объектно-ориентированной декомпозиции:
уменьшается размер систем за счет повторного использования общих механизмов;
объектно-ориентированные системы являются более гибкими и легче эволюционируют со временем;
снижается риск, возникающий при создании сложной программной системы;
объектно-ориентированная декомпозиция позволяет лучше справиться со сложностью, характерной для систем программного обеспечения.
Объектно-ориентированная технология основана на использовании так называемой объектной модели проектирования, или просто объектной модели. К основным принципам этой модели относятся абстракция, инкапсуляция, модульность, иерархия, контроль типов, параллелизм и персистентность.
Языки, которые реализуют объектную модель, называют объектными или объектно-ориентированными. Пример структуры программ, написанных на объектно-ориентированных языках программирования, представлен на рисунке 1.3.
Основным элементом в этих языках является модуль, который представляет собой логическую связанную совокупность объектов и классов (понятие класса будет рассмотрено ниже). Такая структура является графом, а не деревом, как в случае использования алгоритмических языков. Кроме того, в ОО языках исключены глобальные данные. Данные и операции объединяются таким образом, что основными логическими конструктивными элементами ОО систем теперь являются объекты и классы, а не алгоритмы.
Объектная модель допускает масштабирование. В крупных системах образуются целые кластеры, образующие слои. Пример структуры крупных систем приведен на рисунке 1.4.
Объектно-ориентированное программирование – это методология программирования, основанная на представлении программы в виде совокупности взаимодействующих объектов, каждый из которых является экземпляром определенного класса, а классы являются членами определенной иерархии наследования.
Объект – структура, которая объединяет данные и методы, которые эти данные обрабатывают. Фактически, объект является основным строительным блоком объектно-ориентированных программ.
Класс – шаблон для объектов. Каждый объект является экземпляром (instance) какого-либо класса («безклассовых» объектов не существует). В рамках класса задается общий шаблон, структура, на основании которой создаются объекты. Данные, относящиеся к классу, называются полями класса, а программный код для их обработки называется методами класса. Поля и методы иногда называют общим термином – члены класса.
Разница между классом и объектом такая же, как между абстрактным понятием и реальным объектом.
Объект состоит из следующих частей:
имя объекта;
состояние (переменные состояния). Данные, содержащиеся в объекте, представляют его состояние. В терминологии ООП эти данные называются атрибутами. Например, атрибутами работника могут быть: имя, фамилия, пол, дата рождения, номер телефона. В разных объектах атрибуты имеют разное значение. Фактически, в объектах определяются конкретные значения тех переменных (полей класса), которые были заявлены при описании класса;
методы (операции) – применяются для выполнения операций с данными, а также для совершения других действий. Методы определяют, как объект взаимодействует с окружающим миром.
Объекты могут отправлять друг другу сообщения. Сообщение (message) - это практически то же самое, что и вызов функции в обычном программировании. В ООП обычно употребляется выражение "послать сообщение" какому-либо объекту. Понятие "сообщение" в ООП можно объяснить с точки зрения ООП: мы не можем напрямую изменить состояние объекта и должны как бы послать сообщение объекту, что мы хотим как-то изменить его состояние. Очень важно понять, что объект сам меняет свое состояние, а мы можем только попросить его об этом с помощью отсылки сообщения.
В объектно-ориентированной программе весь код должен находиться внутри классов!
В классе описываются, какого типа данные относятся к классу, а также то, какие методы применяются к этим данным. Затем, в программе на основе того или иного класса создается экземпляр класса (объект), в котором указываются конкретные значения полей и выполняются необходимые действия над ними.
Давайте попробуем понять, что такое объект и класс с помощью языка программирования C.
Представим себе, что мы пишем программу для книжной лавки на языке C. В какой-то момент мы сталкиваемся с необходимостью хранить информацию о множестве книг: название книги, кто автор книги, год издания и стоимость книги. Как нам это запрограммировать?
Мы можем воспользоваться массивами и хранить данные о книгах в нескольких массивах.
Теперь мы можем обратиться к i-му номеру каждого массива для получения информации об i-ой книге.
Какими недостатками обладает данный способ работы с данными? Такой подход может приводить к многочисленным ошибкам (например, ошибки при работе с индексами массивов), такие данные тяжело модифицировать (удаление книги приводит к необходимости смещать влево часть элементов массивов), такой код неудобно читать, поддерживать и модифицировать.
Но самое главное - мы мыслим в контексте структуры компьютера, а не решаемой задачи. Для нас книга - это некий единый объект, который имеет некоторые параметры (атрибуты): название, количество страниц и так далее. Мы же представляем атрибуты этого объекта в виде отдельных записей в разных массивах, потому что на языке C мы вынуждены мыслить в терминах имеющихся структур данных (массивов, очередей, деревьев), а не в терминах отдельных объектов и их взаимоотношений. Это затрудняет понимание решаемой задачи (управление книжной лавкой и продажей книг), увеличивает количество ошибок в программе и на некотором этапе мы вообще перестаем понимать, что происходит в программе.
Для хотя бы какого-то решения этой проблемы и облегчения труда программиста, на некотором этапе в язык C было введено понятие структуры (ключевое слово struct
), которое вы должны были изучать в курсе "Алгоритмизация и программирование".
Структура в языке C - это тип данных, создаваемый программистом, предназначенный для объединения данных различных типов в единое целое. Таким образом, мы можем сгруппировать данные, которые относятся к одной книге, в одну структуру.
Сначала мы должны описать структуру (новый тип данных).
Таким образом, некоторое понятие реального мира (то есть, книга вообще, как понятие) в программе описан структурой Book
. Экземпляр этого понятия (какая-то конкретная книга) в программе будет представлен экземпляром структуры - переменной типа Book
. Мы объявляем экземпляр структуры, после чего мы сможет заполнить поля структуры и работать с ней дальше в программе.
Использование структур решает проблему лишь частично. Мы сгруппировали данные, но функции для работы с нашей структурой находятся вне структуры.
Объект реального мира обладает не только атрибутами (автор книги, название книги, количество страниц и так далее), но и определенным поведением (книгу можно продать, купить, переместить на склад и так далее). При разработке сложных программных систем необходимо группировать не только данные, но и функции, которые работают с этими данными.
Рассмотрим простой пример объектно-ориентированной программы. Представим, что мы программируем графический редактор, который может рисовать различные фигуры. Подумав над задачей, мы приходим к выводу, что в нашей программе должны быть объекты, которые представляют различные фигуры. Итак приступим.
Сначала мы должны создать классы и описать внутри этих классов данные, которые относятся к фигурам и поведение фигур в виде набора методов (функций).
Опишем класс для фигуры «Треугольник». Какие характеристики могут быть у класса «Треугольник»? От чего зависит набор характеристик? В принципе, любой объект является бесконечно сложным и для его описания понадобится бесконечно большое количество характеристик. Но для нашего простого графического редактора важными будут следующие параметры: координаты трех точек и цвет фигуры.
Здесь вы должны понять очень важную вещь: набор данных в классе зависит от той программы, которую мы собираемся написать. Например, если бы мы писали более мощный графический редактор, в списке полей класса мы добавили бы отдельно цвет заливки и цвет линий фигуры, сам цвет мог быть не только сплошным, но и в виде какого-то узора и так далее.
Для указания класса в Java используется ключевое слово class
, после чего идет название класса, далее мы ставим фигурные скобки и всё, что будет написано внутри фигурных скобок (переменные и функции) будет относиться к этому классу.
Какие переменные и типы данных будут моделировать координаты точек и цвет? Для моделирования цвета воспользуемся обычным целым числом (очень часто цвета моделируются обычным целочисленным значением).
Теперь попробуем подумать, а как нам смоделировать координаты трех точек? Если бы мы программировали на языке C, мы бы написали что-то вроде
Но давайте подумаем - координаты каждой точки логически связаны между собой и когда мы будем, например, перемещать треугольник или будем менять размер треугольника, будут одновременно меняться две переменные, которые моделируют одну точку. С точки зрения объектной модели, лучшим вариантом будет предусмотреть отдельную сущность, отдельный класс, который моделирует точку на двумерной плоскости. Таким образом, мы сгруппируем данные и код, который будет эти данные изменять (например, менять значение X и Y).
Теперь вернемся к классу Triangle
и укажем, что в качестве координат у нас будут выступать три объекта класса Point2D
.
Теперь давайте определимся с методами классов – то есть, к тому поведению, которое будут осуществлять объекты этих классов.
Какое поведение может быть у объекта класса «точка»? Совершенно точно это будет метод «изменить координаты». То есть, наша точка как отдельный объект может вести себя следующим образом – менять свои координаты. Давайте запрограммируем этот метод.
Теперь давайте подумаем, какое поведение будет у треугольника? Что с ним можно сделать? Ну, например, можно его перекрасить, можно его передвинуть в другое место на плоскости, можно его нарисовать на каком-то полотне и так далее. Давайте опишем некоторые из этих методов.
Итак, мы описали треугольник, но мы определили только понятие «треугольник», у нас нет их физически, код класса - это просто описание. Если вы создали java-проект, у вас, по умолчанию, будет присутствовать класс Main
и функция main()
. Пока что мы не будем объяснять, зачем нужен класс Main
, а обратим внимание на метод main()
. Этот метод является «точкой входа» в программу, с вызова этого метода начинается работа программы. Когда мы дойдем и выполним последнюю инструкцию внутри метода main()
, приложение завершится.
Создадим внутри метода main()
несколько объектов класса Triangle
. Создание объектов мы будем рассматривать подробно в следующих лекциях, на данный момент мы просто скажем, что для создания объекта используется ключевое словоnew
. После создания объекта, мы можем обращаться к объекту и вызывать методы у объектов (как мы вызывали функции в C).
Важно! Вы должны понять, что у разных объектов значения переменных будут разные!
То есть, где-то в оперативной памяти у нас создано два разных объекта. Внутри первого есть объект класса Point2D
, внутри этого объекта есть две переменные типа int
, у которых будут значения 140 и 180. Внутри же второго треугольника будет свой объект Point2D
, внутри которого будут совершенно другие две переменные типа int
, у которых будут значения 50 и 80. Это же будет касаться переменных color в двух разных объектах (рис.1.5).
Таким образом, когда мы описываем классы, мы заявляем, что объекты этих классов будут содержать определенные поля (набор переменных) и будут иметь набор методов (функций), которые можно вызвать у объекта. Каждый объект содержит свои переменные, но методы у них общие.
Ссылка на конспект лекции
При разработке языка Java был взят за основу синтаксис языков С и C++, поэтому, многие аспекты синтаксиса языка покажутся вам знакомыми.
В Java, как и в C, существуют однострочные и блоковые комментарии. Однако, кроме этого, согласно конвенции Oracle, существуют другие виды комментариев: copyright-блок вверху, doc-комментарии, TODO-комментарии, комментарии после statement`ов, комментарии для комментирования кода. Ознакомьтесь с принятыми правилами использования комментариев, на защите лабораторных они будут требоваться в обязательном порядке, согласно принятым конвенциям.
Переменные чувствительны к регистру (var и Var – две разные переменные), могут быть бесконечной длины, состоять из букв юникода и цифр юникода, символов _ и $.
Первый символ переменной может быть буквой, символом _ или $ (использовать _ или $ первым символом КРАЙНЕ НЕ РЕКОМЕНДУЕТСЯ, они существуют для специальных ситуаций, которые нас сейчас не интересуют, так что считайте, что начинаться переменная может только с буквы юникода). Крайне не рекомендуется использовать буквы национальных алфавитов, кириллицу, транслит. Только латинские буквы, названия на английском. Также, названия переменных не должны совпадать со списком зарезервированных слов
Приведенный ниже блок кода даст вам общее представление о том, как надо называть переменные
В языке Java существуют примитивные типы (аналогичны типам данных в C) и ссылочные (или объектные) типы данных. На данный момент нас интересуют только примитивные типы данных.
Java – строго типизированный язык программирования. Это значит, что переменная, перед использованием, должна быть объявлена и ей должен быть присвоен тип, который нельзя поменять. Также, при выполнении операций присваивания, компилятор проверяет соответствие типов (какого-то механизма автоматического приведения типов у Java нет).
Всего существуют восемь примитивных типов данных: int, long, short, byte, double, float, char, boolean. Их очень легко запомнить:
4 типа для целых чисел («короткое short», «среднее int», «длинное long» и байт);
2 типа для чисел с плавающей запятой (старая парочка double и float);
2 «специальных» типа – символ и булевый тип.
Операторы ветвления в C и Java практически идентичны
Работа с циклами в Java мало чем отличается от языка C
Работа с массивами в Java несколько отличается от работы с массивами в C, в основном, из-за механизма выделения памяти под массивы.
Так как Java является объектно-ориентированным языком, функции здесь называются методами (на данный момент мы будем считать, что методы и функции выполняют одну и ту же роль, но методы могут находиться только внутри классов).
Ссылка на конспект лекции
Ссылка на конспект лекции
Ссылка на конспект лекции
abstract
continue
for
new
switch
assert
default
goto
package
synchronized
boolean
do
if
private
this
break
double
implements
protected
throw
byte
else
import
public
throws
case
enum
instanceof
return
transient
catch
extends
int
short
try
char
final
interface
static
void
class
finally
long
strictfp
volatile
const
float
native
super
while
Type
Min
Max
RAM
Default
Объявления и литералы
byte
-128
127
8 bit
0
byte b = 100;
short
-32,768
32,767
16 bit
0
short b = 10000;
int
-2^31
-2^31-1
32 bit
0
int a = 15;
int aHex = 0xaa;
int aBin = 0b0001111;
(это же справедливо и для byte,short,long, если соблюдать диапазоны)
long
-2^63
-2^63-1
64 bit
0L
long number = 10000L;
double
4.9^-324
~1.8^308
32 bit
0.0d
double d = 6.6;
float
~1.4^-45
~3.4^38
64 bit
0.0f
float f = 5.5f;
char
0
65535
16 bit
'\u0000'
char c = ‘f’;
char c = 63;
char c = '\u2422';
boolean
false
true
1 bit
false
boolean b = true;
Ссылка на конспект лекции
Обычно при создании класса вы описываете, как объекты этого класса ведут себя и как они выглядят. Объект появляется только после того, как он будет создан ключевым словом new
, и только начиная с этого момента для него выделяется память и появляется возможность вызова методов.
Но есть ситуации, в которых такой подход недостаточен. Первая ситуация - это когда некоторые данные должны храниться "в единственном числе" независимо от того, сколько было создано объектов класса. Вторая - когда вам потребуется метод, не привязанный ни к какому конкретному объекту класса (то есть метод, который можно вызвать даже при полном отсутствии объектов класса).
Такой эффект достигается путем использования ключевого слова static
, делающего элемент класса статическим. Поля или методы, которые объявлены как static
, не привязаны к определенному экземпляру этого класса. Поэтому даже если в программе еще не создавался ни один объект класса, вы можете вызывать статический метод или получить доступ к статическим полям. С обычным объектом вам необходимо сначала создать его и использовать для вызова метода или доступа к полям, так как нестатические данные должны точно знать объект, с которым работают.
Некоторые ОО языки используют термин данные уровня класса и методы уровня класса, подразумевая, что данные и методы существуют только на уровне класса в целом, а не для отдельных объектов этого класса.
Чтобы сделать данные или метод статическими, просто поместите ключевое слово static
перед их определением. Например, следующий код создает статическое поле класса и инициализирует его:
Теперь, даже при создании двух объектов StaticTest
, для элемента StaticTest.val
выделяется единственный блок памяти. Оба объекта совместно используют одно значение val
. Пример:
В данном примере как st1.val
, так и st2.val
имеют одинаковые значения, равные 924, потому что они ссылаются на один блок памяти.
Существует два способа обратиться к статической переменной. Как было видно выше, на нее можно ссылаться по имени объекта, например st2.val
. Также возможно обратиться к ней прямо через имя класса; для нестатических членов класса такая возможность отсутствует.
После выполнения операции, значения st1.val
и st2.val
будут равны 48.
Синтаксис с именем класса является предпочтительным, потому что он не только подчеркивает, что переменная является статической, но и в некоторых случаях предоставляет компилятору больше возможностей для оптимизации.
Если для инициализации статических переменных требуется произвести вычисления, то для этой цели достаточно объявить статический блок, который будет выполняться только один раз при первой загрузке класса. Пример:
Как только загружается класс NetworkConnector
, выполняются все действия в статическом блоке.
Та же логика верна и для статических методов. Вы можете обратиться к такому методу или через объект, как это делается для всех методов, или в специальном синтаксисе ИмяКласса.метод(). Статические методы определяются по аналогии со статическими данными:
Нетрудно заметить, что метод increment()
класса Incrementable
увеличивает значение статического поля val
. Метод можно вызвать стандартно, через объект:
Или, поскольку метод increment() является статическим, можно вызвать его с прямым указанием класса:
Если применительно к полям ключевое слово static
радикально меняет способ определения данных (статические данные существуют на уровне класса, в то время как нестатические данные существуют на уровне объектов), то в отношении методов изменения не столь принципиальны. Одним из важных применений static
является определение методов, которые могут вызываться без объектов.
На статические методы налагаются следующие ограничения:
они могут непосредственно вызывать только другие статические методы;
им непосредственно доступны только статические переменные;
им недоступны ключевые слова this или super.
В языке Java имеется языковое средство, расширяющее возможности ключевого слова import
и называемое статическим импортом. Оператор import
, предваряемый ключевым словом static
, можно применять для импорта статических членов класса или интерфейса. Благодаря статическому импорту появляется возможность ссылаться на статические члены непосредственно по именам, не используя имя класса. Это упрощает и сокращает синтаксис, требующийся для работы со статическими членами.
Рассмотрим пример без использования статического импорта. Представим себе, что мы вычисляем гипотенузу прямоугольного треугольника. Мы будем часто использовать методы Math.pow()
и Math.sqrt()
Как мы видим, строка
получается слишком громоздкой. Подобных неудобств можно избежать, если воспользоваться статическим импортом. Рассмотрим пример ниже
После использования статического импорта нет нужды использовать имя класс Math
для вызова статических методов pow()
и sqrt()
.
Если предполагается применять много статических методов или полей, определенных в классе, то можно импортировать класс Math
полностью
Каким бы удобным ни был статический импорт, очень важно не злоупотреблять им. Не следует забывать, что библиотечные классы Java объединяются в пакеты для того, чтобы избежать конфликтов пространств имен и непреднамеренного сокрытия прочих имен. Если статический член используется в программе один или два раза, то его лучше не импортировать. Статический импорт следует оставить на тот случай, если статический члены применяются многократно, в частности при выполнении математических вычислений.
В Java определены вложенные классы. Вложенным называется такой класс, который объявляется в другом классе.
Вложенный класс не может существовать независимо от класса, в который он вложен. Следовательно, область действия вложенного класса ограничена его внешним классом. Если вложенный класс объявлен в пределах области действия внешнего класса, то он становится членом последнего. Имеется также возможность объявить вложенный класс, который станет локальным в пределах блока
Существуют два типа вложенных классов. Одни вложенные классы объявляются с помощью модификатора доступа static, а другие - без него. Нестатический вариант вложенных классов называются внутренними.
Внутренние классы позволяют вам группировать классы, логически принадлежащие друг другу, и управлять доступом к ним. Однако важно понимать, что внутренние классы заметно отличаются от композиции.
Внутренний класс может взаимодействовать со своим внешним классом, а код, написанный с использованием внутренних классов, получается более элегантным и понятным.
Создать внутренний класс несложно - достаточно разместить определение класса внутри окружающего класса.
Внутренний класс имеет доступ ко всем переменным и методам внешнего класса, в который он вложен, и может обращаться к ним непосредственно, как и все остальные нестатические члены внешнего класса.
Иногда внутренний класс используется для предоставления ряда услуг внешнему классу, в котором он содержится.
Класс можно вложить в область действия блока. В итоге получается локальный класс, недоступный за пределами блока.
Также внутренний класс можно быть анонимным классом (анонимные классы уже были рассмотрены ранее).
Механизм перечислений был добавлен в JDK 5 версии, он позволяет удобно хранить и работать с так называемыми "категориальными данными".
Категориальные данные - это данные с ограниченным числом уникальных значений или категорий. Приведем примеры категориальных данных:
месяцы года (12 значений: январь, февраль и так далее);
пол (мужской, женский, андроген, бесполый и так далее);
вероисповедание (православие, католицизм, ислам и так далее);
день недели (понедельник, вторник, среда и так далее).
Рассмотрим пример. Для информационной системы «Электронный институт» необходимо создать класс сотрудника кафедры «DepartmentMember», в котором, среди прочих атрибутов, есть атрибут «должность» (position). С целью упрощения, предположим, что сотрудники кафедры могут занимать одну из следующих должностей:
«Инженер» (Engineer);
«Ассистент» (Assistant);
«Старший преподаватель» (Lecturer);
«Доцент» (Senior Lecturer);
«Профессор» (Professor).
Каким образом закодировать данное значение? Самый первый и очевидный вариант - просто использовать тип данных String.
Данное решение имеет несколько очень серьезных недостатков. Вы никак не можете обеспечить корректность указания должностей и контролировать создание нового объекта класса.
Должность старшего преподавателя, например, может быть записана как "ст. преп.", "ст. преподаватель", "ст. пр." и так далее, не говоря про возможные орфографические ошибки. С точки зрения формальной логики, это все разные категории должностей и вы не сможете группировать или фильтровать сотрудников кафедры по полю "должность".
Другой вариант - это создать несколько статических целочисленных констант, которые можно использовать вместо строк.
Теперь мы сможем использовать именованные константы вместо строго фиксированных строк.
Использование именованных констант позволяет отчасти избавиться от недостатков предыдущего подхода, но и этот вариант не является приемлемым. Ничто не мешает пользователю класса при создании объекта указать любое целое числа вместо именованной константы, осуществлять проверку ввода – достаточно трудоемкая задача, если в классе предусмотрено более одного категориального значения будет тяжело определять какие конкретно именованные константы необходимо использовать. К тому же, при использовании конструктора или сеттера поля «должность», нет никакого указания на необходимость использования именованных констант, программист сам должен догадаться о наличии нужных констант или читать сопроводительную документацию.
Подход с использованием именованных констант напоминает улучшенную версию использования механизма специальных кодов и его не следует использовать при разработке коммерческих программ.
Корректным вариантом решения данной проблемы является использование специального механизма перечислений (enumeration). По сути, перечисление – это тип, ссылочная переменная которого может принимать одно из нескольких заранее определенных значений. Реализуем класс «DepartmentMember» с помощью механизма перечислений:
Преимущества такого подхода очевидны: поле position может принять только один из заранее определенных значений, которые указаны при создании перечислений. При вызове конструктора или сеттера поля position, пользователю класса сразу будет понятно, что необходимо передать в качестве аргумента метода.
Обратите внимание на синтаксис перечисления. Перечисление является классом, но очень своеобразным. Вместо ключевого слова class используется ключевое слово enum (от слова enumeration – перечисление). Вместо полей и методов у перечисления идет просто набор констант через запятую.
Как уже было сказано, перечисление – это класс, а это значит, что он может иметь методы, и мы можем инкапсулировать перечисление и операции работы с ним в одной оболочке.
В данном примере, экземпляр перечисления хранит строку с цветом.
В Spring имеется набор модулей для интеграции с различными технологиями хранения данных. Spring позволяет избавить разработчика от рутины при разработке программного кода, реализующего доступ к данным. Вместо возни с низкоуровневым доступом к данным можно положиться на Spring, который выполнит эту работу за вас, и сконцентрироваться на управлении данными в самом приложении.
Что такое JDBC, драйвер, JPA, ORM и как это все между собой соотносится?
Как правило, каждая система управления базами данных (MySQL, PostgreSQL и так далее) имеет свой протокол взаимодействия с клиентами. Чтобы работать с базой данных, клиент должен соблюдать протокол взаимодействия с базой данных.
Чтобы программист не тратил время на самостоятельную реализацию протокола при разработке очередного приложения, разработчик сервера баз данных сам предоставляет всем желающим программный код, который общается с базой данных на понятном этой базе протоколе. Такой программный код и называется драйвером базы данных. Драйвер реализует протокол общения с БД и предоставляет API, которое позволяет нам общаться с базой данных, не вдаваясь в детали реализации протокола.
Как раз для этого разработчики Java предоставили стандарт JDBC (Java DataBase Connectivity) – специальное API, которое используется приложениями Java для взаимодействия с базой данных. Стандарт JDBC позволяет отправлять запросы к базе данных для выполнения операций выбора, вставки, обновления и удаления.
Если разработчики СУБД хотят, чтобы их база данных использовалась Java-разработчиками, они предоставляют JDBC-драйвер для их базы данных. Разработчики Java подключат драйвер и используют его для общения с той или иной базой данных. Если, в какой-то момент, разработчики захотят сменить СУБД, они просто меняют драйвер старой базы на драйвер новой. Благодаря стандарту JDBC, ничего менять в коде работы с базой данных не требуется.
Что такое и зачем нужна технология ORM?
При написании объектно-ориентированного кода, который взаимодействует с базой данных, у разработчика возникает несколько проблем:
данные в программе и в базе данных используют разные парадигмы (объектно-ориентированная и реляционная соответственно). Работу по преобразованию данных из одной парадигмы в другую ложатся на плечи программиста, что влечет за собой лишнюю работу и может приводить к ошибкам в процессе преобразования;
программисту желательно абстрагироваться от конкретной схемы хранения данных. То есть, программисту желательно работать не с реляционной базой данных, а просто с некоторым «хранилищем», а конкретная реализация этого «хранилища» может быстро и безболезненно меняться.
Для устранения этих проблем используется технология ORM (Object-Relational Mapping, «объектно-реляционное отображение») — технология программирования, которая связывает базы данных с концепциями объектно-ориентированных языков программирования, создавая «виртуальную объектную базу данных».
Проще говоря, ORM – это прослойка, посредник между базой данных и объектным кодом. Используя ORM, программист не занимается формированием SQL-запросов и не думает в терминах «таблица», «записи» и «реляционные отношения», а просто работает с «хранилищем объектов» – он может туда записывать и получать объекты, не заботясь о подробностях их хранения.
В Java предусмотрен специальный стандарт JPA (Java Persistence API), который использует концепцию ORM. Существует несколько реализаций этого интерфейса, например, Hibernate, OpenJPA, EclipseLink и другие.
Spring Data JPA – обертка над JPA в Spring, которая предоставляет много полезных «фишек» разработчику. Она позволяет легче создавать Spring-управляемые приложения, которые используют новые способы доступа к данным, например нереляционные базы данных, map-reduce фреймворки, cloud сервисы, а так же уже хорошо улучшенную поддержку реляционных баз данных.
Терминология JPA
Основное понятие JPA – сущность (Entity). Сущность – это Java-класс, который представляет бизнес-логику приложения и определяет данные, которые будут храниться в базе данных и извлекаться из нее.
Как правило, класс сущности представляет таблицу в базе данных, поля или свойства класса представляют собой колонки в таблице, а объект сущности представляет собой одну запись в таблице.
Важным моментом при работе с JPA являются аннотации, коих здесь будет очень много. Разберемся с некоторыми из них:
@Entity – позволяет серверу узнать, что это не просто какой-то класс, а сущность;
@Id – помечает первичный ключ в таблице. Вопрос составных ключей в данном занятии не рассматривается;
@Table – позволяет настраивать отображение класса в таблицу. В данном случае, мы можем указать, какое имя будет иметь соответствующая таблица в базе данных;
@GeneratedValue – указывает, что данное поле является генерируемым значением. Очень часто этой аннотацией помечают первичные ключи, чтобы они генерировались автоматически при добавлении новых записей в таблицу;
@Column – позволяет настраивать отображение колонки в таблице. В данном случае, мы можем указать, какое имя будет иметь соответствующая колонка в таблицу.
Репозитории. Главными компонентами для взаимодействий с БД в Spring Data являются репозитории. Каждый репозиторий работает со своим классом-сущностью.
В большинстве случаев, структура запросов к репозиторию будет одинаковая: «получить все записи», «получить записи, где столбец равен определенному значению» и так далее.
Spring Data JPA позволяет вам избежать рутинного создания запросов. Для этого вместо класса создадим интерфейс, который будет наследоваться от стандартного generic-интерфейса. Первый параметр означает тип класса-сущности, второй параметр – тип первичного ключа.
Установим СУБД Postgres и запустим pgAdmin 4.
Создадим пользователя ejournal_user, после чего создадим базу данных для нашего приложения.
Добавляем в pom.xml зависимости для работы с Spring Data JPA и JDBC драйвер для Postgres.
Далее необходимо настроить подключение к СУБД и нужной базе данных.
Для настройки приложения Spring воспользуемся языком YAML. Для этого удалим файл resources/application.properties и создадим вместо него файл application.yml.
Создадим класс сущности Student
Для уменьшения количества кода, мы будем использовать плагин Lombok.
Проект Lombok — это плагин компилятора, который добавляет в Java новые «ключевые слова» и превращает аннотации в Java-код, уменьшая усилия на разработку и обеспечивая некоторую дополнительную функциональность.
Lombok преобразует аннотации в исходном коде в Java-операторы до того, как компилятор их обработает: зависимость lombok
отсутствует в рантайме, поэтому использование плагина не увеличит размер сборки.
При использовании Lombok наш исходный код не будет валидным кодом Java. Поэтому потребуется установить плагин для IDE, иначе среда разработки не поймёт, с чем имеет дело. Lombok поддерживает все основные Java IDE. Интеграция бесшовная. Все функции вроде «показать использования» и «перейти к реализации» продолжают работать как и раньше, перемещая вас к соответствующему полю/классу.
Далее подключим библиотеку в pom.xml.
Вернемся в класс Student, добавим аннотацию для геттеров, сеттеров, а также конструктор со всеми параметрами.
Service – это Java класс, который содержит в себе основную бизнес-логику. В основном сервис использует готовые DAO/Repositories или же другие сервисы, для того чтобы предоставить конечные данные для пользовательского интерфейса. Сервисы, как правило, вызываются контроллерами или другими сервисами.
Объект службы создается контейнером Spring, каждая служба является «одиночкой» (синглтоном), который создается в момент запуска приложения и уничтожается в момент закрытия приложения. Обратите внимание на аннотацию @Service. Этой аннотацией мы сообщаем контейнеру Spring, что это не просто класс, а класс сервиса.
Итак, мы создали службу, у которой есть два публичных метода. Первый метод добавляет нового студента, второй метод возвращает список всех студентов. В дальнейшем служба будет обращаться к объекту репозитория за данными, а пока что оставим код таким, какой он есть.
Вернемся к созданию веб-слоя. Создадим класс контроллера, создадим две конечные точки: для добавления студента и для получения списка всех студентов.
Обратите внимание, что мы не создаем объект службы, а получаем его «извне» с помощью аннотации @Autowired. Контейнер Spring «внедрит» ссылку на объект службы в поле service. Подробнее про внедрение зависимостей будет изложено в следующем занятии.
Главными компонентами для взаимодействий с БД в Spring Data являются репозитории. Каждый репозиторий работает со своим классом-сущностью.
В большинстве случаев, структура запросов к репозиторию будет одинаковая: «получить все записи», «получить записи, где столбец равен определенному значению» и так далее.
Spring Data JPA позволяет вам избежать рутинного создания запросов. Для этого вместо класса создадим интерфейс, который будет наследоваться от стандартного generic-интерфейса. Первый параметр означает тип класса-сущности, второй параметр – тип первичного ключа.
Теперь перейдем в класс службы и создадим ссылку на объект репозитория.
Обратите внимание, что мы не создавали класс, который реализует интерфейс StudentRepository, тогда откуда мы его получим объект интерфейсного типа? Дело в том, что Spring сгенерирует класс за нас. Этот сгенерированный класс будет иметь набор стандартных операций для работы с сущностями. В нашем случае, это операция findAll(), которая возвращает все сущности в таблице student.
Запустим сервер и выполним два клиентских запроса - один на создание студента, второй - на получение списка всех студентов.
Добавляем нового студента
Теперь получим список всех студентов.
Как мы знаем, важной составляющей реляционных баз данных является отношения между таблицами "один-к-одному", "один-ко-многим", "многие-ко-многим".
Реализуем отношение "один-ко-многим". Создадим сущность Group - студенческая группа. В студенческой группе может быть от 0 до N студентов.
Прежде всего перейдем в сущность Student. Добавим поле group, который будет ссылаться на студенческую группу, в которой будет состоять студент. Так как в группе может быть много студентов, указываем аннотацию @ManyToOne. Также указываем аннотацию @JoinColumn, которая указывает на имя колонки, которая будет содержать Foreign Key.
Технология ORM позволяет создавать двусторонние связи между таблицами. В этом случае, при выдаче JSON, может возникнуть бесконечный цикл. Чтобы его избежать, укажем аннотацию @JsonIgnore. В этом случае, колонка group будет проигнорирована в процессе сериализации\десериализации.
Далее создадим сущность Group.
Обратите внимание, что отношение один-ко-многим мы моделируем с помощью обычной коллекции. Указываем аннотацию @OneToMany, также в свойстве mappedBy указываем, какое поле "держит" отношение со стороны студента.
Далее модифицируем класс контроллера. Создадим конечные точки для добавления новой группы, а также для получения списка всех групп. Также модифицируем конечную точку для добавления студента, чтобы указать id группы, в которую необходимо добавить студента.
Теперь создадим репозиторий для сущности Group.
Далее модифицируем класс сервиса. Добавим методы для добавления новой группы, а также для получения списка всех групп. Также модифицируем метод добавления новой группы. Метод работает следующим образом: получаем объект группы по id, после чего добавляем ссылку на группу в поле group объекта Student.
Запустим приложение и проверим его работу. Сначала добавим группу, после чего получим список групп.
Добавим новую группу
Получим список групп
Теперь добавим нового студента
Получим список всех групп