Гид по технологиям

Generics в Java: пример с Promotion и Customer

5 min read Java Обновлено 01 Jan 2026
Generics в Java: Promotion и Customer
Generics в Java: Promotion и Customer

Силуэты людей на бумажных фигурах, символизирующие обобщённые типы

Generics — это концепция программирования, позволяющая указать тип данных, который будет храниться в коллекции или использоваться в классе/методе. Обобщённый (generic) тип обычно представляет собой ссылочный тип (не примитив). В практическом примере ниже мы используем Customer как базовый обобщённый тип и создаём типо-безопасный класс Promotion, который хранит победителей акции.

Краткое объяснение ключевых терминов

  • Generics: механизм параметризации типа в Java.
  • Параметр типа: буква или имя в угловых скобках, например T или E.
  • Ограничение (bounded type): запись T extends Customer ограничивает допустимые типы.

Создание обобщённого типа (Customer)

В примере Customer реализован как абстрактный класс — это означает, что напрямую объекты Customer создать нельзя; нужно использовать подклассы. Абстрактный базовый тип служит «контрактом» для конкретных типов клиентов из разных городов.

public abstract class Customer {  
  private String name;  
  public Customer(String name) {  
    this.name = name;  
  }  
  public String getName() {  
    return name;  
  }  
}

Обратите внимание: ключевое слово abstract запрещает создание экземпляра Customer напрямую.

Каждое физическое местоположение магазина имеет собственный подкласс Customer. Это даёт возможность различать клиентов по «жёстким» типам:

public class City1Customer extends Customer{  
  public City1Customer(String name) {  
    super(name);  
  }  
}

(Аналогично можно создать City2Customer и City3Customer.)

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

Чтобы использовать обобщённый тип, нужен обобщённый класс или метод, принимающий этот тип как параметр. В объявлении класса вместо конкретного типа ставится параметр типа в угловых скобках:

public class Promotion {}

Если нужно ограничить допустимые типы только подтипами Customer, используется ограничение:

public class Promotion {}

Это значит: Promotion может параметризоваться только типами, унаследованными от Customer.

Полная реализация класса Promotion, используемая в примере:

import java.util.ArrayList;

public class Promotion {

  private String promoName;     
  private ArrayList winners = new ArrayList<>();

  public Promotion(String promoName) {
    this.promoName = promoName;
  }

  public String getPromoName() {
    return promoName;
  }

  public void addCustomer(T customer) {
    if (winners.contains(customer)) {
      System.out.println( customer.getName() + " is already a winner of this prize.");      
    } else {
      winners.add(customer);
      System.out.println( customer.getName() + " is a winner in the " + this.promoName);
    }
  }

  public int numWinners() {
    return this.winners.size();
  }           
}

Класс Promotion хранит имя акции promoName и список победителей winners. Метод addCustomer добавляет клиента, предотвращая дубли, а numWinners возвращает количество победителей.

Создание коллекций с использованием обобщённого класса

Сначала создаём объекты клиентов:

public class Main {
  public static void main(String[] args) {
    City1Customer john = new City1Customer("John Brown");
    City1Customer kelly = new City1Customer("Kelly James");
    City2Customer jane = new City2Customer("Jane Doe");
    City3Customer jess = new City3Customer("Jess Smith");
  }
}

Далее создаём объекты Promotion, параметризованные соответствующими типами клиентов:

Promotion city1promo = new Promotion("City1 Promo");
Promotion city2promo = new Promotion("City2 Promo");
Promotion city3promo = new Promotion("City3 Promo");

Затем добавляем клиентов в соответствующие промо-списки:

city1promo.addCustomer(john);
city1promo.addCustomer(kelly);

В консоли это даст вывод победителей для первого города:

Консольный вывод: победители акции для первого города

Если попытаться добавить клиента из другого города в список не его города, IDE укажет на ошибку компиляции:

Сообщение об ошибке компиляции в IDE из-за несовместимого типа

Это защищает данные: Jane и Jess не являются клиентами первого магазина, и их нельзя добавить в Promotion.

Правильный вызов для второго города:

city2promo.addCustomer(jane);

Результат для второго города:

Консольный вывод: победитель акции для второго города

Преимущества использования обобщённых типов

  • Проверка типов во время компиляции: исключает ошибки несовместимых типов до запуска программы.
  • Переиспользуемость кода: один класс Promotion заменяет три почти идентичных подкласса.
  • Масштабируемость: при расширении компании на новые города достаточно добавить новый подкласс Customer и параметризовать Promotion.
  • Ясность намерений API: сигнатура Promotion явно показывает, какие типы допустимы.

