9. Exceptions and Files

What We Will Cover


Illuminations

Midterm Questions

Questions from last class?

Questions on Assignment?

  • Nothing

9.1: Exception Handling Basics

Learner Outcomes

At the end of the lesson the student will be able to:

  • Describe the Java syntax for exception handling
  • Write code to catch and handle exceptions
  • Generate code to handle user-input errors

9.1.1: About Exceptions

  • A common way to develop a program is to first make the code work assuming that nothing unusual happens
  • After the usual case works, then you start to consider the exceptional cases like:
    • What if a users enter letters when you want numbers
    • What if a file your program needs was moved or deleted
  • Java helps you to write your code in just this way:
    1. You first write your code as if nothing unusual will happen
    2. When the usual case works correctly, you add code to handle exceptional cases
  • The mechanism to handle the unusual cases is known as exception handling
  • Exception handling is an important part of developing "solid" or "robust" programs
  • A solid or robust program means that you can run the program day after day
  • If crazy or unusual things happen, the program copes or at least recognizes that something is wrong

Terminology

  • Exception: an error condition that changes the normal flow of a program
  • Throw an exception: either the Java runtime or your code signals that something unusual happened
  • Catch an exception: take appropriate action to deal with an exception
  • Exception handler: the code that processes an exception

Example Program the Causes an Exception

1
2
3
4
5
6
public class ExceptionDemo {
    public static void main(String[] args) {
        String str = null;
        System.out.println(str.length());
    }
}

How Exceptions Work

  • An exception indicates an unusual or error condition has occurred
  • When an exception occurs, program control is transferred, or "thrown", to an area of code designated to handle the condition
  • Oftentimes the action is to display a stack trace like the following and exit the program:
    Exception in thread "main" java.lang.NullPointerException
            at ExceptionDemo.main(ExceptionDemo.java:4)
    
  • A stack trace like this is useful for debugging
  • However, you can, and often should, specify other actions

9.1.2: Exception Example

  • Many exceptions can occur because of user input errors
  • As an example, let us look at how you might handle user input errors
  • Following is a simple program showing the usual case

The Usual Case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.*;

public class TotalCalculator1 {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);
        double cost = 0, tax = 0;

        System.out.print("Enter cost: ");
        cost = input.nextDouble();
        System.out.print("Enter tax: ");
        tax = input.nextDouble();
        double total = cost + tax;
        System.out.println("Total: " + total);
        System.out.println("Thanks for paying!");
    }
}

  • The program works for the usual case but can fail on invalid data
  • What happens if the user enters a dollar sign before the numbers?
  • What happens if the user enters letters instead of numbers?
  • What if the user enters Ctrl-C
Enter cost: $100
Exception in thread "main" java.util.InputMismatchException
      at java.util.Scanner.throwFor(Scanner.java:840)
      at java.util.Scanner.next(Scanner.java:1461)
      at java.util.Scanner.nextDouble(Scanner.java:2387)
      at TotalCalculator1.main(TotalCalculator1.java:9)
  • The program prints the message shown above and then ends
  • This is not desirable behavior, so we need to catch and handle the exception
  • In Java, you catch and handle exceptions with a try-catch statement

9.1.3: Handling Exceptions

  • To catch and handle exceptions you use a try-catch statement
  • The code that can cause an exception goes inside a try block
  • Code to handle the exception goes inside a catch block
  • Syntax:
    try {
      // One or more statements that can throw an exception
    } catch(ExceptionType exceptionInstance) {
      // Exception handling code
      // Can have many catch blocks
    }
    
  • Where:
    • ExceptionType: the type of exception
    • exceptionInstance: the name you make up for the exception object
  • Each try block is followed by one or more catch blocks
  • The following example shows how to handle exceptions with try and catch blocks

Example of Exception Handling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.*;

public class TotalCalculator2 {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);
        double cost = 0, tax = 0;

        try {
            System.out.print("Enter cost: ");
            cost = input.nextDouble();
            System.out.print("Enter tax: ");
            tax = input.nextDouble();
            double total = cost + tax;
            System.out.println("Total: " + total);
        } catch(InputMismatchException e) {
            System.out.println(
                    "Error -- enter digits only!");
        } catch(NoSuchElementException e) { // Ctrl-C
            System.out.println("\nGoodbye!");
            System.exit(0);
        }
        System.out.println("Thanks for paying!");
    }
}

  • The program works the same way in the normal case
  • When the uses enters bad data, our program can do something about it now:
    Enter cost: $100
    Error -- enter digits only!
    Thanks for paying!
    
  • Exiting when there is a user input error is usually not the best thing to do
  • Ideally, we want our program to:
    1. Describe the problem to the user
    2. Let the user correct the entry
  • However, our program does not allow the user to correct the problem yet
  • To allow the user to correct the problem, we must first understand an exception's flow of control

9.1.4: Exception Flow of Control

  • When an exception occurs:
    1. The method stops processing at the point of failure
    2. The code creates an object with the error information
    3. The JVM tries to locate the exception handling code
    4. If the JVM finds the exception-handling code, the matching catch block is passed the exception object
    5. If the JVM does not find a matching catch block, it uses the general exception handler instead
  • General exception handler displays an error messages on the console like the following and then ends the program
