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

Что сделать, чтобы интерфейс принимал и возвращал значения

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 ? extendsProductionКовариантностьНе даёт писать
Wildcards ? superConsumptionКонтравариантностьНе даёт читать
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();

Итоги

  1. Используй <T> для одного типа параметра
  2. Используй <IN, OUT> когда нужны разные типы
  3. Используй extends для ограничения типов
  4. Используй ? extends для чтения (production)
  5. Используй ? super для записи (consumption)
  6. Помни про type erasure - generics работают только в compile time
Что сделать, чтобы интерфейс принимал и возвращал значения | PrepBro