1

(Note: this is not the same as this question about vs. !)

I'm confused by Java's generics.

ArrayList<? extends Object> x = new ArrayList<String>();
ArrayList<Object> y = new ArrayList<String>();

In this example, the first line compiles while the second one doesn't. Why is that?

I thought an assignment of the shape List<A> x = new ArrayList<B>() should be valid as long as B extends A, i.e. the right side has higher specificity than the left side of the assignment, but apparently I'm mistaken.

Could somebody elaborate on the similarities and differences of these statements?

  • 2
    I don't think it is a duplicate. I came across that thread before in my search, but this here is not about > vs. extends Object>. – Mandelmus100 Nov 12 '18 at 14:12
  • This is also a good thread to read about collections and generics: https://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super – Thilo Nov 12 '18 at 14:31

3 Answers3

3

In Java, the type arguments must match exactly.

Consider the following code snippet:

ArrayList<Object> list = new ArrayList<String>();
list.add(new SomeClass());

We have now succeeded in adding a non-string to ArrayList<String>. There is nothing wrong with the second statement: we're adding some object to an ArrayList<Object>. So there must be something wrong with the first statement! That's why Java does not let you do that.

If you change the snippet to:

ArrayList<? extends Object> list = new ArrayList<String>();
list.add(new SomeClass());

you will notice that the error is now in the second line. You cannot add an object to the list.

The difference between the two is the following:

  • ArrayList<Object> means "an ArrayList containing Objects". Such a list can of course also contain instances of subclasses of Object. Since every object is an instance of Object, this list can contain any object (in this case).

  • ArrayList<? extends Object> means "an ArrayList containing instances of some unknown subclass of Object". Such an ArrayList cannot contain every object. Only instances of this unknown subclass can be added to it. Since the specific subclass is not known, it is not possible to add elements to it here. (But elsewhere, the same list may be known as an ArrayList<String>, and there it is of course possible to add elements to it.)

Hoopje
  • 12,677
  • 8
  • 34
  • 50
  • To your first example - but `Object[] z = new Integer[3];` and then `z[2] = "ALP3";` all compiles just fine. I just get an ArrayStoreException during runtime. – Mandelmus100 Nov 12 '18 at 14:28
  • 1
    Your two bullet points beautifully clarified the distinction for me. Thank you very much! :) – Mandelmus100 Nov 12 '18 at 14:31
  • @ChrisOffner. Yes, arrays predate generics and work differently. In particular, `String[]` is considered as subtype of `Object[]`, but `List` is not a subtype of `List`. – Hoopje Nov 13 '18 at 22:27
1

Both below cases won't work because you are initializing different object type than expected:

ArrayList<Object> y = new ArrayList<String>();
ArrayList<String> y2 = new ArrayList<Object>();

If you use ? wildcard you get some more options to have different, but related by inheritance types (String extends Object implicitly):

ArrayList<? extends Object> x = new ArrayList<String>();

Consider also using the diamond operator

ArrayList<Object> y = new ArrayList<>();

You can replace the type arguments required to invoke the constructor of a generic class with an empty set of type parameters (<>) as long as the compiler can infer the type arguments from the context. This pair of angle brackets is informally called the diamond.

Ori Marko
  • 56,308
  • 23
  • 131
  • 233
0
String abc = "abc";
Object x = abc;

works but

List<Object> list = new ArrayList<String>();

does not because Generic expressions have to match exactly if they do not contain a wildcard.

Please note that on the declaration side it is better to use List (and not ArrayList) because it allows you to change the implementation at a later stage, e.g. to LinkedList.

michaeak
  • 1,548
  • 1
  • 12
  • 23
  • Why then does `Object obj = new String();` work? Is there a logic and conscious design decision behind the fact that Generic expressions don't work the same way, or is just just historically grown inconsistencies? – Mandelmus100 Nov 12 '18 at 14:16
  • @ChrisOffner `Object obj = new String();` works because every String is also an Object. But a `List` is not also a `List`, because you can put a `Long` into a `List`, but you cannot put a `Long` into a `List`. That's why the compiler does not allow this assignment. – Thilo Nov 12 '18 at 14:28