Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Практические задачи с 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
- Используйте bounded types для ограничения:
public <T extends Number> void process(T value) { }
- Следуйте 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) { }
- Избегайте raw types:
// Плохо
List list = new ArrayList();
// Хорошо
List<String> list = new ArrayList<>();
- Используйте generic методы вместо generic классов где возможно:
// Если нужен generics только для одного метода
public <T> T getData(Class<T> type) { }
Вывод: Generics в Java решают проблемы типизации и повторного использования кода. Главные задачи: Repository Pattern, Type-Safe коллекции, Bounded parameters, Wildcard типы и паттерны проектирования как Event Handling и Dependency Injection.