Menu
Coddy logo textTech

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

Часть раздела Объектно-ориентированное программирование путешествия по 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>, вы сообщаете компилятору, что этот список будет содержать только строки. Если вы попытаетесь добавить целое число, компилятор немедленно обнаружит ошибку, вместо того чтобы произошел сбой во время выполнения.

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

challenge icon

Задание

Легко

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

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

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

    Сначала используйте старый подход: создайте 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 компилятор знает тип, и приведение не требуется. Это главное преимущество дженериков — типобезопасность без лишней мороки с приведением типов!

Шпаргалка

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

Без обобщений (используя Object):

List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0);  // Manual casting required

С обобщениями (типобезопасно):

List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0);  // No casting needed

Создание обобщенного класса:

public class GenericBox<T> {
    private T content;
    
    public GenericBox(T content) {
        this.content = content;
    }
    
    public T getContent() {
        return content;
    }
}

Использование обобщенного класса:

GenericBox<String> stringBox = new GenericBox<String>("Hello");
String value = stringBox.getContent();  // No casting

GenericBox<Integer> intBox = new GenericBox<Integer>(42);
Integer number = intBox.getContent();  // Type-safe

Угловые скобки <T> обозначают параметр типа. Преимущества включают: отсутствие необходимости в явном приведении типов и обнаружение ошибок типов на этапе компиляции, а не во время выполнения.

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

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, сохраняющий word
        // Извлеките со значением, приведенным к String, и выведите: "OldBox (with cast): [value]"
        
        // TODO: Создайте GenericBox<String>, сохраняющий word
        // Извлеките без приведения типов и выведите: "GenericBox (no cast): [value]"
        
        // TODO: Создайте GenericBox<Integer>, сохраняющий number
        // Извлеките и выведите: "GenericBox Integer: [value]"
        
        // TODO: Выведите пустую строку, затем выведите:
        // "Type safety: Generics catch errors at compile time!"
    }
}
quiz iconПроверьте себя

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

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