bannerbanner
Объектно-ориентированное программирование на Java. Платформа Java SE
Объектно-ориентированное программирование на Java. Платформа Java SE

Полная версия

Объектно-ориентированное программирование на Java. Платформа Java SE

Язык: Русский
Год издания: 2019
Добавлена:
Настройки чтения
Размер шрифта
Высота строк
Поля
На страницу:
8 из 9

И это отлично подходит для объектов.

Но как насчет примитивов?

К сожалению, вы не можете просто создать ArrayList из, например, int.

Поэтому вам нужно использовать так называемый класс-оболочку, который является простым классом, хранящим только int внутри него.

Это класс Integer.

То же самое существует для double и char.

ArrayList поставляется с огромным набором методов, чтобы сделать жизнь проще.




Вы не только можете добавить элемент в самом конце, но вы также можете добавить элемент по определенному индексу.

Вы можете очистить массив, вы можете выполнить поиск по массиву.

Например, вы ищете конкретное слово, но вы не знаете, в каком индексе оно находится.

Это метод indexOf.

Вы также можете удалить и установить определенный индекс.

По сути, ArrayList это массив внутри класса, который имеет большой размер 2^32—1, так что вы не сможете использовать всю длину массива.



ArrayList имеет переменную размера, которую он всегда поддерживает.

Вы добавляете элемент в массив и удаляете, при этом изменяется переменная размера.

Абстракция


Абстракция в объектно-ориентированном программировании помогает скрыть сложные детали объекта.

Абстракция является одним из ключевых принципов OOAD (объектно-ориентированный анализ и дизайн).

И абстракция достигается композицией (разделением) и агрегацией (объединением).

Например, автомобиль оснащен двигателем, колесами и многими другими деталями.



И мы можем записать все свойства автомобиля, двигателя и колеса в одном классе.

В этом примере атрибуты колеса и двигателя добавляются к типу автомобиля.

При программировании это не вызовет каких-либо проблем.

Но когда дело доходит до поддержки приложения, это становится более сложным.

Теперь, используя абстракцию, мы можем отделить вещи, которые можно сгруппировать в другом типе.

Часто изменяющиеся свойства и методы можно сгруппировать в отдельный тип, чтобы основной тип не нуждался в изменении.

Это добавляет силы принципу OOAD – «Код должен быть открыт для расширения, но закрыт для модификации».

И это упрощает представление модели.

Применяя абстракцию с композицией (разделением) и агрегацией (объединением), приведенный пример может быть изменен следующим образом.

Вы можете видеть, что атрибуты и методы, связанные с двигателем и колесом, перемещаются в соответствующие классы.



Двигатель и колесо относятся к типу автомобиля.

Когда создается экземпляр автомобиля, оба – двигатель и колесо будут доступны для автомобиля, а когда будут изменения этих типов (двигателя и колеса), изменения будут ограничиваться только этими классами и не будут влиять на класс Car.

Абстракция известна как отношение Has-A, например, у студента есть имя, у ученика есть ручка, у машины есть двигатель, то есть у каждого есть отношение Has-A.

И используя это отношение, мы разделяем на части, а затем одна часть может использовать другие части в виде объектов.

Абстракция является одним из основополагающих принципов языков объектно-ориентированного программирования.

И абстракция помогает снизить сложность, а также улучшает поддерживаемость системы.

В сочетании с концепциями инкапсуляции и полиморфизма абстракция дает больше возможностей объектно-ориентированным языкам программирования.

Абстракция – это принцип обобщения.

Абстракция принимает множество конкретных экземпляров объектов и извлекает их общую информацию и функции для создания единой обобщенной концепции, которая может использоваться для описания всех конкретных экземпляров как одно.

При этом мы переходим от конкретного экземпляра к более обобщенному понятию, думая о самой базовой информации и функции объекта.

Тем самым абстракция помогает скрыть сложные детали объекта.

Например, когда вы используете электронную почту, сложные детали того, что происходит, когда вы отправляете электронное письмо, например, протокол, используемый вашим почтовым сервером, скрыт от пользователя.

