CISC 3115
Modern Programming Techniques
Lecture 4
Exception-Handling


Overview

Handling Exceptional Situations in the Absence of Exception Handling

To begin with, exception-handling means dealing with exceptional situation. Exceptional does not necessarily mean erroneous, though errors are a large part of that category. Exceptional mean 'outside the course of normal events'.

The Issue

Exceptional situations often arise as the result of making a call to a method and something happening in that method that is outside the usual stream of logic for the code. There are two main issues to be addressed in such a situation.
Being Notified of the Exceptional Situation
Processing the Exceptional Situation
All of the above is compounded by the fact that there may be more than one sort of exceptional situation, each with its own notification requirement as well as flow of logic.

Handling the Issue at the Point of Occurrence or Elsewhere

As mentioned before, Regardless of which entity becomes responsible for dealing with the error or exceptional situation, the resulting logic is a distraction and results in a break in the flow of normal processing.

Some Examples

We present several coding contexts in which an exception (often interpreted as an error) situation can occur, and show various ways of dealing with it in the absence of exception handling.

Calculating the Average of an Array

double average(int [] arr) {

	int total = 0;
	for (int i = 0; i < arr.length; i++)
		total += i;
	return (double)total / arr.length;
}

Using a Trailer Value

// Uses a (special) trailer value 

import java.io.*;
import java.util.*;

public class Averager {
	public static void main(String [] args) throws Exception {
		int [] 
			arr1 = {1, 2, 3, 4},
			arr2 = {};

		doIt(arr1);
		System.out.println();
		doIt(arr2);
	}

	static void doIt(int [] arr) {
		System.out.println("The array: " + toString(arr));

		double d = average(arr);
		if (Double.isNaN(d))
			System.out.println("0-length array ... average couldn't be taken");
		else
			System.out.println("average: " + d);
	}
				
	static double average(int [] arr) {
		if (arr.length == 0) return Double.NaN;
		double total = 0;
		for (int i = 0; i < arr.length; i++) 
			total += arr[i];

		return (double)total/arr.length;
	}

	static String toString(int [] arr) {
		String result = "{";
		for (int i = 0; i < arr.length; i++)
			result += arr[i] + (i < arr.length-1 ? ", " : "");
		return result + "}";
	}
}

Notes

Using a Result Object

// Uses a 'result' object containing status of operation and value

import java.io.*;
import java.util.*;

public class Averager {
	static class Result {
		Result(double average, boolean wasSuccessful) {
			this.average = average;
			this.wasSuccessful = wasSuccessful;
		}
		Result(boolean b) {this(0, false);}
		Result(double average) {this(average, true;}
		public double average;
		public boolean wasSuccessful;
	}

	public static void main(String [] args) throws Exception {
		int [] 
			arr1 = {1, 2, 3, 4},
			arr2 = {};

		doIt(arr1);
		System.out.println();
		doIt(arr2);
	}

	static void doIt(int [] arr) {
		System.out.println("The array: " + toString(arr));

		Result result = average(arr);
		if (!result.wasSuccessful)
			System.out.println("0-length array ... average couldn't be taken");
		else
			System.out.println("average: " + result.average);
	}

	static Result average(int [] arr) {
		if (arr.length == 0) return new Result(false);
		double total = 0;
		for (int i = 0; i < arr.length; i++) 
			total += arr[i];

		return new Result((double)total / arr.length);
	}
				
	static String toString(int [] arr) {
		String result = "{";
		for (int i = 0; i < arr.length; i++)
			result += arr[i] + (i < arr.length-1 ? ", " : "");
		return result + "}";
	}
}
Notes

Using a Guard Method

// Uses a 'guard' (pre-condition) method -- similar to the hasXXXX methods of Scanner

import java.io.*;
import java.util.*;

public class Averager {
	public static void main(String [] args) throws Exception {
		int [] 
			arr1 = {1, 2, 3, 4},
			arr2 = {};

		doIt(arr1);
		System.out.println();
		doIt(arr2);
	}

	static void doIt(int [] arr) {
		System.out.println("The array: " + toString(arr));

		if (!hasAverage(arr)) 
			System.out.println("0-length array ... average couldn't be taken");
		else
			System.out.println("average: " + average(arr));
	}

	static boolean hasAverage(int [] arr) {return arr.length != 0;}

	static double average(int [] arr) {
		// if !hasAverage(arr) ...
		double total = 0;
		for (int i = 0; i < arr.length; i++) 
			total += arr[i];

		return (double)total / arr.length;
	}
				
