我创建了一个将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.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 {
答案 0 :(得分:6)
问题是ZipFile.stream()
的相当不寻常的签名:
public Stream<? extends ZipEntry> stream()
它是以这种方式定义的,因为它允许子类JarFile
与override签名:
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>
兼容。但是当涉及通配符时,它会失败,因为您的lambda表达式推断出Consumer<? extends ZipEntry>
,该表达式被认为与目标类型Consumer<? super ? extends ZipEntry>
不兼容。
当您使用类型为Consumer<ZipEntry>
的临时变量时,您已明确定义了lambda表达式的类型,并且该类型与目标类型兼容。或者,您可以通过说:
source.stream().forEach(ExceptionWrapper.wrapConsumer(
(ZipEntry e) -> addEntryContent(out, source, e, entryNames)));
或仅使用JarFile
代替ZipFile
。如果基础文件是普通的zip文件(即没有清单),则JarFile
不介意。由于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>
,但这种差异没有后果......