项目作者: mtumilowicz

项目描述 :
Covariance, invariance, contravariance overview of collections in Java 11, vavr, guava.
高级语言: Java
项目地址: git://github.com/mtumilowicz/java11-covariance-contravariance-invariance.git


Build Status

java11-covariance-contravariance-invariance

Covariance, invariance, contravariance overview of collections in Java 11, vavr, guava.

Reference: https://dzone.com/articles/covariance-and-contravariance
Reference: https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance
Reference: https://medium.freecodecamp.org/understanding-java-generic-types-covariance-and-contravariance-88f4c19763d2

preface

formal

  • Covariance - enables you to use a more derived type than
    originally specified.

    If A is a subtype of B then X[A] should be a subtype
    of X[B].

  • Contravariance - enables you to use a more generic
    (less derived) type than originally specified.

    If A is a supertype of B then X[A] should be a
    supertype of X[B].

  • Invariance - means that you can use only the type
    originally specified; so an invariant generic type
    parameter is neither covariant nor contravariant.

java

Variance refers to how subtyping between more complex
types relates to subtyping between their components.

Generics in Java are invariant. (Java has no way
of knowing at runtime the type information of the
type parameters, due to type erasure)

  • Covariance: accept subtypes

    Arrays in java are covariant.

  • Contravariance: accept supertypes

    No easy examples in java (unless wildcards).

  • Invariance: neither covariant nor contravariant

    Generics are invarant

With wildcards, it’s possible for generics to support
covariance and contravariance.

  • Covariance: ? extends Integer
    1. List<Integer> ints = new LinkedList<>();
    2. List<? extends Number> nums = ints;
  • Contravariance: ? super Integer
    1. List<Integer> ints = new LinkedList<>();
    2. List<? super Integer> nums = ints;
  • Remark:
    • covariance is read-only
    • contravariance is write-only
    • otherwise compile-time error

Covariance could be dangerous (JavaUtilCollectionsTest):

  1. Dog[] dogs = new Dog[5];
  2. Animal[] animals = dogs;
  3. animals[0] = new Cat(); // ArrayStoreException

Mutability combined with covariance would break type safety:

  1. List<String> strs = new ArrayList<String>();
  2. List<Object> objs = strs; // !!! The cause of the upcoming problem sits here. Java prohibits this!
  3. objs.add(1); // Here we put an Integer into a list of Strings
  4. String s = strs.get(0); // !!! ClassCastException: Cannot cast Integer to String

Note that covariance can’t be dangerous with
immutable / readonly collections, cause we can’t
modify them.

use case

  1. suppose we have method that we cannot modify
    1. static void process(List<Animal> animals) {
    2. ...
    3. }
  2. then we cannot invoke (because of compile time error)
    1. process(new LinkedList<Dog>()) // compile time error
  3. but we could try to bypass it with a trick
    1. process(Collections.unmodifiableList(new LinkedList<Dog>()))

project description

  • we have hierarchical structure:

    • interface Animal extends Comparable<Animal>
    • class Cat implements Animal
    • class Dog implements Animal
    • class BigDog extends Dog
  • what we can’t do?

    1. List<Dog> dogs = new LinkedList<>();
    2. List<Animal> animals = dogs; // compile time error
  • what we can do?

    • using vavr:

      1. HashSet<Dog> dogs = HashSet.of(new Dog(), new Dog());
      2. HashSet<Animal> animals = HashSet.ofAll(dogs);
      3. HashSet<Animal> animals2 = HashSet.narrow(dogs);
      4. HashSet<Animal> animals3 = HashSet.of(new Dog(), new Dog());
      • HashSet<T> ofAll(java.lang.Iterable<? extends T> elements)
      • HashSet<T> narrow(io.vavr.collection.HashSet<? extends T> hashSet)
      • in VavrCollectionsTest tests for: HashSet, List,
        TreeSet, HashMap
    • using guava
      1. ImmutableSet<Dog> dogs = ImmutableSet.of(new Dog(), new Dog());
      2. ImmutableSet<Animal> animals = ImmutableSet.of(new Dog(), new Dog());
      3. ImmutableSet<Animal> animals2 = ImmutableSet.copyOf(Collections.<Dog>emptySet());
      • ImmutableSet<E> copyOf(Collection<? extends E> elements)
      • in GuavaCollectionsTest tests for: ImmutableSet,
        ImmutableList, ImmutableSortedSet, ImmutableMap
    • pure java

      • factory methods
        1. List<Dog> dogs = List.of(new Dog(), new Dog());
        2. List<Animal> animals = List.of(new Dog(), new Dog());
        3. List<Animal> animals2 = List.copyOf(dogs);
      • Collections.unmodifiableXXX

        1. List<Dog> dogs = new LinkedList<>();
        2. dogs.add(new Dog());
        3. List<Animal> dogsAsAnimals = Collections.unmodifiableList(dogs);
      • in JavaUtilCollectionsTest tests for:
        List, Collections.unmodifiableList,
        Set, Collections.unmodifiableSet,
        Map, Collections.unmodifiableMap,
        Collections.unmodifiableNavigableSet,
        Collections.unmodifiableSortedSet,
        Collections.unmodifiableCollection
    • note that you could also use wildcards:

      • Please refer my other github project for PECS principle:
        https://github.com/mtumilowicz/java11-pecs-principle
      • covariance, read-only

        1. Dog dog = new Dog();
        2. List<Dog> dogs = new LinkedList<>();
        3. dogs.add(dog);
        4. List<? extends Animal> dogsAsAnimal = dogs;
        5. // dogsAsAnimal.add(new Dog()); compile time error
        6. // Dog getDog = dogsAsAnimal.get(0);
        7. Animal getDog = dogsAsAnimal.get(0);
        8. assertThat(getDog, is(dog));
      • contravariance, write-only

        1. List<Dog> dogs = new LinkedList<>();
        2. List<? super Dog> onlyDogSpecies = dogs;
        3. onlyDogSpecies.add(new Dog());
        4. onlyDogSpecies.add(new BigDog());
        5. // Animal getDog = dogsAsAnimal.get(0); compile time error
        6. assertThat(onlyDogSpecies.size(), is(2));