Menu
Coddy logo textTech

Внутренние, вложенные и анонимные классы

Часть раздела Объектно-ориентированное программирование путешествия по 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();
    }
};

Внутренние классы особенно полезны, когда класс имеет отношение только к одному другому классу, что позволяет сохранять ваш код организованным и инкапсулированным.

challenge icon

Задание

Легко

Давайте создадим систему корзины покупок, которая демонстрирует все три типа внутренних классов. Вы создадите класс ShoppingCart, который использует вложенный внутренний класс для представления товаров, локальный внутренний класс для расчета скидок и анонимный внутренний класс для форматирования итогового чека.

Вы организуете свой код в двух файлах:

  • ShoppingCart.java: Создайте внешний класс, который управляет процессом покупки. Ваш ShoppingCart должен иметь приватное поле customerName (String).

    Внутри ShoppingCart определите вложенный внутренний класс с именем Item с двумя приватными полями: name (String) и price (double). Дайте Item конструктор для инициализации обоих полей и метод getDetails(), который возвращает: [name]: $[price]

    Вашему ShoppingCart нужен конструктор, принимающий имя клиента, и метод с именем checkout(), который принимает Item и процент скидки (double). Внутри checkout():

    1. Создайте локальный внутренний класс с именем DiscountCalculator с методом applyDiscount(double originalPrice), который возвращает цену после применения процента скидки.
    2. Используйте локальный класс для расчета итоговой цены.
    3. Создайте анонимный внутренний класс, который реализует интерфейс 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, затем вывести результат
        
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование