为什么内联 Consumer<ZipEntry> 编译失败但在外部工作?

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

我创建了一个实用程序,可以将 zip 文件存档合并到一个存档中。为此,我最初采用了以下方法(有关 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)));
}

这是 ExceptionWrapper.wrapConsumerConsumerWrapper

的代码
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;
}

这会导致编译错误:

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

但是,以下更改可以正常编译并按预期工作:

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);
}

请注意,我所做的只是将 Consumer 的内联创建提取到一个单独的变量中。任何规范专家都知道 Consumer 内联时编译器会发生什么变化?

编辑:根据要求,这是 addEntryContent(...) 的签名:

private void addEntryContent(final ZipOutputStream out, 
                             final ZipFile source, 
                             final ZipEntry entry, 
                             final Set<String> entryNames) throws IOException {

问题是 ZipFile.stream() 的相当不寻常的签名:

public Stream<? extends ZipEntry> stream()

它是这样定义的,因为它允许子类 JarFile 使用签名 override 它:

public Stream<JarEntry> stream()

现在,当您在 ZipFile 上调用 stream() 时,您会得到一个带有 forEach 方法和有效签名 void forEach(Consumer<? super ? extends ZipEntry> action)Stream<? extends ZipEntry>,这是一个挑战到类型推断。

通常,对于 Consumer<? super T> 的目标类型,会推断出函数签名 T → void,并且由此产生的 Consumer<T>Consumer<? super T> 的目标类型兼容。但是,当涉及通配符时,它会失败,因为 Consumer<? extends ZipEntry> 被推断为您的 lambda 表达式,它被认为与目标类型 Consumer<? super ? extends ZipEntry>.

不兼容

当您使用 Consumer<ZipEntry> 类型的临时变量时,您已经明确定义了 lambda 表达式的类型,并且该类型与目标类型兼容。或者,您可以通过以下语句使 lambda 表达式的类型更明确:

source.stream().forEach(ExceptionWrapper.wrapConsumer(
                        (ZipEntry e) -> addEntryContent(out, source, e, entryNames)));

或者简单地使用 JarFile 而不是 ZipFileJarFile 不介意底层文件​​是否为普通 zip 文件(即没有清单)。由于 JarFile.stream() 不使用通配符,类型推断没有问题:

JarFile source = getZipFileFromFile(f);// have to adapt the return type of that method
source.stream().forEach(ExceptionWrapper.wrapConsumer(
                        e -> addEntryContent(out, source, e, entryNames)));

当然,它现在将推断类型 Consumer<JarEntry> 而不是 Consumer<ZipEntry> 但这种差异没有任何后果......