I've created a utility that combines zip file archives into a single archive. In doing so, I originally had the following method (see this question for some background on ExceptionWrapper
):
private void addFile(File f, final ZipOutputStream out, final Set<String> entryNames){
ZipFile source = getZipFileFromFile(f);
source.stream().forEach(ExceptionWrapper.wrapConsumer(e -> addEntryContent(out, source, e, entryNames)));
}
Here is the code for ExceptionWrapper.wrapConsumer
and ConsumerWrapper
public static <T> Consumer<T> wrapConsumer(ConsumerWrapper<T> consumer){
return t -> {
try {
consumer.accept(t);
} catch (Exception e) {
throw new IllegalStateException(e);
}
};
}
public interface ConsumerWrapper<T>{
void accept(T t) throws Exception;
}
This results in the compilation errors:
Error:(128, 62) java: incompatible types: java.util.function.Consumer<capture#1 of ? extends java.util.zip.ZipEntry> cannot be converted to java.util.function.Consumer<? super capture#1 of ? extends java.util.zip.ZipEntry> Error:(128, 97) java: incompatible types: java.lang.Object cannot be converted to java.util.zip.ZipEntry
However, the following change compiles just fine and works as expected:
private void addFile(File f, final ZipOutputStream out, final Set<String> entryNames){
ZipFile source = getZipFileFromFile(f);
Consumer<ZipEntry> consumer = ExceptionWrapper.wrapConsumer(e -> addEntryContent(out, source, e, entryNames));
source.stream().forEach(consumer);
}
Notice that all I did was pull out the in-lined creation of the Consumer
into a separate variable. Any spec experts know what changes for the compiler when the Consumer
is in-lined?
EDIT: As requested, this is the signature of addEntryContent(...)
:
private void addEntryContent(final ZipOutputStream out,
final ZipFile source,
final ZipEntry entry,
final Set<String> entryNames) throws IOException {
The problem is the rather unusual signature of ZipFile.stream()
:
public Stream<? extends ZipEntry> stream()
it was defined this way because it allows the subclass JarFile
to override it with the signature:
public Stream<JarEntry> stream()
Now when you call stream()
on a ZipFile
you get a Stream<? extends ZipEntry>
with a forEach
method with the effective signature void forEach(Consumer<? super ? extends ZipEntry> action)
which is a challenge to the type inference.
Normally, for a target type of Consumer<? super T>
, the functional signature T → void
gets inferred and the resulting Consumer<T>
is compatible to a target type of Consumer<? super T>
. But when wildcards are involved, it fails as a Consumer<? extends ZipEntry>
is inferred for your lambda expression which is considered not to be compatible with the target type Consumer<? super ? extends ZipEntry>
.
When you use a temporary variable of type Consumer<ZipEntry>
, you have explicitly defined the type of the lambda expression and that type is compatible with the target type. Alternatively you can make the type of the lambda expression more explicit by saying:
source.stream().forEach(ExceptionWrapper.wrapConsumer(
(ZipEntry e) -> addEntryContent(out, source, e, entryNames)));
or simply use a JarFile
instead of ZipFile
. The JarFile
doesn’t mind if the underlying file is a plain zip file (i.e. has no manifest). Since JarFile.stream()
doesn’t use wildcards, the type inference works without problems:
JarFile source = getZipFileFromFile(f);// have to adapt the return type of that method
source.stream().forEach(ExceptionWrapper.wrapConsumer(
e -> addEntryContent(out, source, e, entryNames)));
Of course, it will now infer the type Consumer<JarEntry>
rather than Consumer<ZipEntry>
but that difference has no consequences…
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With