Как работают принципы ООП в Java: примеры
Принципы и понятия объектно-ориентированного программирования (ООП, или OOPS, Object-Oriented Programming Concepts) очень важны. Не разбираясь в понятиях ООП, вы не сможете проектировать системы в модели объектно-ориентированного программирования.
Модель объектно-ориентированного программирования
Модель объектно-ориентированного программирования построена вокруг понятия объекта. Объект – это экземпляр класса. Он содержит свойства и функции. По сути они похожи на объекты реального мира: как, например, машина, дом, ноутбук и т. д. У них есть свои свойства и определенные методы для выполнения действий. Что такое класс? Класс определяет схему объектов. Он определяет свойства и функции объектов. К примеру, книга — это класс, а конкретно ваша книга — его экземпляр.
Принципы ООП
Основными принципами ООП являются:
- Абстракция
- Инкапсуляция
- Полиморфизм
- Наследование
- Ассоциация и агрегация
- Композиция
Теперь по порядку рассмотрим эти понятия объектно-ориентированного программирования. Чтобы вы лучше поняли, как реализовать концепции ООП, мы будем использовать фрагменты кода на языке программирования Java.
Абстракция
Абстракция — это скрытие внутренних деталей и описание вещей простыми терминами. Возьмем, к примеру, метод, который складывает два целых числа . Внутренняя обработка метода скрыта от внешнего мира. Достичь абстракци в ООП можно несколькими способами, например, через инкапсуляцию и наследование. Отличным примером абстракции является программа на Java: язык программирования сам заботится о преобразовании простых операторов в машинный язык и скрывает детали внутренней реализации от внешнего мира.
Инкапсуляция
Инкапсуляция — это метод, используемый для реализации абстракции в ООП. Она ограничивает доступ к членам и методам класса. Для инкапсуляции в ООП применяются ключи модификаторов доступа. Например, в языке java инкапсуляция достигается с помощью ключевых слов private, protected и public.
Полиморфизм
Полиморфизм указывает, что в разных ситуациях объект ведет себя по-разному. Существует два типа полиморфизма – полиморфизм во время компиляции и во время выполнения. Полиморфизм во время компиляции достигается перегрузкой метода.
Для примера рассмотрим такой класс:
Все методы draw, которые встречаются в этом коде, ведут себя по-разному. Это пример перегрузки метода, потому что имена методов одинаковы, а аргументы разные. Поскольку компилятор сможет определить метод вызова во время компиляции, данный метод называется полиморфизмом во время компиляции. Полиморфизм во времени выполнения реализуется, когда между объектами есть отношения наследования «IS-A». Также этот подход называется переопределением метода, поскольку подкласс должен переопределить метод суперкласса. Фактический класс определяется во время выполнения с точки зрения суперкласса. Компилятор не может решать, какой метод класса будет вызван. Это решение принимается во время выполнения, отсюда и название – «полиморфизм во время выполнения» или «динамическая диспетчеризация методов».
Тут Shape — это суперкласс, у которого есть два подкласса, Circle и Square. Ниже приведен пример полиморфизма во время выполнения.
В этих примерах компилятор Java не знает фактического класса реализации Shape, который будет использоваться во время выполнения.
Наследование
Наследование — это понятие объектно-ориентированного программирования, которое указывает, что один объект основан на другом объекте, вытекает из него. Проще говоря, наследование — это механизм повторного использования кода. Наследуемый объект называется суперклассом, а объект, который наследует суперкласс, называется подклассом. В java для реализации наследования используется ключевое слово extends. Давайте посмотрим на следующий пример наследования в java.
Ассоциация
Такое поняте ООП, как ассоциация, определяет связи между объектами, а также разнообразие самих объектов. Предположим, что у нас есть объекты “Учитель” и “Ученик”: множество учеников может общаться с одним учителем, и, конечно, один ученик также может общаться с несколькими учителями.
Агрегация
Агрегация – это особый тип ассоциации. Это понятие подразумевает, что все объекты имеют свой собственный жизненный цикл, но со взаимосвязью «HAS-A» – то есть один дочерний объект может принадлежать одному родительскому . Каждый раз, когда вы встречаете взаимосвязь «HAS-A» между объектами, знайте – это называется агрегацией.
Композиция
Композиция – специальная “ограничительная” форма агрегации. В композиции содержащийся в отношении «HAS-A» объект не может существовать сам по себе. Представим, например, комнату, которая находится в доме . Одна комната не может быть частью двух разных домов. Конечно, если вы удалите дом, комната тоже будет удалена.
Name already in use
Объектно-ориентированное программирование (ООП) — методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию наследования.
- объектно-ориентированное программирование использует в качестве основных логических конструктивных элементов объекты, а не алгоритмы;
- каждый объект является экземпляром определенного класса
- классы образуют иерархии.
Программа считается объектно-ориентированной, только если выполнены все три указанных требования. В частности, программирование, не использующее наследование, называется не объектно-ориентированным, а программированием с помощью абстрактных типов данных.
Согласно парадигме ООП программа состоит из объектов, обменивающихся сообщениями. Объекты могут обладать состоянием, единственный способ изменить состояние объекта — послать ему сообщение, в ответ на которое, объект может изменить собственное состояние.
Назовите основные принципы ООП.
- Инкапсуляция — сокрытие реализации.
- Наследование — создание новой сущности на базе уже существующей.
- Полиморфизм — возможность иметь разные формы для одной и той же сущности.
- Абстракция — набор общих характеристик.
- Посылка сообщений — форма связи, взаимодействия между сущностями.
- Переиспользование— все что перечислено выше работает на повторное использование кода.
Это единственно верный порядок парадигм ООП, так как каждая последующая использует предыдущие.
Что такое «инкапсуляция»?
Инкапсуляция – это свойство системы, позволяющее объединить данные и методы, работающие с ними, в классе и скрыть детали реализации от пользователя, открыв только то, что необходимо при последующем использовании.
Цель инкапсуляции — уйти от зависимости внешнего интерфейса класса (то, что могут использовать другие классы) от реализации. Чтобы малейшее изменение в классе не влекло за собой изменение внешнего поведения класса.
Представим на минутку, что мы оказались в конце позапрошлого века, когда Генри Форд ещё не придумал конвейер, а первые попытки создать автомобиль сталкивались с критикой властей по поводу того, что эти коптящие монстры загрязняют воздух и пугают лошадей. Представим, что для управления первым паровым автомобилем необходимо было знать, как устроен паровой котёл, постоянно подбрасывать уголь, следить за температурой, уровнем воды. При этом для поворота колёс использовать два рычага, каждый из которых поворачивает одно колесо в отдельности. Думаю, можно согласиться с тем, что вождение автомобиля того времени было весьма неудобным и трудным занятием.
Теперь вернёмся в сегодняшний день к современным чудесам автопрома с коробкой-автоматом. На самом деле, по сути, ничего не изменилось. Бензонасос всё так же поставляет бензин в двигатель, дифференциалы обеспечивают поворот колёс на различающиеся углы, коленвал превращает поступательное движение поршня во вращательное движение колёс. Прогресс в другом. Сейчас все эти действия скрыты от пользователя и позволяют ему крутить руль и нажимать на педаль газа, не задумываясь, что в это время происходит с инжектором, дроссельной заслонкой и распредвалом. Именно сокрытие внутренних процессов, происходящих в автомобиле, позволяет эффективно его использовать даже тем, кто не является профессионалом-автомехаником с двадцатилетним стажем. Это сокрытие в ООП носит название инкапсуляции.
Модификатор private делает доступными поля и методы класса только внутри данного класса. Это означает, что получить доступ к private полям из вне невозможно, как и нет возможности вызвать private методы.
Сокрытие доступа к методу openConnection, оставляет нам также возможность к свободному изменению внутренней реализации этого метода, так как этот метод гарантированно не используется другими объектами и не нарушит их работу.
Для работы с нашим объектом мы оставляем открытыми методы call и ring с помощью модификатора public. Предоставление открытых методов для работы с объектом также является частью механизма инкапсуляции, так как если полностью закрыть доступ к объекту – он станет бесполезным.
Что такое «наследование»?
Наследование – это свойство системы, позволяющее описать новый класс на основе уже существующего с частично или полностью заимствующейся функциональностью.
Класс, от которого производится наследование, называется предком, базовым или родительским. Новый класс – потомком, наследником или производным классом.
Представим себя, на минуту, инженерами автомобильного завода. Нашей задачей является разработка современного автомобиля. У нас уже есть предыдущая модель, которая отлично зарекомендовала себя в течение многолетнего использования. Всё бы хорошо, но времена и технологии меняются, а наш современный завод должен стремиться повышать удобство и комфорт выпускаемой продукции и соответствовать современным стандартам.
Нам необходимо выпустить целый модельный ряд автомобилей: седан, универсал и малолитражный хэтч-бэк. Очевидно, что мы не собираемся проектировать новый автомобиль с нуля, а, взяв за основу предыдущее поколение, внесём ряд конструктивных изменений. Например, добавим гидроусилитель руля и уменьшим зазоры между крыльями и крышкой капота, поставим противотуманные фонари. Кроме того, в каждой модели будет изменена форма кузова.
Очевидно, что все три модификации будут иметь большинство свойств прежней модели (старый добрый двигатель 1970 года, непробиваемая ходовая часть, зарекомендовавшая себя отличным образом на отечественных дорогах, коробку передач и т.д.). При этом каждая из моделей будет реализовать некоторую новую функциональность или конструктивную особенность. В данном случае, мы имеем дело с наследованием.
Пример: Рассмотрим пример создания класса смартфон с помощью наследования. Все беспроводные телефоны работают от аккумуляторных батарей, которые имеют определенный ресурс работы в часах. Поэтому добавим это свойство в класс беспроводных телефонов:
Сотовые телефоны наследуют свойства беспроводного телефона, мы также добавили в этот класс реализацию методов call и ring:
И, наконец, класс смартфон, который в отличие от классических сотовых телефонов имеет полноценную операционную систему. В смартфон можно добавлять новые программы, поддерживаемые данной операционной системой, расширяя, таким образом, его функциональность. С помощью кода класс можно описать так:
Как видите, для описания класса Smartphone мы создали совсем немного нового кода, но получили новый класс с новой функциональностью. Использование этого принципа ООП java позволяет значительно уменьшить объем кода, а значит, и облегчить работу программисту.
Полиморфизм – это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.
Преимуществом полиморфизма является то, что он помогает снижать сложность программ, разрешая использование одного и того же интерфейса для задания единого набора действий. Выбор же конкретного действия, в зависимости от ситуации, возлагается на компилятор языка программирования. Отсюда следует ключевая особенность полиморфизма — использование объекта производного класса, вместо объекта базового (потомки могут изменять родительское поведение, даже если обращение к ним будет производиться по ссылке родительского типа).
Любое обучение вождению не имело бы смысла, если бы человек, научившийся водить, скажем, ВАЗ 2106 не мог потом водить ВАЗ 2110 или BMW X3. С другой стороны, трудно представить человека, который смог бы нормально управлять автомобилем, в котором педаль газа находится левее педали тормоза, а вместо руля – джойстик.
Всё дело в том, что основные элементы управления автомобиля имеют одну и ту же конструкцию, и принцип действия. Водитель точно знает, что для того, чтобы повернуть налево, он должен повернуть руль, независимо от того, есть там гидроусилитель или нет. Если человеку надо доехать с работы до дома, то он сядет за руль автомобиля и будет выполнять одни и те же действия, независимо от того, какой именно тип автомобиля он использует. По сути, можно сказать, что все автомобили имеют один и тот же интерфейс, а водитель, абстрагируясь от сущности автомобиля, работает именно с этим интерфейсом. Если водителю предстоит ехать по немецкому автобану, он, вероятно выберет быстрый автомобиль с низкой посадкой, а если предстоит возвращаться из отдалённого маральника в Горном Алтае после дождя, скорее всего, будет выбран УАЗ с армейскими мостами. Но, независимо от того, каким образом будет реализовываться движение и внутреннее функционирование машины, интерфейс останется прежним.
Полиморфная переменная, это переменная, которая может принимать значения разных типов, а полиморфная функция, это функция, у которой хотя бы один аргумент является полиморфной переменной. Выделяют два вида полиморфных функций:
- ad hoc, функция ведет себя по разному для разных типов аргументов (например, функция draw() — рисует по разному фигуры разных типов);
- параметрический, функция ведет себя одинаково для аргументов разных типов (например, функция add() — одинаково кладет в контейнер элементы разных типов).
Принцип в ООП, когда программа может использовать объекты с одинаковым интерфейсом без информации о внутреннем устройстве объекта, называется полиморфизмом.
Давайте представим, что нам в программе нужно описать пользователя, который может пользоваться любыми моделями телефона, чтобы позвонить другому пользователю. Вот как можно это сделать:
Теперь опишем различные модели телефонов. Одна из первых моделей телефонов:
Плюсы и минусы программирования на Java

