Введение в обобщения
Часть раздела Объектно-ориентированное программирование путешествия по 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) предоставляют два ключевых преимущества: они устраняют необходимость в явном приведении типов и позволяют обнаруживать ошибки типизации на этапе компиляции, а не во время выполнения. Это делает ваш код более безопасным и чистым. В следующих уроках мы изучим, как создавать собственные обобщенные классы и методы.
Задание
ЛегкоДавайте создадим простую систему хранения, которая демонстрирует мощь дженериков (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!"
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»