1. Обзор
Когда класс Java компилируется, создается файл класса с тем же именем. Однако в случае вложенных классов или вложенных интерфейсов создается файл класса с именем, объединяющим имена внутреннего и внешнего классов, включая знак доллара.
В этой статье мы увидим все эти сценарии.
2. Детали
В Java мы можем написать класс внутри класса. Класс, написанный внутри, называется вложенным классом, а класс, содержащий вложенный класс, называется внешним классом. Область действия вложенного класса ограничена областью действия окружающего его класса.
Точно так же мы можем объявить интерфейс внутри другого интерфейса или класса. Такой интерфейс называется вложенным интерфейсом.
Мы можем использовать вложенные классы и интерфейсы для логической группировки сущностей, которые используются только в одном месте. Это не только делает наш код более читабельным и удобным для сопровождения, но также увеличивает инкапсуляцию.
В следующих разделах мы подробно обсудим каждый из них. Мы также рассмотрим перечисления.
3. Вложенные классы
Вложенный класс — это класс, объявленный внутри другого класса или интерфейса. Каждый раз, когда нам нужен отдельный класс, но при этом мы хотим, чтобы этот класс вел себя как часть другого класса, вложенный класс — лучший способ добиться этого.
Когда мы компилируем файл Java, он создает файл .class
для включающего класса и отдельные файлы классов для всех вложенных классов. Сгенерированный файл класса для включающего класса будет иметь то же имя, что и класс Java.
Для вложенных классов компилятор использует другое соглашение об именах — OuterClassName$NestedClassName.class.
Прежде всего, давайте создадим простой класс Java:
public class Outer {
// variables and methods...
}
Когда мы компилируем класс Outer
, компилятор создаст файл Outer.class
.
В следующих подразделах мы добавим вложенные классы в класс Outer
и посмотрим, как называются файлы классов.
3.1. Статические вложенные классы
Как следует из названия, вложенные классы, объявленные как статические
, называются статическими вложенными классами. В Java статическими могут быть только вложенные классы.
Статические вложенные классы могут иметь как статические, так и нестатические поля и методы. Они привязаны к внешнему классу, а не к конкретному экземпляру. Следовательно, нам не нужен экземпляр внешнего класса для доступа к ним.
Давайте объявим статический вложенный класс внутри нашего внешнего
класса:
public class Outer {
static class StaticNested {
public String message() {
return "This is a static Nested Class";
}
}
}
Когда мы компилируем наш класс Outer
,
компилятор создает два файла классов, один для Outer
, а другой для StaticNested
:
3.2. Нестатические вложенные классы
Нестатические вложенные классы, также называемые внутренними классами
, связаны с экземпляром включающего класса и могут обращаться ко всем переменным и методам внешнего класса.
Внешний класс может иметь только общедоступный доступ или доступ по умолчанию, тогда как внутренний класс может быть частным, общедоступным, защищенным или с доступом по умолчанию. Однако они не могут содержать никаких статических членов. Кроме того, нам нужно создать экземпляр внешнего класса для доступа к внутреннему классу.
Давайте добавим еще один вложенный класс в наш класс Outer
:
public class Outer {
class Nested {
public String message() {
return "This is a non-static Nested Class";
}
}
}
Он генерирует еще один файл класса:
3.3. Местные классы
Локальные классы, также называемые внутренними классами, определяются в блоке — группе операторов между сбалансированными фигурными скобками. Например, они могут находиться в теле метода, цикле for или предложении
if
. Область действия локального класса ограничена внутри блока точно так же, как и локальные переменные. Локальные классы при компиляции отображаются в виде знака доллара с автоматически сгенерированным номером.
Файл класса, сгенерированный для локального класса, использует соглашение об именах — OuterClassName$1LocalClassName.class.
Давайте объявим локальный класс внутри метода:
public String message() {
class Local {
private String message() {
return "This is a Local Class within a method";
}
}
Local local = new Local();
return local.message();
}
Компилятор создает отдельный файл класса для нашего локального
класса:
Точно так же мы можем объявить локальный класс в предложении if
:
public String message(String name) {
if (StringUtils.isEmpty(name)) {
class Local {
private String message() {
return "This is a Local class within if clause";
}
}
Local local = new Local();
return local.message();
} else
return "Welcome to " + name;
}
Хотя мы создаем еще один локальный класс с тем же именем, компилятор не жалуется. Он создает еще один файл класса и называет его с увеличенным номером:
3.4. Анонимные внутренние классы
Как следует из названия, анонимные классы — это внутренние классы без имени. Компилятор использует автоматически сгенерированное число после знака доллара, чтобы назвать файл класса.
Нам нужно объявить и создать экземпляры анонимных классов в одном выражении одновременно. Обычно они расширяют существующий класс или реализуют интерфейс.
Давайте посмотрим на быстрый пример:
public String greet() {
Outer anonymous = new Outer() {
@Override
public String greet() {
return "Running Anonymous Class...";
}
};
return anonymous.greet();
}
Здесь мы создали анонимный класс, расширив класс Outer
, и компилятор добавил еще один файл класса:
Точно так же мы можем реализовать интерфейс с анонимным классом.
Здесь мы создаем интерфейс:
interface HelloWorld {
public String greet(String name);
}
Теперь давайте создадим анонимный класс:
public String greet(String name) {
HelloWorld helloWorld = new HelloWorld() {
@Override
public String greet(String name) {
return "Welcome to "+name;
}
};
return helloWorld.greet(name);
}
Давайте посмотрим на пересмотренный список файлов классов:
Как мы видим, создается файл класса для интерфейса HelloWorld
и еще один для анонимного класса с именем Outer$2
.
3.5. Внутренний класс внутри интерфейса
Мы видели класс внутри другого класса, далее мы можем объявить класс внутри интерфейса. Если функциональность класса тесно связана с функциональностью интерфейса, мы можем объявить его внутри интерфейса. Мы можем использовать этот внутренний класс, когда хотим написать реализацию по умолчанию для методов интерфейса.
Давайте объявим внутренний класс внутри нашего интерфейса HelloWorld :
interface HelloWorld {
public String greet(String name);
class InnerClass implements HelloWorld {
@Override
public String message(String name) {
return "Inner class within an interface";
}
}
}
И компилятор генерирует еще один файл класса:
4. Вложенные интерфейсы
Вложенные интерфейсы, также известные как внутренние интерфейсы, объявляются внутри класса или другого интерфейса. Основная цель использования вложенных интерфейсов — разрешение пространства имен путем группировки связанных интерфейсов.
Мы не можем напрямую обращаться к вложенным интерфейсам. Доступ к ним можно получить только с помощью внешнего класса или внешнего интерфейса. Например, интерфейс Entry
внутри интерфейса Map
является вложенным и может быть доступен как Map
. Вход
.
Давайте посмотрим, как создавать вложенные интерфейсы.
4.1. Интерфейс внутри интерфейса
Интерфейс, объявленный внутри интерфейса, неявно является общедоступным.
Давайте объявим наш интерфейс внутри интерфейса HelloWorld
:
interface HelloWorld {
public String greet(String name);
interface HelloSomeone{
public String greet(String name);
}
}
Это создаст новый файл класса с именем HelloWorld$HelloSomeone
для вложенного интерфейса.
4.2. Интерфейс внутри класса
Интерфейсы, объявленные внутри класса, могут принимать любые модификаторы доступа.
Давайте объявим интерфейс внутри нашего класса Outer :
public class Outer {
interface HelloOuter {
public String hello(String name);
}
}
Он создаст новый файл класса с именем: OuterClass$StaticNestedClass.
5. Перечисления
Перечисление было введено в Java 5. Это тип данных, который содержит фиксированный набор констант, и эти константы являются экземплярами этого
перечисления . ``
Объявление enum
определяет класс
, называемый числовым
типом enum (также известный как перечисляемый тип данных). Мы можем добавить в перечисление
много вещей, таких как конструктор, методы, переменные и нечто, называемое телом класса, зависящим от константы.
Когда мы создаем enum
, мы создаем новый класс и неявно расширяем класс Enum .
Enum
не может наследовать какой-либо другой класс или расширяться. Однако он может реализовать интерфейс.
Мы можем объявить перечисление
как отдельный класс, в его собственном исходном файле или в другом члене класса. Давайте рассмотрим все способы создания перечисления
.
5.1. Перечисление как класс
Во-первых, давайте создадим простое перечисление
:
enum Level {
LOW, MEDIUM, HIGH;
}
Когда он скомпилирован, компилятор создаст файл класса с именем Level
для нашего перечисления.
5.2. Перечисление внутри класса
Теперь давайте объявим вложенное перечисление
в нашем классе Outer :
public class Outer {
enum Color{
RED, GREEN, BLUE;
}
}
Компилятор создаст отдельный файл класса с именем Outer$Color
для нашего вложенного перечисления.
5.3. Перечисление в интерфейсе
Точно так же мы можем объявить перечисление
внутри интерфейса:
interface HelloWorld {
enum DIRECTIONS {
NORTH, SOUTH, EAST, WEST;
}
}
Когда интерфейс HelloWorld
скомпилирован, компилятор добавит еще один файл класса с именем HelloWorld$Directon.
5.4. Перечисление внутри перечисления
Мы можем объявить перечисление
внутри другого перечисления
:
enum Foods {
DRINKS, EATS;
enum DRINKS {
APPLE_JUICE, COLA;
}
enum EATS {
POTATO, RICE;
}
}
Наконец, давайте взглянем на сгенерированные файлы классов:
Компилятор создает отдельный файл класса для каждого типа перечисления
.
6. Заключение
В этой статье мы видели различные соглашения об именах, используемые для файлов классов Java. Мы добавили классы, интерфейсы и перечисления в один файл Java и наблюдали, как компилятор создает для каждого из них отдельный файл класса.
Как всегда, примеры кода для этой статьи доступны на GitHub .