最通用的解决方案如下:
interface IOConsumer<T> {
void accept(T t) throws IOException;
}
public static void processRessource(URI uri, IOConsumer<Path> action) throws IOException{
try {
Path p=Paths.get(uri);
action.accept(p);
}
catch(FileSystemNotFoundException ex) {
try(FileSystem fs = FileSystems.newFileSystem(
uri, Collections.<String,Object>emptyMap())) {
Path p = fs.provider().getPath(uri);
action.accept(p);
}
}
}
主要的障碍是处理两种可能性,要么拥有一个我们应该使用但不关闭的现有文件系统(例如 file URI 或 Java 9 的模块存储),要么必须打开并因此安全自行关闭文件系统(如 zip/jar 文件)。
因此,上面的解决方案将实际操作封装在interface 中,处理两种情况,然后在第二种情况下安全关闭,并且从 Java 7 工作到 Java 18。它在打开之前探测是否已经有一个打开的文件系统一个新的,因此它也适用于应用程序的另一个组件已经为同一个 zip/jar 文件打开文件系统的情况。
它可以在上面提到的所有 Java 版本中使用,例如将包的内容(示例中为java.lang)列为Paths,如下所示:
processRessource(Object.class.getResource("Object.class").toURI(),new IOConsumer<Path>(){
public void accept(Path path) throws IOException {
try(DirectoryStream<Path> ds = Files.newDirectoryStream(path.getParent())) {
for(Path p: ds)
System.out.println(p);
}
}
});
使用 Java 8 或更高版本,您可以使用 lambda 表达式或方法引用来表示实际操作,例如
processRessource(Object.class.getResource("Object.class").toURI(), path -> {
try(Stream<Path> stream = Files.list(path.getParent())) {
stream.forEach(System.out::println);
}
});
做同样的事情。
Java 9 的模块系统has broken 上述代码示例的最终版本。从 9 到 12 的 Java 版本不一致地返回路径 /java.base/java/lang/Object.class 为 Paths.get(Object.class.getResource("Object.class")),而它应该是 /modules/java.base/java/lang/Object.class。当父路径报告为不存在时,可以通过在缺少的 /modules/ 前面添加来解决此问题:
processRessource(Object.class.getResource("Object.class").toURI(), path -> {
Path p = path.getParent();
if(!Files.exists(p))
p = p.resolve("/modules").resolve(p.getRoot().relativize(p));
try(Stream<Path> stream = Files.list(p)) {
stream.forEach(System.out::println);
}
});
然后,它将再次适用于所有版本和存储方法。从 JDK 13 开始,不再需要这种变通方法。