поле в интерфейсе java

Интерфейсы

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

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

Чтобы создать интерфейс, используйте ключевое слово interface вместо class. Как и в случае с классами, вы можете добавить перед словом interface спецификатор доступа public (но только если интерфейс определен в файле, имеющем то же имя) или оставить для него дружественный доступ, если он будет использоваться только в пределах своего пакета. Интерфейс может содержать поля, но они автоматически являются статическими (static) и неизменными (final). Все методы и переменные неявно объявляются как public.

Класс, который собирается использовать определённый интерфейс, использует ключевое слово implements. Оно указывает, что интерфейс лишь определяет форму, а вам нужно наполнить кодом. Методы, которые реализуют интерфейс, должны быть объявлены как public.

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

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

Если класс содержит интерфейс, но не полностью реализует определённые им методы, он должен быть объявлен как abstract.

Интерфейсы — это не классы. С помощью ключевого слова new нельзя создать экземпляр интерфейса:

Но можно объявлять интерфейсные переменные:

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

Рассмотрим быстрый пример создания интерфейса. Выберите в меню File | New | Interface и придумайте имя для нового интерфейса. В полученной заготовке добавьте два имени метода (только имена, без кода).

Создайте или откройте какой-нибудь класс, к которому нужно применить интерфейс, и добавьте к нему implements SimpleInterface. Среда разработки подчеркнёт красной линией имя класса и предложит добавить методы, которые требуются интерфейсом. Соглашаемся и получаем результат:

Среда разработки сгенерировала два метода и использовала в качестве возвращаемых результатов значения по умолчанию. Это могут быть и нулевые значения и null. Осталось подправить шаблоны созданных методов под свои задачи. Например, так:

Здесь важно понять роль интерфейса. Мы лишь придумываем имена, а класс уже реализует нужную задачу. Для примера можно создать в интерфейсе метод play() для класса Пианино и класса Гитара, так как играть можно на обеих инструментах. Но код в методах будет отличаться, так как принцип игры на инструментах совершенно разный.

Константы в интерфейсах

Интерфейсы можно использовать для импорта констант в несколько классов. Вы просто объявляете интерфейс, содержащий переменные с нужными значениями. При реализации интерфейса в классе имена переменных будут помещены в область констант. Поля для констант становятся открытыми и являются статическими и конечными (модификаторы public static final). При этом, если интерфейс не будет содержать никаких методов, то класс не будет ничего реализовывать. Хотя данный подход не рекомендуют использовать.

Расширение интерфейсов

Интерфейс может наследоваться от другого интерфейса через ключевое слово extends.

Методы обратного вызова

Интерфейсы часто используются для создания методов обратного вызова (callback). Рассмотрим такой пример. Создадим новый класс SubClass с интерфейсом MyCallback:

У интерфейса мы определили один метод callBackReturn(). Далее в классе мы создали объект интерфейса и инициализировали его в конструкторе класса. В классе также был создан метод doSomething(), в котором может содержаться какой-то сложный код. В конце метода вызывается метод интерфейса. В данном случае мы сами создали метод и знаем его код. Но во многих случаях, вы будете использовать готовый метод какого-то класса и вы не будете знать, что именно содержится в этом методе. Вам надо только знать, что такой метод существует, например, из документации и он выполняет конкретную задачу.

Переходим в код активности и подключаем интерфейс через ключевое слово implements:

Среда разработки поможет вставить шаблон метода интерфейса.

Теперь мы можем использовать метод обратного вызова callBackReturn() для решения своих задач. Допустим у нас есть текстовая метка и кнопка. При щелчке выполняется какой-то сложный код из класса SubClass. Когда он закончит работу, то сработает метод обратного вызова callBackReturn(), в котором пропишем нужные действия.

Слушатели

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

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

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

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

Источник

Поля в интерфейсах

У меня есть основной вопрос на Java, но это общий вопрос в ООП. Почему интерфейсы позволяют устанавливать поля? Разве это не противоречит тому, что должен делать интерфейс?

Как я понял, интерфейс-это то, что в английском языке было бы прилагательным. Итак, если мой класс реализует интерфейсы Runnable и Serializable, я гарантирую пользователю, что мой класс удовлетворит условиям Runnable и Seriablizable. Однако это означает, что интерфейсы «без гражданства», но им разрешено иметь поля в Java.

Я что-то пропустила?

4 ответов

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

прежде всего, есть разница между парадигмы ООП и реализация ООП в Java, так же слова могут означать немного разные вещи.

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

а насчет константы ( public final static поля)? Являются ли они частью реализации или интерфейса. Это могут быть как. Е. Г. интерфейс «программист» может иметь постоянный WORK_HOURS значение «8». Таким образом, Java позволяет описывать константы и в интерфейсах.