Exception in thread "main" java.util.InputMismatchException
        at java.util.Scanner.throwFor(Scanner.java:819)
        at java.util.Scanner.next(Scanner.java:1431)
        at java.util.Scanner.nextDouble(Scanner.java:2335)
        at ReceiptCalculator.main(ReceiptCalculator.java:9)
  • Let us trace through the flow for our example code
  1. The user enters bad data and the main() method stops processing
    Enter cost: $100
    
  2. The scanner object code creates an exception object of type InputMismatchException
  3. the JVM passes the exception object to the catch statement:
    catch(InputMismatchException e)
  4. The exception handling code executes
  5. The program continues with the code after the list of catch blocks
  • Note that the program does not execute the second input statement
  • As soon as the error occurs, the program jumps to the exception handler
  • Program execution continues after the exception handler code

9.1.5: Trying Again

  • To allow the user to try again when there is an error, we need to code a loop
  • One way is to use a looping statement like the following

Example of Verification Using a Loop

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
import java.util.*;

public class TotalCalculator3 {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);
        double cost = 0, tax = 0;

        boolean more = true;
        while (more) {
            try {
                System.out.print("Enter cost: ");
                cost = input.nextDouble();
                System.out.print("Enter tax: ");
                tax = input.nextDouble();
                double total = cost + tax;
                System.out.println("Total: " + total);
                more = false;
            } catch(InputMismatchException e) {
                System.out.println(
                    "Error -- enter digits only!");
                input.nextLine(); // clear the buffer
            } catch(NoSuchElementException e) {
                System.out.println("\nGoodbye!");
                System.exit(0);
            }
        }
        System.out.println("Thanks for paying!");
    }
}

  • Running the program we see:
  • Enter cost: 100
    Enter tax: $6
    Error -- enter digits only!
    Enter cost: 100
    Enter tax: 6
    Total: 106.0
    Thanks for paying!
    
  • The program now allows the user to correct their errors and continue
  • However, the code requires the user to reenter all the numbers
  • A better solution is to reenter only the number in error
  • To do this we need to loop on each statement that could cause an exception
  • This leads to duplicate code
  • To eliminate the duplicate code, we use a method to handle the entire data input process

9.1.6: Verifying in Methods

  • Using a method we can group our input and verification code and reduce duplication
  • Running the program we see:
  • Enter net cost: ten
    Error -- enter digits only!
    Enter net cost: 10
    Enter tax: $1.23
    Error -- enter digits only!
    Enter tax: 1.23
    Total: 11.23
    Thanks for paying!
    
  • Users can correct individual errors without having to redo all their entries
  • Also note how much simpler the code in main() appears

Example of Verification Using a Method

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
import java.util.*;

public class TotalCalculator4 {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);
        double cost = 0, tax = 0;

        cost = readDouble(input, "Enter cost: ");
        tax = readDouble(input, "Enter tax: ");
        double total = cost + tax;
        System.out.println("Total: " + total);

        System.out.println("Thanks for shopping!");
    }

    /**
     * Read a double from the console.
     *
     * @param input Scanner object to read from.
     * @param prompt User prompt to show before input
     * @return A double entered by the user.
     */
    public static double readDouble(Scanner input,
            String prompt) {
        double number = 0;
        boolean more = true;
        while (more) {
            try {
                System.out.print(prompt);
                number = input.nextDouble();
                more = false;
            } catch(InputMismatchException e) {
                System.out.println(
                    "Error -- enter digits only!");
                input.nextLine(); // clear the buffer
            } catch(NoSuchElementException e) {
                System.out.println("\nGoodbye");
                System.exit(0);
            }
        }
        return number;
    }
}

  • Note that there is still a loop in the readDouble() method -- can you see it?
  • Programmers commonly develop a library of static input methods like these

9.1.7: Summary

  • A common way to develop a program is to first make the code work assuming that nothing unusual happens
  • After the usual case works, then you consider the exceptional cases
  • Java provides exception handling to handle these exceptional cases
  • Many exceptions can occur because of user input errors
  • To catch and handle exceptions you use both try and catch statements
  • try {
      // One or more statements that can throw an exception
    } catch(Exception e) {
      // Exception handling code
      // Can have many catch blocks
    }
    
  • To allow users to correct their errors, you need to add a looping mechanism around the input statement
  • boolean more = true;
    while (more) {
        try {
            System.out.print("Enter net cost: ");
            cost = input.nextDouble();
            // May have more statements
            more = false;
        } catch(InputMismatchException e) {
            System.out.println(
                "Error -- enter digits only!");
            input.nextLine(); // clear the '\n'
        } // may need more catch statements
    }
    
  • Since input is a frequent occurrence, it is common to write user input methods
  • One example is readDouble():
  • public static double readDouble(Scanner input,
            String prompt) {
        double number = 0;
        boolean more = true;
        while (more) {
            try {
                System.out.print(prompt);
                number = input.nextDouble();
                more = false;
            } catch(InputMismatchException e) {
                System.out.println(
                    "Error -- only enter numbers!");
                input.nextLine(); // clear the input
            } catch(NoSuchElementException e) {
                System.out.println("\nGoodbye");
                System.exit(0);
            }
        }
        return number;
    }
    

Check Yourself

  • What is an exception?
  • What is an exception handler?
  • What is a stack trace?
  • What is the Java syntax for exception handling
  • When is the code within a catch block executed?

Exercise 9.1

Take one minute to prepare an answer the following question.

  1. By number, which lines are executed by the following if the user enters "$100" for the cost?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.*;

