← Назад к вопросам

Какую задачу решал на Generics?

1.0 Junior🔥 211 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Практические задачи с Generics в Java

Generics — это мощный инструмент для создания типобезопасного кода. Давайте рассмотрим реальные задачи, которые решаются с помощью generics.

Задача 1: Универсальный Repository Pattern

Проблема: Нужно создать repository для различных типов сущностей без дублирования кода.

Решение с Generics:

// Generic base repository
public interface Repository<T, ID> {
    T findById(ID id);
    List<T> findAll();
    void save(T entity);
    void delete(T entity);
}

// Абстрактная реализация
public abstract class BaseRepository<T, ID> implements Repository<T, ID> {
    protected EntityManager entityManager;
    protected Class<T> entityClass;
    
    public BaseRepository(Class<T> entityClass) {
        this.entityClass = entityClass;
    }
    
    @Override
    public T findById(ID id) {
        return entityManager.find(entityClass, id);
    }
    
    @Override
    public List<T> findAll() {
        String query = "SELECT e FROM " + entityClass.getSimpleName() + " e";
        return entityManager.createQuery(query, entityClass).getResultList();
    }
    
    @Override
    public void save(T entity) {
        entityManager.persist(entity);
    }
}

// Конкретные реализации
@Repository
public class UserRepository extends BaseRepository<User, Long> {
    public UserRepository() {
        super(User.class);
    }
}

@Repository
public class ProductRepository extends BaseRepository<Product, Long> {
    public ProductRepository() {
        super(Product.class);
    }
}

Задача 2: Type-Safe Cache

Проблема: Нужен кэш, который гарантирует типизацию ключей и значений.

Решение с Generics:

public class TypeSafeCache {
    private Map<String, Object> cache = new HashMap<>();
    
    public <T> void put(String key, T value) {
        cache.put(key, value);
    }
    
    public <T> T get(String key, Class<T> type) {
        Object value = cache.get(key);
        if (value != null && type.isInstance(value)) {
            return type.cast(value);
        }
        return null;
    }
}

// Использование
TypeSafeCache cache = new TypeSafeCache();
cache.put("user_1", new User("Alice"));
cache.put("product_1", new Product("Laptop"));

User user = cache.get("user_1", User.class);
Product product = cache.get("product_1", Product.class);

Задача 3: Bounded Type Parameters

Проблема: Нужна функция, которая сравнивает объекты, реализующие Comparable.

Решение:

// Bounded generic — T должен быть Comparable
public class Comparator<T extends Comparable<T>> {
    
    public T max(List<T> list) {
        if (list.isEmpty()) {
            throw new IllegalArgumentException("List is empty");
        }
        
        T max = list.get(0);
        for (T item : list) {
            if (item.compareTo(max) > 0) {
                max = item;
            }
        }
        return max;
    }
    
    public T min(List<T> list) {
        if (list.isEmpty()) {
            throw new IllegalArgumentException("List is empty");
        }
        
        T min = list.get(0);
        for (T item : list) {
            if (item.compareTo(min) < 0) {
                min = item;
            }
        }
        return min;
    }
}

// Использование
Comparator<Integer> intComparator = new Comparator<>();
Integer max = intComparator.max(Arrays.asList(1, 5, 3, 9, 2)); // 9

Comparator<String> stringComparator = new Comparator<>();
String longest = stringComparator.max(
    Arrays.asList("hello", "world", "java") // world
);

Задача 4: Wildcard Generics

Проблема: Нужна функция, которая может работать с любым List, но также должна быть типобезопасной.

Решение:

// Функция для работы с List любого типа
public class CollectionUtils {
    
    // Extractor — получение данных из коллекции
    public static <T> List<String> toStringList(List<? extends T> list) {
        return list.stream()
            .map(Object::toString)
            .collect(Collectors.toList());
    }
    
    // Consumer — добавление в коллекцию
    public static <T> void addAll(List<? super T> list, T... items) {
        for (T item : items) {
            list.add(item);
        }
    }
    
    // PECS: Producer Extends, Consumer Super
    public static <T extends Comparable<T>> void sort(List<T> list) {
        java.util.Collections.sort(list);
    }
}

// Использование
List<String> strings = Arrays.asList("hello", "world");
List<String> stringRepresentation = CollectionUtils.toStringList(strings);

List<Object> objects = new ArrayList<>();
CollectionUtils.addAll(objects, "a", "b", "c");

Задача 5: Dependency Injection с Generics

Проблема: Spring не может различить beans, если используется List<Service>.

Решение с Generics:

// Создание специального типа для группировки сервисов
public class ServiceRegistry<T> {
    private List<T> services = new ArrayList<>();
    
