当前位置: 代码迷 >> java >> 为什么编译失败内联消费者 在外部工作?
  详细解决方案

为什么编译失败内联消费者 在外部工作?

热度:70   发布时间:2023-07-27 09:17:25.0

我创建了一个将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 {

问题是的相当不寻常的签名:

public Stream<? extends ZipEntry> stream()

它是以这种方式定义的,因为它允许子类JarFile使用签名它:

public Stream<JarEntry> stream()

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

通常,对于目标类型的Consumer<? super T> Consumer<? super T> ,函数签名T → void被推断,结果Consumer<T>与目标类型的Consumer<? super T> Consumer<? super T> 但是当涉及通配符时,它作为Consumer<? extends ZipEntry> Consumer<? extends ZipEntry>是为你的lambda表达式推断的,它被认为与目标类型Consumer<? super ? extends ZipEntry>不兼容Consumer<? super ? extends ZipEntry> Consumer<? super ? extends ZipEntry> Consumer<? super ? extends ZipEntry>

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

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

或者只是使用JarFile而不是ZipFile JarFile不介意底层文件??是否是普通的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>但这种差异没有后果......