Why aren’t Java’s generics implicitly polymorphic?

Because ArrayList<Dog>, where Dog is a subclass of Animal, is not a logical subclass of ArrayList<Animal>. This, in turn, is because generic containers are invariant (as opposed to covariant or contravariant) in their type parameters.
Covariant means that subtyping relationships in parameters correspond to subtyping relationships in parameterized types. This happens, for example, when you can only read data of the parameterized type, and not write it. Thus, a ReadableStream<Animal> is a logical superclass of ReadableStream<Dog>- any code that is written to expect a ReadableStream<Animal> will work just fine with a ReadableStream<Dog>. Any Dog objects read from the stream will also be Animals, so everything works out fine.
Contravariant means that subtyping relations are reversed. This happens, for example, when you can only write data of the parameterized type, and not read it. Thus, a WritableStream<Animal> is a logical subclass (not a superclass!) of a WritableStream<Dog>. Any code that is written to expect a WritableStream<Dog> will work just fine with a WritableStream<Animal>. Any Dog objects written to the stream will also be Animals, so everything works out fine.
Invariant means that there is no relation between parameter type hierarchies and parameterized type hierarchies. This happens, for example, if you can both read and write data of the parameterized type. A ReadWriteStream<Animal> thus has no relation to a ReadWriteStream<Dog>, because
1. If you pass a ReadWriteStream<Animals> to code expecting a ReadWriteStream<Dog>, you cannot guarantee that all objects read from the stream will in fact be of type Dog, and
2. If you pass a ReadWriteStream<Dog> to code expecting a ReadWriteStream<Animals>, you cannot guarantee that all objects written to the stream will be of type Dog.
Some other programming languages (like C#) have means of allowing the programmer to specify the precise variance of different parameters and enforcing the associated access restrictions, but Java does not. Java must therefore assume that all parameters are invariant- assuming either other option would result in the inability to enforce type safety in some circumstances.
The <? extends …> syntax is a kludge to allow you to tell the compiler "treat this parameter as though it were covariant, and I, the programmer, promise to take full responsibility for anything that goes wrong if it actually isn't."

Answer by Logan R. Kearsley:

Because ArrayList<Dog>, where Dog is a subclass of Animal, is not a logical subclass of ArrayList<Animal>. This, in turn, is because generic containers are invariant (as opposed to covariant or contravariant) in their type parameters.
Covariant means that subtyping relationships in parameters correspond to subtyping relationships in parameterized types. This happens, for example, when you can only read data of the parameterized type, and not write it. Thus, a ReadableStream<Animal> is a logical superclass of ReadableStream<Dog>- any code that is written to expect a ReadableStream<Animal> will work just fine with a ReadableStream<Dog>. Any Dog objects read from the stream will also be Animals, so everything works out fine.
Contravariant means that subtyping relations are reversed. This happens, for example, when you can only write data of the parameterized type, and not read it. Thus, a WritableStream<Animal> is a logical subclass (not a superclass!) of a WritableStream<Dog>. Any code that is written to expect a WritableStream<Dog> will work just fine with a WritableStream<Animal>. Any Dog objects written to the stream will also be Animals, so everything works out fine.
Invariant means that there is no relation between parameter type hierarchies and parameterized type hierarchies. This happens, for example, if you can both read and write data of the parameterized type. A ReadWriteStream<Animal> thus has no relation to a ReadWriteStream<Dog>, because
  1. If you pass a ReadWriteStream<Animals> to code expecting a ReadWriteStream<Dog>, you cannot guarantee that all objects read from the stream will in fact be of type Dog, and
  2. If you pass a ReadWriteStream<Dog> to code expecting a ReadWriteStream<Animals>, you cannot guarantee that all objects written to the stream will be of type Dog.
Some other programming languages (like C#) have means of allowing the programmer to specify the precise variance of different parameters and enforcing the associated access restrictions, but Java does not. Java must therefore assume that all parameters are invariant- assuming either other option would result in the inability to enforce type safety in some circumstances.
The <? extends …> syntax is a kludge to allow you to tell the compiler "treat this parameter as though it were covariant, and I, the programmer, promise to take full responsibility for anything that goes wrong if it actually isn't."

Why aren't Java's generics implicitly polymorphic?

Advertisements

Leave a comment

Filed under Life

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s