Не так много технологий могут похвастаться тем, что они актуальны уже более 20 лет. Однако в этом году Java заняла пятое место в списке самых популярных технологий, уступив только неоспоримым лидерам: JavaScript, HTML, CSS и SQL. Java занимает 18-е место в рейтинге любимых технологий (по результатам опроса StackOverflow) и не попадает в рейтинги ненавистных. Сегодня обсудим плюсы и минусы Java — близкого и дорогого для многих программистов языка, проверенной временем технологии с узнаваемым логотипом, в виде чашки горячего кофе.
Что такое Java-программирование: история и вклад
Java — это язык программирования общего назначения, который следует парадигме объектно-ориентированного программирования и подходу «Написать один раз и использовать везде» . Java используется для настольных, сетевых, мобильных и корпоративных приложений. Подробная информация:
Java — это не только язык программирования, но и экосистема инструментов, охватывающая почти все, что может понадобиться при программировании на Java. В нее входят:
- Java Development Kit (JDK) — комплект разработчика Java. С помощью JDK и стандартного блокнота можно писать и запускать/ компилировать код на Java;
- Java Runtime Environment (JRE) — исполняющая система Java. Механизм распространения программного обеспечения, состоит из автономной виртуальной машины Java, стандартной библиотеки Java (Java Class Library) и инструментов настройки.
- Integrated Development Environment (IDE) — интегрированная среда разработки. Инструменты, которые помогают запускать, редактировать и компилировать код. Самые популярные из них — IntelliJ IDEA, Eclipse и NetBeans.
Java можно найти везде. Это основной язык разработки для Android. Он используется в веб-приложениях, правительственных веб-сайтах и технологиях обработки больших данных, таких как Hadoop и Apache Storm. Java подходит и для научных проектов, особенно в области обработки естественного языка. Язык Java преобладал и в программировании для мобильных устройств, задолго до появления смартфонов — первые мобильные игры в начале 2000-х годов были написаны на Java. Java, благодаря своей долгой истории, заработал свое место в Зале славы программирования. Индекс TIOBE, один из самых авторитетных индексов популярности программ в мире, при составлении рейтинга использует результаты поисковой выдачи. Несмотря на растущую популярность Go и Python, Java остается на вершине списка уже более десятилетия.
Индекс TIOBE, Август 2018 года
Все началось в начале 1990-х, когда команда Sun Microsystems начала разрабатывать улучшенную версию C ++ — независимую от конкретной платформы, удобную для начинающих и с автоматическим управлением памятью. Исследование привело к созданию совершенно нового языка. Название Java — одно из десятков других, предложенных командой. Сегодня логотип кофейной чашки с паром — это неприметный, но узнаваемый символ программирования. И уже неясно, что было первым: одержимость программистов кофеином или ассоциация с Java.