public class TotalCalculator2 {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);
        double cost = 0, tax = 0;

        try {
            System.out.print("Enter cost: ");
            cost = input.nextDouble();
            System.out.print("Enter tax: ");
            tax = input.nextDouble();
            double total = cost + tax;
            System.out.println("Total: " + total);
        } catch(InputMismatchException e) {
            System.out.println(
                    "Error -- enter digits only!");
        } catch(NoSuchElementException e) { // Ctrl-C
            System.out.println("\nGoodbye!");
            System.exit(0);
        }
        System.out.println("Thanks for paying!");
    }
}

9.2: More Exception Topics

Learner Outcomes

At the end of the lesson the student will be able to:

  • Explain the difference between checked and unchecked exceptions
  • Use the throws clause when defining a method
  • Throw an exception
  • Use predefined Exception classes
  • Write a custom class that defines a new exception

9.2.1: Kinds of Exceptions

  • Java has three categories of exceptional conditions:
    1. Errors
    2. Checked exceptions
    3. Unchecked exceptions
  • These exceptions are organized in an inheritance hierarchy as shown below

    Types of exceptions

  • Exceptions with RuntimeException as an ancestor are unchecked exceptions

Errors

  • Errors are exceptional conditions that are external to the application
  • Usually the application cannot recover from these types of errors
  • For example, suppose that while your program is reading from a file that the computer has a hardware failure
  • You may want to alert the user, but there is little that the program can do to recover

Checked Exceptions

  • A checked exception is an exceptional condition that a program should be able to recover from
  • Java forces these types of problems to be provided for if they occur
  • The compiler checks that some mechanism is in place for receiving and processing the exception object
  • Since it is checked by the compiler, it is known as a checked exception
  • An example of this type of exception is the FileNotFoundException
  • Normally, your program reads a file using a name supplied by the user
  • However, sometimes a user will supply a wrong name
  • In these cases your program should notify the user and ask for the correct name

Unchecked (Runtime) Exceptions

  • An unchecked exception is usually the result of a programming error
  • For example, consider the example:
    String str = null;
    System.out.println(str.length());
    
  • This code will cause a NullPointerException
  • There is nothing that the program can do to recover from this error
  • Thus, a NullPointerException was written as an unchecked exception
  • The compiler does not ensure there is a handler for unchecked exceptions
  • If an unchecked exception does not have an error handler, the general error handler is used
  • The general exception handler produces a stack trace and then exits the program

9.2.2: What Exception Can Occur?

  • The are two usual ways to determine what exceptions can occur in a program
  • The first way is to try the code and see what happens
  • If we tried to code our TotalCalculator program without a try-catch:
  • Scanner input  = new Scanner(System.in);
    double cost = 0, tax = 0;
    
    System.out.print("Enter cost: ");
    cost = input.nextDouble();
    System.out.print("Enter tax: ");
    tax = input.nextDouble();
    double total = cost + tax;
    System.out.println("Total: " + total);
    System.out.println("Thanks for paying!");
    
  • We found we could get a an exception like:
Enter cost: $100
Exception in thread "main" java.util.InputMismatchException
      at java.util.Scanner.throwFor(Scanner.java:840)
      at java.util.Scanner.next(Scanner.java:1461)
      at java.util.Scanner.nextDouble(Scanner.java:2387)
      at TotalCalculator1.main(TotalCalculator1.java:9)
  • This tells you that you need to code a try-catch for an InputMismatchException
  • Sometimes you might want to know what the possible exceptions might be in your code
  • To find out, you can look up exceptions in the Java API
  • For example, you can look up the documentation for the Scanner method: nextDouble()
  • You then examine the list of possible exceptions and determine if and how they might occur in your program
  • Note that exceptions with RuntimeException as an ancestor are unchecked
  • Otherwise, they are checked exceptions

More Information

9.2.3: Using the throws Clause

  • Anytime your program uses a method that can cause a checked exception, your program must do something with it
  • If you decide the method cannot handle the exception, you use a throws clause to send the exception to the caller
  • The general syntax for throwing an exception:
    [accessModifier] returnType methodName(parameterList)
        throws ExceptionType1, ExceptionType2,... {
        // statements
    }
    
  • Where:
    • accessModifier: determines which classes can access this method
    • returnType: the type of value returned by the method, or void for no return value
    • methodName: the name you make up for the method
    • parameterList: the types and names of the parameters
    • ExceptionTypeX: the type of exception a method throws
  • For example:
    public static void main(String[] args)
        throws java.io.IOException {
    
  • Throwing an exception is like passing the buck:
    • Requires the calling method to deal with the exception
  • The calling method can also throw the exception
  • Eventually some method should catch it or the general exception handler is called

How Exceptions are Propagated

  • When methods cannot handle an exception, that method should throw the exception
  • The user of the method can then decide how to handle the exception
  • Methods can call other methods that can throw an exception
  • This chain continues usually until one method catches the exception

Example of Exception Propagation

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
public class ExceptionPropogation {

    public static void main(String[] args) {
        methodD();
    }

    public static void methodD() {
        try {
            methodC();
        } catch (Exception e) {
            System.out.println("Caught exception");
        }
    }

    public static void methodC() throws Exception {
        methodB();
    }

    public static void methodB() throws Exception {
        methodA();
    }

    public static void methodA() throws Exception {
        throw new Exception();
    }
}

9.2.4: Throwing Exceptions

  • You may be wondering how the code in Scanner and other classes generates an exception
  • The answer is the Java throw statement
  • Syntax:
    throw new ThrowableObject
  • Where:
    • ThrowableObject: the exception class to throw
  • For example:
  • throw new Exception("Exception message");
  • Typically, you decide to throw an exception after checking for an error condition
  • if (cost < 0) {
        throw new Exception(
            "Cost cannot be less than 0");
    }
    
  • To demonstrate, let us throw an exception in an example program

Example Program Using throw

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
import java.util.*;

public class TotalCalculator5 {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);
        double cost = 0, tax = 0;

        boolean more = true;
        while (more) {
            try {
                System.out.print("Enter cost: ");
                cost = input.nextDouble();
                if (cost < 0) {
                    throw new Exception(
                        "Cost cannot be less than 0");
                }
                System.out.print("Enter tax: ");
                tax = input.nextDouble();
                if (tax < 0) {
                    throw new Exception(
                        "Tax cannot be less than 0");
                }
                double total = cost + tax;
                System.out.println("Total: " + total);
                more = false;
            } catch(Exception e) {
                String message = e.getMessage();
                System.out.println(message);
                input.nextLine(); // clear the buffer
            }
        }
        System.out.println("Thanks for paying!");
    }
}

  • Note the use the getMessage() method to explain the error
  • There are a few other commonly used exception methods shown in the table below

