3

So, I was looking through the Oracle Java Tutorials, specifically this piece of code;

List<EvenNumber> le = new ArrayList<>();
List<? extends NaturalNumber> ln = le;
ln.add(new NaturalNumber(35));  // compile-time error

which can be found here. It is my understanding (and please correct me if I'm wrong) that the above code will not work because the compiler does not know that lnrefers to a List of EvenNumbers and so this prevents you from accidentally adding any object that could be a supertype of the elements that are meant for the List. IF that is the case, then why is it that if you have a statement like Number num = new Integer(6); the compiler is able to correctly determine that num is an Integer object if you write an if statement like so; if (num instanceof Integer) {...}?

I guess my question is, how is the compiler able to determine that num is referring to an Integer object in the second example, but not able to determine that ln is referring to a List<EvenNumber> object in the first example?

BlaqICE
  • 309
  • 2
  • 11
  • 2
    "why is it that if you have a statement like Number num = new Integer(6); the compiler is able to correctly determine that num is an Integer object" - the compiler has no idea! `instanceof` is computed at runtime. – user2357112 Sep 27 '15 at 23:34
  • see also my [article](http://bayou.io/draft/Capturing_Wildcards.html) on wildcard in case it helps. – ZhongYu Sep 28 '15 at 01:06

3 Answers3

1

Your two examples are the opposite of each other.

An Integer is a Number, so

List<Number> list;
list.add(new Integer(1)); // compiles

But EvenNumber is a NaturalNumber, not the other way around, so

List<EvenNumber> list;
list.add(new NaturalNumber(1)); // compile error

because while EvenNumber is a NaturalNumber, NaturalNumber is not (necessarily) an EvenNumber.

If you swap Integer and Number in your reference case then rework it, it should make sense, ie this will compile:

List<NaturalNumber> list;
list.add(new EvenNumber(2)); // compiles OK
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • I think you're describing a scenario in which `List ln = le;` would occur, but that's not the case. That would cause a compilation error on the line above, but not when doing the `add`. – Makoto Sep 27 '15 at 23:40
  • @Makoto No. I'm saying that his analogy of adding an Integer to a list of Number is irrelevant, because he has the opposite class hierarchy in his code - ie just as Integer is a subclass of Number, EvenNumber is a subclass of NaturalNumber, but his code is trying to add a NaturalNumber to a list of EvenNumber, which would be like trying to add a Number to a list of Integer... which obviously won't work. – Bohemian Sep 28 '15 at 00:26
1

In general, the compiler is able to determine many things and it uses its observations when optimizing the code.

However, Java is statically and strongly typed language and compiler has no freedom when it comes to type safety.

1) Adding to the ln list is prohibited, because one does not know exactly which type of elements it is supposed to contain. In your example this rule prevented bringing the list to an invalid state.

2) "...the compiler is able to correctly determine that num is an Integer..."

Not true, the compiler must not determine it (although it could if Java were weakly typed language).

3) num instanceof Integer

This is evaluated at runtime, not at compile time. However, the following would produce the compile time error:

num instanceof String

because a Number can never be a String.

Community
  • 1
  • 1
Dragan Bozanovic
  • 23,102
  • 5
  • 43
  • 110
  • Oops I'm sorry. I did mean "ln" and not "le". I edited my post. I did not realize that instanceof was evaluated at runtime. How is it able to figure out that num is an Integer object and not a Number object? – BlaqICE Sep 28 '15 at 00:37
  • @BlaqICE Each Java object contains the information about its type: [Object header](http://stackoverflow.com/questions/26357186/what-is-in-java-object-header). – Dragan Bozanovic Sep 28 '15 at 13:00
0

The primary reason that this is not able to compile is due to the way that ? extends T behaves. In general, one should familiarize themselves with Producer Extends, Consumer Super, or PECS for short.

Now, I haven't looked at this particular code example, but I imagine the hierarchy being EvenNumber extends NaturalNumber. This makes the assignment statement valid, since an EvenNumber is a NaturalNumber (although I bet that the mathematicians are snickering about this).

In the scenario highlighted above, the reason you can't add anything to the collection is due it being primarily for reading from. That is, the list is a producer, so it is bound with extends.

If you want to both read and write to a collection, you would just leave off the wildcard.

You second example has nothing to do with generics at all, but rather simple inheritance. Since Integer inherits from Number, we can say that Integer is-a Number, and thus can be treated as a number. Any class that is inherited has this ability. Check the Liskov Substitution Principle for more info on that.

Community
  • 1
  • 1
Makoto
  • 104,088
  • 27
  • 192
  • 230