обратите внимание, что Java только помогает вы, чтобы сделать хороший дизайн ООП, но это не сильно требует этого. В частности, не все публичные методы объекта должны существовать в интерфейсе. Например, методы getter и setter обычно являются общедоступными, но на самом деле они являются части реализации, не интерфейс, и поэтому не стоит вводить их в интерфейс.

(обратите внимание, что большинство вещей, которые я описал здесь, относятся к mainstream ООП, как в Java, но есть и другие виды ООП, такие как прототип на основе одного, в частности, реализован в JavaScript).

что, если этот интерфейс относится к константам? Разве не было бы естественно объявить их в интерфейсе?

Да, у вас могут быть постоянные поля в интерфейсах, но вы правы, когда говорите, что «это противоречит тому, что должен делать интерфейс», поскольку это не очень хорошая практика. Почему вы хотите, чтобы все ваши классы реализовывали интерфейс с одинаковыми константами? Вы можете просто иметь их в классе, который их использует, или, если вам действительно нужно их как-то экспортировать, иметь их в отдельном классе utiliy, как это:

у вас также есть перечисления, если вам нужно представляют собой набор постоянных полей с определенным значением. Я не вижу никакого «варианта использования», где вам действительно нужны константы в интерфейсе. Но может быть неправильно:)

Источник

Java 8 интерфейсы

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

Интерфейсы в Java являются ссылочными типами, как классы, но они могут содержать в себе только константы, сигнатуры методов, default методы (методы по умолчанию), static методы (статические методы) и вложенные типы. Тела методов могут быть только у статических методов и методов по умолчанию.

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

Пример интерфейса, описывающий общие методы для всех монстров:

Ключевое слово public означает, что интерфейс будет доступен из всех пакетов. Можно не указывать модификатор доступа, и тогда интерфейс будет package-private.

Ключевое слово abstract для метода означает, что у метода нет реализации, а ключевое слово abstract у всего интерфейса означает, что все методы экземпляров не имеют реализации (кроме статических методов и методов по умолчанию). Для классов abstract имеет примерно такое же действие, оно будет объяснено в статье про наследование.

Чтобы использовать интерфейс нужно объявить класс, реализующий этот интерфейс, с помощью ключевого слова implements :

Источник

История эволюции интерфейсов в Java

поле в интерфейсе java. Смотреть фото поле в интерфейсе java. Смотреть картинку поле в интерфейсе java. Картинка про поле в интерфейсе java. Фото поле в интерфейсе java

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

Оригинальные интерфейсы

Интерфейсы в Java 1.0 были достаточно простыми по сравнению с тем, какие они сейчас. Они могли содержать лишь два типа элементов: константы и публичные абстрактные методы.

Поля-константы

Интерфейсы могут содержать поля, так же как и обычные классы, но с несколькими отличиями:

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

Абстрактные методы

Наиболее важными элементами интерфейса являются его методы. Методы интерфейса также отличаются от методов обычного класса:

Вложенность

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

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

Перечисления и аннотации

В Java 5 были введены ещё два типа: Перечисления и Аннотации. Они также могут быть помещены внутрь интерфейсов.

Обобщенные типы

Java 5 ввела концепцию «дженериков» — обобщенных типов. Вкратце: «дженерики» позволяют вам использовать обобщенный тип вместо указания конкретного типа. Таким образом, вы можете писать код, который работает с различным количеством типов, не жертвуя при этом безопасностью и не предоставляя отдельную реализацию для каждого типа.

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

Интерфейс Box работает независимо от того, используете ли вы его для хранения объектов типа String, Integer, List, Shoe или каких-либо других.

Статические методы

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

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

Наследование статических методов

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

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

Почему это полезно

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

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

Вы можете найти примеры использования данного подхода прямо в JDK: интерфейс java.util.Collection и сопутствующий ему утилитный класс java.util.Collections.

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

Методы по умолчанию

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

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

Хотя есть одно исключение. В интерфейсе не может быть методов по умолчанию с такой же сигнатурой, как у методов toString, equals и hashCode класса Object. Взгляните на ответ Брайана Гетца, чтобы понять обоснованность такого решения: Allow default methods to override Object’s methods.

Почему это полезно

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

У интерфейсов есть одна проблема. Как только вы предоставите свой API другим людям, он навсегда «окаменеет» (его нельзя будет изменить безболезненно).

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

Конфликты

Давайте представим, что у нас есть класс, реализующий два интерфейса. У этих интерфейсов есть метод по умолчанию с одинаковыми именем и сигнатурой.

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

Итак, как наш класс узнает, какую из двух различных реализаций использовать?

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

Приватные методы

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

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

