вложенные классы зачем нужны
Внутренние и вложенные классы java. Часть 1
Внутренние и вложенные классы java. Часть 1
02.03.2017 — 2019 год
Цель статьи: Рассказать о внутренних, вложенных, локальных, анонимных классах. Показать примеры их использования. Написать и протестировать классы в коде на java. Рассказать о свойствах этих классов. Материал предназначен для лучшего понимания безымянных классов, лямбда выражений, адаптеров и многопоточности. То есть перед их изучением.
Небольшое вступление. Предлагаю вашему вниманию цикл из трех статей.
В них я рассказываю о внутренних, вложенных, локальных, анонимных классах. Речь идет о терминологии и применении. Для этих статей я написал довольно много кода.
Это учебный код, а не руководство к действию. То есть сам код я написал для лучшего понимания. Также я постарался объяснить работу учебного кода. На написание данной публикации, ушло довольно много времени. Публикация состоит из трех частей. Прошу отнестись с пониманием.
Для лучшего изучения материала у вас должна быть некоторая подготовка.
То есть вам нужно знать: синтаксис языка java, область видимости переменных, классы, статические и нестатические члены класса, создание экземпляров класса, наследование, модификаторы доступа.
Начнем с того, что же такое внутренние и вложенные классы. Посмотрим терминологию, встречающуюся в документации и литературе:
В Java существуют 4 типа вложенных (nested) классов:
Существуют четыре категории вложенных классов:
Попытаемся разобраться, что же это такое.
Начнем немного отдаленно, так как всё это имеет непосредственное отношение к нашим вопросам. Вспомним объектно-ориентированное программирование. Отношения композиции и наследования.
В своей книге «Java 2 Руководство разработчика» Майкл Морган очень хорошо и подробно описывает взаимосвязи классов и объектов. Мы рассмотрим некоторые из них. Взаимосвязь «это — есть — то» выражается наследованием, а взаимосвязь «имеет часть» описывается композицией.
В наших примерах мы в основном рассматриваем композицию. Так как вложенные классы — это и есть часть чего-то. То есть у нас есть класс оболочка и вложенный класс определенный внутри класса оболочки. Пример композиции: машина имеет двигатель, двери, 4 колеса, корпус. И мы можем описать машину с помощью внутренних (Inner) классов.
Пример такого использования вы можете найти в книге Брюса Эккеля «Философия Java»
Есть некоторое предупреждение автора по использованию кода в таком виде:
Так как композиция объекта является частью проведенного анализа задачи (а не
просто частью реализации класса), объявление членов класса открытыми, помогает программисту-клиенту понять, как использовать класс, и упрощает создателю написание кода. Однако нужно помнить, что описанный случай является особым, и в основном поля класса нужно объявлять как private.
Выше я не случайно упомянул наследование и композицию. Это напрямую относится к дальнейшему материалу.
Статические вложенные классы
Определение вложенных классов:
Класс называется вложенным (nested), если он определен внутри другого класса.
То есть класс просто определен внутри другого, даже не важно статически определен или не статически. Вложенный класс создается для того, чтобы обслуживать окружающий его класс. Если вложенный класс оказывается полезен в каком-либо ином контексте, он должен стать классом верхнего уровня.
Вложенные классы применяются в тех случаях, когда нужно написать небольшой вспомогательный код для другого класса. Вложенный класс создают также, чтобы скрыть его переменные и методы от внешнего мира. Таким образом, вложенный класс еще один элегантный способ ограничения области видимости. Внутренние классы также есть смысл использовать, если предполагается, что они будут использовать элементы родителя, чтобы не передавать лишнего в конструкторах.
Пример вложенного класса вы можете увидеть в документации Оракле:
У нас нет, пока что, никакого контекста использования данной конструкции. С таким же успехом вложенный класс мы можем назвать вместо: «Вложенный класс» (NestedClass) — «Внутренний класс» InnerClass. Далее будем разбираться, в чем же отличия, и в каких контекстах используются классы. Брюс Эккель пишет в книге «Философия Java» так:
«Класс называется вложенным (nested), если он определен внутри другого класса»
Документацию Oracle вы можете посмотреть по этой ссылке: >>>
Существует четыре категории вложенных классов:
Причины использования вложенных классов такие. Если класс полезен только для одного другого класса, то вполне логично встроить его в этот класс и хранить их вместе. Использование вложенных классов увеличивает инкапсуляцию. Рассмотрим два класса верхнего уровня, A и B, где B нужен доступ к членам, которые иначе были бы объявлены закрытыми.
Скрывая класс «B» в пределах класса «А», члены класса «А» могут быть объявлены закрытыми, и «B» может получить доступ к ним. Кроме того, сам «B» может быть скрыт от внешнего мира.
Продемонстрируем это в коде:
Использование вложенных классов приводит к более читабельному и поддерживаемому коду: Размещение класса ближе к тому месту, где он будет использован, делает код более читабельным.
Статические Вложенные Классы
Static Nested Classes
Причины использования статических вложенных классов такие.
Для случая, когда связь между объектом вложенного класса и объектом внешнего класса не нужна, можно сделать вложенный класс статическим(static).
Так как внутренний класс связан с экземпляром, он не может определить в себе любые статические члены.
Статические вложенные классы не имеют ограничений по объявлению своих данных и полей как static.
Из вложенного статического класса мы не имеем доступа к внешней не статической переменной внешнего класса.
Приведенный ниже код демонстрирует это:
Вывод: Мы не имеем доступа к не статическому полю внешнего класса, через статический контекст вложенного класса. Это подобно тому, как мы не имеем доступа из статического метода к нестатическим переменным класса. Точно также из статического вложенного класса мы не имеем доступа к нестатическим переменным внешнего класса.
Но мы имеем доступ к приватным статическим полям внешнего класса из вложенного статичного класса.
Приведенный ниже фрагмент кода демонстрирует это:
В этом примере кода мы создали экземпляр внутреннего класса с именем «nestedObj».
То есть мы получаем доступ к приватной статической переменной внешнего класса, через экземпляр внутреннего класса. В контексте экземпляра связанного с внешним классом, у нас получился внутренний класс.
Майкл Морган. «Java 2. Руководство разработчика» ISBN 5-8459-0046-8
Брюс Эккель. «Философия Java.» ISBN 5-272-00250-4
Герберт Шилдт «Java. Полное руководство. 8-е издание.» ISBN: 978-5-8459-1759-1
Все вопросы, комментарии, дополнения, критика приветствуются.
Продолжение следует…
Часть 2 >>>
Если у вас есть возможность, вам пригодилось, и вы можете помочь, то нажмите кнопку поддержать автора материально.
Для чего в java нужны вложенные классы?
Для чего в java нужны вложенные классы?
3 ответа 3
Для начала нужно понимать разницу между нестатическими вложенными классами (non static nested classes) и статическими вложенными классами (static nested classes).
Нестатические вложенные классы (non static nested classes)
Где применяется?
Там где вы описываете структуру будущего объекта, который имеет не только состояние и поведение, но и имеет какие-то дополнительные (составные) части. Без которых сам объект не существовал бы.
Например, машина. Смысл от нее теряется, если она стоит во дворе на кирпичах, а колеса от нее лежат в гараже, а вам нужно сесть и поехать. В таком случае вам придется потратить время, чтобы взять машину, взять домкрат, взять колеса и проделать все действия по их установке.
Как применяется?
Внутренний класс имеет доступ через методы ко всем полям и методам внешнего класса и может ссылаться к ним напрямую. Но внешний класс может обращаться к методам и полям внутреннего только по ссылке. Выглядит это так:
Или чтобы из метода main вызвать напрямую метод внутреннего класса, мы может сделать это так:
Внутренний класс так же можно определять внутри методов и циклов.
Статические вложенные классы (static nested classes)
Где применяется?
Как применяется?
Все обращения к полям и методам внешнего класса и наоборот к полям и методам вложенного класса происходит через класс.
Постараюсь объяснить максимально просто:
Обычно внутренний класс наследует от класса или реализует интерфейс, а код внутреннего класса манипулирует объектом внешнего класса, в котором он был создан. Значит, можно сказать, что внутренний класс —это нечто вроде «окна» во внешний класс.
Возникает естественный вопрос: «Если мне нужна ссылка на интерфейс, почему бы внешнему классу не реализовать этот интерфейс?» Ответ здесь таков: «Если это все, что вам нужно, —значит, так и следует поступить». Но что же отличает внутренний класс, реализующий интерфейс, от внешнего класса, реализующего тот же интерфейс? Далеко не всегда удается использовать удобство интерфейсов —иногда приходится работать и с реализацией. Поэтому наиболее веская причина для использования внутренних классов формулируется так:
Каждый внутренний класс способен независимо наследовать определенную реализацию. Таким образом, внутренний класс не ограничен при наследовании в ситуациях, где внешний класс уже наследует реализацию.
Без способности внутренних классов наследовать (фактически) реализацию более чем одного конкретного или абстрактного класса некоторые задачи планирования и программирования имели бы крайне сложное решение. Поэтому внутренний класс выступает как «довесок» множественного наследования. Интерфейсы берут на себя часть этой задачи, в то время как внутренние классы фактически обеспечивают «множественное наследование реализации». То есть внутренние классы позволяют вам наследовать от нескольких «не-интерфейсов».
Чтобы понять сказанное, рассмотрим ситуацию, где два интерфейса тем или иным способом должны быть реализованы в классе. Вследствие гибкости интерфейсов у вас есть два варианта: одиночный класс или внутренний класс:
Конечно, выбор того или иного способа организации кода зависит от конкретной ситуации. Впрочем, сама решаемая вами задача должна подсказать, что для нее пред почтительно: один отдельный класс или внутренний класс. Но при отсутствии иных ограничений оба подхода, использованные в рассмотренном примере, ничем не от личаются с точки зрения реализации. Оба они работают. Однако, если вместо интерфейсов у вас имеются конкретные или абстрактные классы, придется «звать на помощь» внутренние классы, если новый класс должен как-то за действовать функциональность двух других классов:
Если вам не приходится решать задачу «множественного наследования реализации», скорее всего, вы сможете написать любую программу без использования особенностей внутренних классов. С другой стороны, внутренние классы открывают следующие дополнительные возможности.
У внутреннего класса может существовать произвольное количество экземпляров, каждый из которых содержит собственную информацию, не зависящую от состояния объекта внешнего класса.
Один внешний класс может содержать несколько внутренних классов, которые по-разному реализуют один интерфейс или наследуют от единственного базового класса. Пример такой конструкции вскоре будет рассмотрен.
Место создания объекта внутреннего класса не привязано к месту и времени создания объекта внешнего класса.
Внутренний класс не создает взаимосвязи классов типа «является тем-то», способной вызвать путаницу; он представляет собой отдельную сущность.
Вложенные классы в Java
Добрый день, Хабровчане! Я уже довольно давно программирую на java, и нередко использую вложенные классы, но недавно наткнулся на статический вложенный класс и понял, что я о нем почти ничего не знаю. Поэтому я решил разобраться в этом, систематизировать свои знания, а заодно и поделиться этими знаниями с вами.
Вложенный класс (InnerClass)
Из него видны:
— все (даже private) свойства и методы OuterClassа обычные и статические.
— public и protected свойства и методы родителя OuterClassа обычные и статические. То есть те, которые видны в OuterClassе.
Его видно:
— согласно модификатору доступа.
Может наследовать:
— обычные классы.
— такие же внутренние классы в OuterClassе и его предках.
Может быть наследован:
— таким же внутренним классом в OuterClassе и его наследниках.
Может имплементировать интерфейс
Может содержать:
— только обычные свойства и методы (не статические).
Экзэмпляр этого класса создаётся так:
Статический вложенный класс (StaticInnerClass)
Из него (самого класса) видны:
— статические свойства и методы OuterClassа (даже private).
— статические свойства и методы родителя OuterClassа public и protected. То есть те, которые видны в OuterClassе.
Из его экземпляра видны:
— все (даже private) свойства и методы OuterClassа обычные и статические.
— public и protected свойства и методы родителя OuterClassа обычные и статические. То есть те, которые видны в OuterClassе.
Его видно:
— согласно модификатору доступа.
Может наследовать:
— обычные классы.
— такие же статические внутренние классы в OuterClassе и его предках.
Может быть наследован:
— любым классом:
— вложенным
— не вложенным
— статическим
— не статическим!
Может имплементировать интерфейс
Может содержать:
— статические свойства и методы.
— не статические свойства и методы.
Экзэмпляр этого класса создаётся так:
Локальный класс (LocalClass)
Из него видны:
— все (даже private) свойства и методы OuterClassа обычные и статические.
— public и protected свойства и методы родителя OuterClassа обычные и статические. То есть те, которые видны в OuterClassе.
Его видно:
— только в том методе где он определён.
Может наследовать:
— обычные классы.
— внутренние классы в OuterClassе и его предках.
— такие же локальные классы определённые в том же методе.
Может быть наследован:
— таким же локальным классом определённом в том же методе.
Может имплементировать интерфейс
Может содержать:
— только обычные свойства и методы (не статические).
Анонимный класс (имени нет)
Локальный класс без имени. Наследует какой-то класс, или имплиментирует какой-то интерфейс.
Из него видны:
— все (даже private) свойства и методы OuterClassа обычные и статические.
— public и protected свойства и методы родителя OuterClassа обычные и статические. То есть те, которые видны в OuterClassе.
Его видно:
— только в том методе где он определён.
Не может быть наследован
Может содержать:
— только обычные свойства и методы (не статические).
На этом всё. Жду ваших комментариев: какие есть неточности и ошибки, что я не покрыл и т.п.
Надеюсь, статья будет многим полезна.
Внутренние и вложенные Java классы
В Java вложенные классы – это классы, которые определены внутри другого класса.
Цель – четко сгруппировать вложенный класс с окружающим его классом, сигнализируя о том, что эти два класса должны использоваться вместе. Или, возможно, что вложенный должен использоваться только внутри его вмещающего (владеющего) класса.
Java-разработчики часто используют внутренние классы (нестатические вложенные) – это только один из нескольких различных типов:
Вложенные считаются членами включающего их класса. Таким образом, могут быть объявлены как открытые, пакетные (без модификатора доступа), защищенные и приватные. Поэтому могут наследоваться подклассами.
Статические
Чтобы создать экземпляр класса Nested, вы должны сослаться на него, добавив к нему префикс имени внешнего класса, например:
Статический тип – это, по сути, обычный класс, который только что был вложен в другой класс. Может обращаться к переменным экземпляра включающего класса только через ссылку на его экземпляр.
Нестатические внутренние классы
Нестатические вложенные также называются внутренними и связаны с экземпляром включающего класса. Таким образом, вы должны сначала создать экземпляр окружающего класса, чтобы создать экземпляр внутреннего:
Вот как вы создаете экземпляр класса Inner:
Обратите внимание, как вы ставите new после ссылки на внешний класс, чтобы создать экземпляр внутреннего.
Нестатические классы (внутренние классы) имеют доступ к полям включающего класса, даже если они объявлены закрытыми:
Обратите внимание, как метод printText() класса Inner ссылается на поле частного текста класса Outer. Это вполне возможно. Вот как вы бы вызвали метод printText():
Shadowing
Если внутренний класс объявляет поля или методы с теми же именами, что и у поля или методов в своем включающем классе, внутренние поля или методы называются внешними полями или методами :
В приведенном выше примере класс Outer и Inner содержит поле с именем text. Когда класс Inner ссылается на текст, он ссылается на свое собственное поле. Когда Outer ссылается на текст, он также ссылается на свое собственное поле.
Теперь метод Inner.printText() будет печатать поля Inner.text и Outer.text.
Локальные
Локальные классы в Java похожи на внутренние (нестатические вложенные), которые определены внутри метода или блока контекста(<…>) внутри метода:
Характеристика локальных классов:
Анонимные
Анонимные классы – это вложенные без имени класса. Они обычно объявляются либо как подклассы существующего класса, либо как реализации некоторого интерфейса.
Определяются, когда они создаются. Вот пример, который объявляет анонимный подкласс суперкласса с именем SuperClass:
Запуск этого кода приведет к тому, что анонимный класс doIt() будет напечатан в System.out. Он расширяет SuperClass и переопределяет метод doIt().
Анонимный класс также может реализовывать интерфейс вместо расширения класса:
Как вы можете видеть, анонимные классы, реализующий интерфейс и расширяющий другой класс очень похожи.
Этот тип класса может получить доступ к членам включающего класса. Также к локальным переменным, которые объявлены как final.
Вы можете объявить поля и методы внутри такого класса, но не можете объявить конструктор. Вместо этого возможно объявить статический инициализатор:
К этому типу класса применяются те же правила дублирования, что и к внутренним.
Преимущества
Преимущества в том, что вы можете группировать классы, которые принадлежат друг другу. Возможно сделать это, поместив их в один и тот же пакет, но размещение одного класса внутри другого делает еще более сильную группировку.
Иногда они видны только включающему классу, используется только внутри и, следовательно, никогда не видны вне охватывающего класса. В других случаях вложенный виден за пределами его окружающего класса, но может использоваться только вместе с включающим.
Примером может быть класс Cache. Внутри Cache вы можете объявить класс CacheEntry, который может содержать информацию о конкретной записи в кэше (кэшированное значение, время вставки, количество обращений и т. д.).
Пользователи Cache могут никогда не увидеть CacheEntry, если им не нужно получать информацию о самом CacheEntry, а только кэшированное значение. Тем не менее, Cache может сделать CacheEntry видимым для внешнего мира, чтобы они могли получить доступ не только к кэшированному значению (например, к информации о том, когда значение было обновлено в последний раз и т. д.).
Вот два скелета реализации Cache, иллюстрирующие эти моменты:
Первый класс Cache скрывает свой вложенный класс CacheEntry, в то время как второй класс Cache предоставляет его.
Вложенные классы
— Сегодня будет очень интересная тема. Я сегодня расскажу о внутренних классах.
Если один класс объявить внутри другого, то такой класс называется внутренним.
Объекты внутренних классов при этом вложены в объекты внешних классов и могут обращаться к переменным внешних классов.
Обрати внимание, у класса Door(дверь) есть метод, который возвращает высоту двери – getDoorHeight, этот метод использует переменную height объекта Car.
Объект типа Door не может существовать отдельно от объекта типа Car – ведь он использует его переменные. Компилятор незаметно добавляет в конструктор и в класс Door ссылку на объект внешнего класса Car, чтобы методы внутреннего класса Door могли обращаться к переменным и вызвать методы внешнего класса Car.
— Вложенные объекты. Мне все понятно. Судя по картинке тут все элементарно.
— Так и есть. Лишь пара нюансов.
Во внутреннем классе Door имеется ссылка на объект класса Car, поэтому:
1) Нельзя создать объект Door внутри статического метода в классе Car: негде взять ссылку на объект типа Car, который неявно передается в конструктор типа Door.
Правильно | Неправильно |
---|
2) Класс Door не может содержать статические переменные и методы.
Правильно | Неправильно |
---|
— А если мне нужна общая переменная для всех объектов Door?
Ты всегда можешь объявить ее просто в классе Car – это и будет общая переменная для всех объектов Door, вложенных в объект Car.
3) Обрати внимание, что если внутренний класс объявлен как public, то его объекты можно создавать вне внешнего класса, но объект внешнего класса при этом обязан присутствовать:
4) И еще одно замечание – чуть не забыла.
Т.к. у нас два вложенных объекта, то в методах внутреннего объекта доступно две ссылки this:
Я специально объявила в классах одинаковые переменные.
Чтобы обратиться к переменной внешнего класса, когда она скрыта, или к this внешнего класса, достаточно написать «имя класса» точка this:
— Т.е. если мы внутри метода внутреннего класса пишем this, то этот this относится к внутреннему классу?
Как тебе внутренние классы, Амиго?
— Очень интересно. Я бы не сказал, что очень сложно.
Есть много ограничений, но они вполне логичны, когда ты мне объяснила, откуда и зачем они берутся.
А я уже два месяца в практических заданиях постоянно пишу вложенные классы и только сейчас понял, что же я писал на самом деле.