Covariant structure fails with capture error in Java

Consider the following Java class definitions:

class Animal {} class Lion extends Animal {} 

When defining Cage covariance for Animal I use this code in Java:

 class Cage<T extends Animal> { void add(T animal) { System.out.println("Adding animal..."); } } 

But the following Java example ...

 public static void main(String... args) { Cage<? extends Animal> animals = null; Cage<Lion> lions = null; animals = lions; // Works! animals.add(new Lion()); // Error! } 

... could not compile the following error:

The add (capture # 2-of? Extends Animal) method in the Cage type is not applicable for arguments (Lion)

This is done because otherwise after animals = lions another type of Tiger type can be added and not executed at runtime?

Could a special (hypothetical) rule be made that would not reject it if only one subtype of Animal existed?

(I know that I could replace add T with Animal .)

+4
source share
4 answers

In java:

 Cage<? extends Animal> animals = null; 

It is a cage, but you do not know what animals it takes.

 animals = lions; // Works! 

Well, you do not add any opinion about what animals are in the cage, so the lion does not violate any expectations.

 animals.add(new Lion()); // Error! 

You do not know what these cells are. In this particular case, it turns out to be a cage for lions in which you put a lion, but this is a rule that will allow this to simply allow any kind of animal to be placed in any cage. This is correctly prohibited.

In Scala: Cage[+T] : if B extends A , then a Cage[B] should be considered a Cage[A] .

Given that animals = lions allowed.

But is this different from java, a parameter of type definitely Animal , and not a wildcard ? extends Animal ? extends Animal . You are allowed to put an animal in Cage[Animal] , a lion is an animal, so you can put a lion in a [Animal] cage, which can be a [Bird] cage. This is pretty bad.

Except that it is actually not allowed (fortunately). Your code should not compile (if it was compiled for you, you noticed a compiler error). A covariant general parameter cannot be displayed as an argument to a method. The reason is that it would allow lions to be placed in a bird cage. It is displayed as +T in the Cage definition; it cannot be displayed as an argument to the add method.

Thus, both languages โ€‹โ€‹prohibit placing lions in bird cages.


Regarding your updated questions.

This is done because otherwise you can add a tiger?

Yes, this, of course, is the reason, the point of the type system is to make this impossible. Will this lead to an un runtime error? In all likelihood, this will happen at some point, but not at the moment when you call add, since the actual generic type is not checked at run time (erasure type). But the type system usually rejects every program for which it cannot prove that (some) errors will not happen, and not just a program where it can prove that they really happen.

Could there be a special (hypothetical) rule that would not reject it if only one subtype of Animal existed?

May be. Note that you still have two types of animals, namely Animal and Lion . Therefore, an important fact is that the Lion instance belongs to both types. On the other hand, an instance of Animal not of type Lion . animals.add(new Lion()) can be allowed (a cage or a cage for any animals, or only for lions, both are normal), but animals.add(new Animal()) should not (because animals can be a cage only for lions )

But in any case, it sounds like a very bad idea. The point of inheritance in an object-oriented system is that sometime later, someone else working somewhere else can add a subtype, and this will not lead to an incorrect system. In fact, the old code does not even need to be recompiled (perhaps you have no source). With this rule, this will not be true

+3
source

I think this question can answer this question:

java generics covariance

Basically, Java generics are not covariant.

The best explanation I know is, of course, from Effective Java 2nd Edition.

You can read about it here:

http://java.sun.com/docs/books/effective/generics.pdf

I think that a hypothetical rule will be quite difficult to enforce at runtime. The compiler can theoretically check whether all objects explicitly added to the list are really of the same type of animals, but I'm sure there are conditions that can violate this at runtime.

+1
source

When you declare your variable with this type: Cage<? extends Animal> Cage<? extends Animal> ; you basically say that your variable is a cell with some unclassified class that inherits from Animal . It could be Tiger or Whale ; therefore, the compiler does not have enough information to add Lion to it. To have what you want, you declare your variable as either Cage<Animal> or Cage<Lion> .

+1
source

If so, this is probably a bug in the Scala compiler. Odersky et al. write in the Scala Programming Language Overview :

Type system

Scala s ensures that annotations are variations in sound by tracking the position where the pa- type indicator is used. These positions are classified as covariant for types of immutable fields and method results, and contravariant for types of argument methods and the upper type of the parameter boundary. Enter arguments in an invariant type. The parameter is always in an invariant position. The ips position between contra and co-variant is inside the type argument, which corresponds to the contravariant parameter. The type system ensures that covariant (respectively, contravariant) type parameters are used only in covariant (contravariant) positions.

Therefore, the parameter of covariance type T should not be displayed as an argument of the method, because this is a contravariant position.

A similar rule (with more special cases, none of which matters in this case) is also present in the Scala Language Specification (version 2.9) , section 4.5.

0
source

All Articles