    public void register(T service) {
        services.add(service);
    }
    
    public List<T> getAll() {
        return new ArrayList<>(services);
    }
    
    public T getFirst() {
        return services.isEmpty() ? null : services.get(0);
    }
}

// Использование в Spring
@Configuration
public class ServiceConfig {
    
    @Bean
    public ServiceRegistry<PaymentService> paymentServices() {
        ServiceRegistry<PaymentService> registry = new ServiceRegistry<>();
        registry.register(new StripePaymentService());
        registry.register(new PayPalPaymentService());
        return registry;
    }
}

// Инъекция
@Service
public class OrderService {
    private ServiceRegistry<PaymentService> paymentServices;
    
    public OrderService(ServiceRegistry<PaymentService> paymentServices) {
        this.paymentServices = paymentServices;
    }
    
    public void processOrder(Order order) {
        for (PaymentService service : paymentServices.getAll()) {
            if (service.supports(order.getPaymentType())) {
                service.process(order);
                break;
            }
        }
    }
}

Задача 6: Transformer Pattern

Проблема: Нужна функция преобразования объектов одного типа в другой.

Решение:

// Generic transformer
public interface Transformer<FROM, TO> {
    TO transform(FROM source);
}

// Implementation
public class UserToUserDTOTransformer implements Transformer<User, UserDTO> {
    @Override
    public UserDTO transform(User user) {
        return new UserDTO(user.getId(), user.getName(), user.getEmail());
    }
}

// Утилита для трансформации коллекций
public class TransformerUtil {
    public static <FROM, TO> List<TO> transform(
            List<FROM> source, 
            Transformer<FROM, TO> transformer) {
        return source.stream()
            .map(transformer::transform)
            .collect(Collectors.toList());
    }
}

// Использование
List<User> users = userRepository.findAll();
Transformer<User, UserDTO> transformer = new UserToUserDTOTransformer();
List<UserDTO> dtos = TransformerUtil.transform(users, transformer);

Задача 7: Event Handling с Generics

Проблема: Нужна система обработки событий различных типов.

Решение:

// Base event class
public abstract class Event {
    private LocalDateTime timestamp;
    
    public Event() {
        this.timestamp = LocalDateTime.now();
    }
}

// Specific events
public class UserCreatedEvent extends Event {
    private Long userId;
    private String email;
}

public class OrderPlacedEvent extends Event {
    private Long orderId;
    private BigDecimal amount;
}

// Generic event handler
public interface EventHandler<E extends Event> {
    void handle(E event);
}

// Implementations
@Component
public class UserCreatedEventHandler implements EventHandler<UserCreatedEvent> {
    @Override
    public void handle(UserCreatedEvent event) {
        // Send welcome email
        emailService.sendWelcome(event.getEmail());
    }
}

@Component
public class OrderPlacedEventHandler implements EventHandler<OrderPlacedEvent> {
    @Override
    public void handle(OrderPlacedEvent event) {
        // Create invoice
        invoiceService.create(event.getOrderId(), event.getAmount());
    }
}

// Event bus
@Service
public class EventBus {
    private Map<Class<? extends Event>, List<EventHandler<?>>> handlers = new HashMap<>();
    
    public <E extends Event> void subscribe(
            Class<E> eventClass, 
            EventHandler<E> handler) {
        handlers.computeIfAbsent(eventClass, k -> new ArrayList<>())
            .add(handler);
    }
    
    @SuppressWarnings("unchecked")
    public <E extends Event> void publish(E event) {
        List<EventHandler<?>> eventHandlers = handlers.get(event.getClass());
        if (eventHandlers != null) {
            for (EventHandler<?> handler : eventHandlers) {
                ((EventHandler<E>) handler).handle(event);
            }
        }
    }
}

Лучшие практики с Generics

  1. Используйте bounded types для ограничения:
public <T extends Number> void process(T value) { }
  1. Следуйте PECS (Producer Extends, Consumer Super):
// Producer (read) — используй extends
public <T> void addAll(List<? extends T> source, List<T> dest) { }

// Consumer (write) — используй super
public <T> void addAll(List<T> source, List<? super T> dest) { }
  1. Избегайте raw types:
// Плохо
List list = new ArrayList();

// Хорошо
List<String> list = new ArrayList<>();
  1. Используйте generic методы вместо generic классов где возможно:
// Если нужен generics только для одного метода
public <T> T getData(Class<T> type) { }

Вывод: Generics в Java решают проблемы типизации и повторного использования кода. Главные задачи: Repository Pattern, Type-Safe коллекции, Bounded parameters, Wildcard типы и паттерны проектирования как Event Handling и Dependency Injection.