When discussing the do..while loop in 1-10 we talked about the importance of input validation as a proactive technique to avoiding run-time errors in our programs. But sometimes errors can’t be avoided. For example, if we’re expecting the user to enter an integer using the nextInt() method of a Scanner object but the user enters a String instead our program will crash before we can even attempt any kind of input validation! You’ll recall from 1-4, this kind of error is called an exception or run-time error.
To deal with such exceptions so that they don’t end up crashing our programs we need a reactive error handling technique called exception handling. In many cases, handling an exception can allow a program to recover gracefully from a run-time error and continue executing – we call programs like this robust (or fault-tolerant).
Exceptions are quite common when reading and writing to files. Therefore, before we talk about working with files we will discuss how to handle exceptions.
Exception Handling
Consider the following example (without exception handling):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import java.util.Scanner; public class Exceptions1 { public static void main( String args[] ) { Scanner input = new Scanner( System.in ); System.out.print( "Please enter an integer numerator: " ); // nextInt() will cause an exception if an integer is not given int numerator = input.nextInt(); System.out.print( "Please enter an integer denominator: " ); int denominator = input.nextInt(); // integer / and % operations will cause an exception if denominator = 0 int quotient = numerator / denominator; int remainder = numerator % denominator; System.out.printf( "\n%d / %d = %d r%d\n", numerator, denominator, quotient, remainder ); } } |
There are two possible exceptions that can crash the code above. On lines 9 and 11, the user might enter a non-integer value (e.g., a double or String value). This will result in an InputMismatchException. The second is an ArithmeticException if we use / or % with a 0 denominator, just like on your calculator. Until now, we have not had any tools to deal with these kinds of run-time errors.
But now we do! To catch and recover from exceptions like these we use a try..catch statement in Java. Here’s the same program again with exception handling added – see if you can crash it!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import java.util.Scanner; import java.util.InputMismatchException; public class Exceptions2 { public static void main( String args[] ) { Scanner input = new Scanner( System.in ); boolean validInput = false; do { System.out.print( "Please enter an integer numerator: " ); try { // nextInt() will cause an exception if an integer is not given int numerator = input.nextInt(); System.out.print( "Please enter an integer denominator: " ); int denominator = input.nextInt(); // integer / and % operations will cause an exception if denominator = 0 int quotient = numerator / denominator; int remainder = numerator % denominator; System.out.printf( "\n%d / %d = %d r%d\n", numerator, denominator, quotient, remainder ); validInput = true; } // Handle exception if non-integer value entered by user catch ( InputMismatchException inputMismatchException ) { System.err.println( "\nJava Exception: " + inputMismatchException ); input.nextLine(); // clear input stream so user can try again System.out.println( "You must enter integers. Please try again.\n" ); } // Handle exception if / or % by 0 happens catch ( ArithmeticException arithmeticException) { System.err.println( "\nJava Exception: " + arithmeticException ); System.out.println( "Zero is an invalid denominator. Please try again.\n" ); } } while (validInput == false); System.out.println( "All Done!" ); } } |
There’s a lot happening here. First, I put most of the code within a loop such that if an exception occurs the user will be prompted to enter two new values. The loop will only terminate after line 21 has been reached and executed successfully. The loop is optional depending on what you are trying to achieve in your program.
Within the try { .. } block I’ve put all of the statements that might cause an exception to happen. When this block executes, the JVM pays special attention to catch exceptions and give us a chance to handle them instead of simply crashing. For each type of exception that can occur in a try { .. } block we need a separate catch() { .. } block. At least one catch() { .. } block must immediately follow the try { .. } block.
Each catch() { .. } block specifies in parenthesis the type of exception that it handles. When an exception occurs in a try { .. } block, the catch() { .. } block that executes is the first one whose type matches the type of the exception that occurred.
Lines 24 to 28 catch InputMismatchExceptions. Line 25, displays the actual error message from the JVM. Notice the use of System.err rather than System.out. In Dr. Java this will cause the line to be printed in red by default. After that we clear the input stream and give a user-friendly warning message. When this catch block ends, execution jumps to the while statement on line 34 which will then cause the loop to repeat. Lines 30 to 33 are similar but handle ArithmeticExceptions. You can have as many catch() { .. } blocks as you need.
Finally, on line 2, we import the InputMismatchException class from the java.util package. The ArithmeticException class is automatically imported for us (as part of the java.lang package).
With proper exception handling you can now write programs that are very robust!
Files in Java
Storage of data in variables and objects is temporary because once our program ends the data is lost. This kind of program data is called volatile data. But often we need to maintain data between executions of our program; for example, user preference data, or high scores. This kind of data is stored in files, and is called persistent data.
Java views a file as a sequential stream of characters. If you think about it, System.in and System.out can be thought of as streams of ordered characters too. Conveniently, a Scanner object can be used to read input from a file in much the same way we’ve used it to read from the keyboard. To output to a file we will use a class called PrintWriter, which provides methods print(), println(), and printf() just like System.out.
Writing to a File
Let’s start with writing to a file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import java.io.IOException; import java.io.PrintWriter; public class FileWriting { public static void main( String[] args ) { // Try to open, write, and close randInt.txt file try { PrintWriter fileOutput = new PrintWriter( "randInt.txt" ); // Generate 10 random integers between 1 and 100 and output to randInt.txt for ( int count = 0; count < 10; count++ ) { fileOutput.println( (int)(Math.random() * 100) + 1 ); } fileOutput.close(); } catch ( IOException ioException ) { System.err.println( "Java Exception: " + ioException ); System.out.println( "Sorry, error with output file randInt.txt." ); } System.out.println( "All Done!" ); } } |
In the example above, we import the IOException class so that we can catch file input/output exceptions such as being unable to open or write to a file for some reason. We also import the PrintWriter class from the java.io package.
Within a try block, on line 8, we instantiate a PrintWriter object and specify the filename that we would like to write to in the same directory as our code. If the file already exists, it will be overwritten. Once that’s done, we generate 10 random integer values (between 1 and 100) and use the println() method to write them to the randInt.txt file, one integer per line. Although not demonstrated you may also use print() and printf() methods with a PrintWriter object. Once we’ve finished writing to the file, we make sure to close() it on line 14.
If the code within the try block fails because we are unable to open or write to the file (perhaps there is not enough disk space) an IOException will occur. We catch this exception on lines 16 to 19, and notify the user of the problem. After line 20 executes, the program ends.
Appending to a File
To append (add to the end) of an existing file instead of overwriting it, you must first create a FileWriter object. Its constructor takes two parameters: the filename to open, and true indicating that the file should be appended to rather than overwritten. You still need an instance of PrintWriter so that you have access to the print(), println(), and printf() methods. To do this you pass a reference to your FileWriter object into the constructor of PrintWriter like so:
1 2 |
FileWriter appendFile = new FileWriter( “randInt.txt”, true ); PrintWriter fileOutput = new PrintWriter( appendFile ); |
Don’t forget to import java.io.FileWriter at the top of your code!
Reading from a File
Reading from a file is just as easy! Let’s read the randInt.txt file that we just created in the last example and calculate the average of the integers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import java.io.File; import java.util.Scanner; import java.io.IOException; import java.util.InputMismatchException; public class FileReading { public static void main( String[] args ) { int total = 0; int lineNumber = 0; int validInts = 0; // Try to open, read, and close randInt.txt try { Scanner fileInput = new Scanner( new File("randInt.txt") ); while ( fileInput.hasNext() ) { try { // Keep track of the line number and try to add the next integer to our total lineNumber++; total += fileInput.nextInt(); // If an exception did not occur in the line above, add 1 to our validInts counter validInts++; } // If the value read was not a valid integer, report it and skip it. catch ( InputMismatchException inputMismatchException ) { System.err.println( "Bad integer on line " + lineNumber + ", skipping line." ); fileInput.nextLine(); // Clear bad line from input stream } } fileInput.close(); } catch ( IOException ioException ) { System.err.println( "Java Exception: " + ioException); System.out.println( "Sorry, unable to open the randInt.txt file for reading." ); } System.out.print( validInts + " valid integers read, " ); System.out.printf( "average: %.2f\n", (double)total / validInts ); System.out.println( "All Done!" ); } } |
On line 14 we instantiate a Scanner object, but notice that instead of specifying System.in to its constructor we pass a reference to a File object. After this, reading from a text file is similar to reading from the keyboard, we just need to remember to close() the input file when we are done (line 30). The Scanner class includes a method called hasNext() that returns true if there is more data to be read from the file input stream or false if the end of the file has been reached. This will allow us to read any number of integers from the file.
Notice the exception handling here. There are two separate try..catch statements. The outer one catches IOException; for example if the file cannot be found/opened. The inner one deals with the possibility that a non-integer value might be read from the file (i.e., InputMismatchException). In this case we report the bad line number but skip over it so that we can continue reading.
The JFileChooser Class
In 1-4 I introduced you to some useful Swing methods from the JOptionPane class to add a little GUI to your programs. Swing also includes a very useful class called JFileChooser with methods to enable a user to select a file or folder using an interactive dialog box. Let’s modify our FileReading example to allow the user to select any file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
import java.io.File; import java.util.Scanner; import java.io.IOException; import java.util.InputMismatchException; import javax.swing.JOptionPane; import javax.swing.JFileChooser; public class FileChooser { public static void main( String[] args ) { int total = 0; int lineNumber = 0; int validInts = 0; JFileChooser fileChooser = new JFileChooser(); fileChooser.setFileSelectionMode( JFileChooser.FILES_ONLY ); // Require user to select an input file, do not allow them to cancel. int result; do { result = fileChooser.showOpenDialog( null ); if ( result == JFileChooser.CANCEL_OPTION ) { JOptionPane.showMessageDialog( null, "Please select a file of integers.", "File Not Selected", JOptionPane.ERROR_MESSAGE ); } } while (result == JFileChooser.CANCEL_OPTION); File selectedFile = fileChooser.getSelectedFile(); System.out.println("File selected was: " + selectedFile); // Try to open, read, and close the selected file try { Scanner fileInput = new Scanner( selectedFile ); while ( fileInput.hasNext() ) { try { // Keep track of the line number and try to add the next integer to our total lineNumber++; total += fileInput.nextInt(); // If an exception did not occur in the line above, add 1 to our validInts counter validInts++; } // If the value read was not a valid integer, report it and skip it. catch ( InputMismatchException inputMismatchException ) { System.err.println( "Bad integer on line " + lineNumber + ", skipping line." ); fileInput.nextLine(); // Clear bad line from input stream } } fileInput.close(); } catch ( IOException ioException ) { System.err.println( "Java Exception: " + ioException); System.out.println( "Sorry, unable to open the randInt.txt file for reading." ); } System.out.print( validInts + " valid integers read, " ); System.out.printf( "average: %.2f\n", (double)total / validInts ); System.out.println( "All Done!" ); } } |
On lines 5 and 6 I import the JOptionPane and JFileChooser classes. Line 13 instantiates a JFileChooser object. On line 14 we then call one of its methods setFileSelectionMode() passing in the FILES_ONLY constant from the JFileChooser class to configure it so that the user is only allowed to select files (no folders).
Lines 17 to 24 continuously pop-up a JFileChooser dialog window until the user selects a valid file. On line 19 we tell the JFileChooser to open in the middle of the screen (the null parameter does this). The user then interacts with the JFileChooser. If they click to cancel or close the window the showOpenDialog() method returns a value equal to the CANCEL_OPTION constant from the JFileChooser class.
Once the loop above exits successfully, meaning that the user actually selected a file and clicked the “OK” button, we call the getSelectedFile() method which returns a reference to the File selected on disk. This selectedFile is then used on line 31 when instantiating the Scanner. The rest of the code remains unchanged.
You Try!
- Write a program that will translate a simple Turing program into a Python program. Here is a sample Turing program (stored in a file called Sample.t):
1234567891011var team : stringvar win, loss : intvar percentage : realteam := "Toronto Maple Leafs"win := 38loss := 25percentage := 0.603put "The ", team, " have ", win, " wins."put "They have ", loss, " losses."put "This gives them a winning percentage of ", percentage
Your program should use a JFileChooser dialog to allow the user to select a Turing program as an input file, then read the file converting each line into its Python equivalent. The Python program should be saved in a file with the same name as the Turing program, but with the extension .py instead of .t. Here is what the Sample.py file should look like for the Turing code above:
1234567team = "Toronto Maple Leafs"win = 38loss = 25percentage = 0.603print("The", team, "have", win, "wins.")print("They have", loss, "losses.")print("This gives them a winning percentage of", percentage) - Write a program that will use a JFileChooser dialog to allow the user to select an input text file. The application should read and analyse all of the lines in the file, and then output a report to both the screen and a file called report.txt. Your analysis should count the total number of digits, upper case letters, lower case letters, and spaces. For example, if an input file contained: aE8 Y3fAd5 iq7 your report should look like this:
Analysis of filename: sample.txt
4 digits
3 uppercase letters
5 lowercase letters
3 spaces