Как Java изменила мир программирования:
Гибкость. Java доказала, что C — процедурный, управляемый вручную и зависящий от платформы код — это не предел совершенства . Благодаря Java, все больше людей начали применять объектно-ориентированное программирование, которое сейчас используется повсеместно.
Апплеты. Еще до появления JavaScript, в Java добавили апплеты — небольшие веб-программы, которые предоставляют интерактивные элементы для визуализации и обучения. Они не используются ни для чего, кроме простой анимации, однако апплеты привлекли внимание многих программистов и подтолкнули их к разработке HTML5, Flash и JavaScript.
Разработка через тестирование. Java TDD — уже давно не экспериментальная практика, а стандартный способ разработки программного обеспечения. Введение JUnit в 2000 году считается одним из самых больших достижений Java.
Плюсы программирования на Java
Java — уже не единственный официально поддерживаемый язык для разработки на Android. Java далеко не единственный выбор в веб-программировании. Тем не менее, Java идет в ногу со временем. Давайте рассмотрим, какие преимущества предлагает Java.
+ Объектно-ориентированное программирование
Java включает в себя объектно-ориентированное программирование (OOP) — концепцию, в которой вы не только определяете тип данных и его структуру, но и набор функций, применяемых к нему. Таким образом, структура данных становится объектом, которым можно управлять для создания отношений между различными объектами.
При другом подходе — процедурном программировании — нужно следовать четким инструкциям, использовать переменные и функции. При ООП можно группировать эти переменные и функции посредством контекста, маркировать их и ссылаться на функции в контексте каждого конкретного объекта.
Сравнение процедурного и объектно-ориентированного программирования
В чем плюсы ООП?
- При ООП можно повторно использовать объекты в других программах
- ООП предотвращает ошибки, поскольку объекты скрывают информацию, к которой не должно быть доступа
- ООП более эффективно организует структуру программ, в том числе больших
- ООП упрощает обслуживание и модернизацию старого кода
+ Java — язык высокого уровня с простым синтаксисом и плавной кривой обучения
Java — это язык высокого уровня, то есть он похож на человеческий язык. В отличие от языков низкого уровня, которые напоминают машинный код. Языки высокого уровня преобразуется с помощью компиляторов или интерпретаторов. Это упрощает разработку, делая язык более легким для написания, чтения и обслуживания.
Hello World
Синтаксис Java основан на C ++, поэтому Java похожа на C. Тем не менее, синтаксис Java проще, что позволяет новичкам быстрее учиться и эффективнее использовать код для достижения конкретных результатов.
Java не так дружелюбен к новичкам, как Python, однако довольно прост для любого разработчика с базовым пониманием фреймворков, пакетов, классов и объектов. Он прост, типизирован и предсказуем, что позволяет учиться мыслить в правильном направлении. Кроме того, новичок всегда может обратиться к множеству бесплатных онлайн-уроков и курсов.
+ Стандарт для корпоративных вычислительных систем
Корпоративные приложения — главное преимущество Java с 90-х годов, когда организации начали искать надежные инструменты программирования не на C. Java поддерживает множество библиотек — строительных блоков любой корпоративной системы. Библиотеки помогают разработчикам создавать любые функции, которые могут понадобиться компании. Java широко распространен — это язык, который преподают в рамках введения в программирование в большинстве школ и университетов. Возможности интеграции Java впечатляют: большинство хостинг-провайдеров поддерживают Java. Более того, Java — язык, дешевый в обслуживании: работать с Java можно с любого компьютера, вне зависимости от конкретной аппаратной инфраструктуры.
+ Безопасность
Существует мнение, что Java — безопасный язык, однако это не совсем так. Сам язык не защищает вас от уязвимостей, но некоторые его функции устраняют распространенные уязвимости. Во-первых, в отличие от C, в Java нет указателей. Указатель — это объект, который сохраняет адрес ячейки памяти другого значения, что может вызвать несанкционированный доступ к памяти. Во-вторых, в Java есть Security Manager, созданная для каждого приложения политика безопасности, в которой можно указать правила доступа. Это позволяет запускать приложения Java в «песочнице» и устранять таким образом уязвимости.
+ Независимость от платформы («Написать один раз и использовать везде»)
«Написать один раз и использовать везде» (WORA) — популярная в IT-сфере фраза, с помощью которой Sun Microsystems описывает кросс-платформенные возможности Java. Можно создать Java-приложение на Windows, скомпилировать его в байт-код и запустить его на любой другой платформе, поддерживающей виртуальную машину Java (JVM). Таким образом, JVM служит уровнем абстракции между кодом и оборудованием.
Как работает WORA на Java
Все основные операционные системы, включая Windows, Mac OS и Linux, поддерживают JVM. Если ваша программа не опирается на специфичные для платформы функции и пользовательский интерфейс, ее можно с легкостью перенести: по крайней мере, большую ее часть.
+ Язык для распределенного программирования и комфортной удаленной совместной работы
Java создавался как язык для распределенного программирования: он имеет встроенный механизм совместного использования данных и программ несколькими компьютерами, что повышает производительность и эффективность труда.
Сравнение распределенного и параллельного программирования
В других языках нужно использовать внешний API для дистрибуции. В Java эта технология встроена. Специфическая для Java методология распределенных вычислений называется Remote Method Invocation (RMI). RMI позволяет использовать все преимущества Java: безопасность, независимость от платформы и объектно-ориентированное программирование для распределенных вычислений. Кроме того, Java также поддерживает программирование сокетов и методологию распределения CORBA для обмена объектами между программами, написанными на разных языках.
+ Автоматическое управление памятью
Разработчикам Java не нужно вручную писать код для управления памятью благодаря автоматическому управлению памятью (AMM). AMM также используется в языке программирования Swift и при очистке памяти приложениями, которые автоматически обрабатывают распределение и освобождение памяти. Что именно это означает?
Эффективность программы напрямую связана с памятью. При этом объем памяти ограничен. При написании приложения на языках с ручным управлением памятью, разработчики рискуют забыть выделить память, что приведет к увеличению объема занимаемой приложением памяти и проблемам с производительностью. Программы очистки памяти ищут объекты, которые больше не используются программой, и удаляют их. Это влияет на работу процессора, однако умная оптимизация и настройка позволяют снизить это влияние.
+ Многопоточность
Поток — наименьшая единица обработки в программировании. Чтобы максимально эффективно использовать время процессора, Java позволяет запускать потоки одновременно, что называется многопоточностью.
Потоки используют одну и ту же область памяти, поэтому между ними можно быстро переключаться. Потоки независимы друг от друга: один поток не влияет на работу других потоков. Это особенно полезно в играх и программах с большим объемом анимации.
Пример многопоточного выполнения
+ Стабильность и сообщество
Уже много лет развитию Java способствуют сообщество, поддержка Oracle и изобилие приложений и языков на JVM. Кроме того, постоянно выпускаются новые версии Java с новыми интересными функциями.
Сообщество разработчиков Java не имеет себе равных. Около 45% респондентов опроса StackOverflow 2018 используют Java. У Java чрезвычайно большая экосистема хорошо протестированных библиотек и фреймворков для любых задач. Начинающий разработчик, скорее всего, выберет Java: на тему Java-программирования существует более 1000 курсов на Udemy и более 300 на Coursera.
Минусы программирования на Java
Рассмотрим недостатки Java-программирования.
— Платное коммерческое использование
Недавно Oracle объявила, что с 2019 года компания начнет взимать плату за использование Java Standard Edition 8 в «коммерческих целях». За все новые обновления и исправления ошибок придется заплатить. Плата зависит от количества пользователей или компьютеров.
Текущая версия Java бесплатна для простого использования. Таким образом, каждая использующая Java компания должна оценить, насколько эффективно она использует Java. Компания должна понять, что выгоднее: искать альтернативное решение или продолжать пользоваться Java.
— Низкая производительность
У любого языка высокого уровня довольно низкая производительность из-за компиляции и абстракции с помощью виртуальной машины. Однако это не единственная причина низкой скорости Java. Например, приложение очистки памяти: это полезная функция, которая, к сожалению, приводит к значительным проблемам с производительностью, если требует больше 20 процентов времени процессора. Плохая настройка кэширования может вызвать чрезмерное использование памяти. Существует также взаимная блокировка потоков: так происходит, когда несколько потоков пытаются получить доступ к одному и тому же ресурсу. В этом случае происходит кошмар каждого Java-разработчика — ошибка из-за нехватки памяти. Тем не менее умелое планирование может решить все эти проблемы.
— Отсутствие нативного дизайна
Для создания графического интерфейса пользователя (GUI) разработчики используют различные инструменты, ориентированные для конкретного языка. Для Android-приложений есть Android Studio, которая помогает создавать приложения с нативным дизайном. Однако, когда дело доходит до пользовательского интерфейса на ПК, Java-инструмента для создания нативного дизайна нет.
Есть несколько инструментов для разработки GUI для Java: самые популярные из них — Swing, SWT, JavaFX, JSF. Библиотека Swing — это старый, но надежный кросс-платформенный инструмент, интегрированный в различные Java-IDE, в том числе Eclipse и NetBeans. Однако, если вы не используете шаблоны, вы заметите несоответствия интерфейса. SWT использует собственные компоненты, но не подходит для сложного интерфейса. JavaFX — лаконичный и современный, но слишком новый. В целом, перед созданием GUI на Java нужно подробнее изучить инструменты.
— Многословный и сложный код
Многословность кода может показаться преимуществом, которое поможет при изучении языка. Однако, длинные, чрезмерно сложные предложения затрудняют чтение и просмотр кода. Как и естественные языки, многие языки программирования высокого уровня содержат лишнюю информацию. Java — это более легкая версия неприступного C ++, которая вынуждает программистов прописывать свои действия словами из английского языка. Это делает язык более понятным для неспециалистов, но менее компактным.
Сравним Java и Python и увидим, в чем преимущество лаконичного кода Python. В Python не используются точка с запятой, круглые и фигурные скобки. Вместо «и», «или» и «нет» в качестве операторов используются «&&», «||» и «!».
Сравнение Java и Python
В заключение: где используется Java?
Большинство организаций так или иначе используют Java. Широкий спектр вариантов использования Java делает ее практически незаметной в использовании: поэтому часто возникает вопрос «где используется Java?». Давайте посмотрим, в каких сферах используется Java:
Приложения для Android. Несмотря на активный рост Kotlin, Java по-прежнему остается де-факто основным языком Android-приложений. Таким образом, все разработчики Java очень легко могут стать Android-программистами. Хотя Android использует Android SDK вместо JDK, тем не менее, код написан на Java.
Программные продукты. Помимо уже упомянутых Hadoop и Apache Storm, Java использовалась для создания Eclipse, OpenOffice, Gmail, Atlassian и других.
Финансовые программы. Java — один из самых востребованных языков в финансовой отрасли. Он используется для создания надежных, быстрых и простых веб-сайтов как на стороне сервера, так и на стороне клиента. Java также используется для моделирования данных.
Кассовые терминалы. Многие компании используют Java для создания систем PoS, поскольку их создание обычно требует кроссплатформенности и обширного штата специалистов.
Торговые системы. На Java написана Murex, популярная программа управления банками для фронтальной и обратной связи.
Программы для работы с большими данными. Hadoop написан на Java. Scala, Kafka и Spark используют JVM. Кроме того, Java предоставляет доступ к множеству проверенных библиотек, инструментов отладки и мониторинга.
Java Object-Oriented Programming
Объектно-ориентированное программирование — это парадигма, которая предоставляет множество концепций, таких как наследование, полиморфизм и т.д.
Simula считается первым объектно-ориентированным языком программирования.
Парадигма программирования, в которой все представлено в виде объекта, называется истинно объектно-ориентированным языком программирования.
Smalltalk считается первым истинным объектно-ориентированным языком программирования.
Популярными объектно-ориентированными языками являются:
Java
C#
PHP
Python
C++
1.1. ООП
Объект представляет собой реальную сущность из реального мира, например: BMW X5, Boeing 737, Parker Jotter (ручка).
Объектно-ориентированное программирование — это методология или парадигма для разработки программы с использованием классов и объектов. Это упрощает разработку и обслуживание программного обеспечения, предоставляя некоторые концепции:
Объект (Object)
Класс (Class)
Наследование (Inheritance)
Полиморфизм (Polymorphism)
Абстракция (Abstraction)
Инкапсуляция (Encapsulation)

