22

I want to know how generics work in this kind of situation and why Set<? extends Foo<?>> set3 = set1; is allowed but Set<Foo<?>> set2 = set1; is not?

import java.util.HashSet;
import java.util.Set;

public class TestGenerics {
    public static <T> void test() {
        Set<T> set1 = new HashSet<>();
        Set<?> set2 = set1;             // OK
    }

    public static <T> void test2() {
        Set<Foo<T>> set1 = new HashSet<>();
        Set<Foo<?>> set2 = set1;           // COMPILATION ERROR
        Set<? extends Foo<?>> set3 = set1; // OK
    }
}

class Foo<T> {}
Lii
  • 11,553
  • 8
  • 64
  • 88
Stoyan Radnev
  • 237
  • 1
  • 7
  • An interesting reading about this "issue": https://stackoverflow.com/a/4343547/7709086 – kagmole Jan 09 '19 at 09:07
  • 2
    @Lino: This question is similar to and related to "*What is PECS*", but not exactly the same. This question is about PECS, but specifically applied to the case when the type arguments themselves are types which have type parameters. That makes this a particularly tricky special case, which warrants its own question. (But I'd be surprised if there is no other exactly duplicate question some where.) – Lii Jan 09 '19 at 11:11

4 Answers4

9

Simply said, this is because Set<? extends Foo<?>> is covariant (with the extends keyword). Covariant types are read-only and the compiler will refuse any write action, like Set.add(..).

Set<Foo<?>> is not covariant. It does not block write or read actions.

This...

Set<Foo<String>> set1 = new HashSet<>();
Set<Foo<?>> set2 = set1; // KO by compiler

... is illegal because otherwise I could for example put a Foo<Integer> into set1 via set2.

set2.add(new Foo<Integer>()); // Whoopsie

But...

Set<Foo<String>> set1 = new HashSet<>();
Set<? extends Foo<?>> set3 = set1; // OK

... is covariant (extends keyword), so it is legal. For example, the compiler will refuse a write operation like set3.add(new Foo<Integer>()), but accept a read operation like set3.iterator().

Iterator<Foo<String>> fooIterator = set3.iterator(); // OK
set3.add(new Foo<String>()); // KO by compiler

See these posts for a better explanation:

JimmyB
  • 12,101
  • 2
  • 28
  • 44
kagmole
  • 2,005
  • 2
  • 12
  • 27
4

Perhaps the issue becomes clearer if you leave the generic parameter of Foo out of the equation.

Consider

final Set<Foo> set1 = new HashSet<>();
Set<Object> set2 = set1;

This makes the compile error more obvious. If this was valid, it would be possible to insert an object into set2, thus into set1 violating the type constraint.

Set<? extends Foo> set3 = set1;

This is perfectly valid because set1 would also accept types derived from Foo.

leftbit
  • 848
  • 1
  • 7
  • 18
  • 1
    why did you transform `Set>` to `Set` after type erasure? I guess wildcard will be replaced by `Object` since it is closest bound? – Sergey Prokofiev Jan 09 '19 at 09:15
  • 1
    `Foo>` is not `Object`, it is `Foo` "of something". What allows the assignment to `set3` is the covariance. – kagmole Jan 09 '19 at 09:41
  • 2
    Also you answer implies that `set3` is writable, which is not the case. See more about covariance and contravariance here : https://stackoverflow.com/a/4343547/7709086 – kagmole Jan 09 '19 at 09:48
1

Additionally to the answers given already I'll add some formal explanation.

Given by 4.10.2 (emp. mine)

Given a generic type declaration C (n > 0), the direct supertypes of the parameterized type C, where Ti (1 ≤ i ≤ n) is a type, are all of the following:

D < U1 θ,...,Uk θ>, where D is a generic type which is a direct supertype of the generic type C and θ is the substitution [F1:=T1,...,Fn:=Tn].

C < S1,...,Sn> , where Si contains Ti (1 ≤ i ≤ n) (§4.5.1).

The type Object, if C is a generic interface type with no direct superinterfaces.

The raw type C.

Rule for contains are specified at 4.5.1:

A type argument T1 is said to contain another type argument T2, written T2 <= T1, if the set of types denoted by T2 is provably a subset of the set of types denoted by T1 under the reflexive and transitive closure of the following rules (where <: denotes subtyping (§4.10)):

? extends T <= ? extends S if T <: S

? extends T <= ?

? super T <= ? super S if S <: T

? super T <= ?

? super T <= ? extends Object

T <= T

T <= ? extends T

T <= ? super T

Since T <= ? super T <= ? extends Object = ? so applying 4.10.2 Foo<T> <: Foo<?> we have ? extends Foo<T> <= ? extends Foo<?>. But Foo<T> <= ? extends Foo<T> so we have Foo<T> <= ? extends Foo<?>.

Applying 4.10.2 we have that Set<? extends Foo<?>> is a direct supertype of Set<Foo<T>>.

The formal answer to why your first example does not compile may be got by assuming a contradiction. Percisely:

If Set<Foo<T>> <: Set<Foo<?>> we have that Foo<T> <= Foo<?> which is not possible to prove applying reflexive or transitive relations to rules from 4.5.1.

St.Antario
  • 26,175
  • 41
  • 130
  • 318
0

I think simply because the Set element Datatype is different while it must be the same except for Generic Datatype.
the first set Set<Foo<T>> datatype is Foo<T>,
then second set Set<Foo<?>> is Foo<?>,
As I can see the element datatype is different Foo<T> != Foo<?> and not generic type because it use Foo, so then would cause compilation error.
It is same as below invalid different datatype example :

Set<List<T>> set3 = new HashSet<>();
Set<List<?>> set4 = set3;   // compilation error due to different element datatype List<T> != List<?>

Set<? extends Foo<?>> set3 = set1; can because it have ? datatype which is generic and have purpose can accept any datatype.
ex :

Set<List<T>> set4 = new HashSet<>();
Set<?> set5 = set4;  // would be Ok
m fauzan abdi
  • 436
  • 5
  • 12