К сожалению в Java 8 интерфейс не может содержать приватные методы. Это означает, что вы можете использовать:

Хронологический порядок

Ниже представлен хронологический перечень изменений по версиям Java:

Источник

Интерфейсы и внутренние классы

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

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

В Главе 7, Вы узнали о ключевом слове abstract, которое позволяет вам создавать один или несколько методов в классе, которые не имеют определений, Вы предоставляете только интерфейс, а его реализация будет осуществлена уже в наследниках. Ключевое слово interface создает полностью абстрактный класс, который не предоставляет никаких реализаций ни одного своего компонента. Вы далее узнаете, что interface есть нечто большее, чем просто абстрактный класс, доведенный до конца абстракции, поскольку он позволяет вам создавать вариации C++ множественного наследования, посредством создания класса, который может быть приведен к базовому типу больше раз, чем к один.

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

Интерфейсы

Ключевое слово interface осуществляет, на шаг дальше, концепцию, реализованную в abstract. Вы можете думать, что это просто чисто abstract класс. Он позволяет создателю заложить форму (структуру) класса: имена методов, списки аргументов, возвращаемые типы, но только не тела методов. Interface также может содержать поля, но все они будут, хотя и косвенно static и final. Interface предоставляет только форму, образ, но не предоставляет его реализацию.

Interface «говорит»: «Все классы, реализующие этот особый интерфейс будут выглядеть одинаково». Поэтому, любой код, использующий interface знает, какой из методов может быть вызван для этого interface, впрочем, это все. Так что interface используется в качестве установления «протокола» между классами. (Некоторые ООЯ имеют даже встроенное ключевое слово protocol, делающее то же самое действие.)

Что бы создать interface, используйте ключевое слово interface вместо ключевого слова class. Как и у класса, Вы можете добавить ключевое слово public до interface (но только если этот интерфейс определен в файле с тем же именем) или оставить его пустым, тогда он станет » friendly» и его можно будет использовать только членам одного с ним пакета.

Для создания класса согласованного с особенным interface (или группой interface-ов) используйте ключевое слово implements. Тем самым Вы объявляете «Interface это на что похож мой класс, а теперь я скажу, как он должен работать.» Все остальное, кроме этого, выглядит, как наследование. Диаграмма для примера с инструментами:

поле в интерфейсе java. Смотреть фото поле в интерфейсе java. Смотреть картинку поле в интерфейсе java. Картинка про поле в интерфейсе java. Фото поле в интерфейсе java

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

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

Это Вы можете увидеть в измененном примере Instrument. Заметьте, что каждый метод в interface строго определен, только так компилятор и позволяет делать. В дополнение, ни один из методов в Instrument не определен как public, но они автоматически public по любому:

Этот кусок кода работает точно так же. Не имеет значения, если Вы приводите к базовому типу, к обычному классу Instrument, abstract классу Instrument, или к интерфейсу Instrument. Поведение остается одно и то же. В частности, Вы можете видеть в методе tune( ), что в нем нет никаких доказательств того, что Instrument это обычный класс или abstract класс или же интерфейс. Это и есть цель: каждый подход (принцип) дает программисту различные варианты контроля над путем создания и использования объектов.

Множественное наследование в Java

Interface это не просто более чистая форма абстрактного класса. Он имее более «высокое» применение. Поскольку interface не имеет реализации всего, что есть в нтем, то нет и массива-хранилища связанного с ним, нет ничего мешающего для комбинации нескольких интерфейсов. И это ценно, поскольку иногда вам требуется нечто: «x есть a и b и c.» В C++, этот акт множественных интерфейсов называется множественное наследование, и при этом этот тип тянет за собой «прилипший» багаж, поскольку каждый тип имеет свою реализацию. В Java Вы можете осуществить то же самое, но только один из этих классов может иметь реализацию, так что проблемы, возникающие в C++, не возникают в Java, при комбинировании множества интерфейсов:

поле в интерфейсе java. Смотреть фото поле в интерфейсе java. Смотреть картинку поле в интерфейсе java. Картинка про поле в интерфейсе java. Фото поле в интерфейсе java

В этом примере, как Вы можете видеть, класс Hero комбинирует конкретный класс ActionCharacter с интерфейсами CanFight, CanSwim и CanFly. Когда Вы комбинируете именованный класс с интерфейсами, как в этом примере, этот класс должен быть указан первым, а только затем интерфейсы, в противном случае компилятор выдаст сообщение об ошибке.

Заметьте, что сигнатура fight( ) та же самая, в интерфейсе CanFight и в классе ActionCharacter, но обратите внимание, что fight( ) не определена в Hero. Правилом для интерфейса является то, что Вы можете наследовать от него, но тогда Вы получите еще один интерфейс. Если же Вы хотите создать объект нового типа, то это должен быть класс со всеми определениями. Даже в силу того, что Hero не предоставляет определения для fight( ), это определение появляется вместе с ActionCharacter. Поскольку оно предоставляется автоматически и есть возможность создать объект от Hero.