Поэтому, чтобы отправить электронное письмо, вам просто нужно ввести текст, указать адрес получателя и нажать «Отправить».

Аналогично в объектно-ориентированном программировании абстракция – это процесс скрытия деталей реализации от пользователя, и пользователю предоставляется только функциональность.

Другими словами, пользователь будет иметь информацию о том, что делает объект, а не о том, как он это делает.

И в Java это свойство абстракции реализуется с использованием абстрактных классов и интерфейсов, которые мы рассмотрим на следующей лекции.

Интерфейсы. Абстрактные методы и классы


Ранее мы определяли метод в одном классе и переопределяли этот метод в производных классах.

Таким способом можно делать разные вещи в зависимости от класса.

Здесь мы видим разные строки, возвращаемые методом toString.



Так как класс Vehicle является общим для этих классов, нам может вообще не понадобится строка, которая возвращается его методом toString.

В этом случае мы можем вообще не определять тело для метода toString в классе Vehicle.

Мы можем это сделать, и метод без тела называется «абстрактным».



Абстрактные методы обозначаются с помощью ключевого слова «абстрактный» в определении.

И класс с хотя бы одним абстрактным методом называется «абстрактным классом».

Здесь мы видим ключевое слово абстрактный в определении абстрактного класса.

Таким образом, абстрактный метод – это метод без тела.

Конструкторы, статические методы и финальные методы не могут быть абстрактными.

Абстрактный класс – это класс, в котором некоторые методы абстрактные, а некоторые – нет.

Абстрактный класс – это незавершенный класс, и мы не можем создать его объекты.

Чтобы получить объект, мы сначала должны определить производный класс, где тела определены для всех абстрактных методов.

Абстрактный класс может быть расширен до класса, или до другого абстрактного класса.

Кроме того, вы можете определить класс как абстрактный даже без абстрактного метода.

Это допускается, и вы можете это сделать для предотвращения возможности создания экземпляра класса.

Однако, если есть абстрактный метод, вы получите ошибку, если вы не добавите ключевое слово «abstract» в определение класса.

Таким образом, абстрактные методы помогают разделить определение метода от поведения метода.

А абстрактные классы – это незавершенные классы, которые содержат абстрактные методы.

В абстрактном классе могут быть и абстрактные методы, а также обычные методы.

Теперь вопрос в том, что, если мы сделаем все методы абстрактными.



Но класс со всеми абстрактными методами уже не является абстрактным классом.

Это нечто другое.

Если все методы абстрактны, мы называем это интерфейсом.

Обратите внимание, что во всех методах нет тел.



Здесь мы не пишем ключевое слово abstract для методов, но здесь нет путаницы, поскольку все методы в интерфейсе абстрактные.

Кроме того, мы объявляем методы публичными, но нам не нужно писать это явно.



Таким образом, все методы автоматически объявляются публичными, даже если мы не укажем ключевое слово public.

На самом деле не совсем верно, что все методы в интерфейсе должны быть абстрактными.

В Java 8 могут быть методы с телом, но они должны быть статическими методами или методами по умолчанию.

Но мы не будем усложнять, чтобы подчеркнуть концепцию интерфейса в его самой чистой форме.

Итак, для вас, в интерфейсе, все методы абстрактны.

Но что насчет полей?

В интерфейсе могут быть поля.

Но все они автоматически статические и финальные.

То есть, они являются константами.

Они также автоматически публичные, поэтому ключевое слово public не требуется явно указывать.

Таким образом, концепция интерфейса – это полезная абстракция.

Тогда как абстрактный класс реализует абстракцию, показывая некоторую общую функциональность для семейства классов без ее конкретной реализации, интерфейс, например, как физический интерфейс в радио или музыкальном проигрывателе, демонстрирует сервис снаружи и скрывает реализацию, которую мы можем определить.

То есть, в случае интерфейса, абстракция скрывает реализацию объекта от пользователя и предоставляет только интерфейс.

