java.io.File.listFiles 与 java.nio.Files.list 及其抛出的 IOException

java.io.File.listFiles vs java.nio.Files.list and its thrown IOException

为什么 Files.list 会抛出 IOExceptionFile.listFiles 不会?

查看Files.list (Java 8) 的源代码,我更好奇为什么没有抛出一个UncheckedIOException 因为它也在迭代器中抛出。

如果我用 Files.list 替换 File.listFiles-code,我现在需要处理一个异常,而我以前并没有真正处理过。 不用说,大多数开发人员甚至都不知道他们当时必须处理什么 ;-) 或者只是放一个 // TODO and/or e.printStackTrace() 在那里。

这使得在此处使用 Stream 相当麻烦,因为您需要用 try/catch 包围它或重新抛出异常,这在遗留代码中甚至可能是不可能的。

那么为什么做出这个决定?

首先,开发者总是必须处理可能发生的错误情况。

  • File.list():

    Returns null if this abstract pathname does not denote a directory, or if an I/O error occurs.

  • Files.list(Path):

    Throws:

    • NotDirectoryException - if the file could not otherwise be opened because it is not a directory (optional specific exception)
    • IOException - if an I/O error occurs when opening the directory

所以不同之处在于,开发人员可能很容易忘记对 null 的检查,只要没有错误条件,就永远不会被注意到。当问题发生在客户身上并且您的应用程序抛出一个 NullPointerException 而不是处理一个可能微不足道的问题时,您将通过艰难的方式学习它。

您的陈述“不用说,大多数开发人员甚至不知道他们当时必须处理什么”,这很好地说明了这一点。确实如此,但编译器会在编译时告诉您,您必须处理 IOException。与 File.list() 不同,检查 null 的失败可能会被忽略。在这两种情况中,你仍然可能处理得不好,但没有办法避免这种情况。

当然,一旦你明白你必须处理这个问题,你可能会问你想如何处理它,这取决于问题的种类。 return 值为 null 并不能说明问题所在。您可以通过 File.isDirectory() 检查“不是目录”条件并希望它在这期间没有改变,但是如果文件 一个目录,您不需要没有任何关于从哪里开始的提示。

相比之下,抛出的IOException不仅可以让你区分NotDirectoryException和其他错误情况,IOException是一个森林的基础class可以准确描述问题的特定异常,例如AccessDeniedException。即使异常有一个不明确的类型,它也可能有一个有意义的消息,你可以呈现给用户。

请注意,这是具有以下两个 API 的一般模式:

  • File.renameTo(File)

    Returns:

    true if and only if the renaming succeeded; false otherwise

  • File.delete()

    Returns:

    true if and only if the file or directory is successfully deleted; false otherwise

那么当这些方法中的任何一个 returns false 时,你会怎么做?

  • Files.move(Path,Path,CopyOption...)

    Throws:

    • FileAlreadyExistsException - if the target file exists but cannot be replaced because the REPLACE_EXISTING option is not specified (optional specific exception)
    • DirectoryNotEmptyException - the REPLACE_EXISTING option is specified but the file cannot be replaced because it is a non-empty directory (optional specific exception) AtomicMoveNotSupportedException - if the options array contains the ATOMIC_MOVE option but the file cannot be moved as an atomic file system operation.
    • IOException - if an I/O error occurs

这就是我所说的有帮助...

  • Files.delete(Path)
    Throws:
    • NoSuchFileException - if the file does not exist (optional specific exception)
    • DirectoryNotEmptyException - if the file is a directory and could not otherwise be deleted because the directory is not empty (optional specific exception)
    • IOException - if an I/O error occurs

同样,比 boolean 更有帮助。但请注意,还有 boolean deleteIfExists(Path),以防您希望非异常地处理那个微不足道的条件。由于所有其他重要条件仍作为异常处理,因此您不能混淆它们。

当然,API 设计者可以使用未经检查的异常,但这会导致什么结果呢?

That makes the use of the Stream here rather cumbersome as you need to surround it with a try/catch or rethrow the exception, which might not even be possible in legacy code.

没错。您将更改从未抛出此类异常的代码(因为 File.list() returns null 在错误情况下)以调用可能抛出未经检查的异常的新方法,因为这“可能在遗留代码”——但遗留调用者并不期望该异常并且永远不会处理它。

捕获异常并像以前 null 被 returned 时一样表现(如果你曾经在旧代码中检查过),可能确实很麻烦,但这不是预期的方式处理这种情况,那为什么要让人舒服……