3

I already know that the Iterator contains a collection of objects which extend ZipEntry but I can't perform this downcast without an error:

ZipFile zf = new ZipFile("a.zip");

// Type mismatch: cannot convert from Iterator<capture#4-of ? extends ZipEntry> to Iterator<ZipEntry>
Iterator<ZipEntry> it = zf.entries().asIterator();

Downcasts are not errors in Java:

String a = "";

// Works fine
Object b = a;

The exact return type is Iterator<? extends ZipEntry> but I don't need the original type since I only use the behavior provided by ZipEntry.

If I cast it then I still get a warning:

// Type safety: Unchecked cast from Iterator<capture#4-of ? extends ZipEntry> to Iterator<ZipEntry>
Iterator<ZipEntry> it = (Iterator<ZipEntry>)zf.entries().asIterator();

What's wrong with an unchecked cast here if I already know that ? extends ZipEntry? Why would this be unsafe?

Zhro
  • 2,546
  • 2
  • 29
  • 39
  • 1
    Why can't you just use `Iterator extends ZipEntry>` as the type of `it`? – Sweeper Dec 31 '20 at 11:29
  • 2
    Does [this](https://stackoverflow.com/questions/2745265/is-listdog-a-subclass-of-listanimal-why-are-java-generics-not-implicitly-po) help? – Sweeper Dec 31 '20 at 11:31

2 Answers2

1

An Iterator<? extends ZipEntry> represents an iterator of an unknown type. The only thing we know about that type is that it is ZipEntry or a subtype of ZipEntry. In other words, it could be Iterator<ZipEntry>, Iterator<JarEntry>, or Iterator<SomeOtherSubclass> in actuality.

Because of this, you can't be sure that Iterator<? extends ZipEntry> can be assigned to a variable of type Iterator<ZipEntry>. Sure, if it were Iterator<ZipEntry>, then you could assign it, but it could also be Iterator<JarEntry>, or Iterator<SomeOtherSubclass>, in which case you can't. Foo<T> and Foo<U> are unrelated types, even if T is a subclass of U. Have a read at this for why this is true for List<T> and List<U>.

In this case though, this argument (from the linked post):

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

doesn't apply to Iterators, because you can only take things out of iterators, but not add things in. In fact, all the operations you can do on an iterators are safe, even if you were allowed Iterator<Animal> animals = dogs;. However, the compiler simply doesn't check that the all the operations are safe automatically.

The way Java works is, when you use a generic type, you can say "Please let me assign Iterator<JarEntry> to Iterator<ZipEntry>, and don't allow me to use any of the operations that are unsafe". The syntax to do that, is:

Iterator<? extends ZipEntry> it = zf.entries().asIterator();

(This is called use-site variance.)

It is no coincidence that this "don't-allow-me-to-use-any-of-the-operations-that-are-unsafe" type is exactly the same as the return type of asIterator.

Since Iterator doesn't have any such unsafe operations, you get the exact same interface as a regular Iterator<ZipEntry>.

Compare this to when you use this on a List. You would not be able to add anything to a List<? extends ZipEntry>, for example.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
0

Java Generics allows the programmer to bind (or limit) the element types treated by a class at compilation time. Do not forget that Java Generics are not present at runtime, they are erased (search for "Java Type Erasure" on Google for more details, if you do not know it).

In your example, the iterator returned by the call zf.entries().asIterator() is bound to any subclass of ZipEntry, but not to the ZipEntry class itself. So the compiler detects that you are binding your iterator to the ZipEntry class, that is explicitly excluded from the set of all possible classes that can be treated by the returned iterator.

By writing the code

Iterator<? extends ZipEntry> it = zf.entries().asIterator();

you agree with the interface exposed by the ZipFile class, and you are asserting that your iterator will return only objects that are instances of some sub-class of ZipEntry, and never it will return objects that are direct instances of the ZipEntry class.

If you write

Iterator<ZipEntry> it = ...

You are allowing the iterator to return both instances of ZipEntry and instances of all its subclasses.

That's all about differences between Iterator<ZipEntry> and Iterator<? extends ZipEntry>