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

标签 java generics lambda java-8 java-stream

我创建了一个实用程序,可以将 zip 文件存档合并到一个存档中。为此,我最初使用了以下方法(有关 ExceptionWrapper 的一些背景信息,请参阅 this question):

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

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

请注意,我所做的只是将 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()

这样定义是因为它允许子类JarFileoverride它带有签名:

public Stream<JarEntry> stream()

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

通常,对于 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而不是 ZipFile . JarFile不介意底层文件​​是否是普通 zip 文件(即没有 list )。自 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>但这种差异没有任何后果......

关于java - 为什么内联 Consumer<ZipEntry> 编译失败但在外部工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31455188/

相关文章:

java - 从四叉树中提取元素

java - 使用 ~100% CPU 和 250 TPS 测试的 Web 应用程序

python - 将任意函数与 MyPy 的 TypeVar 绑定(bind)属性一起使用

java - 列表 <? 之间的区别扩展 A> 和 List<A>

c# - 在Ilist中使用Func,为什么是lambda表达式?

java - 如何在 Java 中用 lambda 替换匿名类?

java - Stream.forEach 期间出现空指针异常

java - Activemq Topic Consumer 消费某些消息失败

c# - 如何在 DynamicProxy 类中使​​用自定义属性

Ruby 代码评论