В классе Adventure, Вы можете видеть, что в нем есть несколько методов, которые воспринимают в качестве аргументов различные интерфейсы и конкретные классы. Когда объект Hero уже создан, то он может быть передан в качестве параметра в любой их этих методов, что в свою очередь означает, что он может быть приведен к базовому типу к любому из вышеописанных интерфейсов. Поскольку мы рассматриваем путь создания интерфейсов в Java, то на этой дороге программисту не встретится никаких особенных трудностей и не придется специально напрягаться.

Конфликты имен при комбинировании интерфейсов

Вы можете столкнуться с небольшой ловушкой при реализации множественных интерфейсов. В предыдущем примере оба CanFight и ActionCharacter имели идентичные методы void fight( ). Но это не вызвало проблемы поскольку эти методы одинаковы в обоих классах, но что было бы если бы они были бы разными? Вот пример:

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

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

Расширение интерфейса с наследованием

Синтаксис, использованный в Vampire, работает только с наследованием интерфейсов. Обычно, Вы можете использовать extends только с одиночным классом, но в силу того, что интерфейсы могут быть созданы из множества других интерфейсов, extends может ссылаться на множество базовых интерфейсов при создании нового интерфейса. Как Вы видите, имена интерфейсов отделены просто запятыми.

Группировка констант

Поскольку любое поле помещенное вами в интерфейс автоматически становится static и final, то интерфейс, по сути, удобная штука для создания групп констант, так же, как и enum в C или C++. К примеру:

Заметьте, что в стиле Java используются все буквы в верхнем регистре (с подчеркиваниями для разделения слов) для static final «констант».

Теперь Вы можете использовать эти константы снаружи пакета, просто импортируя c08.* или c08.Months, так же, как Вы импортируете другие пакеты и ссылаться на них примерно так Months.JANUARY. Естественно, то, что Вы получите это просто int, поскольку тут нету никакого дополнительного типа безопасности, какой имеет в C++ enum, но эта техника (наиболее часто используемая) может помочь в случае тяжелого программирования в вашей программе.

Если же Вы хотите получить дополнительные безопасные типы, то вам необходимо создать класс на подобии этого[38]:

Этот класс называется Month2, поскольку класс с именем Month уже есть в стандартной библиотеке Java. Этот класс final с конструктором private, поэтому никто не может от него наследовать или создать его представление. Представления final static создаются только однажды, в самом классе: JAN, FEB, MAR и т.д. Эти объекты так же используются в массиве month, что позволяет вам получать доступ к именам по их номеру. (Заметьте, что дополнительный месяц JAN в этом массиве осуществляет смещение на единицу, поэтому то декабрь и будет 12-м, а не 11-м.) В main( ) Вы можете видеть безопасный тип: m является объектом Month2, так что он может быть доступен только как Month2. Предыдущий пример Months.java обрабатывал только значения int, так что, месяц представлялся значением типа int, что само по себе не очень безопасно.

Приведенная техника позволяет вам так же использовать == или equals( ) взаимозаменяемо, как показано в конце метода main( ).

Инициализирование полей в интерфейсах

Поля определенные в интерфейсах автоматически становятся static и final. Они не могут быть пустыми (чистыми) final переменными, но они могут быть инициализированы не постоянными выражениями. К примеру:

Поскольку все поля static, то они инициализируются при первой загрузке класса, что происходит при первом доступе к любой переменной. Вот пример:

Эти поля, естественно, не являются частью интерфейса, вместо этого они размещены в static хранилище этого интерфейса.

Вложенные интерфейсы

[39] Интерфейсы могут быть вложены внутрь классов и других интерфейсов. Такая возможность выявляет несколько очень интересных возможностей:

Синтаксис внутреннего интерфейса внутри класса очевиден и похож на не внутренние интерфейсы, которые могут быть public или friendly. Вы так же видите, что оба внутренних интерфейса public и friendly могут быть реализованы как public, friendly и private внутренние классы.

С этой новой особенностью интерфейсы могут так же быть private, как видно из A.D (тот же самый синтаксис используется для внутренний интерфейсов и для внутренних классов). Что же хорошего в private внутреннем интерфейсе? Как Вы можете догадаться, он может быть реализован только как private внутренний класс, как, например в DImp, но в A.DImp2 видно, что он так же может быть реализован и как public класс. Но все равно, A.DImp2 может быть использован только как сам. Однако не следует пропустить упоминание о том, что реализация private интерфейса это всего лишь путь для принудительного определения методов в этом интерфейсе, без добавления любой информации о типе (это так и есть, без возможности любого приведения к базовому типу).

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

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

