We discussed it with him in some detail, and he was saying something along the lines that if I know exactly what is going to happen I should try to handle it without resorting to try/catch fall-back.
If your instructor is telling you that you should test to avoid IO exceptions, then I'm afraid that he is simply wrong.
Most I/O operations are declared as throwing IOException
which is a checked exception. When you call those methods, your code has to either handle or propagate the exception. You have no choice. The Java language mandates this for all checked exceptions.
It is impractical to test for all possible IOExceptions. For instance, this code can fail:
if (f.exists() && f.canRead()) {
os = new FileInputStream(f);
}
Why? Because there are other reasons that the constructor might throw an IOException, such as a hard disc error, a network error (if the file is on a remote mounted file system), a mandatory access control failure, and so on. The underlying conditions often cannot be detected in any other way than attempting to open the file.
Then there is the problem of race conditions. In the above example, there is a small time window between testing that the file is readable, and trying to open it. In that time window, it is possible that some other thread, or some external process will delete the file or change its access, so that the open will fail. There is no way to close that time window.
Then there is the question of why you would even want to avoid the exception. I suspect that your teacher is an "exception denier"; i.e. one of those people who takes the "exceptions should only be used for exceptional things" advice to an illogical extreme.
The "exceptions should only be used for exceptional things" advice is basically saying that you shouldn't overuse exceptions because they are kind of expensive. But expensive is relative.
In this example, the calls to File.exists()
and to File.canRead()
are also expensive because they each entail a system call, and that is going to take thousands of clock cycles. And worse still, unless there is something unusual about your application those two tests are going to succeed ... so you have just done two unnecessary system calls to avoid an exception that wasn't going to be thrown anyway. Unless there is a high probability (i.e. > 50%) that those tests are going to fail, it is more efficient to skip the test, attempt the open and handle the exception if it occurs.
I would not label my prof as such. We did not talk about intricacies of I/O specifically, it was more in the general terms in vein such that if I can avoid it, I should avoid it. I did not know (and he did not go into that detail) that there are exceptions which are very much unavoidable.
OK. So your Prof didn't say that. Good for him.
And yes, they are unavoidable in more than one sense.
As for rejecting the use of .exists() check, is the performance hit that significant?
Yes. See this - Syscall overhead
Or if you don't believe that, write a micro-benchmark and measure it. And compare it to the overhead of throwing and catching an exception.
Would it be issue of, say, desktop v. mobile and the like?
Nope. Or certainly not for a mobile where the operating system uses virtual memory to stop one "app" from interfering with another.
Is the trade off against clarity for troubleshooting/debugging (if I can pinpoint the problem better while avoiding crashing the programm) worth it?
Well, I would argue that this code:
try (InputStream is = new FileInputStream(f)) {
// use file
} catch (IOException ex) {
System.err.println("IO error: ": ex.getMessage());
}
... is simpler and easier to maintain and debug than this:
if (!f.exists()) {
System.err.println("File " + f + " does not exist");
} else if (!f.isDirectory()) {
System.err.println("File " + f + " is a directory");
} else if (!f.canRead()) {
System.err.println("File " + f + " is not readable");
} ...
} else {
try (InputStream is = new FileInputStream(f)) {
// read
} catch (IOException ex) {
System.err.println("IO error: "+ ex.getMessage());
}
}
Wouldn't you agree?