В Java определены вложенные классы. Вложенным называется такой класс, который объявляется в другом классе.
Вложенный класс не может существовать независимо от класса, в который он вложен. Следовательно, область действия вложенного класса ограничена его внешним классом. Если вложенный класс объявлен в пределах области действия внешнего класса, то он становится членом последнего. Имеется также возможность объявить вложенный класс, который станет локальным в пределах блока
Существуют два типа вложенных классов. Одни вложенные классы объявляются с помощью модификатора доступа static, а другие - без него. Нестатический вариант вложенных классов называются внутренними.
Внутренние классы позволяют вам группировать классы, логически принадлежащие друг другу, и управлять доступом к ним. Однако важно понимать, что внутренние классы заметно отличаются от композиции.
Внутренний класс может взаимодействовать со своим внешним классом, а код, написанный с использованием внутренних классов, получается более элегантным и понятным.
Создать внутренний класс несложно - достаточно разместить определение класса внутри окружающего класса.
Внутренний класс имеет доступ ко всем переменным и методам внешнего класса, в который он вложен, и может обращаться к ним непосредственно, как и все остальные нестатические члены внешнего класса.
Иногда внутренний класс используется для предоставления ряда услуг внешнему классу, в котором он содержится.
Класс можно вложить в область действия блока. В итоге получается локальный класс, недоступный за пределами блока.
Также внутренний класс можно быть анонимным классом (анонимные классы уже были рассмотрены ранее).
Обычно при создании класса вы описываете, как объекты этого класса ведут себя и как они выглядят. Объект появляется только после того, как он будет создан ключевым словом new
, и только начиная с этого момента для него выделяется память и появляется возможность вызова методов.
Но есть ситуации, в которых такой подход недостаточен. Первая ситуация - это когда некоторые данные должны храниться "в единственном числе" независимо от того, сколько было создано объектов класса. Вторая - когда вам потребуется метод, не привязанный ни к какому конкретному объекту класса (то есть метод, который можно вызвать даже при полном отсутствии объектов класса).
Такой эффект достигается путем использования ключевого слова static
, делающего элемент класса статическим. Поля или методы, которые объявлены как static
, не привязаны к определенному экземпляру этого класса. Поэтому даже если в программе еще не создавался ни один объект класса, вы можете вызывать статический метод или получить доступ к статическим полям. С обычным объектом вам необходимо сначала создать его и использовать для вызова метода или доступа к полям, так как нестатические данные должны точно знать объект, с которым работают.
Некоторые ОО языки используют термин данные уровня класса и методы уровня класса, подразумевая, что данные и методы существуют только на уровне класса в целом, а не для отдельных объектов этого класса.
Чтобы сделать данные или метод статическими, просто поместите ключевое слово static
перед их определением. Например, следующий код создает статическое поле класса и инициализирует его:
Теперь, даже при создании двух объектов StaticTest
, для элемента StaticTest.val
выделяется единственный блок памяти. Оба объекта совместно используют одно значение val
. Пример:
В данном примере как st1.val
, так и st2.val
имеют одинаковые значения, равные 924, потому что они ссылаются на один блок памяти.
Существует два способа обратиться к статической переменной. Как было видно выше, на нее можно ссылаться по имени объекта, например st2.val
. Также возможно обратиться к ней прямо через имя класса; для нестатических членов класса такая возможность отсутствует.
После выполнения операции, значения st1.val
и st2.val
будут равны 48.
Синтаксис с именем класса является предпочтительным, потому что он не только подчеркивает, что переменная является статической, но и в некоторых случаях предоставляет компилятору больше возможностей для оптимизации.
Если для инициализации статических переменных требуется произвести вычисления, то для этой цели достаточно объявить статический блок, который будет выполняться только один раз при первой загрузке класса. Пример:
Как только загружается класс NetworkConnector
, выполняются все действия в статическом блоке.
Та же логика верна и для статических методов. Вы можете обратиться к такому методу или через объект, как это делается для всех методов, или в специальном синтаксисе ИмяКласса.метод(). Статические методы определяются по аналогии со статическими данными:
Нетрудно заметить, что метод increment()
класса Incrementable
увеличивает значение статического поля val
. Метод можно вызвать стандартно, через объект:
Или, поскольку метод increment() является статическим, можно вызвать его с прямым указанием класса:
Если применительно к полям ключевое слово static
радикально меняет способ определения данных (статические данные существуют на уровне класса, в то время как нестатические данные существуют на уровне объектов), то в отношении методов изменения не столь принципиальны. Одним из важных применений static
является определение методов, которые могут вызываться без объектов.
На статические методы налагаются следующие ограничения:
они могут непосредственно вызывать только другие статические методы;
им непосредственно доступны только статические переменные;
им недоступны ключевые слова this или super.
В языке Java имеется языковое средство, расширяющее возможности ключевого слова import
и называемое статическим импортом. Оператор import
, предваряемый ключевым словом static
, можно применять для импорта статических членов класса или интерфейса. Благодаря статическому импорту появляется возможность ссылаться на статические члены непосредственно по именам, не используя имя класса. Это упрощает и сокращает синтаксис, требующийся для работы со статическими членами.
Рассмотрим пример без использования статического импорта. Представим себе, что мы вычисляем гипотенузу прямоугольного треугольника. Мы будем часто использовать методы Math.pow()
и Math.sqrt()
Как мы видим, строка
получается слишком громоздкой. Подобных неудобств можно избежать, если воспользоваться статическим импортом. Рассмотрим пример ниже
После использования статического импорта нет нужды использовать имя класс Math
для вызова статических методов pow()
и sqrt()
.
Если предполагается применять много статических методов или полей, определенных в классе, то можно импортировать класс Math
полностью
Каким бы удобным ни был статический импорт, очень важно не злоупотреблять им. Не следует забывать, что библиотечные классы Java объединяются в пакеты для того, чтобы избежать конфликтов пространств имен и непреднамеренного сокрытия прочих имен. Если статический член используется в программе один или два раза, то его лучше не импортировать. Статический импорт следует оставить на тот случай, если статический члены применяются многократно, в частности при выполнении математических вычислений.
Механизм перечислений был добавлен в JDK 5 версии, он позволяет удобно хранить и работать с так называемыми "категориальными данными".
Категориальные данные - это данные с ограниченным числом уникальных значений или категорий. Приведем примеры категориальных данных:
месяцы года (12 значений: январь, февраль и так далее);
пол (мужской, женский, андроген, бесполый и так далее);
вероисповедание (православие, католицизм, ислам и так далее);
день недели (понедельник, вторник, среда и так далее).
Рассмотрим пример. Для информационной системы «Электронный институт» необходимо создать класс сотрудника кафедры «DepartmentMember», в котором, среди прочих атрибутов, есть атрибут «должность» (position). С целью упрощения, предположим, что сотрудники кафедры могут занимать одну из следующих должностей:
«Инженер» (Engineer);
«Ассистент» (Assistant);
«Старший преподаватель» (Lecturer);
«Доцент» (Senior Lecturer);
«Профессор» (Professor).
Каким образом закодировать данное значение? Самый первый и очевидный вариант - просто использовать тип данных String.
Данное решение имеет несколько очень серьезных недостатков. Вы никак не можете обеспечить корректность указания должностей и контролировать создание нового объекта класса.
Должность старшего преподавателя, например, может быть записана как "ст. преп.", "ст. преподаватель", "ст. пр." и так далее, не говоря про возможные орфографические ошибки. С точки зрения формальной логики, это все разные категории должностей и вы не сможете группировать или фильтровать сотрудников кафедры по полю "должность".
Другой вариант - это создать несколько статических целочисленных констант, которые можно использовать вместо строк.
Теперь мы сможем использовать именованные константы вместо строго фиксированных строк.
Использование именованных констант позволяет отчасти избавиться от недостатков предыдущего подхода, но и этот вариант не является приемлемым. Ничто не мешает пользователю класса при создании объекта указать любое целое числа вместо именованной константы, осуществлять проверку ввода – достаточно трудоемкая задача, если в классе предусмотрено более одного категориального значения будет тяжело определять какие конкретно именованные константы необходимо использовать. К тому же, при использовании конструктора или сеттера поля «должность», нет никакого указания на необходимость использования именованных констант, программист сам должен догадаться о наличии нужных констант или читать сопроводительную документацию.
Подход с использованием именованных констант напоминает улучшенную версию использования механизма специальных кодов и его не следует использовать при разработке коммерческих программ.
Корректным вариантом решения данной проблемы является использование специального механизма перечислений (enumeration). По сути, перечисление – это тип, ссылочная переменная которого может принимать одно из нескольких заранее определенных значений. Реализуем класс «DepartmentMember» с помощью механизма перечислений:
Преимущества такого подхода очевидны: поле position может принять только один из заранее определенных значений, которые указаны при создании перечислений. При вызове конструктора или сеттера поля position, пользователю класса сразу будет понятно, что необходимо передать в качестве аргумента метода.
Обратите внимание на синтаксис перечисления. Перечисление является классом, но очень своеобразным. Вместо ключевого слова class используется ключевое слово enum (от слова enumeration – перечисление). Вместо полей и методов у перечисления идет просто набор констант через запятую.
Как уже было сказано, перечисление – это класс, а это значит, что он может иметь методы, и мы можем инкапсулировать перечисление и операции работы с ним в одной оболочке.
В данном примере, экземпляр перечисления хранит строку с цветом.