问题描述
我创建了一个将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.wrapConsumer
和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;
}
这会导致编译错误:
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 {
1楼
问题是的相当不寻常的签名:
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>
但这种差异没有后果......