First of all, the developer always has to handle erroneous situations that might occur.
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
So the difference is that the developer might easily forget the check for null
, which won’t ever be noticed as long as there is no erroneous condition. You will learn it the hard way, when the problem occurs at the customer and your application throws a NullPointerException
instead of handling a possibly trivial issue.
This is perfectly illustrated by your statement “Needless to say, that most developer don't even know what they would have to handle at that point”. Indeed, but the compiler will tell you already at compile time, that you have to handle the IOException
. Unlike File.list()
, where the failure to check for null
may be ignored. You still may handle it poorly in either case, but there’s no way to prevent this.
Of course, once you understood that you have to handle the problem, you might ask how you want to handle it, which depends on the kind of problem. Having a return value of null
doesn’t tell you anything about the problem. You may check for the “not a directory” condition via File.isDirectory()
and hope that it didn’t change in-between, but if the file is a directory, you don’t have any hint at where to start.
In contrast, the thrown IOException
does not only allow you to differentiate between NotDirectoryException
and other error conditions, IOException
is the base class of a forest of specific exceptions which can precisely describe the problem, e.g. AccessDeniedException
. Even if the exception has an unspecific type, it might have a meaningful message, you can present to the user.
Note that this is a general pattern with these two APIs:
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
So what do you do when either of these methods returns false
?
That’s what I call helpful…
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
Again, much more helpful than a boolean
. But note that there’s also boolean deleteIfExists(Path)
, in case you want to handle that one trivial condition non-exceptionally. Since all other non-trivial conditions are still handled as exception, you can’t confuse them.
Of course, the API designers could have used unchecked exceptions, but where would that lead to?
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.
Exactly. You would change code that never threw such an exception (because File.list()
returns null
in the erroneous case) to call the new method that might throw the unchecked exception, because that’s “possible in legacy code”—but the legacy caller isn’t expecting that exception and would never handle it.
Catching the exceptions and behave exactly like before when null
was returned (if you ever checked that in the old code), might indeed be cumbersome, but that’s not the intended way to deal with such situations, so why should that made comfortable…