	static String toString(int [] arr) {
		String result = "{";
		for (int i = 0; i < arr.length; i++)
			result += arr[i] + (i < arr.length-1 ? ", " : "");
		return result + "}";
	}
}
Notes

Using null

Using null is really just a special trailer value case. As null is a legal value for all object types, returning null is a way to indicate that something went wrong. The novelty here is that we are dealing with a primitive return type — double (had it been a String for example — which is an object — it would be more straightforward. We wouldn't expect to be able to use null for double and that is the secondary purpose of this approach: first, to show that null can act as a trailer value and secondly, that there is a way to bring the primitives into the world of objects, namely using the primitive type wrapper classes. We will have a lot more to say about them when we talk about inheritance, the class hierarchy and collections later on.

The main takeaway from this example should not be the Double/double sleight-of-hand; that will be presented later. Rather, it's the use of null as the trailer value of sorts, and that this is thus applicable to all situations using objects as the return value.

/// Uses null as the exceptional case indicator

import java.io.*;
import java.util.*;

public class Averager {
	public static void main(String [] args) throws Exception {
		int [] 
			arr1 = {1, 2, 3, 4},
			arr2 = {};

		doIt(arr1);
		System.out.println();
		doIt(arr2);
	}

	static void doIt(int [] arr) {
		System.out.println("The array: " + toString(arr));

		Double d = average(arr);
		if (d == null)
			System.out.println("0-length array ... average couldn't be taken");
		else
			System.out.println("average: " + d);
	}
				
	static Double average(int [] arr) {
		if (arr.length == 0) return null;
		double total = 0;
		for (int i = 0; i < arr.length; i++) 
			total += arr[i];

		return (double)total/arr.length;
	}

	static String toString(int [] arr) {
		String result = "{";
		for (int i = 0; i < arr.length; i++)
			result += arr[i] + (i < arr.length-1 ? ", " : "");
		return result + "}";
	}
}
Notes

Opening a File

Scanner keyboard = new Scanner(System.in);

System.out.print("File name? ");
String fname = keyboard.next();

Scanner scanner = new Scanner(new File(fname));

Using a Guard Method … Local Code

import java.io.*;
import java.util.*;

public class FileOpener {
	public static void main(String [] args) throws Exception {
		Scanner keyboard = new Scanner(System.in);
		Scanner datafile = null;
		String filename;
		while (true) {
			System.out.print("filename? ");
			filename = keyboard.next();
			File file = new File(filename);
			if (file.exists()) {
				datafile = new Scanner(file);
				break;
			}
			else
				System.out.println("'" + filename + "' not found, try again!");
		}

		// ... and here we go
		System.out.println("=== "  + filename);
		System.out.println("-----------------");
		while (datafile.hasNextLine()) {
			String line = datafile.nextLine();
			System.out.println(line);
		}
	}
}
Notes

Using a Guard Method … Separate Method

import java.io.*;
import java.util.*;

public class FileOpener {
	public static void main(String [] args) throws Exception {
		Scanner keyboard = new Scanner(System.in);
		while (true) {
			System.out.print("filename? ");
			String filename = keyboard.next();
			if (filePrint(filename)) break;
			System.out.println("'" + filename + "' not found, try again!");
		}
	}

	static boolean filePrint(String filename) throws Exception {
		File file = new File(filename);
		if (!file.exists()) return false;
		Scanner scanner = new Scanner(file);

		while (scanner.hasNextLine()) {
			String line = scanner.nextLine();
			System.out.println(line);
		}
		return true;
	}
}
Notes

Processing Possibly Corrupt Input Data

Suppose we had an application that read in student data which was subject to the following constraints:
class DataIntegrityChecker {
	public static void main(String [] args) {
		Scanner fileScanner = new Scanner(new File("data.text"));

		while (fileScanner.hasNext()) {
			String lastName = scanner.next();
			String lastName = scanner.next();

			int numAssignments = scanner.nextInt();
			for (int i = 1; i <= numAssignments; i++) {
				int assignment = scanner.nextInt();
				assignmentTotal += assignment;
			}

			String project = scanner.next();

			int midterm = scanner.nextInt();

			int finalExam = scanner.nextInt();
	}
}

A Smorgesbord of Approaches

class DataValidation {

	// Uses null
	public static String validateName(String name) {
		if (name.length() == 0) return null;
		if (!Character.isUpperCase(name.charAt(0))) return null;
		for (int i = 1; i < name.length(); i++)
			if (!Character.isLowerCase(name.charAt(i))) return null;
		return name;
	}

	// Uses trailer value (-1)
	public static int validateAssignment(int grade) {
		return grade >= 0 && grade <= 10 ? grade : -1;
	}

	// Uses result object	
	static class Result {
		Result(boolean isValid, String grade) {
			this.isValid = isValid;
			this.grade = grade;
		}
		Result(String grade) {this(true, grade);}
		Result(boolean isValid) {this(isValid, "");}

		public boolean isValid;
		public String grade;
	}

	public static Result validateProject(String grade) {
		return "ABCDF".indexOf(grade) >= 0 ? new Result(grade) : new Result(false);
	}

	// Uses guard method
	public static boolean validateExam(int grade) {
		return grade >= 0 && grade <= 100 ? true : false;
	}
}

import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;

class DataIntegrityChecker {
	public static void main(String [] args) throws FileNotFoundException {
		Scanner fileScanner = new Scanner(new File("../data.text"));

		int dataRecord = 0;

		while (fileScanner.hasNextLine()) {
			String line = fileScanner.nextLine().trim();
			if (line.length() == 0) continue;

			dataRecord++;

			Scanner lineScanner = new Scanner(line);

			String lastName = DataValidation.validateName(lineScanner.next());
			if (lastName == null) {
				error(dataRecord, "Invalid last name");
				continue;
			}

			String firstName = DataValidation.validateName(lineScanner.next());
			if (firstName == null) {
				error(dataRecord, "Invalid first name");
				continue;
			}

			int numAssignments = lineScanner.nextInt();
			boolean isInvalid = false;
			for (int i = 1; i <= numAssignments; i++) {
				int assignment = DataValidation.validateAssignment(lineScanner.nextInt());
				if (assignment == -1) {
					error(dataRecord, "Invalid assignment grade");
					isInvalid = true;
				}
			}
			if (isInvalid) continue;

			String project = lineScanner.next();
			DataValidation.Result result = DataValidation.validateProject(project);
			if (!result.isValid) {
				error(dataRecord, "Invalid project grade");
				continue;
			}

			int midterm = lineScanner.nextInt();
			if (!DataValidation.validateExam(midterm)) {
				error(dataRecord, "Invalid midterm grade");
				continue;
			}

			int finalExam = lineScanner.nextInt();
			if (!DataValidation.validateExam(finalExam)) {
				error(dataRecord, "Invalid final grade");
				continue;
			}

			System.out.println("#" + dataRecord + ": " + lastName + " " + firstName + " validated");
		}
	}

	private static void error(int dataRecord, String message) {
		System.out.println("*** In data record #" + dataRecord + ": " + message);
	}
}
Notes

Guards Methods Throughout

Guard methods are always applicable (pass the value to be checked, return true/false based on the validity); and thus using them makes the vakidation iterface uniform.
class DataValidation {

	// Uses guard methods throughout

	public static boolean validateName(String name) {
		if (name.length() == 0) return false;
		if (!Character.isUpperCase(name.charAt(0))) return false;
		for (int i = 1; i < name.length(); i++)
			if (!Character.isLowerCase(name.charAt(i))) return false;
		return true;
	}

	public static boolean validateAssignment(int grade) {
		return grade >= 0 && grade <= 10 ? true : false;
	}

	public static boolean validateProject(String grade) {
		return "ABCDF".indexOf(grade) >= 0 ? true : false;
	}

	// Uses guard method
	public static boolean validateExam(int grade) {
		return grade >= 0 && grade <= 100 ? true : false;
	}
}

import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;

class DataIntegrityChecker {
	public static void main(String [] args) throws FileNotFoundException {
		Scanner fileScanner = new Scanner(new File("../data.text"));

		int dataRecord = 0;

		recordReadLoop:
		while (fileScanner.hasNextLine()) {
			String line = fileScanner.nextLine().trim();
			if (line.length() == 0) continue;

			dataRecord++;

			Scanner lineScanner = new Scanner(line);

			String lastName = lineScanner.next();
			if (!DataValidation.validateName(lastName)) {
				error(dataRecord, "Invalid last name");
				continue;
			}

			String firstName = lineScanner.next();
			if (!DataValidation.validateName(firstName)) {
				error(dataRecord, "Invalid first name");
				continue;
			}

			int numAssignments = lineScanner.nextInt();
			boolean isInvalid = false;
			for (int i = 1; i <= numAssignments; i++) {
				int assignment = lineScanner.nextInt();
				if (!DataValidation.validateAssignment(assignment)) {
					error(dataRecord, "Invalid assignment grade");
					isInvalid = true;
				}
			}
			if (isInvalid) continue;

			String project = lineScanner.next();
			if (!DataValidation.validateProject(project)) {
				error(dataRecord, "Invalid midterm grade");
				continue;
			}

			int midterm = lineScanner.nextInt();
			if (!DataValidation.validateExam(midterm)) {
				error(dataRecord, "Invalid midterm grade");
				continue;
			}

			int finalExam = lineScanner.nextInt();
			if (!DataValidation.validateExam(finalExam)) {
				error(dataRecord, "Invalid final grade");
				continue;
			}

			System.out.println("#" + dataRecord + ": " + lastName + " " + firstName + " validated");
		}
	}

	private static void error(int dataRecord, String message) {
		System.out.println("*** In data record #" + dataRecord + ": " + message);
	}
}



Notes

Handling Errors and Exceptional Situations

There are several issues to concern ourselves with regard to handling exceptions, i.e., exceptional situations:

Detection: Who Detects the Exception?

Processing: Who Should Handle the Exception?

Notification: The Detector Notifying the Handler

Detection

The throw Statement

Handling the Exception — the try/catch Block

Exception classes

Miscellaneous

The Array Averager

// Uses exception handling

import java.io.*;
import java.util.*;

public class Averager {
	public static void main(String [] args) throws Exception {
		int [] 
			arr1 = {1, 2, 3, 4},
			arr2 = {};

		doIt(arr1);
		System.out.println();
		doIt(arr2);
	}

	static void doIt(int [] arr) {
		System.out.println("The array: " + toString(arr));

		try {
			System.out.println("average: " + average(arr));
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
	}

	static double average(int [] arr) throws Exception {
		if (arr.length == 0) throw new Exception("0-length array ... average couldn't be taken");
		double total = 0;
		for (int i = 0; i < arr.length; i++) 
			total += arr[i];

		return (double)total / arr.length;
	}
				
	static String toString(int [] arr) {
		String result = "{";
		for (int i = 0; i < arr.length; i++)
			result += arr[i] + (i < arr.length-1 ? ", " : "");
		return result + "}";
	}
}

The File Opener

// uses exception-handling
import java.io.*;
import java.util.*;

public class FileOpener {
	public static void main(String [] args) {
		Scanner keyboard = new Scanner(System.in);
		while (true) {
			try {
				System.out.print("filename? ");
				String filename = keyboard.next();
				filePrint(filename);
				break;
			} catch (Exception e) {
				System.out.println(e.getMessage());
			}
		}
	}

	static void filePrint(String filename) throws Exception {
		File file = new File(filename);	
		if (!file.exists()) throw new Exception("'" + filename + "' not found, try again!");
		Scanner scanner = new Scanner(new File(filename));

		while (scanner.hasNextLine()) {
			String line = scanner.nextLine();
			System.out.println(line);
		}
	}
}

The Bad Data Handler

class DataValidation {
	public static String validateName(String name) throws Exception {
		if (name.length() == 0) throw new Exception("0-length name");
		if (!Character.isUpperCase(name.charAt(0))) throw new Exception("Non-uppercase first letter");
		for (int i = 1; i < name.length(); i++)
			if (!Character.isLowerCase(name.charAt(i))) throw new Exception("Non-lowercase internal letter");
		return name;
	}

	public static int validateAssignment(int grade) throws Exception {
		if (grade < 0 || grade > 10) throw new Exception("Invalid assignment grade");
		return grade;
	}

	public static String validateProject(String grade) throws Exception {
		if ("ABCDF".indexOf(grade) < 0) throw new Exception("Invalid project grade");
		return grade;
	}

	public static Integer validateExam(int grade) throws Exception {
		if (grade < 0 || grade > 100) throw new Exception("Invalid exam grade");
		return grade;
	}
}

		

import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;

class DataIntegrityChecker {
	public static void main(String [] args) throws FileNotFoundException {
		Scanner fileScanner = new Scanner(new File("../data.text"));

		int dataRecord = 0;

		while (fileScanner.hasNext()) {
			String line = fileScanner.nextLine().trim();
			if (line.length() == 0) continue;

			dataRecord++;

			Scanner lineScanner = new Scanner(line);

			try {
				String lastName = DataValidation.validateName(lineScanner.next());
				String firstName = DataValidation.validateName(lineScanner.next());

				int numAssignments = lineScanner.nextInt();
				for (int i = 1; i <= numAssignments; i++) {
					int assignment = DataValidation.validateAssignment(lineScanner.nextInt());
				}

				String project = DataValidation.validateProject(lineScanner.next());

				int midterm = DataValidation.validateExam(lineScanner.nextInt());
				int finalExam = DataValidation.validateExam(lineScanner.nextInt());

				System.out.println("#" + dataRecord + ": " + lastName + " " + firstName + " validated");
			} catch (Exception e) {
				System.out.println("*** Exception ... In data record #" + dataRecord + ": " + e.getMessage());
			}
		}
	}
}

Files used in this Lecture