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 😉