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

Что такое ковариантность типов?

2.4 Senior🔥 71 комментариев
#ООП

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

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

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

Что такое ковариантность типов?

Ковариантность типов (Covariance) — это свойство системы типов, которое определяет, как параметры и результаты функций/методов могут быть подменены более специфичными типами в иерархии наследования.

Это часть вариантности типов — способности типов подставляться в другие типы в зависимости от их отношения в иерархии наследования.

Три вида вариантности

  1. Ковариантность (Covariance) — ? extends Type
  2. Контравариантность (Contravariance) — ? super Type
  3. Инвариантность (Invariance) — точный тип, без подстановок

Ковариантность: ? extends

Правило: если B extends A, то List<B> extends List<? extends A>

import java.util.*;

public class CovarianceExample {
    
    static class Animal {}
    static class Dog extends Animal {}
    static class Cat extends Animal {}
    
    // ✅ Ковариантная функция — принимает список подтипов
    public static void printAnimals(List<? extends Animal> animals) {
        for (Animal animal : animals) {
            System.out.println(animal);
        }
    }
    
    public static void main(String[] args) {
        List<Dog> dogs = new ArrayList<>();
        dogs.add(new Dog());
        
        List<Cat> cats = new ArrayList<>();
        cats.add(new Cat());
        
        // ✅ Можно передать List<Dog> в метод принимающий List<? extends Animal>
        printAnimals(dogs);
        printAnimals(cats);
        
        // Это работает потому что Dog является Animal
    }
}

Инвариантность (по умолчанию)

public class InvarianceExample {
    
    static class Animal {}
    static class Dog extends Animal {}
    
    // ❌ Инвариантный метод — только точный тип
    public static void addAnimal(List<Animal> animals, Animal animal) {
        animals.add(animal);
    }
    
    public static void main(String[] args) {
        List<Dog> dogs = new ArrayList<>();
        
        // ❌ ОШИБКА КОМПИЛЯЦИИ
        // List<Dog> не совместима с List<Animal>
        // addAnimal(dogs, new Dog());
        
        // Почему? Потому что мы можем случайно добавить Cat в List<Dog>
        // List<Animal> animals = dogs;
        // animals.add(new Cat()); // Oops! Теперь в dogs содержится кот!
    }
}

Практический пример: Collections.copy()

import java.util.*;

public class CollectionsCopyExample {
    
    static class Number {}
    static class Integer extends Number {}
    
    public static void main(String[] args) {
        // ✅ Collections.copy использует ковариантность и контравариантность
        // public static <T> void copy(List<? super T> dest, List<? extends T> src)
        
        List<Integer> integerList = List.of(new Integer(), new Integer());
        List<Number> numberList = new ArrayList<>();
        numberList.add(new Number());
        
        // ✅ Копируем Integer в List<Number> (ковариантность источника)
        Collections.copy(numberList, integerList);
        
        // numberList теперь содержит Integer'ы (которые являются Number)
    }
}

PECS: Producer-Extends, Consumer-Super

Это правило помогает запомнить когда использовать ? extends и ? super:

import java.util.*;

public class PECSExample {
    
    static class Animal {}
    static class Dog extends Animal {}
    
    // ✅ PRODUCER (источник данных) — используй extends
    // Мы READ из этой коллекции
    public static void readFromList(List<? extends Animal> animals) {
        for (Animal animal : animals) {
            System.out.println(animal);
        }
        // animals.add(new Dog()); // ❌ ОШИБКА! Не можем писать
    }
    
    // ✅ CONSUMER (приемник данных) — используй super
    // Мы WRITE в эту коллекцию
    public static void writeToList(List<? super Dog> animals) {
        animals.add(new Dog());
        animals.add(new Dog());
        
        // Animal animal = animals.get(0); // ❌ ОШИБКА! Не можем читать типо-безопасно
    }
    
    public static void main(String[] args) {
        List<Dog> dogs = new ArrayList<>();
        readFromList(dogs); // ✅ OK
        writeToList(dogs); // ✅ OK
    }
}

Ковариантность в возвращаемых типах

public class ReturnTypeCovariance {
    
    static class Animal {}
    static class Dog extends Animal {}
    
    static class AnimalFactory {
        public Animal createAnimal() {
            return new Animal();
        }
    }
    