Помимо этих концепций, есть несколько других терминов, которые используются в объектно-ориентированном дизайне:
Связность (Coupling), Единство (Cohesion)
Ассоциация (Association)
Агрегация (Aggregation), Композиция (Composition)
1.1.1. Object
Любая сущность, которая имеет состояние (state) и поведение (behavior), называется объектом. Например: овчарка, Xiaomi Mi Notebook Pro 15 2021 Ryzen Edition, Samsung S22+. Это может быть как физический предмет, так и нет.
Объект может быть определен как экземпляр класса. Объект содержит адрес и занимает некоторое место в памяти. Объекты могут взаимодействовать, но не знать состояния и реализации друг друга. Единственная необходимая вещь — это контракт взаимодействия.
Пример: собака — это объект, потому что она имеет такие состояния, как цвет , имя , порода , а также поведение, такое как вилять хвостом , лаять , есть .
1.1.2. Class
Класс — это определенный пользователем шаблон или прототип, из которого создаются объекты. Он представляет собой набор свойств или методов, которые являются общими для всех объектов одного типа.
1.1.3. Inheritance
Когда один объект приобретает все свойства и поведение родительского объекта, то это называется наследованием.
Наследование обеспечивает повторное использование кода.
Наследование используется для достижения полиморфизма во время выполнения.
1.1.4. Polymorphism
Если одна задача выполняется разными способами, это называется полиморфизмом.
В Java используется перегрузка и переопределение методов для достижения полиморфизма.
Например, когда вызвать метод говорить , то: кошка говорит мяу , собака лает гав .
1.1.5. Abstraction
Сокрытие внутренних деталей и предоставление функциональности называется абстракцией. Например, автомобиль, нам необязательно знать устройство автомобиля, нам достаточно знаний того, что нам позволяет им управлять (руль, педали и т.д.).
В Java используется абстрактный класс и интерфейс для достижения абстракции.
1.1.6. Encapsulation
Связывание (или упаковка) кода и данных в единый блок называется инкапсуляцией. Например, капсула, имеет одну оболочку, которая содержит различные лекарства.
Java Class является примером инкапсуляции.
Java Bean является полностью инкапсулированным классом, потому что все члены данных здесь являются закрытыми.
1.1.7. Coupling
Связность относится к знаниям/информации/зависимости одного класса о другом классе. Если у класса есть подробная информация о другом классе, существует strong coupling.
В Java используются private , protected , public модификаторы для отображения уровня видимости класса, метода и поля.
Можно использовать интерфейсы для weak coupling, потому что нет конкретной реализации.
1.1.8. Cohesion
Cohesion (сплоченность) относится к уровню компонента, который выполняет одну четко определенную задачу. Одна четко определенная задача выполняется highly cohesive методом. Weakly cohesive метод разделит задачу на отдельные части.
Например: пакет java.io представляет собой highly cohesive пакет, поскольку он имеет связанные с вводом/выводом классы и интерфейс. Тем не менее пакет java.util является weakly cohesive пакетом, потому что он имеет несвязанные классы и интерфейсы.
1.1.9. Association
Ассоциация представляет отношения между объектами. Здесь один объект может быть связан с одним или несколькими объектами. Между объектами может быть четыре типа связи:
Например, одна страна может иметь одного президента ( One to One ), а президент может иметь много министров ( One to Many ). Кроме того, у многих членов парламента может быть один президент ( Many to One ), а у многих министров может быть много департаментов ( Many to Many ).
Ассоциация может быть:
Ассоциация достигается с помощью:
Aggregation
Агрегация — это способ достижения ассоциации. Агрегация представляет собой отношение, в котором один объект содержит другие объекты как часть своего состояния.
Агрегация представляет weak relationship между объектами.
Агрегация также называется связью has-a в Java. Мол, наследование представляет собой отношения is-a.
Агрегация еще один способ повторного использования объектов.
Composition
Композиция представляет отношение, в котором один объект содержит другие объекты как часть своего состояния.
Композиция также является способом достижения ассоциации.
Существует strong relationship между содержащим объектом и зависимым объектом. Это состояние, в котором содержащиеся объекты не имеют самостоятельного существования. Если вы удалите родительский объект, все дочерние объекты будут удалены автоматически.
1.2. Преимущество ООП над процедурно-ориентированным языком программирования
ООП облегчает разработку и сопровождение, в то время как в языке программирования, ориентированного на процедуры, нелегко управлять, если код увеличивается с увеличением размера проекта.
ООП обеспечивает скрытие данных, тогда как в языке программирования, ориентированного на процедуры, глобальные данные могут быть доступны из любого места.
ООП дает возможность имитировать события в реальном мире гораздо более эффективно. Мы можем обеспечить решение проблемы с реальными словами, если мы используем язык объектно-ориентированного программирования.
1.3. В чем разница между object-oriented языком программирования и object-based языком программирования?
Object-based язык программирования следует всем функциям ООП, кроме наследования. JavaScript и VBScript являются примерами object-based языков программирования.
2. Классы и объекты
Java является объектно-ориентированным языком, поэтому такие понятия как «класс» и «объект» играют в нем ключевую роль. Любую программу на Java можно представить как набор взаимодействующих между собой объектов.
Шаблоном или описанием объекта является класс, а объект представляет экземпляр этого класса. Можно еще провести следующую аналогию. У нас у всех есть некоторое представление о человеке — наличие двух рук, двух ног, головы, туловища и т.д. Есть некоторый шаблон — этот шаблон можно назвать классом. Реально же существующий человек (фактически экземпляр данного класса) является объектом этого класса.
Класс определяется с помощью ключевого слова сlass :
В данном случае класс называется Person . После названия класса идут фигурные скобки, между которыми помещается тело класса — то есть его поля и методы.
Вся функциональность класса представлена:
членами класса — полями (fields — переменные класса), которые хранят состояние объекта
методами (methods), которые определяют поведение объекта. Например, класс Person , который представляет человека, мог бы иметь следующее определение:
В классе Person определены два поля: name представляет имя человека, а age — его возраст. И также определен метод displayInfo() , который ничего не возвращает и просто выводит эти данные на консоль.
Теперь используем данный класс. Для этого определим следующую программу:
Как правило, классы определяются в разных файлах. В данном случае для простоты мы определяем два класса в одном файле. Стоит отметить, что в этом случае только один класс может иметь модификатор public (в данном случае это класс Program ), а сам файл кода должен называться по имени этого класса, то есть в данном случае файл должен называться Program.java .
Класс представляет новый тип, поэтому мы можем определять переменные, которые представляют данный тип. Так, здесь в методе main() определена переменная tom , которая представляет класс Person . Но пока эта переменная не указывает ни на какой объект и по умолчанию она имеет значение null . По большому счету мы ее пока не можем использовать, поэтому вначале необходимо создать объект класса Person .
2.1. Конструкторы
Кроме обычных методов классы могут определять специальные методы, которые называются конструкторами (constructors). Конструкторы вызываются при создании нового объекта данного класса. Конструкторы выполняют инициализацию объекта.
Если в классе не определено ни одного конструктора, то для этого класса автоматически создается конструктор без параметров (default constructor).
Выше определенный класс Person не имеет никаких конструкторов. Поэтому для него автоматически создается конструктор по умолчанию, который мы можем использовать для создания объекта Person . В частности, создадим один объект:
Для создания объекта Person используется выражение new Person() . Оператор new выделяет память для объекта Person . И затем вызывается конструктор по умолчанию, который не принимает никаких параметров. В итоге после выполнения данного выражения в памяти будет выделен участок, где будут храниться все данные объекта Person . А переменная tom получит ссылку на созданный объект.
Если конструктор не инициализирует значения переменных объекта, то они получают значения по умолчанию. Для переменных числовых типов это число 0 , а для типа String и других классов — это значение null (то есть фактически отсутствие значения).
После создания объекта мы можем обратиться к переменным объекта Person через переменную tom и установить или получить их значения, например, tom.name = «Tom» .
Если необходимо, что при создании объекта производилась какая-то логика, например, чтобы поля класса получали какие-то определенные значения, то можно определить в классе свои конструкторы. Например:
Теперь в классе определено три конструктора, каждый из которых принимает различное количество параметров и устанавливает значения полей класса.
2.2. Ключевое слово this
Ключевое слово this представляет ссылку на текущий экземпляр класса. Через это ключевое слово мы можем обращаться к переменным, методам объекта, а также вызывать его конструкторы. Например:
В третьем конструкторе параметры называются так же, как и поля класса. И чтобы разграничить поля и параметры, применяется ключевое слово this :
Так, в данном случае указываем, что значение параметра name присваивается полю name .
Кроме того, у нас три конструктора, которые выполняют идентичные действия: устанавливают поля name и age . Чтобы избежать повторов, с помощью this можно вызвать один из конструкторов класса и передать для его параметров необходимые значения:
В итоге результат программы будет тот же, что и в предыдущем примере.
2.3. Инициализаторы
Кроме конструктора начальную инициализацию объекта вполне можно было проводить с помощью инициализатора (initializer) объекта. Инициализатор выполняется до любого конструктора. То есть в инициализатор мы можем поместить код, общий для всех конструкторов:
3. Объекты как параметры методов
Объекты классов, как и данные примитивных типов могут передаваться в методы. Однако в данном случае есть одна особенность — при передаче объектов в качестве значения передается копия ссылки на область в памяти, где расположен этот объект. Рассмотрим небольшой пример. Пусть у нас есть следующий класс Person :
Здесь в метод changeName передается объект Person , у которого изменяется имя. Так как в метод будет передаваться копия ссылки на область памяти, в которой находится объект Person , то переменная kate и параметр p метода changeName() будут указывать на один и тот же объект в памяти. Поэтому после выполнения метода у объекта kate , который передается в метод, будет изменено имя с «Kate» на «Alice» .
От этого случая следует отличать другой случай:
В метод changePerson() также передается копия ссылки на объект Person . Однако в самом методе мы изменяем не отдельные значения объекта, а пересоздаем объект с помощью конструктора и оператора new . В результате в памяти будет выделено новое место для нового объекта Person , и ссылка на этот объект будет присвоена параметру p :
То есть после создания нового объекта Person параметр p и переменная kate в методе main() будут хранить ссылки на разные объекты. Переменная kate , которая передавалась в метод, продолжит хранить ссылку на старый объект в памяти. Поэтому ее значение не меняется.
4. Пакеты
Как правило, в Java классы объединяются в пакеты. Пакеты позволяют организовать классы логически в наборы. По умолчанию java уже имеет ряд встроенных пакетов, например, java.lang , java.util , java.io и т.д. Кроме того, пакеты могут иметь вложенные пакеты.
Организация классов в виде пакетов позволяет избежать конфликта имен между классами. Ведь нередки ситуации, когда разработчики называют свои классы одинаковыми именами. Принадлежность к пакету позволяет гарантировать однозначность имен.
Чтобы указать, что класс принадлежит определенному пакету, надо использовать директиву package , после которой указывается имя пакета:
Как правило, названия пакетов соответствуют физической структуре проекта, то есть организации каталогов, в которых находятся файлы с исходным кодом. А путь к файлам внутри проекта соответствует названию пакета этих файлов. Например, если классы принадлежат пакету my.pack , то эти классы помещаются в проекте в папку my/pack .
Классы необязательно определять в пакеты. Если для класса пакет не определен, то считается, что данный класс находится в пакете по умолчанию, который не имеет имени.
Например, создадим в папке для исходных файлов директорию study . В нем создадим файл Program.java со следующим кодом:
Директива package study в начале файла указывает, что классы Program и Person , которые здесь определены, принадлежат пакету study .
Когда мы работаем в среде разработке, то IDE берет на себя все вопросы компиляции пакетов и входящих в них файлов. Соответственно нам достаточно нажать на кнопку, и все будет готово. Однако если мы компилируем программу в командной строке, то мы можем столкнуться с некоторыми трудностями. Поэтому рассмотрим этот аспект.
4.1. Работа с пакетами в terminal
Для компиляции программы вначале в командной строке/терминале с помощью команды cd перейдем к папке, где находится каталог study .
Например, в моем случае это каталог C:\java (то есть файл с исходным кодом расположен по пути C:\java\study\Program.java ).
Для компиляции выполним команду
После этого в папке study появятся скомпилированные файлы Program.class и Person.class . Для запуска программы выполним команду:
4.2. Импорт пакетов и классов
Если нам надо использовать классы из других пакетов, то нам надо подключить эти пакеты и классы. Исключение составляют классы из пакета java.lang (например, String ), которые подключаются в программу автоматически.
Например, класс Scanner находится в пакете java.util , поэтому мы можем получить к нему доступ следующим способом:
То есть указываем полный путь к файлу в пакете при создании его объекта. Однако такое нагромождение имен пакетов не всегда удобно, и в качестве альтернативы можем импортировать пакеты и классы в проект с помощью директивы import , которая указывается после директивы package :
Директива import указывается в самом начале кода, после чего идет имя подключаемого класса (в данном случае класса Scanner ).
В примере выше мы подключили только один класс, однако пакет java.util содержит еще множество классов. И чтобы не подключать по отдельности каждый класс, мы можем сразу подключить весь пакет:
Теперь мы можем использовать любой класс из пакета java.util .
Возможна ситуация, когда мы используем два класса с одним и тем же названием из двух разных пакетов, например, класс Date имеется и в пакете java.util , и в пакете java.sql . И если нам надо одновременно использовать два этих класса, то необходимо указывать полный путь к этим классам в пакете:
4.3. Статический импорт
В java есть также особая форма импорта — статический импорт. Для этого вместе с директивой import используется модификатор static :
Здесь происходит статический импорт классов System и Math . Эти классы имеют статические методы. Благодаря операции статического импорта мы можем использовать эти методы без названия класса. Например, писать не Math.sqrt(20) , а sqrt(20) , так как функция sqrt() , которая возвращает квадратный корень числа, является статической.
То же самое в отношении класса System : в нем определен статический объект out , поэтому мы можем его использовать без указания класса.
5. Модификаторы доступа
Все члены класса в языке Java — поля и методы — имеют модификаторы доступа. Модификаторы доступа позволяют задать допустимую область видимости для членов класса, то есть контекст, в котором можно употреблять данную переменную или метод.
В Java используются следующие модификаторы доступа:
public — публичный, общедоступный класс или член класса. Поля и методы, объявленные с модификатором public , видны другим классам из текущего пакета и из внешних пакетов.
private — закрытый класс или член класса, противоположность модификатору public . Закрытый класс или член класса доступен только из кода в том же классе.
protected — такой класс или член класса доступен из любого места в текущем классе или пакете или в производных классах, даже если они находятся в других пакетах
default (package): отсутствие модификатора у поля или метода класса предполагает применение к нему модификатора по умолчанию. Такие поля или методы видны всем классам в текущем пакете.
Рассмотрим модификаторы доступа на примере следующей программы:
В данном случае оба класса расположены в одном пакете — пакете по умолчанию, поэтому в классе Program мы можем использовать все методы и переменные класса Person , которые имеют модификатор по умолчанию, public и protected . А поля и методы с модификатором private в классе Program не будут доступны.
Если бы класс Program располагался бы в другом пакете, то ему были бы доступны только поля и методы с модификатором public .
Модификатор доступа должен предшествовать остальной части определения переменной или метода.
6. Инкапсуляция
Казалось бы, почему бы не объявить все переменные и методы с модификатором public , чтобы они были доступны в любой точке программы вне зависимости от пакета или класса?
Возьмем, например, поле age , которое представляет возраст. Если другой класс имеет прямой доступ к этому полю, то есть вероятность, что в процессе работы программы ему будет передано некорректное значение, например, отрицательное число. Подобное изменение данных не является желательным. Либо же мы хотим, чтобы некоторые данные были доступны напрямую, чтобы их можно было вывести на консоль или просто узнать их значение.
В связи с этим рекомендуется как можно больше ограничивать доступ к данным, чтобы защитить их от нежелательного доступа извне (как для получения значения, так и для его изменения). Использование различных модификаторов гарантирует, что данные не будут искажены или изменены не надлежащим образом. Подобное сокрытие данных внутри некоторой области видимости называется инкапсуляцией.
Так, как правило, вместо непосредственного использования полей, как правило, используют методы доступа. Например:
И затем вместо непосредственной работы с полями name и age в классе Person мы будем работать с методами, которые устанавливает и возвращают значения этих полей. Методы setName() , setAge() и setAdult() , которые меняют состояние объекта, иногда называют модифицирующими (mutator). А методы getName() , getAge() и isAdult() , с помощью которых получаем доступ к состоянию класса, называют методы доступа (accessor).
Причем в эти методы мы можем вложить дополнительную логику. Например, в данном случае при изменении возраста производится проверка, насколько соответствует новое значение допустимому диапазону.
7. Наследование
Одним из ключевых аспектов объектно-ориентированного программирования является наследование (inheritance). С помощью наследования можно расширить функционал уже имеющихся классов за счет добавления нового функционала или изменения старого. Например, имеется следующий класс Person , описывающий отдельного человека:
И, возможно, впоследствии мы захотим добавить еще один класс, который описывает сотрудника предприятия — класс Employee . Так как этот класс реализует тот же функционал, что и класс Person , так как сотрудник — это также и человек, то было бы рационально сделать класс Employee производным (subclass, наследником, подклассом) от класса Person , который, в свою очередь, называется базовым классом (superclass, родителем, суперклассом):
Чтобы объявить один класс наследником от другого, надо использовать после имени класса-наследника ключевое слово extends , после которого идет имя базового класса. Для класса Employee базовым является Person , и поэтому класс Employee наследует все те же поля и методы, которые есть в классе Person .
Производный класс имеет доступ ко всем методам и полям базового класса (даже если базовый класс находится в другом пакете) кроме тех, которые определены с модификатором private . При этом производный класс также может добавлять свои поля и методы:
В данном случае класс Employee добавляет поле company , которое хранит место работы сотрудника, а также метод work() .
Если в базовом классе определены конструкторы, то в конструкторе производного класса необходимо вызвать один из конструкторов базового класса с помощью ключевого слова super . Например, класс Person имеет конструктор, который принимает один параметр. Поэтому в классе Employee в конструкторе нужно вызвать конструктор класса Person . После слова super в скобках идет перечисление передаваемых аргументов. Таким образом, установка имени сотрудника делегируется конструктору базового класса.
При этом вызов конструктора базового класса должен идти в самом начале в конструкторе производного класса.
7.1. Переопределение методов
Производный класс может определять свои методы, а может переопределять методы, которые унаследованы от базового класса. Например, переопределим в классе Employee метод display() :
Перед переопределяемым методом указывается аннотация @Override . Данная аннотация в принципе необязательна.
При переопределении метода он должен иметь уровень доступа не меньше, чем уровень доступа в базовом класса. Например, если в базовом классе метод имеет модификатор public , то и в производном классе метод должен иметь модификатор public .
Однако в данном случае мы видим, что часть метода display в Employee повторяет действия из метода display() базового класса. Поэтому мы можем сократить класс Employee :
С помощью ключевого слова super мы также можем обратиться к реализации методов базового класса.
7.2. Запрет наследования
Хотя наследование очень интересный и эффективный механизм, но в некоторых ситуациях его применение может быть нежелательным. И в этом случае можно запретить наследование с помощью ключевого слова final . Например:
Если бы класс Person был бы определен таким образом, то следующий код был бы ошибочным и не сработал, так как мы тем самым запретили наследование:
Кроме запрета наследования можно также запретить переопределение отдельных методов. Например, в примере выше переопределен метод displayInfo() , запретим его переопределение:
В этом случае класс Employee не сможет переопределить метод display() .
7.3. Динамическая диспетчеризация методов
Наследование и возможность переопределения методов открывают нам большие возможности. Прежде всего мы можем передать переменной суперкласса ссылку на объект подкласса:
Так как Employee наследуется от Person , то объект Employee является в то же время и объектом Person . Грубо говоря, любой работник предприятия одновременно является человеком.
Однако несмотря на то, что переменная представляет объект Person , виртуальная машина видит, что в реальности она указывает на объект Employee . Поэтому при вызове методов у этого объектов будет вызывать та версия методов, которая определена в классе Employee , а не в Person . Например:
При вызове переопределенного метода виртуального машина динамически находит и вызывает именно ту версию метода, которая определена в подклассе. Данный процесс еще называется динамическая диспетчеризация методов (dynamic method lookup, динамический поиск метода).
8. Иерархия наследования и преобразование типов
Преобразование объектов классов происходит по-другому, чем с примитивными типами. Допустим, у нас есть следующая иерархия классов:
В этой иерархии классов можно проследить следующую цепь наследования: Object → Person → Employee | Client .

8.1. Преобразование типов в языке Java
Суперклассы обычно размещаются выше подклассов, поэтому на вершине наследования находится класс Object , а в самом низу Employee и Client .
Объект подкласса также представляет объект суперкласса. Поэтому в программе мы можем написать следующим образом:
Это так называемое восходящее преобразование (upcasting) от подкласса внизу к суперклассу вверху иерархии. Такое преобразование осуществляется автоматически.
Обратное не всегда верно. Например, объект Person не всегда является объектом Employee или Client . Поэтому нисходящее преобразование (downcasting) от суперкласса к подклассу автоматически не выполняется. В этом случае нам надо использовать операцию преобразования типов.
В данном случае переменная sam приводится к типу Employee . И затем через объект emp мы можем обратиться к функционалу объекта Employee .
Мы можем преобразовать объект Employee по всей прямой линии наследования от Object к Employee .