0

I was learning Java generics lately and came across the so-called "get-put" principle, i.e. which kind of wildcards allow you to add or remove certain types of objects from a collection (reference, e.g. https://flylib.com/books/en/4.79.1.18/1/).

My problem is, it is said that you cannot get anything but Object(s) from a collection that uses <? super SomeClass>. But the following code is perfectly valid:

List<? super A> list = new ArrayList<>();
list.add(new A());
System.out.println((list.get(0).toString()));

where

class A{
    @Override
    public String toString(){
        return "super.toString();";
    }
}

The funny thing is, it really uses the overridden toString(), contrary to the principle.

Furthermore,

 A a = list.get(0);

fails.

Could anyone explain what's the issue here?

Daniel Pop
  • 456
  • 1
  • 6
  • 23

2 Answers2

1

The reference type of list.get() in this case is Object, and that class declares toString() so you are allowed to call it.

But Java uses dynamic dispatch to choose which method to execute. And since the runtime type of the object is A, that's the version that is called.

Your code is equivalent to this:

Object obj = new A();
System.out.println(obj.toString()); /* Prints A's version */
A a = obj; /* Fails to compile. */

As you can see, the behavior has nothing to do with generics.

erickson
  • 265,237
  • 58
  • 395
  • 493
  • but why doesn't the compiler complain that I try accessing a lower-bounded collection, as the book says it should? – Daniel Pop Jan 22 '20 at 17:17
  • @AvramPop I don't understand your question. Can you indicate which line of code the book says should be a compilation error? – erickson Jan 22 '20 at 17:27
  • System.out.println((list.get(0).toString())); - getting should be forbidden from a lower bounded generic. – Daniel Pop Jan 22 '20 at 17:28
  • 1
    @AvramPop If your book says that's forbidden, it's wrong. You can invoke any (accessible) method of a generic type; a lower bound doesn't magically make a previously callable method uncallable. What the book *should* say is that the type of the result is unbound. All you know about it is that it is-an `Object` (because all classes extend `Object`). I think that remark is referring to what you have discovered: `A a = list.get(0);` doesn't work, but it's because the compile-time type of the result of `list.get(0)` is `Object` rather than `A`, not because `get()` is somehow forbidden. – erickson Jan 22 '20 at 17:31
  • I think the problem was with the proper understanding of the concepts - the thing that you said that get() is not somehow forbidden, that explained the whole thing. thanks – Daniel Pop Jan 22 '20 at 17:38
1

Nothing unexpected is happening here. Let's run through the steps:

List<? super A> list = new ArrayList<>();

We have a List of ?, which are above A in the class hierarchy. Every ? is an Object, so we can think of it as a List<Object>. Proceeding onwards...

list.add(new A());
System.out.println((list.get(0)

All good so far - list has an A inside, and it is fetched as an Object.

                               .toString()));

We call toString, which is invoked on an Object (the A). Dynamic dispatch proceeds to invoke the A's toString method (the lowest definition from the type hierarchy). However this is perfectly legal because toString is defined for Object as well as for A. Moving on...

 A a = list.get(0); //oops!

This breaks, as expected, because we try to convert an Object from a List<Object> into an A, without a cast (e.g. A a = (A) list.get(0);).

Avi
  • 2,611
  • 1
  • 14
  • 26