На самом деле мы можем изменить эту реализацию без изменения интерфейса, и, следовательно, предоставляемых нами услуг.

Интерфейсы обеспечивают уровень абстракции.

Можно использовать предоставленные методы без знания того, как они реализованы.

Но в какой-то момент эти методы должны быть реализованы.

Представьте, что у нас есть интерфейс под названием VehicleIF.



И в этом интерфейсе указан ряд методов.

Помните, что они публичные.



Мы могли бы реализовать класс для этого интерфейса и назвать его Vehicle.

Обратите внимание, что теперь используется ключевое слово implements вместо ключевого слова extends.

Для реализации интерфейса подразумевается определение класса, где для всех методов, дается реализация.

Мы можем также дополнительно определить поля и конструкторы, а также другие методы, возможно приватные.

Класс следует спецификации, заданной интерфейсом, и добавляет конкретные детали о том, как эти методы могут работать.

Однако нам не нужно идти от спецификации интерфейса к реализации класса за один шаг.

Мы могли бы также действовать поэтапно.



Путь от интерфейса к классу, который может быть создан, может быть короче или длиннее.

Во-первых, мы можем расширить один интерфейс от другого интерфейса, например, путем добавления абстрактных методов.

Мы можем частично реализовать интерфейс, путем реализации некоторых методов.

В этом случае мы получаем в результате не класс, а абстрактный класс, потому что не все методы реализованы.

И, наконец, мы можем перейти непосредственно от интерфейса к классу, путем реализации всех методов.

Если у нас есть абстрактный класс, мы можем расширить его другим абстрактным классом, например, если мы реализуем некоторые абстрактные методы, но не все из них.

Интерфейсы помогают нам моделировать системы, которые позволяют нам повторно использовать не только просто код, но и целиком концепции.

Теперь важно то, что класс может реализовать не только один, но и несколько интерфейсов.

В этом случае класс должен реализовать все методы от всех интерфейсов.

Помните, что класс не может расширять несколько классов.

В Java нет множественного наследования, как в других языках программирования, таких как C ++.

Класс не может расширять два класса.

Однако в Java класс может реализовать два интерфейса.

Это способ сказать, что A является B и C.



Например, мы можем определить амфибию как реализацию интерфейса автомобиля и интерфейса лодки.

Этот амфибия-автомобиль будет реализовывать методы обоих интерфейсов.

Теперь, при этом, могут возникнуть конфликты имен.

Что произойдет, если у нас есть одно и тоже имя метода в обоих интерфейсах?

Если у нас есть одинаковое имя метода в обоих интерфейсах, но разные возвращаемые типы, тогда возникает ошибка.

Если имя метода и тип возвращаемого значения совпадают, тогда все в порядке.

Если, кроме того, совпадают и параметры методов, тогда класс должен реализовать этот метод один раз.

Они неразличимы.

Если типы параметров методов не совпадают, мы имеем случай перегрузки, который обрабатывается таким же образом, как будто эти методы принадлежат одному интерфейсу.

Оба эти методы должны быть реализованы.

Мы можем также определить класс путем реализации интерфейса и наследования от класса одновременно.

Также нужно сказать, что помимо абстрактных классов и интерфейсов для структурирования кода используются классы-утилиты, в которых определены только статические члены.

Как правило, такие классы используются для объединения родственных алгоритмов.

Примером такого класса может служить класс Math, предоставляющий реализации различных математических функций.

Таким образом, отношения реализации и наследования помогают нам определить структуры связанных интерфейсов и классов, которые помогают нам масштабировать и упорядочить код.

Проектирование интерфейсов всегда было непростой задачей, потому что, если мы хотим добавить дополнительные методы в интерфейсы, это потребует изменений во всех классах реализации.

По мере старения интерфейса количество реализующих его классов может увеличиться настолько, что будет невозможно расширить интерфейс, изменяя все классы реализации.

Вот почему большинство библиотек сначала обеспечивают базовый класс реализации, а затем они расширяют его и переопределяют его методы, чтобы при изменении интерфейса, изменить только базовый класс реализации.

