Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why compile fails inlining Consumer<ZipEntry> but works externally?

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 {
like image 444
MadConan Avatar asked Jul 16 '15 13:07

MadConan


1 Answers

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…

like image 180
Holger Avatar answered Sep 18 '22 22:09

Holger