为什么内联 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.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 {
问题是 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
而不是 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>
但这种差异没有任何后果......
我创建了一个实用程序,可以将 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 {
问题是 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
而不是 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>
但这种差异没有任何后果......