java — Объекты статических классов
В Java, класс, объявленный в другом классе, называется вложенным классом.
Существует два типа вложенных классов: статические и нестатические.
Статический вложенный класс (static nested class) – это класс, объявленный с модификатором
static
.Нестатический вложенный класс (также называется внутренним классом, inner class) – это, собственно, класс, объявленный без модификатора
static
.
Зачем мы создаем экземпляр статического класса?
Нестатический вложенный класс (внутренний класс) неявно содержит ссылку на экземпляр внешнего класса, за счет этого достигается доступ к нестатическим полям внешнего класса из внутреннего класса.
Так как внутренний класс содержит ссылку на экземпляр внешнего класса, то экземпляр внутреннего класса можно создать только в нестатических методах (или блоках) внешнего класса.
Так как Вы создаете экземпляр вложенного класса в статическом методе внешнего класса, т.е. без создания экземпляра внешнего класса, то этот вложенный класс должен быть статическим.
Следующий код не компилируется:
В этом случае Вы пытаетесь вызвать нестатический метод translate()
без создания экземпляра класса, чего делать нельзя.
Казалось бы, решение этой проблемы заключаться в объявлении методов translate()
и getLanguage()
статическими, однако в данном случае этот вариант не сработает, так как метод getLanguage()
является абстрактным методом, а абстрактный метод нельзя объявить статическим.
Если мы убираем модификатор
static
у классаEnglishTranslator
, то получаем следующую ошибку:Solution.this cannot be referenced from a static context
Если Вы уберете модификатор static
у класса EnglishTranslator
, то этот класс станет внутренним классом, а внутренний класс, как было сказано выше, неявно содержит ссылку на экземпляр внешнего класса.
Вы создаете экземпляр класса EnglishTranslator
в статическом методе внешнего класса, который может быть вызван без создания экземпляра внешнего класса, следовательно, сослаться на this
внешнего класса в этом методе Вы не сможете, откуда следует, что в этом методе невозможно создать экземпляр внутреннего класса EnglishTranslator
.
Так же мы можем вынести Translator и EnglishTranslator за Solution и убрать у них модификатор static это так же компилируется и работает.
В таком случае эти классы перестанут быть вложенными, и их экземпляры можно будет создавать и в статических и в нестатических методах других классов.
Изучать язык Java (собственно, как и любой другой язык программирования) без чтения соответствующих классических книг – это очень и очень плохая идея.
Статьи в интернете (которые могут быть некорректными, например, в некоторых из них ошибочно указано, что в Java объекты передаются при вызове по ссылке), всякие там видео-уроки (это вообще для ленивых, типа как фильм посмотреть с пачкой чипсов и банкой колы) – это не то, с помощью чего следует изучать подобные фундаментальные вещи.
Да, про некоторые моменты можно почитать какие-то статьи, но в приоритете всегда должно быть авторитетное пособие.
По поводу JavaRush – да, ребята предоставляют неплохую практику по языку, но перед выполнением этих практических вещей просто необходимо освоить теорию по выбранной теме в книге.
Так что советую Вам всегда держать под рукой (не только держать, но и вникать :)) что-нибудь из классики по Java, например: Шилдт, Хорстманн, Эккель.
Внутренние классы. Часть 2 – статические вложенные классы.
Статические вложенные классы (nested) очень похожи на классы верхнего уровня. Их обычно используют если необходимо логически связать два класса – внешний и внутренний. Интерфейсы так же могут быть статическими вложенными интерфейсами располагающимися внутри внешнего класса или интерфейса.
Статический вложенный класс определяется внутри другого внешнего класса. Это может выглядеть так:
Как видно из кода, внешним классом является класс OuterClass, статическим вложенным классом является StaticNesterdClass, статическим вложенным интерфейсом является StaticNestedInterface.
Обратите внимание на границы внешнего класса, а так же вложенного статического класса и интерфейса.
По существу внешний класс для статического вложенного класса является как бы мини пакетом.
Статический вложенный класс или интерфейс определен как static член окружающего класса, что делает его аналогом поля и метода класса, которые так же объявлены как static. Как и метод класса, статический вложенный класс не связан ни с одним экземпляром внешнего класса, то есть может использоваться без создания экземпляра внешнего класса. Тем не менее статический вложенный класс имеет доступ ко всем static членам окружающего класса, включая любые другие статические вложенные классы и интерфейсы. Статический вложенный класс может использовать любой статический член окружающего класса без указания имени этого окружающего класса.
Статический вложенный класс имеет доступ ко всем статическим членам окружающего класса, включая private члены. Обратное тоже верно: методы окружающего класса имеют доступ ко всем static членам статического вложенного класса, включая его private static члены, однако при обращении к ним необходимо указывать имя статического вложенного класса, которому они принадлежат.
Поскольку статические вложенные классы сами являются членами класса, то статический вложенный класс может быть объявлен с любыми модификаторами доступа. Эти модификаторы имеют одно и то же значение для статических вложенных классов и для остальных членов класса.
Статический вложенный класс не может называться так же, как называется любой из окружающих классов. Кроме того, статические вложенные классы и интерфейсы могут быть объявлены только в классах верхнего уровня и других статических вложенных классах и интерфейсах. В действительности это часть более общего запрета на использование static членов любого вида во внутренних, локальных и анонимных классах.
Из статических вложенных классов вы не можете обращаться к нестатическим членам внешнего класса. Обратное тоже верно – из внешних классов вы не можете обращаться к нестатическим членам вложенного статического класса. Статический вложенный класс для доступа к нестатическим членам и методам внешнего класса должен создавать его объект. Обратное так же верно – для доступа к нестатическим членам вложенного класса, внешний должен создать его объект.
Если в статический вложенный класс вкладывается еще один класс, то это не делает его автоматически статическим, он будет вложенным inner классом, даже если его окружающий класс является вложенным в интерфейс. Чтобы класс вложенный в статический класс был статическим необходимо это явно указать при помощи ключевого слова static.
Ну и теперь немного практики…
Вывод данной программы представлен слева. Обратите внимание на то, каким образом был создан экземпляр класса Nested. Перед ним использовалось уточняющее имя внешнего класса Outer. Данный синтаксис можно изменить импортом внутреннего класса Nested.
nst.getName() выводит private поле nestedName. Заметьте что оно не статическое и private. nst.getOuterName() выводит статическое private поле outerName внешнего класса Outer. Далее выводиться статическое поле out внешнего класса Outer. Мы смогли это сделать так как оно статическое. Далее выводится статическое поле nst вложенного класса Nested через уточнение именем класса Outer.
Затем мы создаем экземпляр класса Outer и получаем через него доступ к private static полю outerName через вызов метода getName() на экземпляре класса Outer. И последняя строка выводит private static поле вложенного класса через метод внешнего класса getStnst(), который имеет доступ даже к private static полям вложенного класса. Ну как не запутались? 🙂 По идее пока все не очень сложно 🙂
Ну и еще стоит отметить, что компилятор все равно нам создал три . class файла. Но вложенный класс Nested создан с уточняющим именем внешнего класса Outer через знак $. Поэтому все вложенные классы являются чистым синтаксическим сахарком, но достаточно нужным и удобным.
В коде, расположенном вне окружающего класса, на статический вложенный класс или интерфейс ссылаются по имени внешнего класса с последующим добавлением имени внутреннего класса (например, Outer.Nested). Для импорта статических классов-членов можно применять директиву import:
import
pro.java.nested.Outer.Nested; // импорт вложенного класса Nestedimport pro.java.nested.Outer.*; // импорт всех вложенных классов из класса Outer
Импортировать внутренние классы не рекомендуется, потому что данная операция скрывает факт того, что класс тесно связан с содержащим его классом. Поскольку строка создания экземпляра класса будет уже выглядеть, например, вот так:
Nested
nst = new Nested(«MyNested»);То есть в данном случае мы уже не видим что класс Nested является вложенным классом класса Outer, как это было явно видно до импорта:
Outer
. Nested nst = new Outer.Nested(«MyNested»);
Теперь еще немного поговорим о вложенных интерфейсах. Я добавил в класс Outer вложенный интерфейс IGetNames и вложенный класс GetNames в котором реализовал этот интерфейс, а так же в класс Main добавил создание экземпляра класса GetNames и использование его методов.
Здесь я привел лишь отрывки кода который был добавлен. В этом примере класс GetNames является вложенным в класс Outer, но ни что не мешает вложить его в интерфейс IGetNames.
Но тогда уже придется уточнять имя класса вложенного в интерфейс через имя внешнего класса и имя вложенного интерфейса:
Outer
.IGetNames.GetNames gn = new Outer.IGetNames.GetNames();Для наглядности, так же приведу отрывки измененного кода в классе Outer. в классе Main была изменена лишь строка создания экземпляра класса GetNames, которая уже была приведена выше.
Оба два предыдущих примера генерируют следующий вывод:
Три последние строки генерируются вызовом методов на экземпляре класса GetNames.
Вызов этих методов приведен на отрывке кода представленного на скриншоте выше.
Интересно так же посмотреть на те .class файлы которые получились в результате наших изменений в коде классов.
Как видим у нас получилось пять .class файлов. По одному на каждый из наших классов и интерфейсу. Особое внимание следует обратить на имена вложенных классов и интерфейса. Уровень вложенности классов разделяется знаком $. Знак доллара является валидным символов в именах классов, но предназначен для использования только компилятором и JVM.
Кстати сказать избавиться от длинной строки создания экземпляра класса GetNames можно все тем же импортом. Для этого нам надо импортировать этот класс по его полному имени:
import
pro.java.nested.Outer. IGetNames.GetNames;Тогда строка создания экземпляра класса GetNames будет выглядеть так:
GetNames
gn = new GetNames();Но опять же тут не очевидно что GetNames это вложенный класс, хотя эта запись гораздо короче.
Вложенные классы можно размещать и просто в интерфейсах. Любой класс, помещенный в интерфейс, автоматически объявляется как открытый (public) и статический (static). Так как класс объявляется как static, он не нарушает правил обращения с интерфейсом – этот вложенный класс всего лишь размещается в пространстве имен интерфейса. И как я уже показывал, вы даже можете реализовать окружающий интерфейс во внутреннем классе. Хитрый пример показан слева. И если вы его просто попробуете запустить из Eclipse, то ни чего не выйдет, хотя этот файл без проблем и ошибок скомпилируется. И он даже работает и его даже можно запустить, но используя магию правильное понимание. Это понимание можно почерпнуть здесь. Но все же его стоит чуть обновить до нынешних наших знаний.
И поможет нам в этом магия утилиты javap
То есть нам надо запускать класс содержащий метод main() по его полному имени и делать это правильным образом из правильного места.
Но можно сделать это и из Eclipse указав класс где находится метод main():
Вложение классов в интерфейсы особенно удобно при создании общего кода, который должен использоваться со всеми реализациями этого интерфейса.
Статические вложенные классы можно так же использовать для проверки работоспособности классов, то есть для их тестирования. Поскольку в них вы можете расположить метод main(). Как вы уже знаете при компиляции будет скомпилирован отдельный .class файл для вложенного класса, который вы можете запускать для тестирования работы отдельного класса не запуская всю программу. Кроме того, в окончательную сборку программы можно не включать файлы .class которые были сгенерированы для тестирования, их можно просто удалить и собрать программу, таким образом в ней не будет лишнего кода, который вы создали для тестирования.
На примерах слева и сверху представлены два класса Main, содержащий метод main() и SomeClass, содержащий вложенный класс Test, который так же содержит метод main().
После компиляции получается соответственно три файла с расширением .class:
Если посмотреть на содержимое файлов SomeClass.class и SomeClass$Test.class при помощи javap, то увидим следующее:
Как видим в байт коде класса SomeClass не присутствует код вложенного в него класса Test, поскольку он находится в отдельном своем .class файле, в котором есть метод main(). И этот .class файл мы можем запустить отдельно от класса Main. Так же мы можем запустить на исполнение и класс Main, и даже если мы удалим откомпилированный .class файл класса Test, то наша программа все равно будет работать правильно.
В дополнение ко всему вышесказанному предлагаю посмотреть хорошее видео по вложенным статическим классам:
Практики ради, я написал почти такой же код, как и в этом видео, но естественно его немного изменил дабы лишний раз запутать напомнить о пространстве видимости имен в коде.
Что тут стоить отметить, так это то что в классе Human, а так же в его вложенном классе Relations используется идентификатор relations во множестве мест и при этом не происходит ни какой путаницы. Кроме того, в отличие от примера на видео, я сперва создал объект статического вложенного класса, а уже только потом создал объект внешнего класса, чтобы подчеркнуть независимость одного от другого.
Теперь еще стоит поговорить о наследовании в статических вложенных классах.
Статический вложенный класс способен наследовать другие классы, реализовывать интерфейсы и являться объектом наследования для любого класса, обладающего необходимыми правами доступа. Подкласс вложенного класса не способен унаследовать возможность доступа к членам внешнего класса, которыми наделен его суперкласс, если он не является вложенным классом своего суперкласса. Похоже опять получилось сильно заумно :), хотя как всегда ни чего сложного нет.
И теперь опять попрактикуемся…
Вывод у программы следующий:
Как видим в этом примере у нас есть два класса наследника вложенного класса Nested. Это классы Ext1 и Ext2. Причем Ext1 является внутренним классом для класса Nested и соответственно имеет доступ ко всем полям внешнего класса Outer. Обращаю внимание что класс Ext1 не статический, а внутренний, то есть inner (это наша следующая тема). Класс же Ext2 не является внутренним классом Nested хотя и наследует его и поэтому он не имеет доступа к полям класса Nested. Следует обратить внимание на интересный синтаксис создания экземпляра класса Ext1. Поскольку класс Ext1 не статический, то он не может быть создан без привязки к экземпляру его внешнего класса.
Если же класс Ext1 сделать статическим, то строка создания его экземпляра будет выглядеть для нас уже привычным образом:
Outer
.Nested.Ext1 ext1 = new Outer.Nested.Ext1();Во всем остальном программа не изменится и будет работать так же как и работала.
Можно так же привести примеры использования статических вложенных классов из реальной жизни. Что может быть более реального чем стандартная библиотека java в которой существует класс java.awt.geom.Rectangle2D который имеет два вложенных класса, Float и Double. Это очень простые классы форм и было бы просто ни к чему умножать количество высокоуровневых классов в этом пакете еще на два.
Ну и завершим все небольшим примером реализации простенького стека с помощью вложенных классов. Это несколько измененный пример стека который мы уже делали, но там я не приводил вывода данной программы. Да и вообще текущий пример я сильно переделал. Теперь в стеке можно размещать вообще любые объекты а не только целые числа, кроме того я добавил наследование, что сократило код класса вложенного класса DynStack. Данный пример я сделал двумя коммитами, первый без реализации динамического стека, второй с реализацией.
Тут представлен окончательный вариант с реализацией интерфейса IStack двумя классами FixedStack и DynStack. Причем класс DynStack является вложенным в класс FixedStack, который в свою очередь вложен в интерфейс IStack. Класс DynStack наследуется от класса FixedStack и поэтому там переопределен только один метод push(), который увеличивает размер стека в два раза при его заполнении.
Стоит обратить внимание на то что для класса FixedStack мы не писали слово static, хотя он таковым и является поскольку является вложенным в интерфейс классом. А вот для класса DynStack нам уже пришлось написать слово static, так как если бы мы этого не сделали, то этот класс был бы внутренним, то есть inner – не статическим.
Ну и на последок длинная портянка вывода данной программы:
Первая строка выводится сразу же после создания объекта fix. Тогда еще стек пустой.
Затем мы добавили в стек единицу и вывели его состояние.
После добавили строку и опять вывели состояние.
Затем добавили значение типа double 55.55 и вывели состояние.
Затем попытались добавить ссылку null, что вполне легитимно для Object, но нам выдалось сообщение что стек полон.
После этого мы произвели операцию изъятия из стека значения и вывели опять стек, теперь у нас там два элемента – строка и единица.
После этого напечатали звездочки и создали объект динамического стека.
Добавили туда 10 вывели значение.
Добавили другую строку и снова вывели значение.
Затем добавили туда значение 77.77, но поскольку стек у нас был из двух элементов, то произошло увеличение размера стека, о чем мы и получили сообщение и уже в увеличенные стек было помещено значение 77.77 что мы и видим в выводе программы.
После этого значение 77.77 было изъято из стека и содержимое стека было выведено на экран.
За сим все! Кино про статические вложенные классы закончено 🙂
Далее в программе изучение внутренних inner классов.
Да и вообще далее еще много чего 🙂
Почему в Java нет статического класса
спросил
Изменено 3 года, 3 месяца назад
Просмотрено 4к раз
Я новичок в java. Когда я просматривал спецификацию языка, я обнаружил, что статические классы нельзя объявлять, но мы можем иметь статические внутренние классы. Я немного смущен, почему у нас не может быть статического класса верхнего уровня. Если я хочу создать класс, содержащий только служебные методы, я могу выбрать статический класс. Любая причина, по которой разработчики Java ограничили эту функцию?
4
Это не ограничение, вам нужно не статический класс
для определения вспомогательного класса, вам нужно только статических методов
. Например, класс Math
в java полон статических методов, но сам класс , а не static.
Статический класс может понадобиться только тогда, когда вы определяете внутренний класс
, который вы хотите использовать без создания экземпляра включающего класса, что разрешено в Java.
Вы можете определить свой класс полезности следующим образом:
class Util { общедоступный статический недействительный метод () { // ваш служебный метод } }
Класс верхнего уровня по определению уже является верхним уровнем, поэтому нет смысла объявлять его статическим; это ошибка.
Объявления статических классов
статический
— относительный термин.
static
означает «независимо от содержащего экземпляра». Таким образом, статическое поле имеет одно и то же значение, независимо от экземпляра класса. Статический внутренний класс действителен для каждого экземпляра родительского класса.
Но от чего статический класс верхнего уровня
будет «независим от содержащего его экземпляра»? Нет содержащего экземпляра для класса верхнего уровня. Вот почему это не может быть static
(или всегда static
, в зависимости от вашей точки зрения, но в любом случае указывать это не нужно).
С точки зрения реализации, нестатический внутренний класс
Потому что это не добавляет никакого смысла. «статический» имеет смысл применительно к вложенным классам. Это не имеет значения для внешнего класса. Так что можно и не указывать.
AFAIK, если он позволит объявлять классы верхнего уровня как статический класс, тогда он будет хранить ссылку в памяти кучи все время , даже когда вы ее не используете . И это то, что называется Утечка памяти .поэтому это запрещено.
1
Зарегистрируйтесь или войдите в систему
Зарегистрируйтесь с помощью Google
Зарегистрироваться через Facebook
Зарегистрируйтесь, используя адрес электронной почты и пароль
Опубликовать как гость
Электронная почта
Требуется, но не отображается
Опубликовать как гость
Электронная почта
Требуется, но не отображается
Эффективная Java! Предпочтение статическим классам-членам вместо нестатических
Java предоставляет четыре способа объявления класса в другом классе: статический класс-член , нестатический класс-член , анонимные классы и локальные классы .
Статический класс-член : это самый простой из вариантов вложенного класса. В итоге получается обычный класс, который оказывается внутри другого класса. Его можно получить, не имея ссылки на окружающий класс. Эти классы являются статическими членами окружающего класса и подчиняются тем же правилам видимости, что и другие члены класса. Обычно этот тип класса используется в качестве помощника для окружающего класса. Например, давайте рассмотрим Калькулятор
класс. Возможно, у него будет вложенный класс Operation
, который включает ряд констант, определяющих различные операции, которые можно выполнять в калькуляторе. Доступ к ним можно получить следующим образом: Calculator.Operation.PLUS
или Calculator.Operation.MINUS
. Это создает хорошую организацию для доступа к этим вспомогательным классам. Еще одно место, где вы увидите использование этих статических вложенных классов, — это шаблон Builder, где объект Builder является вложенным классом внутри создаваемого класса.
Нестатический класс-член : Единственная разница между объявлением статического вложенного класса и нестатического вложенного класса — это слово static
, но это различие в одном слове имеет большую разницу между двумя типами. Каждый экземпляр включающего класса имеет неявное соединение с вмещающим классом в этом случае и имеет доступ к членам включающего класса. Этот метод нельзя использовать, если вложенный класс должен иметь возможность существовать вне контекста окружающего класса, поскольку это невозможно в нестатическом вложенном классе. Связь между включающим классом и вложенным классом создается при создании включающего класса. Это соединение требует времени для создания, а также занимает место.
Map
keyset
, entryset
и values
, а также Set
и List
iterator
. Итак, нестатические вложенные классы-члены, похоже, обладают большой силой, каковы недостатки? Как упоминалось выше, соединение вмещающего и вложенного классов требует времени на создание, а также памяти в куче. Если вам не нужны преимущества нестатического типа, вам не нужно платить эту цену. Еще хуже то, что становится намного проще создавать утечки памяти с нестатическим вложенным классом, поскольку вложенный класс может содержать ссылку на включающий класс, что может привести к тому, что класс, который должен быть допущен к сборке мусора, не будет собираться. Анонимный класс : Анонимные классы имеют ряд ограничений. Вы не можете создавать их экземпляры, кроме как в точке объявления, вы не можете выполнять instanceof
запросов класса или делать что-либо еще, для чего требуется имя класса. Анонимные классы также не могут реализовывать несколько интерфейсов или расширять класс и реализовывать интерфейс одновременно. До того, как лямбда-выражения появились в Java 8, анонимные классы были предпочтительным способом создания небольших объектов на лету, теперь предпочтительным методом являются лямбда-выражения.
Локальный класс : Это наименее используемый метод, которым я лично никогда не пользовался. Их можно объявить практически везде, где можно объявить локальную переменную. Локальные классы могут иметь имена, могут использоваться повторно и могут ссылаться на членов окружающего класса. Они должны быть короткими, чтобы не повредить удобочитаемости.
Итак, как мы видим, у каждого типа есть своя причина существования и свое место в мире.