When to Throw an Exception

  • Your program should only throw exceptions for exceptional conditions
  • This may happen when a method encounters a situation where it cannot complete its task
  • For example: the wrong file name was passed to the method opening a file
  • Throw an exception to the calling method, which can ask the user for the correct name

Commonly Used Methods of the Throwable Class

Method Description
getMessage() Returns the message associated with the exception, if available.
printStackTrace() Prints the stack trace to the standard error stream.
toString() Returns a string with the name of the exception class along with the message associated with the exception, if available.

9.2.5: Predefined Exception Classes

  • As we have seen, there are more exception classes than the single class Exception
  • Some of them are shown in the following tree
  • You can also write your own exceptions, which we will cover in the following section
  • However, you should use standard exceptions wherever possible
java.lang.Throwable
  |
  +--java.lang.Error (Unchecked)
  |
  +--java.lang.Exception (Checked)
       |
       +--java.lang.InterruptedException
       |
       +--java.io.IOException
       |
       +--java.lang.RuntimeException (Unchecked)
            |
            +--java.lang.ArithmeticException
            |
            +--java.lang.IllegalArgumentException
            |
            +--java.lang.IllegalStateException
            |
            +--java.lang.IndexOutOfBoundsException
            |
            +--java.lang.NullPointerException
            |
            +--java.lang.UnsupportedOperationException
            |
            +--java.util.InputMismatchException
  • Throwable is the root class of all exceptions
    • Has the commonly used methods for all errors and exceptions
  • Error means that a serious problem has occurred
    • Applications cannot recover from these types of problems
  • Exception is the root class of exceptions that a reasonable program might catch
  • RuntimeException are errors caught by the JVM rather than the compiler
    • Usually due to program bugs
  • All exceptions other than Error and RuntimeException (and subclasses) are checked exceptions
    • The compiler forces you to deal with the exception
  • Predefined exceptions usually include a meaningful message that is retrieved with method getMessage():
    System.out.println(e.getMessage());

9.2.6: Writing Custom Exception Classes

  • You can define your own exception class
  • There are two main reasons to write your own exception class:
    • To carry the precise information you want thrown to the catch block
    • To have a different type for every kind of exceptional case
  • You must derived your exception from some already defined exception class
  • Often all you need to code is the constructors
  • You should always define two constructors for an exception class:
    • A constructor taking no arguments that defines a default message string and calls the superclass constructor
    • A constructor that takes a String message argument and calls the superclass constructor
  • Following is a "toy" example of a custom exception class

Custom Exception Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DivisionByZeroException extends Exception {
    /**
     * Default constructor with a default error message.
     */
    public DivisionByZeroException() {
        super("Division by zero!");
    }

    /**
     * Overloaded constructor for custom error message.
     *
     * @param message The custom error message.
     */
    public DivisionByZeroException(String message) {
        super(message);
    }
}

Program Using the Custom Exception

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
import java.util.*;

public class Divide2Nums {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);
        double numerator = 0, denominator = 0;
        double quotient = 0;
        boolean valid = false;

        numerator = readDouble(input,
            "Enter numerator: ");
        while (!valid) {
            try {
                denominator = readDouble(input,
                    "Enter denominator: ");
                if (denominator == 0) {
                    throw new DivisionByZeroException();
                }
                quotient = numerator / denominator;
                valid = true;
            } catch (DivisionByZeroException e) {
                System.out.println(e.getMessage());
            }
        }
        System.out.println(numerator + " / "
            + denominator + " = " + quotient);
    }

    /**
     * Read a double from the console.
     *
     * @param input Scanner object to read from.
     * @param prompt User prompt to show before input
     * @return A double entered by the user.
     */
    public static double readDouble(Scanner input,
            String prompt) {
        double number = 0;
        try {
            System.out.print(prompt);
            number = input.nextDouble();
        } catch(InputMismatchException e) {
            System.out.println(
                "Error -- enter digits only!");
            input.nextLine(); // clear the '\n'
            return readDouble(input, prompt);
        } catch(NoSuchElementException e) {
            System.out.println("\nGoodbye!");
            System.exit(0);
        }
        return number;
    }
}