Когда generics не подходят или вызывают сложности

  1. Нужны операции с примитивами: Java-дженерики работают только с ссылочными типами, не с примитивами (int, boolean). В таких случаях используют упаковочные типы (Integer, Boolean) или специализированные коллекции (например, IntStream, Trove, fastutil).
  2. Нужна сериализация с сохранением конкретного типа времени выполнения: из-за стирания типов (type erasure) информация о параметре типа недоступна в рантайме без дополнительных приёмов (Class в конструкторе или TypeToken).
  3. Сложные правила ковариантности/контравариантности: для чтения/записи нужны wildcard-типовые параметры (? extends T, ? super T), что усложняет API.

Пример: если нужен список, который может принимать и возвращать разные подтипы Customer в гибкой форме, придётся использовать подходы с wildcard или дополнительными интерфейсами.

Альтернативные подходы

  • Композиция вместо наследования: хранить в поле Customer, а не наследовать от него, если поведение сильно различается.
  • Runtime-проверки: использовать Object и проверять тип во время выполнения (устаревший и опасный подход).
  • Шаблоны проектирования (стратегия, фабрика): обеспечить различное поведение промо через паттерны, а не через разные типы.

Практическая методология внедрения generics (мини-SOP)

  1. Определите доменные сущности, которые могут выступать параметризованными типами (например, Customer).
  2. Выделите повторяющуюся логику (Promotion) и сделайте её параметризованной типом.
  3. Ограничьте параметр типа (T extends Customer), если нужно контроль допустимых типов.
  4. Добавьте unit-тесты для типичных сценариев: добавление, дубликаты, границы.
  5. При необходимости используйте Class для сохранения информации о типе в рантайме.

Чек‑лист по ролям

  • Разработчик:
    • Использовать bounded generics для доменных ограничений.
    • Не забывать про equals/hashCode для объектов, которые хранятся в списках/множествах.
  • Архитектор:
    • Оценить влияние type erasure на сериализацию и отражение.
    • Решить, где нужна ковариантность через wildcard.
  • Тестировщик:
    • Проверить, что добавление неподходящего типа компилируется с ошибкой.
    • Написать тесты на дедупликацию и границы списка.

Тестовые случаи и критерии приёмки

  • Критерии приёмки:

    • Promotion принимает только подклассы Customer.
    • При добавлении одного и того же объекта дважды не происходит дублирования.
    • numWinners() возвращает корректное число победителей.
  • Тестовые сценарии:

    1. Добавить двух разных клиентов одного города — ожидается size() == 2.
    2. Добавить один и тот же объект дважды — ожидается size() == 1 и сообщение о том, что клиент уже выиграл.
    3. Попытка компиляции при добавлении клиента другого города — тест компиляции/статического анализа.

Ментальные модели и эвристики

  • Модель «контракта»: обобщённый тип — это обещание: “Promotion будет работать только с типами, которые соответствуют Customer”.
  • Эвристика «параметризация вместо наследования»: если у вас повторяется одна и та же структура с безопасной вариацией по типам данных — используйте generics.

Короткий словарь (1 строка)

  • Generics: параметризация типов в Java для обеспечения типовой безопасности и повторного использования кода.

Риски и смягчения

  • Риск: потеря информации о типе в рантайме (type erasure). Смягчение: передавать Class при создании или использовать сериализацию с метаданными.
  • Риск: неправильное использование wildcard (? extends / ? super) ведёт к сложному API. Смягчение: документировать намерения API и давать примеры использования.

Краткое резюме

Generics в Java — мощный инструмент для написания типобезопасного, повторно используемого и масштабируемого кода. Пример с Promotion иллюстрирует основные преимущества: статическую проверку типов, отсутствие дублирования классов для каждого города и простоту расширения при добавлении новых типов клиентов. Однако существует ряд ограничений (type erasure, работа с примитивами), которые нужно учитывать при проектировании.

Важно: выбор между generics, композицией или runtime-проверками зависит от требований проекта: безопасности типов, производительности и потребностей сериализации.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Как подключить Bluetooth‑наушники к Fire TV Stick
Руководство

Как подключить Bluetooth‑наушники к Fire TV Stick

Как смотреть фильмы в Kodi
Медиа

Как смотреть фильмы в Kodi

Как транслировать экран на Amazon Fire TV Stick
How-to

Как транслировать экран на Amazon Fire TV Stick

Веб-интерфейс Kodi: настройка и управление
Руководство

Веб-интерфейс Kodi: настройка и управление

Как ускорить Amazon Fire TV Stick
Руководства

Как ускорить Amazon Fire TV Stick

Устранение неполадок Fire TV Stick
Руководство

Устранение неполадок Fire TV Stick