Menu
Coddy logo textTech

Обобщенные классы

Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy — урок 54 из 87.

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

class Box<T> {
    private T content;
    
    public void set(T content) {
        this.content = content;
    }
    
    public T get() {
        return content;
    }
}

T — это параметр типа, заполнитель, который заменяется фактическим типом при создании экземпляра. Вы можете использовать любое имя, но общепринято использовать одиночные заглавные буквы: T для Type (Тип), E для Element (Элемент), K для Key (Ключ), V для Value (Значение).

При создании экземпляра универсального класса вы указываете фактический тип в угловых скобках:

Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String value = stringBox.get();  // Приведение типов не требуется

Box<Integer> intBox = new Box<>();
intBox.set(42);
Integer num = intBox.get();

Обратите внимание на <> (diamond-оператор) в правой части — Java выводит тип из объявления. Вы также можете использовать несколько параметров типа для классов, которым необходимо работать с более чем одним типом:

class Pair<K, V> {
    private K key;
    private V value;
    
    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
    
    public K getKey() { return key; }
    public V getValue() { return value; }
}

Pair<String, Integer> pair = new Pair<>("age", 25);
challenge icon

Задание

Легко

Давайте создадим систему реестра подарков, которая продемонстрирует мощь универсальных классов (generics). Вы создадите гибкую систему хранения, способную содержать любой тип подарка, а также реестр, который связывает имена получателей с деталями подарков, используя несколько параметров типа.

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

  • GiftBox.java: Создайте универсальный класс, который может упаковать любой тип подарка. Ваш GiftBox<T> должен иметь приватное поле item типа T и приватное поле wrapped (boolean), которое изначально равно false. Добавьте конструктор, принимающий предмет, метод getItem(), возвращающий предмет, метод wrap(), который устанавливает wrapped в true, и метод isWrapped(), возвращающий статус упаковки. Добавьте метод getStatus(), который возвращает либо [item] (wrapped), либо [item] (unwrapped) в зависимости от состояния упаковки.
  • Registry.java: Создайте универсальный класс с двумя параметрами типа, который связывает получателя с его подарком. Ваш Registry<K, V> должен иметь приватные поля recipient типа K и gift типа V. Добавьте конструктор, принимающий оба значения, методы-геттеры getRecipient() и getGift(), а также метод getEntry(), который возвращает: [recipient] -> [gift]
  • Main.java: Оживите ваш реестр подарков! Вы получите три входных значения: имя получателя (String), описание подарка (String) и стоимость подарка (целое число, представляющее доллары).

    Сначала создайте GiftBox<String>, содержащий описание подарка. Упакуйте его с помощью метода wrap(), затем выведите его статус.

    Затем создайте GiftBox<Integer>, содержащий стоимость подарка (представляющую сумму подарочной карты). Не упаковывайте этот подарок — выведите его статус, чтобы показать, что он не упакован.

    Наконец, создайте Registry<String, String>, связывающий имя получателя с описанием подарка, и выведите запись реестра.

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

Обратите внимание, как один и тот же класс GiftBox беспрепятственно работает как с типом String, так и с типом Integer — в этом и заключается прелесть дженериков! А класс Registry демонстрирует, как использование нескольких параметров типа позволяет создавать гибкие пары между любыми двумя типами.

Шпаргалка

Обобщенный класс (generic class) объявляет один или несколько параметров типа в угловых скобках после имени класса, которые могут использоваться во всем классе:

class Box<T> {
    private T content;
    
    public void set(T content) {
        this.content = content;
    }
    
    public T get() {
        return content;
    }
}

T — это параметр типа — заполнитель, заменяемый реальным типом при создании экземпляра. По общепринятым соглашениям используются одиночные заглавные буквы: T для типа (Type), E для элемента (Element), K для ключа (Key), V для значения (Value).

При создании экземпляра обобщенного класса укажите фактический тип в угловых скобках:

Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String value = stringBox.get();  // No casting needed

Box<Integer> intBox = new Box<>();
intBox.set(42);
Integer num = intBox.get();

Оператор <> (diamond operator) позволяет Java выводить тип из объявления.

Используйте несколько параметров типа для классов, которые работают с более чем одним типом:

class Pair<K, V> {
    private K key;
    private V value;
    
    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
    
    public K getKey() { return key; }
    public V getValue() { return value; }
}

Pair<String, Integer> pair = new Pair<>("age", 25);

Попробуйте сами

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Чтение входных данных
        String recipientName = scanner.nextLine();
        String giftDescription = scanner.nextLine();
        int giftValue = scanner.nextInt();
        
        // TODO: Создайте GiftBox<String>, содержащий описание подарка
        // TODO: Упакуйте его с помощью метода wrap()
        // TODO: Выведите его статус с помощью getStatus()
        
        // TODO: Создайте GiftBox<Integer>, содержащий стоимость подарка
        // TODO: Не упаковывайте этот подарок — выведите его статус (должно отобразиться unwrapped)
        
        // TODO: Создайте Registry<String, String>, связывающий получателя с описанием подарка
        // TODO: Выведите запись реестра с помощью getEntry()
    }
}
quiz iconПроверьте себя

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

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