How do I fix this Java generics wildcard error?
-
05-07-2019 - |
Question
In this question, TofuBeer was having problems creating a genericized IterableEnumeration
.
The answer came from jcrossley3 pointing to this link http://www.javaspecialists.eu/archive/Issue107.html which pretty much solved the problem.
There is still one thing I don't get. The real problem, as effectively pointed out by erickson, was that:
You cannot specify a wildcard when constructing a parameterized type
But removing the wildcard in the declaration didn't work either:
final IterableEnumeration<ZipEntry> iteratable
= new IterableEnumeration<ZipEntry>(zipFile.entries());
Results in the following error:
Main.java:19: cannot find symbol
symbol : constructor IterableEnumeration(java.util.Enumeration<capture#469 of ? extends java.util.zip.ZipEntry>)
location: class IterableEnumeration<java.util.zip.ZipEntry>
final IterableEnumeration<ZipEntry> iteratable = new IterableEnumeration<ZipEntry>( zipFile.entries());
^
1 error
But the samples in the JavaSpecialist do work:
IterableEnumeration<String> ie =
new IterableEnumeration<String>(sv.elements());
The only difference I can spot is that in the JavaSpecialists blog, the Enumeration
comes from a Vector
whose signature is:
public Enumeration<E> elements()
while the one that fails comes from ZipFile
whose signature is:
public Enumeration<? extends ZipEntry> entries()
Finally, all of this is absorbed by the for-each construct and the static make method suggested in the link
for(final ZipEntry entry : IterableEnumeration.make( zipFile.entries() )) {
if(!(entry.isDirectory())) {
names.add(entry.getName());
}
}
But!! the point in that newsletter was not to solve this problem, but to avoid the need to specify a generic type, just because the syntax looks ugly!!
So.. my questions is:
What is happening?
Why doesn't creating an instance of IterableEnumeration
work when the parameter is an Enumeration
whose type is <? extends SomeClass>
? And why does the make for-each construct swallow the problem?!!!
Why does this work:
for(final ZipEntry entry : IterableEnumeration.make( zipFile.entries() )) {
but this not work?
final IterableEnumeration<ZipEntry> iteratable
= IterableEnumeration.make( zipFile.entries() );
Below is a (slightly) modified version of TofuBeer's original code:
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.Vector;
public class Main {
private ZipFile zipFile;
public Set<String> entries() {
final Vector<ZipEntry> vector = new Vector<ZipEntry>();
// why this works.
//final IterableEnumeration<ZipEntry> iteratable = new IterableEnumeration<ZipEntry>( vector.elements() );
// but this do not.
//final IterableEnumeration<ZipEntry> iteratable = new IterableEnumeration<ZipEntry>( zipFile.entries() );
// nor this
final IterableEnumeration<ZipEntry> iteratable = IterableEnumeration.make( zipFile.entries() );
// And what's with the for-each that doesn't care about the type?
final Set<String> names = new HashSet<String>();
for(final ZipEntry entry : IterableEnumeration.make( zipFile.entries() )) {
if(!(entry.isDirectory())) {
names.add(entry.getName());
}
}
return (names);
}
}
class IterableEnumeration<T> implements Iterable<T> {
private final Enumeration<T> enumeration;
public IterableEnumeration(final Enumeration<T> e) {
enumeration = e;
}
public Iterator<T> iterator() {
return new Iterator<T>() {
public boolean hasNext() {
return (enumeration.hasMoreElements());
}
public T next() {
return (enumeration.nextElement());
}
public void remove() {
throw new UnsupportedOperationException("Cannot remove via an Enumeration");
}
};
}
// As suggested by http://www.javaspecialists.eu/archive/Issue107.html
// but doesn't help with: final IterableEnumeration<ZipEntry> iteratable = IterableEnumeration.make( zipFile.entries() );
public static <T> Iterable<T> make(Enumeration<T> en) {
return new IterableEnumeration<T>(en);
}
}
I want to understand it!!
Solution
I'm not sure what's happening with the foreach loop, but you need to add the wildcard to your declaration of IterableEnumeration to accept the unspecified type returned from ZipFile.entries().
Replace
private final Enumeration<T> enumeration;
public IterableEnumeration(final Enumeration<T> e) {
enumeration = e;
}
public static <T> Iterable<T> make(Enumeration<T> en) {
return new IterableEnumeration<T>(en);
}
With
private final Enumeration<? extends T> enumeration;
public IterableEnumeration(final Enumeration<? extends T> e) {
enumeration = e;
}
public static <T> Iterable<T> make(Enumeration<? extends T> en) {
return new IterableEnumeration<T>(en);
}
OTHER TIPS
The underlying problem here is that when ZipFile was changed to support generics, the maintainer chose to make the return type of the entries()
methods Enumeration<? extends ZipEntry>
(presumably so that the method in the subclass JarFile
can return Enumeration<JarEntry>
). This causes the problem you are seeing.
Because Enumeration<T>
is used covariantly (as it is always - it only ever returns values), you should always make method arguments take Enumeration<? extends T>
.