    // ✅ Подкласс может вернуть более специфичный тип (ковариантность возвращаемого типа)
    static class DogFactory extends AnimalFactory {
        @Override
        public Dog createAnimal() { // Dog вместо Animal
            return new Dog();
        }
    }
    
    public static void main(String[] args) {
        AnimalFactory factory = new DogFactory();
        Animal animal = factory.createAnimal(); // ✅ Получим Dog, но присвоим в Animal
    }
}

Ковариантность в массивах (опасно!)

public class ArrayCovariance {
    
    static class Animal {}
    static class Dog extends Animal {}
    static class Cat extends Animal {}
    
    public static void main(String[] args) {
        // ⚠️ Массивы КОВАРИАНТНЫ в Java! Это источник ошибок
        Dog[] dogs = {new Dog(), new Dog()};
        Animal[] animals = dogs; // ✅ Компилируется (ковариантность)
        
        // ❌ Но! Мы можем случайно добавить Cat
        animals[0] = new Cat(); // ArrayStoreException только в runtime!
        
        // ❌ Поэтому в generic'ах это не допускается
        // List<Dog> dogList = new ArrayList<>();
        // List<Animal> animalList = dogList; // ОШИБКА КОМПИЛЯЦИИ
        
        // Это сделано для типобезопасности!
    }
}

Ковариантность в Functional Interfaces

import java.util.function.*;

public class FunctionalCovariance {
    
    static class Animal {}
    static class Dog extends Animal {}
    static class Cat extends Animal {}
    
    // ✅ Function<? super T, ? extends R>
    // Input — контравариантен (? super)
    // Output — ковариантен (? extends)
    public static <T> void apply(
            T input,
            Function<? super T, ? extends Object> function) {
        Object result = function.apply(input);
        System.out.println(result);
    }
    
    public static void main(String[] args) {
        // ✅ Function<Animal, Object> совместима
        apply(new Dog(), (Animal a) -> a.toString());
    }
}

Сложный пример с методами

import java.util.*;

public class ComplexCovariance {
    
    static class Person {}
    static class Employee extends Person {}
    
    // ✅ Ковариантные типы
    public static List<? extends Person> getEmployees() {
        return Arrays.asList(new Employee(), new Employee());
    }
    
    // ✅ Контравариантные типы
    public static void processPeople(Consumer<? super Person> processor) {
        processor.accept(new Person());
        processor.accept(new Employee());
    }
    
    public static void main(String[] args) {
        List<? extends Person> people = getEmployees();
        
        // ✅ Можно читать Person'ов
        for (Person person : people) {
            System.out.println(person);
        }
        
        // Обработка
        processPeople(person -> System.out.println(person));
    }
}

Ограничения ковариантности

public class CovarianceRestrictions {
    
    static class Animal {}
    static class Dog extends Animal {}
    
    // ❌ Когда используешь ? extends, нельзя добавлять элементы
    public static void processAnimals(List<? extends Animal> animals) {
        // animals.add(new Dog()); // ❌ ОШИБКА КОМПИЛЯЦИИ
        // animals.add(new Animal()); // ❌ ОШИБКА КОМПИЛЯЦИИ
        
        // Но можно читать
        for (Animal animal : animals) { // ✅
            System.out.println(animal);
        }
    }
    
    // Это сделано для типобезопасности:
    // Если бы можно было добавлять, можно было бы случайно добавить неправильный тип
}

Практические применения

  1. Collections API — copy(), sort(), binarySearch()
  2. Streams — filter, map, collect
  3. Optional — Optional<? extends T>
  4. Callbacks и listeners
import java.util.*;
import java.util.stream.*;

public class PracticalCovariance {
    
    static class Shape {}
    static class Circle extends Shape {}
    
    public static void main(String[] args) {
        List<Circle> circles = List.of(new Circle());
        
        // ✅ Streams используют ковариантность
        circles.stream()
            .filter(c -> c != null)
            .map(Object::toString)
            .forEach(System.out::println);
    }
}

Ключевые выводы

  • Ковариантность — подтипы могут подставляться на место супертипов (читать)
  • ? extends Type — ковариантная подстановка (PRODUCER)
  • ? super Type — контравариантная подстановка (CONSUMER)
  • PECS — помогает запомнить когда что использовать
  • Типобезопасность — ограничения существуют для защиты от ошибок

Понимание вариантности типов — критично для эффективной работы с generics в Java!