Внутренние, вложенные и анонимные классы
Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy — урок 49 из 87.
Java позволяет определять классы внутри других классов. Эти внутренние классы помогают организовать код, группируя связанную функциональность, и могут напрямую обращаться к членам внешнего класса.
Вложенный внутренний класс определяется как член другого класса. Он имеет доступ ко всем полям и методам внешнего класса, включая приватные (private):
class Outer {
private String message = "Hello";
class Inner {
void display() {
System.out.println(message); // Может обращаться к приватному полю
}
}
}
// Использование
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.display(); // Выводит: HelloЛокальный внутренний класс определяется внутри метода. Он может использоваться только внутри этого метода и может обращаться к локальным переменным, которые являются фактически неизменяемыми (effectively final):
void process() {
String prefix = "Result: ";
class LocalHelper {
void print(int value) {
System.out.println(prefix + value);
}
}
new LocalHelper().print(42);
}Анонимный внутренний класс — это класс без имени, создаваемый и инициализируемый в одном выражении. Он обычно используется для реализации интерфейсов или расширения классов «на лету»:
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
};Внутренние классы особенно полезны, когда класс имеет отношение только к одному другому классу, что позволяет сохранять ваш код организованным и инкапсулированным.
Задание
ЛегкоДавайте создадим систему корзины покупок, которая демонстрирует все три типа внутренних классов. Вы создадите класс ShoppingCart, который использует вложенный внутренний класс для представления товаров, локальный внутренний класс для расчета скидок и анонимный внутренний класс для форматирования итогового чека.
Вы организуете свой код в двух файлах:
ShoppingCart.java: Создайте внешний класс, который управляет процессом покупки. Ваш ShoppingCart должен иметь приватное полеcustomerName(String).Внутри ShoppingCart определите вложенный внутренний класс с именем
Itemс двумя приватными полями:name(String) иprice(double). Дайте Item конструктор для инициализации обоих полей и методgetDetails(), который возвращает:[name]: $[price]Вашему ShoppingCart нужен конструктор, принимающий имя клиента, и метод с именем
checkout(), который принимает Item и процент скидки (double). Внутриcheckout():- Создайте локальный внутренний класс с именем
DiscountCalculatorс методомapplyDiscount(double originalPrice), который возвращает цену после применения процента скидки. - Используйте локальный класс для расчета итоговой цены.
- Создайте анонимный внутренний класс, который реализует интерфейс
Formatter(вы его определите) для форматирования чека. Анонимный класс должен переопределять методformat(), который возвращает многострочную строку чека.
Метод
checkout()должен возвращать отформатированную строку чека в следующем точном формате:--- Receipt --- Customer: [customerName] Item: [item details from getDetails()] Discount: [discount]% Final Price: $[calculated price]- Создайте локальный внутренний класс с именем
Formatter.java: Определите простой интерфейс с одним методом:String format(). Этот интерфейс будет реализован вашим анонимным внутренним классом внутри метода checkout.Main.java: Оживите свою корзину покупок! Вы получите три входных значения: имя клиента (String), название товара (String) и цена товара (double). Процент скидки фиксирован и равен10.0.Создайте ShoppingCart с именем клиента. Затем создайте Item, используя синтаксис ссылки на внешний класс:
cart.new Item(name, price). Вызовитеcheckout()с товаром и скидкой и выведите полученный чек.
Вы получите три входных значения по порядку: имя клиента, название товара и цена товара.
Например, если входные данные — Alice, Laptop и 1000.0, итоговая цена после 10% скидки составит 900.0.
Обратите внимание, как каждый тип внутреннего класса служит разным целям: вложенный класс группирует связанные данные с внешним классом, локальный класс инкапсулирует логику, необходимую только внутри одного метода, а анонимный класс обеспечивает быструю разовую реализацию интерфейса!
Шпаргалка
Java позволяет определять классы внутри других классов. Эти внутренние классы помогают организовать код, группируя связанную функциональность, и могут напрямую обращаться к членам внешнего класса.
Вложенный внутренний класс
Вложенный внутренний класс определяется как член другого класса. Он имеет доступ ко всем полям и методам внешнего класса, включая приватные:
class Outer {
private String message = "Hello";
class Inner {
void display() {
System.out.println(message); // Может обращаться к приватному полю
}
}
}
// Использование
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.display(); // Выводит: HelloЛокальный внутренний класс
Локальный внутренний класс определяется внутри метода. Его можно использовать только внутри этого метода, и он может обращаться к локальным переменным, которые являются фактически финальными (effectively final):
void process() {
String prefix = "Result: ";
class LocalHelper {
void print(int value) {
System.out.println(prefix + value);
}
}
new LocalHelper().print(42);
}Анонимный внутренний класс
Анонимный внутренний класс — это класс без имени, созданный и инициализированный в одном выражении. Он обычно используется для реализации интерфейсов или расширения классов «на лету»:
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
};Внутренние классы особенно полезны, когда класс имеет отношение только к одному другому классу, что позволяет сохранять код организованным и инкапсулированным.
Попробуйте сами
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Считать входные данные
String customerName = scanner.nextLine();
String itemName = scanner.nextLine();
double itemPrice = scanner.nextDouble();
double discount = 10.0;
// TODO: Создать ShoppingCart с именем клиента
// TODO: Создать Item, используя: cart.new Item(itemName, itemPrice)
// TODO: Вызвать checkout() с item и discount, затем вывести результат
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово thisМетодыПоля (атрибуты)Метод-конструкторПерегрузка конструкторовИтоги — Простой калькулятор4Наследование
Основы наследования (extends)Ключевое слово superПереопределение методов (@Override)Цепочка конструкторовКласс ObjectОдиночное и многоуровневое наследованиеПочему нет множественного наследования классовИтоги — Иерархия сотрудников7Специальные методы и класс Object
Метод toString()equals() и hashCode()Метод clone()compareTo() и ComparableИнтерфейс ComparatorПовторение — Кастомная сортировка2Модификаторы доступа и инкапсуляция
Обзор уровней доступаМетоды геттеры и сеттерыСокрытие данныхКлючевое слово finalИтоги — Менеджер банковского счета5Полиморфизм
Основы перегрузки методовПереопределение методов (Run-Time)Upcasting и DowncastingОператор instanceofАбстрактные классы и методыИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияАгрегация против композицииВнутренние, вложенные и анонимные классыEnums и методы перечисленийRecords (Java 16+)Sealed-классы (Java 17+)11Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy3Свойства класса и статические члены
Переменные экземпляра и статические переменныеСтатические методыСтатические блокиКонстанты (static final)Итоги: Счетчик и утилиты6Интерфейсы и абстрактные классы
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовDefault и Static методы в интерфейсахАбстрактные классы vs ИнтерфейсыФункциональные интерфейсыИтоги — Платежная система9Обобщения
Введение в обобщенияОбобщенные классыОбобщенные методыОграниченные параметры типаСимволы подстановки (Wildcards: ?, extends, super)Итоги — Обобщенный контейнер12Паттерны проектирования. Часть 2
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»