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

Как задать ограничения снизу для Generics

2.0 Middle🔥 61 комментариев
#ООП#Основы Java

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

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

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

# Ограничения снизу для Java Generics (Lower Bounded Wildcards)

Ограничение снизу (lower bounded wildcard) позволяет указать, что генерик-параметр должен быть определённым типом или его суперклассом. Синтаксис: ? super Type.

Основной синтаксис

// Ограничение сверху (upper bound) — ? extends Type
// Ограничение снизу (lower bound) — ? super Type

List<? super Number> numbers;  // Принимает Number, Object, но не Integer, Double

Когда использовать ограничения снизу

Ограничения снизу используются при записи данных в структуру. Это противоположно ограничениям сверху, которые используются при чтении.

// Ограничение сверху — для ЧТЕНИЯ (get)
List<? extends Number> numbers;  // Можем читать как Number

// Ограничение снизу — для ЗАПИСИ (put)
List<? super Integer> numbers;   // Можем писать Integer

Практические примеры

Пример 1: Метод, который добавляет элементы

public class CollectionUtils {
    
    // Метод принимает List, в которую можно добавить Integer
    // List может быть Integer, Number, Object и выше по иерархии
    public static void addIntegers(List<? super Integer> list) {
        list.add(1);
        list.add(2);
        list.add(3);
    }
    
    public static void main(String[] args) {
        List<Integer> integers = new ArrayList<>();
        addIntegers(integers);  // Работает
        
        List<Number> numbers = new ArrayList<>();
        addIntegers(numbers);   // Работает
        
        List<Object> objects = new ArrayList<>();
        addIntegers(objects);   // Работает
        
        // List<Double> doubles = new ArrayList<>();
        // addIntegers(doubles);  // Ошибка компиляции — Double не суперкласс Integer
    }
}

Пример 2: Копирование коллекций

public class DataProcessor {
    
    // Копируем элементы из источника в назначение
    // Source: читаем любые Number (extends Number)
    // Destination: пишем как Number (super Number)
    public static <T extends Number> void copy(
            List<? extends T> source,
            List<? super T> destination) {
        for (T item : source) {
            destination.add(item);
        }
    }
    
    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3);
        List<Number> numbers = new ArrayList<>();
        
        copy(integers, numbers);  // Работает
        System.out.println(numbers);  // [1, 2, 3]
    }
}

Пример 3: Comparator и ограничения снизу

public class Sorting {
    
    // Comparator<? super T> — может сравнивать T или его суперклассы
    public static <T> void sort(List<T> list, Comparator<? super T> comparator) {
        list.sort(comparator);
    }
    
    static class Animal {
        String name;
        public Animal(String name) { this.name = name; }
    }
    
    static class Dog extends Animal {
        public Dog(String name) { super(name); }
    }
    
    static class AnimalComparator implements Comparator<Animal> {
        @Override
        public int compare(Animal a1, Animal a2) {
            return a1.name.compareTo(a2.name);
        }
    }
    
    public static void main(String[] args) {
        List<Dog> dogs = Arrays.asList(
            new Dog("Rex"),
            new Dog("Buddy")
        );
        
        // AnimalComparator может сравнивать Dog, так как Dog extends Animal
        sort(dogs, new AnimalComparator());
    }
}

Пример 4: Комбинация верхних и нижних ограничений

public class Transform {
    
    // T — тип элементов в списке
    // List<? extends T> — читаем T или его подтипы
    // List<? super T> — пишем T в список (принимаем T или его суперклассы)
    public static <T> void transform(
            List<? extends T> source,
            List<? super T> destination,
            Function<T, T> transformer) {
        for (T item : source) {
            destination.add(transformer.apply(item));
        }
    }
    
    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3);
        List<Number> numbers = new ArrayList<>();
        
        transform(integers, numbers, x -> x * 2);
        System.out.println(numbers);  // [2, 4, 6]
    }
}

PECS правило (Producer Extends, Consumer Super)

Это мнемоническое правило для выбора между extends и super:

// Producer (производитель) — читаем из коллекции → используем extends
List<? extends Number> producer = new ArrayList<>();
Number num = producer.get(0);  // Можем читать как Number

// Consumer (потребитель) — пишем в коллекцию → используем super
List<? super Integer> consumer = new ArrayList<>();
consumer.add(1);  // Можем писать Integer

Сравнение с ограничениями сверху

АспектОграничение снизу ? super TОграничение сверху ? extends T
Синтаксис? super Integer? extends Number
НазначениеПисать в коллекциюЧитать из коллекции
Какие типыT и все суперклассыT и все подклассы
get()Возвращает ObjectВозвращает T
add()Принимает TОшибка компиляции
ПримерList<? super Integer>List<? extends Number>

Частые ошибки

// ❌ Ошибка — пытаемся читать из ? super Integer как конкретный тип
List<? super Integer> list = new ArrayList<>();
Integer value = list.get(0);  // Ошибка! Возвращает Object

// ✅ Правильно — читаем как Object
Object value = list.get(0);

// ❌ Ошибка — неправильное направление
List<Integer> integers = new ArrayList<>();
List<? super Number> list = integers;  // Ошибка! Integer не суперкласс Number

// ✅ Правильно
List<Number> numbers = new ArrayList<>();
List<? super Integer> list = numbers;  // OK

Выводы

  1. Ограничения снизу используются для методов, которые пишут в коллекцию
  2. Синтаксис: List<? super Type> — принимает Type и его суперклассы
  3. Противоположность extends — если extends для чтения, то super для записи
  4. PECS правило — Producer Extends, Consumer Super, облегчает запомнить
  5. Гибкость API — позволяет методам принимать более широкий спектр типов
Как задать ограничения снизу для Generics | PrepBro