← Назад к вопросам
Что сделать, чтобы интерфейс принимал и возвращал значения
1.0 Junior🔥 91 комментариев
#Другое
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что сделать, чтобы интерфейс принимал и возвращал значения
Это вопрос о generic типах и bounded wildcards в Java интерфейсах. Рассмотрю различные подходы.
Проблема
Основная задача — создать интерфейс, который может работать с разными типами данных, но при этом контролировать, какие значения можно передавать и возвращать.
Решение 1: Generic интерфейс с одним типом
// Интерфейс принимает и возвращает один и тот же тип
public interface Container<T> {
void put(T value);
T get();
}
// Реализация
public class StringContainer implements Container<String> {
private String value;
@Override
public void put(String value) {
this.value = value;
}
@Override
public String get() {
return value;
}
}
// Использование
Container<String> container = new StringContainer();
container.put("Hello");
String result = container.get(); // Hello
Решение 2: Generic интерфейс с несколькими типами
Если нужны разные типы для входа и выхода:
public interface Converter<IN, OUT> {
OUT convert(IN input);
}
// Реализация
public class StringToIntegerConverter implements Converter<String, Integer> {
@Override
public Integer convert(String input) {
return Integer.parseInt(input);
}
}
// Использование
Converter<String, Integer> converter = new StringToIntegerConverter();
Integer result = converter.convert("123"); // 123
Решение 3: Bounded wildcards (ограниченные типы)
Если нужно ограничить, какие типы принимаются:
// Принимает только Number и его подклассы
public interface Calculator<T extends Number> {
T add(T a, T b);
T multiply(T a, T b);
}
// Реализация
public class IntegerCalculator implements Calculator<Integer> {
@Override
public Integer add(Integer a, Integer b) {
return a + b;
}
@Override
public Integer multiply(Integer a, Integer b) {
return a * b;
}
}
// Использование
Calculator<Integer> calc = new IntegerCalculator();
Integer result = calc.add(5, 3); // 8
Решение 4: Multiple bounds (несколько ограничений)
// Тип должен быть и Number, и Comparable
public interface Comparable<T extends Number & java.lang.Comparable<T>> {
boolean isGreater(T a, T b);
}
public class IntegerComparable implements Comparable<Integer> {
@Override
public boolean isGreater(Integer a, Integer b) {
return a > b;
}
}
Решение 5: Wildcard с нижней границей (super)
Когда нужно принимать значения, но ограничить выход:
public interface Producer<T> {
// Производит значения типа T
T produce();
}
public interface Consumer<T> {
// Потребляет значения типа T
void consume(T value);
}
// Метод, который работает с Producer super типов
public static void printNumbers(Producer<? super Integer> producer) {
// Если producer производит Number или Object, это допустимо
}
Решение 6: PECS правило (Producer Extends, Consumer Super)
// Producer (return type) - используй extends
public interface Source<T> {
T get();
}
// Consumer (parameter type) - используй super
public interface Sink<T> {
void put(T value);
}
// Метод, который читает данные
public static void copy(Source<? extends Number> source) {
Number n = source.get(); // Работает
}
// Метод, который пишет данные
public static void copy(Sink<? super Integer> sink) {
sink.put(5); // Работает
}
Решение 7: Вложенные типы (nested generics)
public interface Repository<T> {
List<T> findAll();
Optional<T> findById(Long id);
void save(T entity);
}
public class UserRepository implements Repository<User> {
@Override
public List<User> findAll() {
return new ArrayList<>();
}
@Override
public Optional<User> findById(Long id) {
return Optional.empty();
}
@Override
public void save(User entity) {}
}
// Использование
Repository<User> repo = new UserRepository();
List<User> users = repo.findAll();
Решение 8: Интерфейс с type token (для runtime информации)
public interface Parser<T> {
T parse(String input);
Class<T> getType();
}
public class IntegerParser implements Parser<Integer> {
@Override
public Integer parse(String input) {
return Integer.parseInt(input);
}
@Override
public Class<Integer> getType() {
return Integer.class;
}
}
Сравнение подходов
| Подход | Использование | Плюсы | Минусы |
|---|---|---|---|
| Один generic <T> | Контейнер одного типа | Просто | Только один тип |
| Два generics <IN, OUT> | Конвертеры | Гибко | Более сложно |
| Bounded <T extends X> | Работа с иерархией | Типобезопасно | Ограничивает типы |
| Wildcards ? extends | Production | Ковариантность | Не даёт писать |
| Wildcards ? super | Consumption | Контравариантность | Не даёт читать |
| Multiple bounds | Специальные случаи | Мощно | Редко нужно |
Практический пример с Spring
@Repository
public interface CrudRepository<T, ID> {
<S extends T> S save(S entity);
Optional<T> findById(ID id);
List<T> findAll();
void delete(T entity);
}
// Использование
@Repository
public interface UserRepository extends CrudRepository<User, Long> {
Optional<User> findByEmail(String email);
}
Ошибки которых избегать
// Плохо - нет generics
public interface Container {
void put(Object value);
Object get();
}
// Хорошо - типизировано
public interface Container<T> {
void put(T value);
T get();
}
// Плохо - тип stricken at runtime
if (container instanceof Container<String>) {} // Ошибка компиляции
// Хорошо - использовать type token
Class<?> type = container.getClass();
Итоги
- Используй <T> для одного типа параметра
- Используй <IN, OUT> когда нужны разные типы
- Используй extends для ограничения типов
- Используй ? extends для чтения (production)
- Используй ? super для записи (consumption)
- Помни про type erasure - generics работают только в compile time