В Java 8 вводится понятие метода по умолчанию интерфейса.



Для создания метода по умолчанию в интерфейсе нам нужно использовать ключевое слово «default» в сигнатуре метода.

Теперь, когда класс реализует интерфейс, необязательно предоставлять реализацию для методов по умолчанию интерфейса.

Эта функция помогает, помимо подхода с базовым классом реализации, с расширением интерфейсов с помощью дополнительных методов, и все, что нам здесь нужно, это обеспечить реализацию по умолчанию.

Мы знаем, что Java не позволяет нам расширять несколько классов, потому что это приведет к проблеме, когда компилятор не может решить, какой метод суперкласса использовать.

При использовании методов по умолчанию эта же проблема возникает и для интерфейсов.

Так как, если класс реализует интерфейс 1 и интерфейс 2 и не реализует общий метод по умолчанию, компилятор не может решить, какой из них выбрать.

Поэтому, обязательным является обеспечение реализации общих методов интерфейсов по умолчанию.

И поэтому, если класс реализует оба вышеупомянутых интерфейса, он должен будет обеспечить реализацию для метода log, иначе компилятор будет выбрасывать ошибку времени компиляции.

Таким образом, методы по умолчанию интерфейса Java помогают расширить интерфейсы, не опасаясь сломать классы реализации.

И методы по умолчанию интерфейса стирают различия между интерфейсами и абстрактными классами.

Если какой-либо класс в иерархии имеет метод с той же сигнатурой, метод по умолчанию теряет смысл для внедрения, чтобы избежать путаницы.

Теперь, статический метод интерфейса похож на метод по умолчанию, за исключением того, что мы не можем переопределить его в классах реализации.



Эта функция помогает избежать нежелательных результатов, связанных с плохой реализацией в классах реализации.

Статический метод интерфейса виден только для методов интерфейса, однако, как и другие статические методы, мы можем использовать статические методы интерфейса, используя его имя.

Статические методы интерфейса хороши для предоставления вспомогательных методов, не требующих реализации в классах.

И можно использовать статические методы интерфейса Java для удаления классов-утилит, и переместить все статические методы классов-утилит в соответствующий интерфейс, который будет легко найти и использовать.

Однако мы не можем определить статический метод интерфейса для методов класса Object, мы получим ошибку компилятора.

Это связано с тем, что Object является базовым классом для всех классов.

В Java 9 вводятся приватные методы и приватные статические методы в интерфейсах.



Чтобы был выбор, какие выставлять клиентам методы реализации.

Соответственно приватные методы интерфейса предназначены для использования в методах по умолчанию интерфейса, а приватные статические методы интерфейса предназначены для использования в статических методах интерфейса.

И приватные методы должны иметь реализацию, они не могут быть абстрактными.

И мы не можем получить доступ или наследовать приватные методы от интерфейса к другому интерфейсу или классу.

Пакеты


Давайте рассмотрим концепцию пакета, которая позволяет программистам лучше структурировать свои программы, что облегчает их понимание и управление.

В большом приложении классов создается тысячи и десятки тысяч.

Поэтому возникает вопрос: Если классов много, их все в одном каталоге держать? И как потом с ними разбираться?

Конечно же необходим некий механизм упорядочивания.

И такой механизм создан.

Причем достаточно простой и очевидный – каталоги.

Мы уже привыкли, что на диске наши файлы лежат в разных каталогах, которые мы сами организовываем в удобном порядке.

В Java сделано тоже самое – физически класс кладется в определенный каталог файловой системы, представляющий собой пакет.

Существует даже некоторые правила именования этих каталогов или пакетов.



Например, для коммерческих проектов каталог должен начинаться сначала с префикса «com» а за ним следует название компании или доменное имя компании – например «mycompany».

Далее следует название проекта.

Потом уже идет более точное разделение по какому-либо признаку – чаще всего функциональному.

Пакет в Java представляет собой группу связанных классов и интерфейсов, которые имеют схожие свойства.

