Skip to content

Instantly share code, notes, and snippets.

@darichey
Last active March 22, 2019 00:35
Show Gist options
  • Save darichey/ee4c4bd2765e6104131c123a8fef5625 to your computer and use it in GitHub Desktop.
Save darichey/ee4c4bd2765e6104131c123a8fef5625 to your computer and use it in GitHub Desktop.
Variance

The reason you get Object when you use andThen with Consumer<? super EmbedCreateSpec> is because ? super EmbedCreateSpec indicates contravariance. Variance affects the subtyping relationship between types depending on the subtyping relathionship of their type parameters. You've probably seen ? extends which indicates covariance. I think this one is a bit easier to explain, so we'll do that and then go back to contravariance.

Covariance "forwards" the subtyping relationship to the containing type. That is, if T is a subtype of U (notated as T <: U) then List<T> is a subtype of List <? extends U>. For example, List<Double> is a subtype of List<? extends Number>. This is a useful relationship if we are only reading (that is, get()) from the list. We can never write (that is add() to this list). The following example helps show why:

List<? extends Number> list;
list = new ArrayList<Double>();
// list = new ArrayList<Long>();

Number x = list.get(0); // allowed

Double y = list.get(0); // Not allowed. What if we had said ArrayList<Long> earlier?
list.add(3.0d);         // Not allowed. What if we had said ArrayList<Long> earlier?

Number z = ...;
list.add(z); // Not allowed. Who knows what the actual type of z is? What if it's a Long?

Contravariance is essentially the opposite of covariance. It flips around the subtyping relationship. That is, if T <: U then List<U> <: List<? super T>. List<Number> is a subtype of List<? super Double>. This is a useful relationship if we are only writing (that is, add()) to the list. We can never read (that is get() from the list).

List<? super Double> list;
list = new ArrayList<Number>();
// list = new ArrayList<Object>();

list.add(3.0d); // allowed

Number x = list.get(0); // Not allowed. What if we had said ArrayList<Object> earlier?

Number y = ...;
list.add(y); // Not allowed. Who knows what the actual type of y is? What if it's a Long?

The only exception to the reading rule here is if we just read everything as Object because obviously everything is a subtype of Object.

In the case of Consumer, you can think of accept() as "writing" and andThen() as "reading." That's because for the former you put something in and for the latter you get something out (indirectly, it gives you the result of of the last operation in the lambda). As explained earlier, you can't "read" when dealing with contravariant things.

fun fact, there's a 4th type of variance 😉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment