Java 8 中判断 /path/a 是否是 /path 的子目录的正确方法是什么?

What's the right way in Java 8 to evaluate if /path/a is a subdirectory of /path?

和标题差不多。

假设我传递了一个路径“/tmp/foo/bar”,我想特别确保该路径是路径“/tmp”的子目录,什么是最 "Java 8"ish怎么办?

具体来说,我有兴趣询问 "Given two independent paths, /foo and /foo/bar/baz how can I test if /foo/bar/baz is a subdirectory of /foo without recursing the directory tree?" 我对探索 /foo 下的所有子目录并寻找 /foo/bar/baz 下游不感兴趣。

我一直在研究

@Test
public void test() {
    final Path root = Paths.get("/tmp");
    final Path path0 = Paths.get("/");
    final Path path1 = Paths.get("/opt/location/sub");
    final Path path2 = Paths.get("/tmp/location/sub");

    final Pattern ptrn = Pattern.compile("^[a-zA-Z].*$");

    final Function<Path, String> f = p -> root.relativize(p).toString();

    Assert.assertFalse("root",ptrn.matcher(f.apply(root)).matches());
    Assert.assertFalse("path0",ptrn.matcher(f.apply(path0)).matches());
    Assert.assertFalse("path1",ptrn.matcher(f.apply(path1)).matches());
    Assert.assertTrue("path2",ptrn.matcher(f.apply(path2)).matches());
}

但这感觉就像我一直工作到 Java 8 的边缘,然后回到旧模式并错过了船。

boolean <a href="https://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.html#startsWith-java.nio.file.Path-" rel="nofollow noreferrer">startsWith</a>(<a href="https://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.html" rel="nofollow noreferrer">Path</a> other)

Tests if this path starts with the given path.

获取有意义的断言消息

与 AssertJ (JUnits 3,4,5+...)

tl;博士

import static org.assertj.core.api.Assertions.assertThat;

    ...
    assertThat(path2 + "").startsWith("/tmp/"));

使用香草 JUnit 4

import static org.junit.Assert.assertThat;        // !DEPRECATED! see: https://junit.org/junit4/javadoc/latest/org/junit/Assert.html#assertThat(T,%20org.hamcrest.Matcher)
import static org.hamcrest.core.StringStartsWith;

    assertThat(path2 + "", startsWith("/tmp/"))

注意:Harmcrest 的 assertThat 已从 JUnit 5 中删除。因此,在 2020 年,更常见的做法是使用 AssertJ,这对于将来的升级更容易:https://mvnrepository.com/artifact/org.assertj/assertj-core


详细信息: Path#startsWith 的单元测试问题是它 return 是布尔值。所以失败的测试会 return 一个愚蠢的理由:

"Expected TRUE, but was FALSE"

因此,为了更好地维护单元测试/改进代码,更好的方法是使用断言框架。通过像这样开箱即用的消息传递,可以加快您的故障排除速度:

"Expecting: <"/opt/location/sub"> to start with: <"/tmp/foo">."

注意:虽然AssertJ有路径友好的方法,如isDirectorystartsWith(::Path),但根据环境,它们可能导致failed to resolve actual real path 消息。因此在断言之前将 Path 转换为 String 更稳定: -- 通过 path2.toString() -- 或者通过 path2 + "".