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

Почему нельзя присвоить коллекции Number коллекцию Integer?

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

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

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

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

Инвариантность типов коллекций в Java: почему Collection<Number> не может содержать Collection<Integer>

Это одна из самых важных концепций дженериков Java, и неправильное понимание этого может привести к критическим ошибкам типобезопасности.

Проблема: интуитивное, но неправильное решение

На первый взгляд, кажется логичным:

Integer интуитивно выглядит как подкласс Number
Integer extends Number // верно

// Поэтому Collection<Integer> должна быть подтипом Collection<Number>, правда?
// ❌ НЕПРАВИЛЬНО!

Collection<Number> numbers = new ArrayList<Integer>();  // Ошибка компиляции!

Но это вызвало бы серьезную проблему безопасности типов:

Collection<Number> numbers = new ArrayList<Integer>();
numbers.add(3.14);  // Double является Number
// Но ArrayList содержит только Integer!
// → ClassCastException при получении

Концепция инвариантности (Invariance)

Инвариантность означает, что Collection<Integer> и Collection<Number> — это полностью разные типы, несвязанные иерархией:

// Два независимых типа:
Collection<Integer> integers = new ArrayList<>();
Collection<Number> numbers = new ArrayList<>();

// Вы не можете присвоить одно другому:
numbers = integers;      // ❌ Ошибка компиляции
integers = numbers;      // ❌ Ошибка компиляции

Это правило действует потому, что коллекция — изменяемая структура данных. Вы можете добавлять элементы, и Java должна гарантировать полную тип-безопасность.

Правильные решения с использованием Wildcards

1. Ковариантность (Covariance) — только для чтения:

public void processNumbers(Collection<? extends Number> numbers) {
    for (Number num : numbers) {
        System.out.println(num);  // Безопасно читать
    }
    // numbers.add(3.14);  // ❌ Ошибка! Не можете добавлять
}

// Теперь вы можете передать Collection<Integer>
List<Integer> integers = Arrays.asList(1, 2, 3);
processNumbers(integers);  // ✅ Работает!

2. Контравариантность (Contravariance) — только для записи:

public void addNumbers(Collection<? super Integer> numbers) {
    numbers.add(42);  // ✅ Безопасно добавлять Integer
    // Number num = numbers.get(0);  // ❌ Не можете читать как Number
}

List<Number> numberList = new ArrayList<>();
addNumbers(numberList);  // ✅ Работает!

3. Инвариантность (Invariance) — полная поддержка:

public void workWithNumbers(Collection<Number> numbers) {
    // Можете читать и писать:
    Number num = numbers.stream().findFirst().orElse(null);
    numbers.add(3.14);
}

// Но коллекция должна быть точно Collection<Number>:
List<Number> numberList = new ArrayList<>();
workWithNumbers(numberList);  // ✅ Работает!

Почему именно инвариантность?

Пример опасности без инвариантности:

// Представьте, если бы Collection<Integer> был подтипом Collection<Number>
List<Integer> intList = new ArrayList<>();
intList.add(100);

Collection<Number> numbers = intList;  // Если бы это было разрешено
numbers.add(3.14);  // Double является Number

// Теперь intList содержит Double!
// Когда вы попытаетесь получить элемент:
Integer i = intList.get(1);  // ❌ ClassCastException!

Это нарушило бы гарантию типобезопасности.

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

Это мнемоника для запоминания wildcard правил:

// Producer (производитель) — используйте extends
public List<? extends Number> getNumbers() {
    return Arrays.asList(1, 2.5, 3);
}

// Consumer (потребитель) — используйте super
public void addToNumbers(List<? super Integer> list) {
    list.add(42);
}

Best Practices

// ✅ Правильно: специфичный тип для полной функциональности
public void process(List<Number> numbers) {
    // Читать и писать
}

// ✅ Правильно: extends для только-читаемых параметров
public void read(List<? extends Number> data) {
    for (Number n : data) { }
}

// ✅ Правильно: super для только-писаемых параметров
public void write(List<? super Integer> data) {
    data.add(42);
}

Вывод: Java использует инвариантность дженериков для гарантирования полной тип-безопасности. Хотя Integer является подклассом Number, Collection<Integer> НЕ является подтипом Collection<Number>. Для допуска вариативности используйте wildcards (? extends и ? super) в соответствии с принципом PECS.