Внутренние классы

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

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

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

Внутренний класс, использованный внутри ship( ), выглядит так же, как и любой другой класс. И только одно различие бросается в глаза, это то, что его имя расположено после Parcel1. Но в дальнейшем Вы увидите, что это далеко не единственное различие.

Если Вы хотите создать объект внутреннего класса, где либо еще, кроме не статического метода внешнего класса, то Вы должны определить тип этого объекта, как OuterClassName.InnerClassName, как, например, в main( ).

Внутренний класс и приведение к базовому типу

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

Сперва общие интерфейсы должны быть определены в их собственных файлах, тогда они могут быть использованы во всех примерах:

Когда Вы получите назад ссылку на базовый класс или на интерфейс, то возможно, что Вы уже не сможете когда либо найти настоящий тип этого объекта, как показано ниже:

Заметьте, поскольку main( ) в Test, то когда Вы захотите запустить эту программу, Вы не выполните Parcel3, а вместо этого:

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

Нормальный (не внутренний) класс не может быть сделан private или protected, только как public или friendly.

Внутренние классы в методе и контексте

В следующих примерах, предыдущий код будет изменен для получения следующих «результатов»:

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

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

В первом примере показывается создание целого класса внутри контекста метода (вместо контекста другого класса):

Класс PDestination скорее часть dest( ) чем Parcel4. (Так же заметьте, что Вы можете использовать идентификатор класса PDestination для внутреннего класса внутри каждого класса в одной и той же поддиректории без конфликта имен.) Следовательно, PDestination не может быть доступен снаружи dest( ). Заметьте, что приведение к базовому типу происходит в операторе возврата, ничего не попадает наружу из dest( ), кроме ссылки на Destination, т.е. на базовый класс. Естественно, факт того, что имя класса PDestination помещено внутри dest( ) еще не означает, что PDestination не правильный объект, который возвращает dest( ).

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

Класс TrackingSlip помещен внутри контекста, а так же внутри оператора if. Но это не означает, что этот класс условно создается, он будет скомпилирован вместе с остальным кодом. Тем не менее он не будет доступен снаружи контекста, в котором он был объявлен. Кроме этой особенности он выглядит точно так же, как обычный класс.

Анонимный внутренний класс

Следующий пример несколько странен:

Метод cont( ) комбинирует создание возвращаемого значения с описанием класса, который и есть это возвращаемое значение! В дополнение этот класс еще и не имеет своего имени. Делая тему обсуждения немного запутанной, он выглядит как будто Вы начинаете создавать объект Contents:

Но затем, до того, как Вы поставите точку запятую, Вы заявляете: «Но подождите, я думаю, я описался в определении класса»:

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

То есть, Вы просто передаете соответствующий аргумент в конструктор базового класса, конкретно здесь x передается в new Wrapping(x). Анонимный класс не может иметь конструктор, в котором Вы могли бы нормально вызвать super( ).

В обоих предыдущих примерах, точка с запятой не означает конец тела класса (как в C++). Вместо этого, они показывают конец выражения, которое содержит анонимный класс. Таким образом, это равносильно использованию точки запятой где нибудь еще (т.е. они имеют то же значение, что и в любом другом месте).

Что случиться, если вам потребуется осуществить некую часть инициализации для объекта внутреннего анонимного класса? Поскольку он анонимный, то у него нет имени, которое можно передать конструктору, поэтому у него не может быть конструктора. Но все равно, Вы можете осуществить инициализацию в точке определения ваших полей:

Как только Вы научились получать доступ к объектам вне внутреннего класса, так сразу же возникает вопрос, а что делать, если нужно осуществить некое действие похожее на конструктор При помощи инициализации экземпляра, Вы можете, в действительности, создать конструткор для анонимного внутреннего класса:

Внутри инициализатора Вы можете видеть код, который не будет исполнен как часть инициализатора полей (т.е., выражение if). На самом же деле, инициализатор экземпляра по своей сути есть ничто иное, чем конструктор анонимного класса. Разумеется, что он несколько ограничен; Вы не можете перегрузить инциализатор экземпляра, поэтому у вас есть только один такой «конструктор».

Связь с внешним классом

Поскольку, внутренний класс предоставлялся только для целей скрытия имени и кода, что несомненно помогает, но все же не является всеобъемлющей особенностью внутренних классов. Тем не менее, имеется еще один способ использования внутренних классов. Когда Вы создаете внутренний класс, объект этого внутреннего класса имеет связь с окружающим его объектом и поэтому он имеет доступ к элементам этого объекта, без каких либо специальных предикатов. В дополнение внутренние классы имеют права доступа ко всем элементам окружающего его класса[40]. Нижеследующий пример как раз это и показывает:

