Menu
CoddyTech

Введение в обобщения

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

Представь, что тебе нужно создать класс, который хранит значение и возвращает его позже. Без обобщений пришлось бы писать отдельный класс для каждого типа: один для Integer, один для String, один для Double и так далее. Это приводит к дублированию кода и проблемам с его поддержкой.

Обобщения решают эту проблему, позволяя писать классы и методы, которые работают с типами любого вида, сохраняя при этом безопасность типов. Вы определяете заполнитель (называемый параметром типа), который заменяется фактическим типом при использовании класса:

// Без обобщений — используется Object, требуется приведение типа
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0);  // Требуется явное приведение типа

// С обобщениями — обеспечивается типобезопасность, приведение типа не требуется
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0);  // Приведение типа не требуется

Угловые скобки <T> обозначают параметр типа. Когда ты пишешь List<String>, ты сообщаешь компилятору, что этот список будет содержать только строки. Если ты попытаешься добавить целое число, компилятор сразу обнаружит ошибку, а не позволит ей проявиться во время выполнения.

Обобщения дают два ключевых преимущества: они устраняют необходимость в явном приведении типов и позволяют обнаруживать ошибки типов во время компиляции, а не во время выполнения. Благодаря этому код становится безопаснее и чище. В следующих уроках мы рассмотрим, как создавать собственные обобщённые классы и методы.

challenge icon

Задание

Легко

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

Чтобы увидеть разницу на практике, вы создадите три файла:

  • OldBox.java: создайте класс, который хранит значения по старому, дообобщённому принципу. В вашем OldBox должно быть приватное поле content типа Object. Добавьте конструктор, который принимает Object и сохраняет его, а также метод getContent(), который возвращает Object. Этот подход работает, но при получении значений требуется приведение типов.
  • GenericBox.java: теперь создайте современную версию с проверкой типов, используя обобщения. В объявлении вашего GenericBox должен использоваться параметр типа T. В нём должны быть приватное поле content типа T, конструктор, принимающий значение типа T, и метод getContent(), возвращающий тип T. При использовании этого класса приведение типов не требуется!
  • Main.java: продемонстрируйте оба подхода рядом. Вы получите два входных значения: слово (String) и число (целое число).

    Сначала используйте старый подход: создайте OldBox, хранящий слово, затем получите его, приведя к String, и выведите: OldBox (with cast): [value]

    Затем используйте подход с обобщениями: создайте GenericBox<String>, хранящий то же слово, получите его без приведения типов и выведите: GenericBox (no cast): [value]

    Затем продемонстрируйте работу обобщений с целыми числами: создайте GenericBox<Integer>, хранящий число, получите его и выведите: GenericBox Integer: [value]

    Наконец, выведите пустую строку, а затем: Type safety: Generics catch errors at compile time!

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

Обратите внимание на ключевое отличие: с OldBox нужно привести тип с помощью (String) box.getContent() и надеяться, что тип указан правильно. С GenericBox компилятор знает тип, и приведение типов не требуется. В этом и заключается основное преимущество обобщений: проверка типов без хлопот с приведением типов!

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

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String word = scanner.nextLine();
        int number = Integer.parseInt(scanner.nextLine());
        
        // TODO: Создайте OldBox, в котором хранится слово
        // Получите значение с приведением к String и выведите: "OldBox (with cast): [value]"
        
        // TODO: Создайте GenericBox<String>, в котором хранится слово
        // Получите значение без приведения типа и выведите: "GenericBox (no cast): [value]"
        
        // TODO: Создайте GenericBox<Integer>, в котором хранится число
        // Получите значение и выведите: "GenericBox Integer: [value]"
        
        // TODO: Выведите пустую строку, затем выведите:
        // "Type safety: Generics catch errors at compile time!"
    }
}
quiz iconПроверьте себя

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор Java