416 views
# Introduction to Exceptions ## An Example Here are classes for a simple course registration application. The `Registration` class maps course names to `CourseData` objects. Each `CourseData` object contains a list of names of students in the course. The provided methods support enrolling students in courses (we're keeping this simple to focus on the point of this exercise). The `Main` class sets up registration for three courses and enrolls a student in one of them. The full code, which is posted separately, also contains the `toString` methods for the two classes and the `import` statements. ```=java= public class Registration { HashMap<String, CourseData> allCourses; // constructor sets up the hashmap with an empty // CourseData object for each course name public Registration(String[] coursenames) { this.allCourses = new HashMap<>(); for (String name : Arrays.asList(coursenames)) { this.allCourses.put(name, new CourseData()); } } // add student to given course public void enroll(String student, String coursename) { allCourses.get(coursename).addStudent(student); System.out.println("Enrollment Successful"); System.out.println("Thanks for visiting!"); } } public class CourseData { LinkedList<String> allStudents; public CourseData() { this.allStudents = new LinkedList<>(); } // addStudent adds student to allStudents if they are not already there public void addStudent(String student) { if (this.allStudents.contains(student)) throw new RuntimeException("Student " + student + " already in course"); this.allStudents.add(student); } } public class Main { public static void main(String[] args) { String[] fall25Courses = {"CS111", "CS17", "CS15"}; Registration fall25 = new Registration(fall25Courses); System.out.println(fall25); fall25.enroll("Priya", "CS111"); System.out.println(fall25); } } ``` #### Some notes on this code - The constructor for the `Registration` class illustrates that we sometimes do significant data structure setup work when creating an object. Here, the constructor puts each course into the courses hashmap as a key that maps to an initial `CourseData` object. By doing this setup in the constructor, other methods can use the `allCourses` hashmap without having to check whether each course already exists. - Line 36 shows how one writes an array succinctly in Java. If you have to pass a list of data into a method, you can create an array of the data (using curly braces), then have Java convert it to a list using `Arrays.asList` (see line 8). This conversion enables writing an iterator-style for loop over the array input. ## Handling already-enrolled students Assume that Priya tried to enroll a second time, say by calling `fall25.enroll("Priya", "CS111");` again on line 41. What will this code do? Right now, the code throws a `RuntimeException` (line 29). This stops the entire program from running. But is this really what we want to happen? Should a possible typo in course name really crash the entire registration system? More realistically, the registration app would handle the situation either by giving an informative error message, or perhaps suggesting related courses that perhaps the student meant to register for instead. The point is that in order to handle errors gracefully in real applications we often need application-specific error handling rather than general-purpose "hey, something went wrong" style errors such as `RuntimeException`. This lecture is about how to define and handle custom errors. ## Creating, Throwing, and Handling Exceptions An *exception* is a special kind of error in Java that can be used to control which part of the code deals with errors that might arise in other parts of the code. For this example, we will introduce a new kind of exception called `StudentAlreadyEnrolledException`. ### Defining our own Exception Classes Here's how we create this in Java: ```=java public class StudentAlreadyEnrolledException extends Exception { // fields store information we might want to use to handle the error private String student; // constructor public StudentAlreadyEnrolledException(String student) { this.student = student; } public String getName() { return this.student; } } ``` For purposes of CS200, our Exception classes will be fairly simple -- they might (or might not) have fields containing information that is useful for reporting errors. They will typically contain just a constructor for the fields and perhaps some methods or getters for those fields. You might do more advanced things with Exception classes in later courses or internships. ### Throwing Custom Exceptions Custom exceptions are thrown similarly to the built-in ones like `RuntimeException`: here's the revised `addStudent` method from the `CourseData` class: ```=java= public CourseData { ... public void addStudent(String student) throws StudentAlreadyEnrolledException { if (this.allStudents.contains(student)) throw new StudentAlreadyEnrolledException(student); this.allStudents.add(student); } } ``` On line 3 above, we now throw the new kind of exception. We also need to add a `throws` annotation to line 1 (as part of the header line of the method). We'll talk about what that does in a moment. ### Handling Custom Exceptions Now, anyplace in our code where we call `addStudent`, we have to insert additional code to tell Java what to do if the error occurs. Here's the enhanced `enroll` method from the `Registration` class: ```=java= public class Registration { ... public void enroll(String student, String coursename) { try { allCourses.get(coursename).addStudent(student); System.out.println("Enrollment Successful"); } catch (StudentAlreadyEnrolledException e) { System.out.println("Student " " + e.getName() + " already enrolled"); } System.out.println("Thanks for visiting!"); } } ``` #### Things to note: - the code that could raise the error gets wrapped in a `try` block (lines 5-7). That line, and any other code that also should not run if the error occurs, goes into the `try` portion - along with the `try` goes a `catch` block (lines 8-10). The `catch` statement indicates the name of the class of exception that the code will handle, along with a variable (here, `e`) for the exception object itself. - The original body of the `enroll` method had the three lines now on lines 6, 7, and 11. The "enrollment successful" message on line 7 is within the `try` block because we don't want to run that code if indeed the registration failed. Line 11 should have been printed whether or not the enrollment was successful, so it is underneath the `try/catch` structure. If Priya were to try to enroll in "CS111" again, line 7 would not run (because line 6 yielded an exception), but line 11 still would run - no additional annotations (like `throws`) are needed on the `enroll` method, because it handles the error that might arise from calling `addStudent`. There are more subtle cases to consider here, some of which we will get to later this semester. But this should suffice for now. **Why is the throws annotation needed on `addStudent`**? Just as Java checks your program for correct use of types before it runs, Java also checks that you've taken care of all potential exceptions. If we left the `try/catch` block out of `enroll`, Java would report an error that a possible exception was being ignored. Java uses the `throws` annotations to identify which methods might throw exceptions, then it checks all places where those methods might be called to make sure they have `try/catch` (or some other mechanism) to deal with the situation.