На первый взгляд, создание SSelector выглядит, как просто создание другого внутреннего класса. Но просмотрите его более внимательно. Заметьте, что каждый из методов end( ), current( ) и next( ) ссылаются на obs, который является ссылкой и не является частью SSelector, но он заменяет private поле в окружающем классе. Тем не менее, внутренний класс может получить доступ к методам и полям окружающего класса, как если бы они были его собственными полями.

Так что внутренний класс имеет автоматически доступ к элементам окружающего его класса. А как же тогда такое происходит? Внутренний класс должен хранить ссылку на объект окружающего класса, ответственный за его создание. Потом, когда Вы будете ссылаться на элемент окружающего класса, то будет использована эта скрытая ссылка, что бы выбрать этот элемент. К счастью, компилятор заботится обо всех этих деталях вместо вас, но Вы все равно должны понимать, что этот объект внутреннего класса может быть создан только в соединении с объектом окружающего класса. Создание объекта внутреннего класса требует ссылки на объект окружающего его класса и компилятор выдаст сообщение об ошибке, если он не сможет получить доступ к этой ссылке. Наиболее часто это случается без какого либо вмешательства в эту часть программистом.

static внутренние классы

Если вам не нужно соединение между внутренним классом и объектом внешнего класса, тогда Вы можете сделать этот внутренний класс static. Для того, что бы понять значение static примененного к внутреннему классу, Вы должны вспомнить то, что этот объект обычного внутреннего класса неявным образом ссылается на объект создавшего его окружающего класса. А если Вы объявите его как static, то это уже не будет правдой. Static внутренний класс означает:

Static внутренние классы отличаются от не-static внутренних классов. Поля и методы в не-static внутренних классах могут быть только на внешнем уровне класса, поэтому не-static внутренние классы не могут иметь static данные, static поля или static внутренние классы. Однако static внутренний класс не ограничен таким ограничением:

В main( ), не требуется объекта Parcel10; вместо этого Вы используете нормальный синтаксис для выбора static элемента, что бы вызвать методы, которые возвращают ссылки на Contents и Destination.

Как Вы видели недавно, в обычном (не static) внутреннем классе, ссылка на объект внешнего класса достигается с помощью специальной ссылки this. Static внутренний класс не имеет этой специальной ссылки, что делает его аналогом static метода.

При этом создается класс называемый TestBed$Tester (для запуска программы, нужно использовать java TestBed$Tester). Вы можете его использовать для тестирования, но вовсе необязательно включать в поставку вашего продукта.

Ссылки на объект внешнего класса

Если вам необходимо сделать ссылку на внешний объект, вы просто указываете имя вашего внешнего объекта и после него добавляете точку и this. К примеру, в классе Sequence.SSelector, любые его методы могут производить хранимые ссылки на внешний класс Sequence просто указывая их как Sequence.this. Результирующая ссылка автоматически становится нужного типа. (Это обстоятельство известно и проверяется на стадии компилирования, поэтому на стадии выполнения не будет излишних накладных расходов.)

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

Для создания объекта внутреннего класса напрямую, Вы уже не должны следовать тем же самым методом и сослаться прямо на внешний класс Parcel11, но вместо этого Вы должны использовать объект внешнего класса для создания объекта внутреннего класса:

Доступ «наружу» из множественно вложенных классов

[41] Совершенно не играет роли, как глубоко может быть вложен внутренний класс, он может совершенно прозрачно получить доступ ко всем элементам всех классов, в которых он вложен, ниже этому пример:

Вы можете видеть, что в MNA.A.B, методы g( ) и f( ) востребуются без каких либо ограничений (несмотря даже на тот факт, что они private). Этот пример также демонстрирует синтаксис требуемый для создания объектов многократно вложенных внутренних классов, когда Вы создаете объект в другом классе. New предоставляет правильную область действия, поэтому вам не нужно квалифицировать имя класса в вызове конструктора.

Наследование от внутренних классов

Поскольку конструктор внутреннего класса должен быть присоединен к ссылке на окружающий его класс, то наследование может выглядеть несколько запутано, если Вы захотите от него наследовать. Проблема заключается в том, что эта «секретная» ссылка на окружающий объект должна быть инициализирована, а в дочернем объекте нет того объекта, к которому можно было бы «привязаться». Решением здесь является использование синтаксиса явной ассоциации:

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

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

Может ли быть внутренний класс перегружен?

Что произойдет, когда Вы создадите внутренний класс, а затем наследуете окружающий класс и переопределите внутренний класс? Что же это такое, что возможно переопределить внутренний класс? Да, это именно так и выглядит, но переопределение внутреннего класса это то же самое, как если бы во внешнем классе был бы другой метод, который ничего бы не делал:

