There is a lot of misunderstanding in the question:
First, there is no such thing as "variance of parametric types". Types (as List[Int]
) are, by themselves, fixed, and therefore not variant (co-/contra-variant). What can be co(ntra)variant, are type constructors, eg. List
, which are not themselves types.
Therefore, the line
<A extends B,B> void store(ArrayList<B> list, A elem)
cannot "indicate variance of types", since it does not define any type constructor. It is a simple example of (bounded) parametric polymorphism.
We have several types of polymorphism in programming languages. The one most natural to OOP languages like Java is subtype polymorphism, which is characterized by the fact that from X <: Y
and t : Y
, it follows that t : Y
(IOW if a term/variable/parameter/whatever has type X
which is a subtype of Y
, it has also type Y
and is usable in any place a real Y
would be).
Another type of polymorphism is parametric polymorphism, which means that some types are actually type variables, which represent a placeholder for any type and are instantiated to real types during typechecking. Bounded parametric polymorphism means the tpe variables can be constrained to be a subtype or supertype of other type. Generics offer parametric polymorphism in Java.
Now, how do these two play together? It seems natural that a file of employees can be read as a file of citizen, since every employee is naturally a citizen (which is captured in the program by the subtype relation). This means ReadableFile[Employee] <: ReadableFile[Citizen]
. However, the rules for determining subtypes don't support this conclusion - both types are totally unrelated.
This is where variance comes to play. If we define some type constructors (in this case ReadableFile
) to be co(contra-)variant, we can use inference rules like "if X <: Y
and T
is covariant, then T[X] <: T[Y]
". In effect, this rule supports the conclusion above.
Parametric type constructors in Java are always invariant.
As you have already noted, we can sometimes get away without variant type constructors and still achieve the same effect. In function parameters,
def foo(x : TCovar[Reader]) : Unit
with covariant TCovar
is functionally equivalent to
def foo[A <: Reader](x : T[A]) : Unit
This doesn't anyhow mean that T
in the second definition is covariant.
However, in function return type and variable types, this trick doesn't generally help. Even in that case, we can get the same effect as variant type constructors with the help of existential types. So
val foo : TCovar[Reader]
becomes
val foo : T[X] forSome { type X <: Reader }
You can see this in action in this example. As you can see in the definition of v4
and v5
, covariant type constructors can make the program much simpler. This simplification applies to the function parameter declarations, too, which is why functional programming languages with subtype polymorphism use variant type constructors (at least for their function types).
However, for a type constructor to be covariant, it needs to satisfy some rules. These rules cause that Java's List<T>
and other interfaces are actually invariant, because they provide both reading and writing of the container data (which is typical for imperative programming). Scala's functional data structures don't work that way, so they can be covariant. It is demonstrated in the example, too - NCov
can't be covariant, because it provides read and write access to its inner data. I believe this is the main reason covariant type constructors don't exist in Java, which is the answer to your question.
Sorry for mixing Java and Scala in this post. I hope the examples are so easy to understand that it doesn't matter.