File I/O and Exceptions
The course is part of this learning path
This course covers how to persist data as well as how to write data from programs to file to be used later, which is called file output. We'll also learn about how to load the data from files and populate our variables with that data, which is called file input.
- Beginner coders or anyone new to Java
- Experienced Java programmers who want to maintain their Java knowledge
- Developers looking to upskill for a project or career change
- College students and anyone else studying Java
This is a beginner-level course and can be taken by anyone with an interest in learning about Java.
In this lecture, we will begin our discussion of File I/O which stands for File input/output, which involves the techniques we use to maintain data persistently. This lecture will focus on file input which involves data from a secondary storage device being brought into main memory. To ensure we better understand what our motivation is for using file I/O, we need to review some concepts. As has been mentioned before in the course, variables and constants live in memory which is also called main memory.
It is also called RAM or Random Access Memory. This is because bytes throughout the memory can be read and written to without sequentially going through every byte to get to the byte the program wants. So, similar to our notion of a raise which support random access, typically using indices, the system does a little math and can jump to the correct area in memory as needed. So, the advantage of main memory is that it is extremely fast, and the CPU can read and write data in main memory very quickly and fluidly. What's the disadvantage then?
Well, main memory is volatile, which means when the computer loses power, the data and the instructions in it are lost. So, is there any way to save data long term or persistently? The answer is yes. We can store data long-term in some form of secondary storage such as a hard disk or a flash drive. Generally, the disadvantage to these is that they are slower than main memory. Many of them such as the mechanical hard drive have moving mechanical parts, and even though they can move thousands of times per minute, this is terribly slow compared to what happens in main memory and the processor. Purely electrical devices are much faster than electromechanical devices.
Even with secondary storage advances like solid state technology, which generally has no moving parts, it is still typically slower than main memory. So, our variables and constants and literals live in main memory. If we want to keep data long-term, we have to store it in secondary storage. So, how do we do that? For a relatively simple and common way to store data, we can use files. So, reading from files, known as file input, and writing to files known as file output, are both important skills to have to make sure your programs can work with data long-term.
We will begin with a focus on file input which is reading data from file. If you haven't done so already, make sure to create the Section8-Projects, IntelliJ Project. And let's create them a file named FileInputFun. So, I'm going to right click here and go to New, Java Class, FileInputFun. Before writing the source code, we need to create a file at the top level of the IntelliJ Project folder and put some integers in it. We'll just call it input.txt. So over here, I'm going to actually go to Project Files, and at the very top here, I'm going to right click and go to New and just call it File, and then input.txt as the name.
So, there's input.txt. And now I'll switch it back right here to Project, and I still have input.txt available here. I'm going to write some numbers here. You can write as many as you'd like and whichever numbers you like, and notice I'm not putting an extra space at the end. See how this recognizes the 13th line. I'm going to delete that so there's no extra lines. So over here, in our FileInputFun code, public static void main(String[ ] args) There's the end of main.
I need to use a scanner to do input just like I do from keyboard. I'm going to call it Scanner infile, and notice I'm not creating it just yet. What we're about to do is going to look a little bit foreign to you but I'll explain it in a second. Okay. First, I got to import Scanner. Notice it did import.java.util.Scanner when I did Alt + Enter right there. And right here, it's saying I don't have that error so it's not trying to catch it, but it'll turn red in just a minute. So, this is called a try-catch statement that we use with exception-handling, another concept you should know in this section.
With exception handling, the try block holds code that could cause what we call an exception, such as the File not being available and the catch block then responds to the exception. So, exceptions are exceptional circumstances. Now, I'm going to actually instantiate infile, and this time instead of system.in for input from the keyboard, I have to put new File, and then I'm going to give it the name right there. And before I forget, I want to do infile.close() here, so that will close the File string. It doesn't know what File is, so I'm going to hit 'Alt Enter' and 'Import classes', this solution that I want. So, I'm going to click that.
And you notice that it has imported java.io.File. If we did not have the try-catch around here, then we would actually get an error. This would say that this could throw a FileNotFoundexception, which it looks like it already automatically imported also, and we weren't handling it. So, we need to handle that type of exception. So, I'm going to create int input, and create a little while loop here, while infile, which is our scanner, (.hasNext), we're going to loop. So, it's a sentinel-controlled loop. And as long as there's input, we want to store that input from the File using nextInt because we're dealing with integers. Storing that data into the input variable. System.out.println(), I'm going to just print the input.
So, basically I'm just echoing the data to the console. All right. Now, in the catch block down here, the catch block will respond to a specific type of exception; in this case, the FileNotFoundexception type. So, we will say ("Can't find File!"), and we can also use the actual exception object, ex. and then say getMessage. There we go. So, let's run this and see what happens. Right click. And then to 'Run'. There we go. And of course, it reads all the items into memory and as it goes along, it prints them. Just like we told it to. So, we're just echoing or repeating the data that we read from the File to the console. But we've shown that we are, in fact, connecting to the File, which is great.
Now let's play around with the code a bit to learn some more. What if we modify the name of the file in the code to a file that doesn't exist? So, remember we had created this file right here named input.txt, which we could create off of this or go to again, Project Files and do it that way. But I'm going to create or name it input2.txt where it's looking for the File but we don't have an input2.txt. So, what we're doing is we're trying to get it to throw an exception. So, let's run this again. Right click 'Run'. And you'll notice that it does not succeed in the try block. It goes here and then it fails because it cannot open that File, it doesn't exist.
So, the type of exception that's thrown is this one right here, FileNotFoundexception, and it prints out ("Can't find File!") just like we told it to. And then the internal message that the exception holds, which is this: input2.txt (The system cannot find the File specified). So, as you can see, our exception is caught by the catch statement. We print out our simple little message that we can't find the File, and then we also see the information that was stored in the exception object. Notice carefully, I said exception object. So, exceptions aren't anything magical. They're not unicorns or anything special. All exceptions derived from a class named Exception with a capital E, and they are all classes just like string is a class, and scanner's a class, and rectangle that we created as a class, they're all classes.
The difference is when an exception occurs often to signal a problem like the File not being found, the object is created by the runtime system and the reference is stored in the associated variable in the catch clause that matches the exception, like this one here. So, that brings up a good point. Can we have more than one catch clause to catch different types of exceptions? And the answer is yes. Before demonstrating that, let's change the name of our file back to the valid name of input.txt. And now let's actually open that file. We've got it open over here, so I'll just use this. Could just double click it over here. At the end of this file, I'm going to put the word John, my name which is not a number. It's not an integer and we're just adding it to the end of this input file.
So, let's run the program again and see what happens because remember this is expecting integers using nextInt(), so let's see what happens. Right click 'Run.' You'll notice it prints out a bunch of the numbers, all the numbers in fact in this case, but when it comes across John, it can't use that because it's expecting an integer. So, you'll see that it says exception in thread "main" java.util.InputMismatchException. And then it says where it came from. This is called the call stack. We've talked about that before. The method call stack tells us that it originated on line 14 in FileInputFun. So, on line 14, this is actually where it occurred. Sometimes, it'll tell you it's on a specific line but it won't really be on that line. The code that's causing the problem, it'll be somewhere around it usually.
But a lot of times, it is the actual line that it points out, and in this case it was. Input= Infile.nextInt. It's an InputMismatchException. And the reason it is, is because it's reading a string and trying to convert it to an integer, so it throws an exception. You'll notice that the exception we get though is in kind of this angry red here because that's how the IDE formats it. And because the default exception handler, which is the global exception handler that job uses to exceptions, is the one that had to catch it. So, you'll see again the type of exception is InputMismatchException, and since this is not a FileNotFoundException nor is it a subclass of FileNotFoundException,
we have to it separately. That's fairly easy to do. So, all we have to do is add another catch clause. So, let's do that now. So, right here at the end I'm going to put catch (InputMismatchException). And in this one, we're going to say system.out.println, ("Error reading input'). And you could of course add onto this and echo out the get message, the message internal to this if you wanted to as well. Let's run it again and see what happens. Right click. We run the file and you'll notice that it does read and write all the numbers here and we don't get the angry red error that we did before, we just get the output that we told it to output. So, we are handling the exception instead of the default error exception handler. So, we're catching both of these exception types.
So, the default exception handler doesn't have to. Nice. This gives us the power to decide what we want to do in response to each type of exception. As an important side note, just like many classes in Java, exceptions are part of a hierarchy of classes where different exceptions inherit from other types of exceptions. We haven't explored inheritance thoroughly yet in the course, but at a basic level of subclass is a type of superclass. So, let me explain. If I have a Dog, that would be a subclass of say, animal. The animal is superclass, dog is the subclass. So, we say dog is-an Animal because that's an inheritance relationship. A FileNotFoundException is an IOException, which is another more general class in the exception hierarchy.
It's a syntax error to put a catch block for a more general exception type before a catch block for a more specific type. So, you can put an IOException catch block below the catch block for for file not found, but you can't do the reverse. Keep that in the back of your mind as you come across these exceptions in the future. Another thought may have crossed your mind as well -why did the compiler refuse to compile when we didn't handle the FileNotFoundException or I did it kind of the right way by putting it in a try immediately. So, if we put it out here, you'll notice that immediately, I hover over it and it says Unhandled exception java.io.FileNotFoundException.
This one has to be in a try block right here or otherwise you have to acknowledge the exception in some other way. So, the answer as to why the compiler cares about this but it didn't give us a warning about the an error about the InputMismatchException that we had before, the reasoning is because some exceptions are checked exceptions like FileNotFoundException and others like InputMismatch, which was caused by this problem here are called unchecked exceptions. Essentially, all of the unchecked exceptions have as their ancestor in the hierarchy, a class named RuntimeException. Unchecked exceptions do not cause the compiler refuse to compile if you don't acknowledge them, right? We wrote this code without the compiler saying I'm not going to compile that. However, checked exceptions, as the name suggests, cause the compiler to check if they are being handled in a try catch or in some other ways such as what we call re-throwing the exception. That's a lot of information, but don't worry if you don't feel like you absorbed it all. There's a lot to learn with exceptions and we just scratched the surface in this lecture. You'll encounter them on more occasions throughout the course and many projects that you work on here and in other courses or scenarios. Before moving on, I'd like to issue you a challenge. You can use the same FileInputFun file for this challenge. First, just remove the string "John" from the end of the input.txt file that we had before, leave the numbers intact, and make sure you don't have any empty spaces at the end of the file. Then I'd like you to calculate the sum of all the numbers in this file and print out the sum after the loop has completed. So, pause the video, give this one your best shot, come back when you're done or if you need some help.
How'd that go for you? Were you able to complete the challenge? Let's work on it together. So, I'm just modifying what we did here. We were just echoing the input but I'm going to actually create a separate variable called sum and I'm going to set it to 0. And now inside here very simply, in the while loop, sum += input which is the same as sum = sum + input. And then of course before we do the close here, we could do it after. It doesn't really matter because we don't need the file after this. We're going to print out sum is, oops, probably should put that in double quotes, right? Sum is and then concatenate sum to it right there.
And of course we're going to run it just to test it out again. Right click, run the file, and there you go. Sum is 3,225. Very good. Awesome. Great work everyone. Hopefully, we were able to complete this challenge. However, it's totally okay if you struggled with it and finally figured it out or even if you couldn't figure it out at all, keep practicing. The more you see code examples and work with code on your own and through the challenges and projects in the course, of course, the better you will become. So, don't get frustrated or disappointed. We all have to start somewhere in our learning journeys. In the next lecture, we'll learn about the complementary functionality to file input - that is, file output where we can write data to a file that can be read later on by our application or some other application. I'll see you there.
John has a Ph.D. in Computer Science and is a professional software engineer and consultant, as well as a computer science university professor and department chair.