Конструктор по умолчанию генерируется компилятором и эти вызовы конструктора базового класса тоже. Вы можете думать, что поскольку BigEgg уже создан, то будет использована переопределенная версия Yolk, но в действительности все не так. Вывод будет такой:

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

Теперь BigEgg2.Yolk ясно расширяет Egg2.Yolk и переопределяет его методы. Метод insertYolk( ) позволяет BigEgg2 привести к базовому типу один из его объектов Yolk к ссылке y в Egg2, так что, когда g( ) вызывает y.f( ), то используется переопределенная версия f( ). Вывод же будет такой:

Второй вызов Egg2.Yolk( ) это вызов конструктора базового класса из конструктора BigEgg2.Yolk. Вы так же можете видеть, что переопределенная версия f( ) используется, когда вызывается g( ).

Идентификаторы внутренних файлов

Поскольку, каждый класс создает файл .class, который содержит все информацию о том, как создавать объект этого типа, то Вы можете догадаться, что внутренний класс может так же создавать файл .class содержащий информацию для его объекта Class. Имя такого класса выбирается следующим образом: имя окружающего класса, символ «, имя внутреннего класса. К примеру, файлы .class созданные InheritInner.java:

Хотя эта схема генерирования внутренних имен проста и непосредственна, она так же устойчива к различным ситуациям[42]. Поскольку такой подход к генерации, является подмножеством стандартной схемы именования в Java, то сгенерированные файлы становятся автоматически платформо-независимомы. (Заметьте, что компилятор изменяет ваши внутренние классы таким образом, что бы они работали.)

Зачем внутренние классы?

До этого момента Вы видели множество примеров синтаксиса и семантического описания работы внутренних классов, но еще не было ответа на вопрос «А зачем они собственно?». Зачем Sun пошел на такие значительные усилия, что бы добавить эту возможность языка?

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

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

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

Что бы увидеть это более детально, представьте себе ситуацию, где у вас было бы два интерфейса, которые должны как-то выполниться внутри класса. В силу гибкости интерфейсов у вас есть два выбора: одиночный класс или внутренний класс:

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

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

Если вам не нужно решать проблему с «множественной реализацией наследования», то вам лучше использовать какие угодно методы, кроме внутренних классов. Но с внутренними классами Вы получаете и дополнительные возможности:

Пример. Если бы Sequence.java не использовал бы внутренние классы, то Вы бы сказали Sequence это есть Selector, и Вы бы могли иметь только один Selector в Sequence. Так же, у вас не было бы второго метода getRSelector( ), который происходит от Selector, который собственно двигается в обратном направлении по последовательности. Гибкость такого рода доступна только с использованием внутренних классов.

Замыкания & обратные вызовы

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

Наиболее неоспоримым аргументом для включения можно назвать разновидность указательного механизма в Java позволяющего осуществлять обратные вызовы (callbacks). С обратным вызовом, некоторые другие объекты, могут получить кусочек информации, которая позволит им в дальнейшем передать управление в исходящий объект. Это очень мощная концепция, как Вы увидите потом, в Главе 13 и Главе 16. Если обратный вызов реализуется через использование указателя, то Вы должны очень осторожно с ним обращаться. Как Вы наверное уже могли понять, в Java имеется тенденция для более осторожного программирования, поэтому указатели не включены в этот язык.

Этот пример так же показывает дальнейшие различия между реализацией интерфейса во внешнем классе и того же самого во внутреннем. Callee1 простое решение в терминах кода. Callee2 наследует от MyIncrement, который уже имеет отличный метод increment( ), который в свою очередь что то делает, при этом еу нужен интерфейс Incrementable. Когда MyIncrement наследуется в Callee2, increment( ) уже не может быть переопределен для использования с Incrementable, поэтому принудительно использовано разделение реализаций с использованием внутреннего класса. Так же заметьте, когда Вы создаете внутренний класс вам уже не нужно добавлять или модифицировать интерфейс внешнего класса.

Обратите внимание на то, что все исключая getCallbackReference( ) в Callee2 с модификатором private. Для того, что бы разрешить любые соединения с внешним миром, можно использовать интерфейс Incrementable. Далее Вы увидите, как интерфейсы поддерживают полное разделение интерфейса и реализации.

Внутренний класс Closure просто реализует Incrementable для того, что бы безопасно перехватить возврат Callee2. Единственный способ получить эту ссылку это вызов increment( ).

Caller передает Incrementable ссылку в его конструктор (хотя захват обратной ссылки может происходить в любое время) и затем, немного погодя, использует эту ссылку для возврата в класс Callee.

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

Внутренние классы и структуры управления

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