Это абстрактная концепция, и это ответственность программиста, чтобы правильно организовать различные классы в пакеты.

Обычно классы, сгруппированные в один и тот же пакет, имеют сходную семантику.

Например, представьте, что у вас есть класс Car.



И он может быть частью пакета с именем transport, который также может содержать другие классы, такие как самолет или поезд.

Пакеты можно подразделить на подпакеты в зависимости от степени абстракции, которую хочет программист, формирующий иерархию пакетов.

Таким образом, наш предыдущий пакет транспорт мог быть разделен на подпакеты наземного транспорта и воздушного транспорта для представления физической среды транспорта.



Подумайте о том, какие классы будут храниться в каждом подпакете.

Когда вы создаете новую программу, очень полезно организовать различные классы и интерфейсы в пакеты, чтобы упростить ваш проект.

Таким образом, вы упрощаете использование классов и интерфейсов.

Далее, мы рассмотрим стандартную библиотеку Java, которая хорошо структурирована на пакеты и подпакеты.

Хорошо, но когда мы пишем новый класс, как мы можем определить, какой класс принадлежит к какому пакету?

Это очень просто.

В верхней части исходного кода класса вы добавляете слово package, за которым следует имя пакета.

Помните, что определение пакета должно быть первым выражением в исходном файле.

Имя пакета определяется программистом.

Это имя должно быть в нижнем регистре, чтобы избежать конфликтов с именами классов и интерфейсов, и не может быть одним из слов, зарезервированных Java, таким как main, for или string.

И подпакеты задаются с использованием символа точки.

Таким образом, для создания пакета, нам нужно в файловой системе создать каталоги и подкаталоги, например, каталог transport и подкаталог air.



Затем поместить в них файлы классов и интерфейсов.

И затем указать в каждом классе и интерфейсе вверху директиву package с именами каталогов и подкаталогов, разделенными точками, то есть путем где находится файл класса или интерфейса.

При написании новых программ вам может потребоваться доступ к некоторым классам и интерфейсам из определенного пакета.

В этом случае вы должны заранее импортировать такие классы.

Этот импорт должен быть размещен сверху исходного кода класса, сразу после объявления пакета класса, используя слово импорт.



За оператором импорта должен следовать весь путь пакета, вместе с классом, который вы хотите импортировать.

И это будет полное квалифицированное имя класса – имя класса вместе с именем его пакета.

Если вы хотите импортировать все классы в пакете, вы можете использовать символ звездочки.

Таким образом, вы сможете получить доступ ко всем публичным полям и методам этих классов.

Полное имя класса – весьма важный момент.

Разделение классов по пакетам служит не только для удобства, но решает еще одну важную задачу – уникальность имен классов.

Наверняка в большом проекте будет участвовать много людей и каждый будет писать свои классы.

И наверняка имена этих классов нередко будут одинаковые.

И скорее всего вы будете подключать сторонние библиотеки и в них будут классы, которые будут называться так же как ваши.

Единственным спасением различать их – это поместить в разные пакеты.

Таким образом, как правило, программа состоит из нескольких пакетов.

И каждый пакет имеет собственное пространство имен для классов и интерфейсов, объявленных в пакете.

И пакеты образуют иерархическую структуру имен.

При этом полные имена классов и интерфейсов, то есть их имена с учетом пакетов, должны быть уникальными.

И для доступа из одного пакета к другим пакетам используется ключевое слово import.

Также пакеты могут быть безымянными.

Классы и интерфейсы безымянного пакета не содержат объявления пакета.

И безымянные пакеты следует использовать только в небольших тестовых программах.

Также в Java можно использовать статический импорт для доступа к статическим методам и полям класса.



Например, в этом выражении, методы pow и sqrt являются статическими, поэтому они должны быть вызваны с указанием имени их класса – Маth.

И это приводит к достаточно громоздкому коду.

Этих неудобств можно избежать, если воспользоваться статическим импортом.

При этом имена методов sqrt и pow становятся видимыми благодаря оператору статического импорта.

На страницу:
8 из 9