9.2.7: Summary

  • Java has three categories of exceptional conditions:
    1. Errors: external to the application
    2. Checked exceptions: recoverable condition that the compiler forces a program to handle
    3. Unchecked exceptions: compiler does NOT force a program to handle
  • These exceptions are organized in an inheritance hierarchy as shown below

    Types of exceptions

  • Exception is the ancestor of checked exceptions and RuntimeException is the ancestor of unchecked exceptions
  • To determine what kind of exception can occur, you can:
    • Try the code and see what happens
    • Look up the exceptions in the Java API
  • If a method cannot handle the exception, it should throw the exception to the calling method
  • To throw an exception you add a throws clause in the method header
    public static void main(String[] args)
        throws java.io.IOException {
    
  • To create an exception condition, you code a throw statement:
    throw new Exception("Exception message");
  • Typically, you decide to throw an exception after checking for an error condition, like:
    if (cost < 0) {
        throw new Exception(
            "Cost cannot be less than 0");
    }
    
  • When you catch an exception, you can use getMessage() to display the error message associated with the exception
  • When throwing an exception, you can use a predefine exception class or write a custom exception class
  • You should use predefined exception class whenever you can
  • However, there are two main reasons to write your own exception class:
    • To carry the precise information you want thrown to the catch block
    • To have a different type for every kind of exceptional case
  • You must derived your exception from some already defined Exception class
  • When writing your own exception, you should always define two constructors:
    • A constructor taking no arguments that defines a default message string and calls the superclass constructor
    • A constructor that takes a String message argument and calls the superclass constructor
  • For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DivisionByZeroException extends Exception {
    /**
     * Default constructor with a default error message.
     */
    public DivisionByZeroException() {
        super("Division by zero!");
    }

    /**
     * Overloaded constructor for custom error message.
     *
     * @param message The custom error message.
     */
    public DivisionByZeroException(String message) {
        super(message);
    }
}

Exercise 9.2

Take one minute to prepare answers to the following questions:

  1. What output is produced by the following code?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class WaitTime {
    public static void main(String[] args) {
        int waitTime = 50;
        System.out.println("Before try-block");
        try {
            System.out.println("Entering try-block");
            if (waitTime > 30) {
                throw new Exception("Over 30");
            } else if (waitTime < 30) {
                throw new Exception("Under 30");
            }
            System.out.println("No exception");
            System.out.println("Exiting try-block");
        } catch(Exception e) {
            System.err.println(e.getMessage());
        }
        System.out.println("After catch-block");
    }
}

9.3: Streams and File I/O

Learner Outcomes

At the end of the lesson the student will be able to:

  • List the types of files
  • Create and use File objects
  • Get information about files from the native file system
  • List directory contents

9.3.1: About Program I/O

  • Program I/O = Program Input/Output
    • Input to and output from programs
  • Input can be from a keyboard, mouse or file
  • Output can be to a display screen, printer or file
  • Note that files can be both input and output devices for programs
  • Advantages of file I/O:
    • Data still exists after the program ends
    • Input can be automated (rather than entered manually)
    • Output from one program can be input to another
  • To store and retrieve data in a file, we need two items:
    • A file
    • A file stream object
  • We will look at files first

9.3.2: Files

File: a collection of data stored under a common name on a storage medium.

  • Files provide long-term storage of large amounts of data
  • Usually, you store files on non-volatile storage mediums
    • Magnetic disks
    • Optical disks
    • Flash storage, like USB storage devices
  • File are a single sequence of bytes

    Byte 0 Byte 1 Byte 2 ... Byte n−1 End-of-file marker

  • The operating system keeps track of the number of bytes in a file
  • Files must have a name
    • Naming requirements depend on the underlying operating system (OS)
  • The operating system organizes files into directories

9.3.3: Types of Files

  • All data in a file is ultimately just zeros and ones
  • It is up to the program using the file to understand the meaning and internal format of the data
  • In general, programs interpret data using two broad categories: text and binary

Text Files

  • In text files, the bits represent printable characters
  • Files are usually stored as one byte per character (ASCII)
  • Each line is delimited by end-of-line characters:
    • Macintosh (before OS-X): "\r"
    • Unix: "\n"
    • Windows: "\r\n"
  • You can read text files because each byte is interpreted by a program as textual characters
  • Some of these programs, like TextPad, then display the textual data to your computer's screen
  • Since there are many programs that read and display text, text files are called human readable

Binary Files

  • Data other than text is usually referred to as binary data
  • Each bit represents some type of encoded information
    • E.g.: program instructions or integer data
  • Binary files are easily read by the computer but not by humans
  • The following table compares binary and text values saved in a file
  • First we consider the value "1234" as ASCII codes and compare these bits to a binary value of 1234

Comparing Binary and Textual Data

Description Byte 0 Byte 1 Byte 2 Byte 3
"1234" as char's '1' '2' '3' '4'
"1234" as ASCII codes (bytes) 49 50 51 52
"1234" as ASCII codes (bits) 00110001 00110010 00110011 00110100
(int) 1234 as binary bits 00000000 00000000 00000100 11010010

9.3.4: Creating File Objects

  • File class represents a file on the native file system
  • You create a File object referring to a file
  • Then you can query the object for information about that file

Common Constructors for Class File

Class Description
File(String pathname) Creates a File object that refers to the specified pathname.
File(String parent, String child) Creates a File object that refers to the pathname created by combining the parent and child pathnames.
File(File parent, String child) Creates a File object that refers to the pathname created by combining the pathname of parent with the child pathname.

Examples of Creating Files Objects

  • You can create a File object in the current directory
  • File myFile = new File("myfile.txt");
  • Can create a File object using an absolute pathname
  • Specifies the entire path and file name for the file
  • File myFile = new File("C:/docs/myfile.txt");
  • You can use a relative pathname to specify a file
  • Refers to a file starting from the current directory
  • File myFile = new File("docs/myfile.txt");
  • You can use a Universal Naming Convention to refer to a file
  • Starts with two slashes (//) followed by a hostname and pathname
    File myFile = new File("//server/path/myfile.txt");
    
  • You can use two strings to specify a file:
    String pathname = "C:/docs";
    String filename = "myfile.txt";
    File myFile = new File(pathname, filename);
    
  • You can use a File object and a string to specify a file
    File dir = new File("C:/docs");
    File myFile = new File(dir, "myfile.txt");
    
  • Note that forward slashes work on both UNIX and Windows systems
  • Always use forward slashes to make your code more portable

9.3.5: Using File Objects

  • We can use File objects for many tasks
  • For instance we can:
    • Test some attributes of files or directories
    • Get information about files
    • Get lists of directory contents
    • Create files and directories
    • Delete files and directories
  • You can see some of the commonly used methods listed below

Some Methods that Test a File

Method Description
canRead() Returns true if the pathname exists and can be read by the program.
canWrite() Returns true if the pathname exists and a program can write to it.
exists() Returns true if the pathname exists.
isDirectory() Returns true if the pathname exists and refers to a directory.
isFile() Returns true if the pathname exists and refers to a file.

Some Methods that Return Information about a File

Method Description
getName() Returns the name of the file or directory as a String.
getPath() Returns the name of the file or directory as a String.
lastModified() Returns the time that the file was last modified as the number of milliseconds since January 1, 1970.
length() Returns the length of the file as a long type.

Some Methods that work with Directories

Method Description
list() If the object refers to a directory, returns an array of strings naming the files and directories.
listFiles() If the object refers to a directory, returns an array of File objects for the files and directories.
listRoots() A static method that returns an array of File objects representing the drives available to the current system.
mkdir() Creates the directory named by the File pathname.

Other Methods that work with File Objects

Method Description
createNewFile() Creates a new, empty file if a file with this name does not yet exist.
delete() Deletes the file or directory referred to by the File object.
setReadOnly() Makes the file read-only. If successful, returns true.

9.3.6: Working with Files and Directories

  • The following programs show some uses for the File object

Getting Information About a File

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.*;

public class FileInfo {
  public static void main(String[] args) throws IOException {
    File myFile = new File("myfile.txt");
    myFile.createNewFile();
    if (myFile.exists()) {
        System.out.println("Name:     " + myFile.getName());
        System.out.println("Path:     " + myFile.getPath());
        System.out.println("Writable: " + myFile.canWrite());
    } else
        System.out.println("The " + myFile.getName()
                + " file doesn't exist.");
  }
}

Listing Directory Contents

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.*;

public class DirList {
  public static void main(String[] args)
      throws IOException {
    File dir = new File("./"); //current directory
    if (dir.exists() && dir.isDirectory()) {
        System.out.println("Dir:     "
            + dir.getCanonicalPath());
        String[] files = dir.list();
        for (int i = 0; i < files.length; i++)
          System.out.println(files[i]);
    }
  }
}

Working with I/O Exceptions

  • Note the throws IOException clause in the above examples
  • File I/O requires you to work with I/O exceptions
  • Your code will not compile unless you handle the exception
    • Either using try-catch or throws
  • There are many types of I/O exceptions you can work with as shown below
  • However, IOException is the ancestor of all I/O exceptions

Common I/O Exceptions

Exception Description
IOException General purpose I/O exception.
EOFException Thrown when a program reads beyond the end of a file.
FileNotFoundException Thrown when a program tries to open a file that does not exist.

9.3.7: Summary

  • To identify a file you create a File object
  • You identify the file with either an absolute pathname or a relative pathname
  • To identify a file on a remote computer, you use the Universal Naming Convention
  • The File class provides many methods that you can use to:
    • Check whether a file or directory exists
    • Get information about a file
    • Create or delete files and directories
  • Many methods of Java I/O classes throw I/O exceptions that you need to handle

Check Yourself

  1. What is the difference between a text file and a binary file?
  2. What is the purpose of the File class?
  3. What is an absolute pathname?
  4. What is a relative pathname?
  5. What is an I/O exception?
  6. Why must you handle I/O exceptions?
  7. What can you do to handle I/O exceptions?

Exercise 9.3

Take one minute to prepare an answer to the Check Yourself questions. We will review the questions as time permits.

9.4: Text File I/O

Learner Outcomes

At the end of the lesson the student will be able to:

  • Describe the purpose of a stream
  • Work with I/O exceptions
  • Write text to files
  • Read text from files

9.4.1: Streams

Stream: a one-way transmission path that either delivers data to a destination (screen, file, etc.) or that takes data from a source (keyboard, file, etc.)

  • A stream connects a program to an I/O object
  • Input stream: an object that provides a sequence of bytes to a program

  • Output stream: an object that accepts a sequence of bytes from a program

File Streams

File stream: a one-way transmission path used to connect a program to a file.

  • File streams can be either input or output streams
  • File input streams receive data from a file
  • File output streams send data to a file
  • Each file your program uses will need a separate file stream object

Basic File Streams

  • Since there are two types of files, there are streams to support each type
  • Use character streams to read and write text files:
  • Use binary streams to read and write binary files:

9.4.2: Text Streams

  • Streams are implemented using classes and objects
  • To write and read text, you use character streams
  • A character stream processes characters through the stream
  • There is a separate set of classes for writing and reading character streams
  • These classes are organized in an inheritance hierarchy as shown below

Some Classes from the Writer Hierarchy

Writer: Foundation class for writing character streams
  |
  +--BufferedWriter: Add a buffer to the stream
  |
  +--PrintWriter: Convert binary data to text
  |
  +--OutputStreamWriter: Bridge character to binary streams
       |
       +--FileWriter: Connects a stream to a file

Some Classes from the Reader Hierarchy

Reader: Foundation class for reading character streams
  |
  +--BufferedReader: Add a buffer to the stream
  |
  +--InputStreamReader: Bridge character to binary streams
       |
       +--FileReader: Connects a stream to a file

9.4.3: Layering Streams

  • Usually, any one stream does have not all the functionality you want
  • Instead, it is common to layer two or more streams

Writing to a Text Stream

  • To write text to a file, we use a FileWriter stream:
    FileWriter fw = new FileWriter("out.txt");
    
  • However, a FileWriter does not know how to write different types of data
  • That's why we combine the FileWriter with a PrintWriter:
    PrintWriter out = new PrintWriter(fw);
    
  • Now we can use the overloaded print() methods of a PrintWriter object to write various data types

Buffering

  • Every time a byte or sequence of bytes is written or read to a file, the OS must perform a series of operations
  • Buffering improves performance of I/O by reducing the number of calls to the OS
  • A program copies each output to a block of memory called a buffer
  • The entire buffer is output to disk at once
  • One long disk access takes less time than many smaller ones

Adding a Buffered Layer

  • You use buffered stream classes to improve I/O performance:
  • We can add a buffering layer to our previous stream example:
    FileWriter fw = new FileWriter("myfile.txt");
    BufferedWriter buf = new BufferedWriter(fw);
    PrintWriter out = new PrintWriter(buf);
    
  • Another way to write the same functionality:
    PrintWriter out = new PrintWriter(
                      new BufferedWriter(
                      new FileWriter("myfile.txt")));
    

9.4.4: Connecting a Character Output Stream to a File

  • To write to a text file, you create a character output stream
  • To do this, you need to layer two or more classes from the Writer hierarchy
  • An example of how to accomplish this task is shown below

Classes Connecting a Character Output Stream to a File

File data = new File("data.txt");
PrintWriter out = new PrintWriter(
                  new BufferedWriter(
                  new FileWriter(data)));

Mix and Match

  • There are many ways to connect a character output stream to a file
  • You can "mix and match" Writer classes to get the functionality you need
  • This list of constructors should give you some idea how this is possible
  • Note that any class ending in the word "Writer" is a Writer subclass:
    FileWriter(File file)
    FileWriter(String pathname)
    FileWriter(String pathname, boolean append)
    
    BufferedWriter(Writer out)
    
    PrintWriter(Writer out)
    
  • Note that the FileWriter constructor accepts a second argument
  • Setting the second argument to true appends data to the file

9.4.5: Procedure for Writing a Text File

  1. Create a File object with a pathname to your file:
    File data = new File("data.txt");
    
  2. Connect the file to a stream with a FileWriter:
    FileWriter fw = new FileWriter(data);
    

    If you want to append data then use:

    FileWriter fw = new FileWriter(data, true);
    
  3. Add a buffer for the output with a BufferedWriter:
    BufferedWriter buf = new BufferedWriter(fw);
    
  4. Add methods to convert binary data to character data with a PrintWriter:
    PrintWriter out = new PrintWriter(buf);
    
  5. Write to the file using the print() and println() methods:
    out.println("This is a string");
    out.println('c');
    out.println(1234);
    out.println(1.234);
    for (int i = 0; i <= 10; i++) {
        out.print(i + ",");
    }
    out.println();
    
  6. Close the streams when finished writing:
    out.close();
    

    Note that you can nest the constructor calls as shown in the example below

Example Program Writing a Text File

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.*;

class TextWriterApp {
    public static void main(String[] args)
            throws IOException {
        File data = new File("data.txt");
        PrintWriter out = new PrintWriter(
                          new BufferedWriter(
                          new FileWriter(data)));
        out.println("This is a string");
        out.println('c');
        out.println(1234);
        out.println(1.234);
        for (int i = 0; i <= 10; i++) {
            out.print(i + " ");
        }
        out.println();
        out.close();
        System.out.println("Finished writing file");
    }
}

9.4.6: Connecting a Character Input Stream to a File

  • To read from a text file, you need to create a character input stream
  • To do this, you need to layer two or more classes from the Reader hierarchy
  • Note that there is no "PrintReader" class
  • Instead we can use the same Scanner object we used for reading from the keyboard
  • An example of how to accomplish this task is shown below

Classes Connecting a Character Output Stream to a File

File data = new File("data.txt");
Scanner in = new Scanner(
             new BufferedReader(
             new FileReader(data)));

Mix and Match

  • There are many ways to connect a character input stream to a file
  • You can "mix and match" Reader classes to get the functionality you need
  • This list of constructors should give you some idea how this is possible
  • Recall that any class ending in the word "Reader" is a Reader subclass
  • In addition, all "reader" classes implement the Readable interface
  • FileReader(File file)
    FileReader(String pathname)
    
    BufferedReader(Reader in)
    
    Scanner(Readable source)
    

Some Commonly Used Methods of a Scanner Object

Method Description
next() Returns the next token as a String object.
nextLine() Returns the rest of the current line as a String object.
nextDouble() Provides classes to handle text, dates, and numbers
nextInt() Returns the next token as an int value.

9.4.7: Procedure For Reading a Text File

  1. Create a File object with a pathname to your file.
  2. File data = new File("data.txt");
    
  3. Connect the file to a stream with a FileReader.
  4. FileReader fr = new FileReader(data);
    
  5. Add a buffer for the input with a BufferedReader.
  6. BufferedReader bin = new BufferedReader(fr);
    
  7. Add a Scanner object to parse the data.
  8. Scanner in = new Scanner(bin);
    
  9. Read data using methods calls for the correct type
  10. int x = in.nextInt();
    
  11. Close the streams when finished reading.
  12. in.close();
    

Note that you can nest the constructor calls as shown in the example below

Example Program Reading a Text 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
import java.io.*;
import java.util.Scanner;

class TextReaderApp {
    public static void main(String[] args)
            throws IOException {
        File data = new File("data.txt");
        Scanner in = new Scanner(
                     new BufferedReader(
                     new FileReader(data)));
        String str = in.nextLine();
        System.out.println(str);

        char ch = in.nextLine().charAt(0);
        System.out.println(ch);

        int x = in.nextInt();
        System.out.println(x);

        double d = in.nextDouble();
        System.out.println(d);

        for (int i = 0; i <= 10; i++) {
            x = in.nextInt();
            System.out.print(x + " ");
        }
        System.out.println();

        in.close();
    }
}

9.4.8: Testing for End of File

  • Sometimes you do not know how many lines are in a file
  • To solve this, the typical approach is to use a loop to process the file
  • The Scanner object has methods like hasNext() to see if there is any more input in the stream
  • You can use these methods to signal when your program reaches the end of a file

Example Program Testing for End-of-File

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.*;
import java.util.Scanner;

class TextReaderApp2 {
    public static void main(String[] args)
            throws IOException {
        File data = new File("TextReaderApp2.java");
        Scanner in = new Scanner(
                     new BufferedReader(
                     new FileReader(data)));

        while (in.hasNext()) {
            String line = in.nextLine();
            System.out.println(line);
        }
        in.close();
    }
}

Some Test Methods of a Scanner Object

Method Description
hasNext() Returns true if this scanner has another token in its input.
hasNextLine() Returns true if there is another line in the input of this scanner.
hasNextDouble() Returns true if the next token can be interpreted as a double value.
hasNextInt() Returns true if the next token can be interpreted as an int value.

9.4.9: Summary

  • Programs read and write to files using streams
  • A stream is a one-way transmission path that either delivers data to a destination or that takes data from a source.
  • To read and write files, you use input and output streams
  • Buffers improve the performance of disc operations by reducing the number of device operations
  • To create a stream object with the desired functionality, you layer streams

Writing a Text File

  • To write a text file, you typically layer three streams:
    • FileWriter to connect a character stream to the file
    • BufferedWriter to optimize performance
    • PrintWriter class to write binary data as characters
  • Using a File object for the pathname, you often use code that looks like:
    File data = new File("data.txt");
    PrintWriter out = new PrintWriter(
                      new BufferedWriter(
                      new FileWriter(data)));
    
  • You write to the file using the print() and println() methods like:
    out.println(1234);
    
  • To prevent data from being lost, always close the output stream when finished:
    out.close();
    

Reading a Text File

  • To read a text file, you typically layer two streams:
    • FileReader to connect a character stream to the file
    • BufferedReader for performance and to use readLine()
  • Since there is no "PrintReader" class you use a Scanner object
  • Using a File object for file and path names, you often use code that looks like:
    File data = new File("data.txt");
    Scanner in = new Scanner(
                 new BufferedReader(
                 new FileReader(data)));
    
  • You then read from a stream using the Scanner methods you use for reading from a keyboard:
    int x = in.nextInt();
    
  • Sometimes you do not know how many lines are in a file
  • To solve this, the typical approach is to use a loop to process the file
  • The Scanner object has methods such as hasNextLine() to see if there is any more input to read from the stream
  • You can use these methods to signal when your program reaches the end of a file:
    while (in.hasNextLine()) {
        String line = in.nextLine();
        System.out.println(line);
    }
    

Check Yourself

  1. How are I/O operations handled in Java?
  2. What is an output stream?
  3. What is an input stream?

Exercise 9.4

Take one minute to prepare an answer to the following question:

  1. What does the following snippet of code do?
    File data = new File("data.txt");
    Scanner in = new Scanner(
                 new BufferedReader(
                 new FileReader(data)));
    
    int count = 0;
    while (in.hasNextLine()) {
        String line = in.nextLine();
        count++;
        System.out.println(count + ": " + line);
    }
    in.close();
    

Wrap Up

Home | Blackboard | Announcements | Schedule | Room Policies | Course Info
Help | FAQ's | HowTo's | Links
Last Updated: April 05 2009 @21:53:20