Для того, что бы увидеть, как внутренние классы позволяют с легкостью создавать и использовать структуры управления, давайте представим себе структуру управления, чьей задачей будет выполнение событий, когда они будут готовы. Хотя «готовы» здесь может означать что угодно, поэтому выберем вариант зависящий от времени. Теперь у нас есть структура управления без какой либо конкретной информации о том, что же ей нужно в действительности контролировать. Сперва, тут есть интерфейс, который описывает управляемые события. В этой роли выступает абстрактный класс, вместо настоящего интерфейса, поскольку у нас используется поведение на основе таймера:

Конструктор просто запоминает время, когда Вы хотите запустить Event, затем ready( ) сообщает, когда приходит время для запуска. Естественно ready( ) должен быть переопределен в дочернем классе на нечто более другое, чем время.

EventSet поддерживает Event-ов. (Если бы использовался настоящий контейнер из главы 9, то вам не нужно было бы беспокоиться о максимальном размере, поскольку он изменяет размер самостоятельно). Index используется для сохранения пути на следующее свободное место, а next используется, когда Вы ищите следующий Event в списке, что бы понять, не зациклились ли Вы. Информация о зацикливании особенна важна при вызове getNext( ), поскольку объекты Event должны удаляться из списка (используется removeCurrent( )) как только они были выполнены, поэтому getNext( ) должен найти промежутки в списке и осуществлять выборку без них.

Заметьте, что removeCurrent( ) не просто устанавливает некоторый флаг, сигнализирующий, что объект уже не используется. Вместо этого он устанавливает ссылку в null. Это поведение достаточно важно, поскольку сборщик мусора не сможет очистить объект, на который все еще существует ссылка. Если Вы думаете, что ваши ссылки могут таким образом зависнуть, то тогда хорошей идеей устанавливать их в null, дабы дать возможность сборщику мусора удалить их.

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

Рассмотрим частную реализацию структуры управления, спроектированную для управления функциями гринхауза (greenhouse functions)[43]. Каждое из действий полностью уникально и отличается от других: включение света и термостатов, выключение их, звон колокольчиков и рестартинг всей системы. Но структура управления просто изолирована в этом отличном (от других) коде. Внутренний класс позволяет вам получить множественно наследуемые классы от одного и того же базового класса (т.е. несколько наследников от одного в одном), Event, в одном единственном классе. Для каждого типа действия Вы наследуете новый внутренний класс от Event и пишите код контроля внутри action( ).

Как и для всех остальных структур управления, класс GreenhouseControls наследуется от Controller:

Заметьте, что light, water, thermostat и rings связаны с внешним классом GreenhouseControls и поэтому внутренние классы могут получать доступ к его полям без каких либо особенностей или специального доступа. Так же многие из методов action( ) осуществляют некоторый вид аппаратного контроля, который лучше всего перевести не в Java код.

Большинство из классов Event выглядят одинаково, но Bell и Restart особенны. Bell звонит и если он не звонит некоторое время, то добавляется новый объект Bell в список, так что он прозвонит позже. Заметьте, что внутренние классы почти что выглядят, как множественное наследование: Bell содержит все методы Event и он так же имеет доступ ко всем метода внешнего класса GreenhouseControls.

Restart ответственен за инициализацию системы, он добавляет все необходимые события. Естественно, лучше было бы отказаться от жестко зашитых событий в программе и читать их из файла. (Упражнение в главе 11 попросит вас сделать данное предположение.) Поскольку Restart( ) это просто другой объект Event, Вы так же можете добавить объект Restart внутрь Restart.action( ) так что система будет периодически рестартовать сама себя. И все, что нужно будет сделать это лишь в main( ) создать объект GreenhouseControls и добавить объект Restart который выполнял бы эту работу.

Резюме

Интерфейсы и внутренние классы более мудреная концепция, чем те, которые Вы можете найти в других ОО языках программирования. К примеру, ничего похожего в C++ просто нет. Сообща они решают некоторые проблемы, которые в C++ решаются путем множественного наследования. Но все равно в C++ использовать множественное наследование достаточно сложно, если сравнивать с Java, где интерфейсы и вложенные классы использовать гораздо проще.

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

Упражнения

Решения для этих упражнений доступны в электронном документе The Thinking in Java Annotated Solution Guide, доступном за небольшую плату с www.BruceEckel.com.

[38] Этот подход был вдохновлен по имейлу Rich-м Hoffarth-м.

[39] Спасибо, Martin-у Danner-у за задание этого вопроса во время семинара.

[40] Это очень отличается по дизайну вложенных классов в in C++, который есть только механизм скрытия имен. Здесь нет ссылки на окружающий объект и нет предполагаемых ограничений, в C++.

Источник

Добавить комментарий

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