This is because inheritance in collections does not work the same way it works with classes. In oracle tutorial it is mentioned that:
In general, if Foo
is a subtype (subclass or subinterface) of Bar
, and G
is some Generic type declaration, it is not the case that G<Foo>
is a subtype of G<Bar>
.
That is, if B
is a subtype of A
(be they classes or interfaces), nay it is true that a Set<B>
is a subtype of Set<A>
. And that doesn’t just apply to Set
, but for any generic type. It is counterintuitive because we imagine that the "logical" would be a collection of B
should be a subtype of a collection of A
, if B
is a subtype of A
. But it’s not.
The aforementioned tutorial itself gives an example similar to yours:
List<String> ls = new ArrayList<String>();
List<Object> lo = ls; // erro, não compila
After all, if String
is a Object
, then why a list of String
is not a list of Object
? Because if the above code compiled, then it would be possible to do this:
lo.add(new Object()); // adicionando um Object que não é uma String
String s = ls.get(0); // ops, não vai retornar uma String
Probably to avoid this type of situation, they decided that it is best not to allow a list of String
on a list of Object
.
In your case, the same could happen. Suppose I have another sub-interface A
:
public interface C extends A { ... }
public class ImplementoC implements C { ... }
public class RecebeA {
public RecebeA(Collection<A> as) {
as.add(new ImplementoC()); // isso compila
}
}
...
Set<B> bs = new HashSet<>();
RecebeA qualquerCoisa = new RecebeA(bs); // isso não compila, mas vamos supor que compilasse
B b = bs.iterator().next(); // aqui seria retornado uma instância de ImplementoC (que é um C, não um B)
That is, if it were possible to pass one Set<B>
for the builder who only receives Collection<A>
, within it I could add any implementation of A
(even if it has no relation of subtype to B
), and trying to get the values of Set<B>
they would not be instances of B
. In the above case, it would be possible to add instances of C
in a Set<B>
, even though B
and C
are not subtype of each other.
Interestingly, this doesn’t work either:
public RecebeA(Collection<? extends A> as) {
as.add(new ImplementoC()); // agora essa linha não compila
}
Set<B> bs = new HashSet<>();
RecebeA qualquerCoisa = new RecebeA(bs); // mas essa compila
Yes, even if the parameter is declared as Collection<? extends A>
, i can’t add a C
on it. This is because the value received can be a collection of any subclass/sub-interface of A
(as an example B
, which has no relation of subtype to C
, that is, if the line above compiled it would be possible to add a C
in a Set<B>
).
Borrowing the example of Jon Skeet, to make it a little clearer:
List<Dog> dogs = new ArrayList<Dog>();
// uma lista de cachorros é uma lista de animais, certo? (não, a linha abaixo não compila)
List<Animal> animals = dogs;
// se a linha acima compilasse, seria possível fazer isso:
animals.add(new Cat()); // adicionando um gato na lista de cachorros
Dog dog = dogs.get(0); // ops, não é um cachorro
In the case, Animal
would be the interface A
, Dog
would be the interface B
and Cat
would be C
.
And if I have a class that gets a list of animals:
public class RecebeAnimal {
public RecebeAnimal(Collection<Animal> c) {
c.add(new Cat());
}
}
...
List<Dog> dogs = new ArrayList<>();
RecebeAnimal r = new RecebeAnimal(dogs); // não compila
If the line above compiled, I could add a cat to the dog list.
Now if I change the builder:
public RecebeAnimal(Collection<? extends Animal> c) {
c.add(new Cat()); // não compila
}
...
List<Dog> dogs = new ArrayList<>();
RecebeAnimal r = new RecebeAnimal(dogs); // compila
The last line compiles because now the Collection
may be from any subclass of Animal
. But the line with c.add(new Cat())
does not compile, because if compile it would still be possible to add a cat in the list of dogs.
The only thing you can do in the builder is access the elements of the collection as animals: for (Animal a : c) { etc }
(just as in your example you could make a for
in Collection<? extends A>
). Of course you can use it there instanceof
and cast to get specific types, but then it already depends on what you need to do.
And how do you solve it? It depends. What exactly RecebeA
should receive? A collection of A
, of any subtype of A
, of only B
?
In fact, I think it’s only by having the case concrete to find the best solution. With generic names like this, you can only speculate, but anyway, knowing the limitations and the functioning of wildcards, you can evaluate the requirements and see what is best for your case.
I edited the title, but I don’t know if it’s exactly the "essence" of what you wanted to know...
– hkotsubo
The title is good, is also a doubt, although the essential doubt was why Collection<? extends Interface> works and Collection<Interface> does not. But this question I took in the comments of the reply, thank you.
– Piovezan