A4. The Java Object Model and Exceptions


In this section, you will learn about:

  • Java classes, including the use of access rights, inheritance, method definitions, constructors, and instance versus class members.
  • Java Packages
  • Java interfaces, and why are they are so important.
  • Java dynamic messages, and how they differ from C++'s vtable approach.
  • Java dynamic loading, and how application classes are loaded on demand.
  • The idea behind exceptions, and how Java exceptions differ from C++ exceptions.
  • The common Java exception types.
  • How to throw and catch exceptions.
  • The differences between runtime vs. non-runtime exceptions.
  • How to define your own exceptions.

Classes

Classes are like C struct or Pascal record definitions that allow function definitions within. While Java class definitions look basically the same as those of C++, Java classes and objects have very different behavior. Java classes support strictly single class inheritance, but allow multiple interface inheritance.

Defining a class in the Java language is similar to defining a class in C++:

class ClassName {

  methods and variables

}

Java class inheritance is specified via the extends keyword (instead of a ':' as in C++). For example:

// Java code
class List {
    ...
}

class OrderedList extends List {                              
    ...
}

By contrast, in C++ you would write:

// C++ code
class OrderedList : public List {
    ...
}

All classes without explicit superclasses are considered direct subclasses of class Object. Therefore, all classes directly or indirectly inherit from Object.

Magercises

  • 1. A Simple Class.
  • Method Definitions

    Java method syntax is basically the same as in C/C++ except that Java has no global functions; methods must be members of classes. Method syntax is:

    
    ReturnTypeName methodName(argument-list) {
      body
    }
    
    

    where ReturnTypeName can be a primitive type, a class, or an array. For example, to return an array of strings, use:

    String[] foo() {...}
    

    Except for constructors, all methods must have a return type, even if it is void.

    There are no default arguments and no inline methods in the Java language. (However, the Java compiler, javac, has an optimization option that will inline some methods.)

    Code blocks for Java methods are always specified within the class itself; there is no separate declaration and definition as there is in C++ code or as you might separate functions and their prototypes in C.

    // C++ code
    class List {
    public:
      void add(void *o);
    }
    
    void List::add(void *o) {...}
    
    
    /* C code */
    extern void add(void *o);
    ...
    void add(void *o) {...}
    
    

    By contrast, Java method declarations and definitions are never separated; methods are always defined within classes:

    // Java code
    class List {
      public void add(Object o) {...}
    }
    

    There are two reasons that C and C++ separate method declarations and definitions:

    • C/C++ compilers cannot see forward definitions.
    • C/C++ compilers cannot see definitions in other files.

    The Java language has no such definition-ordering problems. The Java run-time system automatically examines class definition files (.class files) for information about other compilation units. If you modify one of your Java class files, there is no need to recompile other classes or class headers as in C/C++.

    Consider the following classes with a cyclic-dependency:

    // Java code
    class A {
      private B b; // refers to class below
    }
    
    class B {
      private A a; // refers to class above
    }
    

    There is no linear ordering of these class references that would satisfy a one-pass compiler. In C++ you would have to do the following:

    // C++ code
    class A; // tell C++ compiler that A is a class
    class B; // tell C++ compiler that B is a class
    class A {
    private:
      B *b; // refers to class below
    }
    
    class B {
    private:
      A *a; // refers to class above
    }
    

    Constructor Syntax

    Java class constructors are methods that have the same name as the enclosing class. Java constructors have the same syntax as in those in C++, except that Java superclass initializations may be done via explicit calls. For example,

    // C++ code
    class MyButton : public Button {
      int counter;
    public:
      MyButton(char *label) : Button(label) {
        counter=0;
      }
    };
    

    The equivalent Java code is:

    // Java code
    class MyButton extends Button {
      private int counter = 0;
      public MyButton(String label) {
        super(label);
      }
    }
    

    Note that any call to super() must be the first statement of the Java constructor. A call to super() is inserted by default if one is not specified explicitly.

    Also, the first statement may be a call to another constructor with the help of the this keyword, allowing you to provide default values for constructor arguments.

    Magercises

  • 2. Some Methods.
  • Order of Initialization and Constructor Calls

    When an object of class T is instantiated, the following order of events is observed:

    1. An implied or explicit call to the superclass constructor of T is called, thereby, initializing its portion of the object.
    2. If a static initializer is defined for the class, it is executed. Static initializers will be discussed shortly.
    3. The member variables of T are initialized in the order they are defined.
    4. The remainder of the constructor for T is executed (the portion after an explicit reference to the superclass).

    Using the MyButton class above as an example, the order of initialization would be:

    1. The constructor for class Button (super()) is called.
    2. The constructor for class Component (Button's superclass) is called.
    3. The constructor for class Object (Component's superclass) is called.
    4. The member variables of Object are initialized.
    5. The constructor for Object is executed.
    6. The member variables of Component are initialized.
    7. The constructor for Component is executed.
    8. The member variables of Button, the superclass, are initialized.
    9. The constructor for Button is executed.
    10. Member counter is initialized to 0.
    11. The constructor for MyButton is executed, which is empty in this case.

    Object is the ultimate superclass of all classes in Java.

    Variable Definitions

    Java variable definitions may be initialized to any constant or nonconstant object (except forward references to members, because they will not yet have been initialized). In C or C++, by contrast, you must define the member or struct field, and then, in the constructor or outside the class, specify the value. For example,

    // C++ code
    class T {
      int i;
      static int version;
      T(){i = 0;} // instance variable initialization
    };
    
    int T::version = 3; // class variable init
    
    
    /* C code */
    int T_version = 3; /* fake a class variable */
    struct T {
      int i;
    };
    ...
    void foo() {
      T *t = (T *)calloc(1, sizeof(T));
      t.i = 0;
    }
    

    The equivalent Java code is:

    // Java code
    class T {
      int i = 0; // instance variable initialization
      static int version = 3; // class variable init
    }
    

    The Java language has a static block initializer for initializing such things as static arrays:

    class TT {
      static int[] a = new int[100];
      static {
        // init all elements to 2
        for (int i=0; i<100; i++) a[i]=2;
      }
    }
    

    Class Inheritance

    The Java language has only single class inheritance. Inheritance is specified by the extends keyword. For example:

    class Manager extends Employee {
       ...
    }
    

    In C++, inheritance would be specified by:

    class Manager : public Employee {
       ...
    }
    

    We say that Manager is a subclass of the superclass Employee. All non-private members (data and methods) inherited from the superclass (base class in C++-speak) are visible to the subclass.

    C++ programs often use multiple inheritance simply to inherit part of an interface from abstract classes. The Java language provides interfaces for this purpose. Interfaces will be discussed in more detail shortly.

    Access Rights to Class Members

    Note: packages will be discussed shortly; for now, consider them to be logical, "friendly" groups of classes that share some priviledged information about each other.

    In order of least to most protective, here are the Java member access specifiers:

    • A public member is visible to any other class. (Classes outside the package can see the member only if its defining class is public.) Java's public specifier is identical to C++'s specifier.
    • A protected member is visible to any class within the package and to any subclass even if the subclass is defined in another package. In C++, there are no packages and protected means that only subclasses may see the member.
    • A member without a specifier is considered "package friendly" and can be seen by any class in the package. C++ has no packages, but you can think of this as declaring all classes in a group as mutual friends in C++.
    • A private member is visible only within its defining class. Java's private specifier is identical to C++'s specifier.

    In summary, a member of a class is visible throughout the package in which the class is defined when the member is public, protected, or unmodified.

    Magercise

  • 3. Using Inheritance.
  • Classes Are "Conscious"

    Java classes are "conscious" in the sense that they know their type at run-time whereas C++ objects are blocks of unconscious data. For example, if you call a method f() in a class that has been modified and no longer has f(), you will get a run-time exception called NoSuchMethodException.

    The class of an object can be determined at run-time via the instanceof operator, which returns a boolean value. For example,

    boolean isButton(Object o) {
      return (o instanceof Button);
    }
    

    Abstract Classes

    An abstract class is a class that doesn't provide implementations for all of its declared methods. Abstract classes cannot be instantiated; instead, you must subclass them and provide implementations for any methods that have none.

    The Java language has explicit abstract classes that behave just like their C++ cousins. For example, the following Java class is abstract:

    // Java code
    abstract class Shape {
      float angle = 0.0;
      public abstract void draw();
      public void rotate(float degrees) {
        angle = degrees;
        draw();
      }
    }
    

    The analogous abstract class in C++ is similar:

    // C++ code
    class Shape {
      float angle = 0.0;
    public:
      virtual void draw() = 0;
      virtual void rotate(float degrees) {
        angle = degrees;
        draw();
      }
    }
    

    All subclasses of Shape inherit the interface (draw() and rotate()) and partial implementation (that of rotate()):

    // Java code
    class Box extends Shape {
      public void draw() {
        // code to draw box at angle
      }
    }
    

    Abstract classes cannot be instantiated; you can only inherit from them. Abstract classes typically are used to describe the generic behavior and partial implementation of a set of related classes. For example, all shapes could be rotated by simply changing the angle and redrawing as we show in method rotate() above.

    When you provide no implementation and no data members in an abstract class, that class is probably better reformulated as an interface, which specifies only the behavior of an object.

    The Relationship between C Structures and Java Classes

    This section is mostly review for C++ programmers.

    Consider how you would build a list manager for arbitrary elements in C. First, you would define operations on a list such as add and get:

    void list_add(void *item) {..}
    void *list_get(int i) {..}
    

    Second, you might choose the implementation data structure such as an array of "void *":

    static void *data[MAX_ELEMENTS];
    static int nelements = 0;
    

    To ensure that users of the list manager do not access the data structure explicitly (isolating implementation from interface), we have made the data static, thus making it invisible outside of the file.

    Naturally, the problem with this scheme is that only one list manager can be created. To solve this, you would put the data within a struct and then list manager users would each allocate one of the structs:

    struct List {
      void *data[MAX_ELEMENTS];
      int nelements;
    };
    
    void list_add(struct List *list, void *item) {..}
    void *list_get(struct List *list, int i) {..}
    void list_init(struct List *) {nelements=0;}
    

    Notice that now each list function receives the particular list to operate on as the first argument. Also note that an explicit initialization function is required to initially set the fields of a List structure.

    Unfortunately, this struct-based scheme opens up all of the data elements to the users as they must see the definition of struct List to be able to use it.

    A Java Implementation

    A similar, but simpler and safer, solution to the struct-based scheme, is available to Java programmers: Change the name struct to class. Place the functions within the class definition. Finally, remove the first argument that indicates which list to manipulate.

    class List {
      private  Object data[];
      private  int nelements=0;
      public   void add(void *item) {..}
      public   Object get(int i) {..}
      public   List() {
          data = new Object[MAX_ELEMENTS];
      }
    }
    

    Notice that we have also changed:

    • void * to Object, which refers to any Java object. (But cannot refer to a variable of a primitive type.)
    • list_add() to add() because moving it inside the class definition for List means that it is now really List.add()! Function names are more general in Java.
    • list_init() to List(), which is called the constructor for class List. Because Java cannot allocate any space with a simple field (member) definition, we use the constructor to allocate the space. We can, however, initialize nelements with the definition.
    • access specifiers have been added to prevent access to List's private data.

    Using the List Managers

    Now compare how you would use the C list manager versus the Java list manager:

    /* C code */
    #include "List.h"
    foo() {
      struct List *mylist = (struct List *)
      calloc(1, sizeof(struct List));
      list_add(mylist, "element 1");
      list_add(mylist, "element 2");
      /* we can write to mylist->data[] directly! */
    }
    

    In Java, you would write:

    // Java code
    void foo() {
      List mylist = new List();
      mylist.add("element 1");
      mylist.add("element 2");
      // cannot write to mylist.data[] directly!
    }
    

    The crucial difference between C and Java is in the function call versus method invocation; i.e.,

    list_add(mylist, "element 1");
    

    versus

    mylist.add("element 1");
    

    In C you write function-centric code. In Java, you write data-centric code. That is, "execute this code using this data" versus "here's some data...operate on it". Object-oriented programmers typically go further than this to say "ask the list to add "element 1" to itself." Another common terminology (from Smalltalk) would describe this operation as "send the message add to the object mylist with an argument of "element 1"."

    Are Classes Only For Object-Oriented Programming?

    The Java version of the list manager described above can be viewed merely as a simpler description than the C version. In fact, classes that are not derived from another class (using inheritance) are really nothing more than fancy struct definitions. All the mumbo-jumbo surrounding object-oriented programming such as polymorphism can be ignored while studying the benefits of encapsulation and data hiding:

    • encapsulation: the methods that operate on an object are packaged up with that object.
    • data hiding: portions of the object's data may be hidden from the user, thus, allowing the implementation of that object to be isolated from the user. Changes to the implementation would not affect a user's code as all access to the data element can be controlled through a set of functions.

    Packages

    Java has a packages feature (similar to Ada) that lets you group together related classes and decide which classes will be visible to classes in other packages. For example, consider the following two incomplete Java compilation units, one called List.java:

    // Define a new package called LinkedList
    package LinkedList;     
                            
    public class List {...} 
      // class is visible outside of package
    
    
    class ListElement {...} 
      // class is not visible outside of package
    

    and the other called A.java:

    // bring in all classes from LinkedList.
    import LinkedList.*;    
    
    // define class A in the default, unnamed package
    public class A {...}
    

    that implement and use a linked list.

    The important points to note in this example are:

    • Class A in file A.java must inform the compiler that it will be using symbols from package LinkedList.

    • Class List is visible to classes outside package LinkedList. For example, class A is able to make List objects.

    • We have hidden the wrapper for list elements, class ListElement, within package LinkedList. Class A may not access ListElement because Class A is in a different package (specifically, the default unnamed package).

    We have demonstrated class visibility and now consider member visibility as we flesh out List.java:

    
    // Define a new package called LinkedList
    package LinkedList;         
    
    // public class List is visible outside of package
    public class List {         
    
      public void add() {...}  
        // visible outside of class and package
    
      void internal() {...}    
        // not visible outside of class
    }
    
    // class ListElement is not visible outside of package
    class ListElement {         
    
      public void getNext() {...} 
        // getNext() is not visible out of package
    
    }
    

    and A.java:

    // bring in all classes from LinkedList
    import LinkedList.*;
    
    public class A {        
      // define class A in the default package.
    
      public static void main(String[] args) {
        List b = new List(); // access to type List OK;
                             // LinkedList.List visible
        b.add();                            // ok, add() is public in List
        b.internal();                       // INVALID! internal() not public
        ListElement c = new ListElement();  // INVALID!
                                            // LinkedList.ListElement not visible
        c.getNext();                        // INVALID! fields of ListElement
    					// also not visible
      }
    }
    

    Magercise

  • 4. Working With Packages.
  • Package Hierarchy and Directory Hierarchy

    The Java run-time system normally insists that the directory structure of your class library match that of the language package hierarchy. For example, class

    LinkedList.List
    

    must exist in file

    LinkedList/List.class
    

    starting from one of the directories listed in your CLASSPATH environment variable. Your CLASSPATH should include '.', the current directory.

    Class files in the default package should reside in the current directory or in one of the directories specified in your CLASSPATH environment variable.

    Interfaces

    Class inheritance is used to define a new class, T, as an extension or specialization of a previous class, S. Further, S describes T's intrinsic identity thereby implying that all subclasses of S are related. For example, if class Employee is a generalization of both Manager and President, then Manager and President must be related. Any common behavior such as paySalary() is generalized and defined in Employee.

    An interface, on the other hand, defines a skill associated with a particular class T that cannot be generalized to all related classes (i.e., other subclasses of T's superclass S). We say that interface inheritance groups classes by common behavior and class inheritance groups classes by common identity.

    Objects Implement Roles

    A convenient way to illustrate the difference between class inheritance and interface inheritance is to consider roles object may take on. The role of "The Terminator" may be described via:

    interface Terminator {
      public void doRandomViolence();
    }
    

    This role may be "played" by any number of objects such as:

    class Arnold extends Actor implements Terminator 
    {
      public void doRandomViolence() {...}
      ...
    }
    

    and

    class Dilbert extends Geek implements Terminator {
      public void doRandomViolence() {...}
      ...
    }
    

    Arnold and Dilbert have totally different identities (Actor versus Geek, respectively), but both may play the role of "The Terminator."

    A single object may play multiple roles; e.g., Dilbert is also able to play the role of an engineer (it is not true that all geeks are engineers so we cannot rely on class Geek having the necessary engineer interface):

    class Dilbert extends Geek
                  implements Terminator, Engineer {
       public void doRandomViolence() {...}
       public void borePeople() {...}
    }
    

    where Engineer is defined as:

    interface Engineer {
      public void borePeople();
    }
    

    The important concept to grasp here is that interfaces allow you to refer to an object not by its specific class name, but by its behavior. For example,

    playMovie(Terminator badGuy) {
      badGuy.doRandomViolence();
    }
    

    An interface allows you to perform similar operations on objects of totally unrelated classes. Unrelated objects may share common behavior by having them implement an interface. You could use class inheritance instead, but only if the objects were related.

    Interfaces are like classes that have nothing but abstract methods and constants (if any). That is, interfaces have no inherent state or behavior, but rather define a protocol for accessing the behavior of objects that implement them.

    Interface Example 1

    As a concrete example, consider the interface Runnable that indicates a class may be run in a thread. Clearly, the set of classes that can be run in threads are related by a skill rather than identity. For example, a scientific computation and an animation are totally unrelated by identity, but both have the skill that they can be run in a thread.

    Interface Runnable prescribes that a class define method run():

    interface Runnable {
      public abstract void run();
    }
    

    The definition of the computation might look like:

    class ComputePrimes implements Runnable {
      public void run() {
        ...
      }
    }
    

    And the animation might be:

    class Animation implements Runnable {
      public void run() {
        ...
      }
    }
    

    Note that we could define a subclass of Animation (a class related by identity) that would also be Runnable:

    class CartoonAnimation extends Animation
                           implements Runnable {
      ...
    }
    

    Interface Example 2

    Java provides a wonderful mechanism for separating the abstract notion of a collection of data from a data structure implementation. During the design process, collections of data should be thought of in the abstract such as List, Set, Queue, etc... It is only at the programming stage that actual data structure implementations should be chosen for each collection. Naturally, appropriate data structures are chosen according to memory constraints and how the collections will be accessed (e.g., how fast can data be stored, retrieved, sorted etc...). In Java, we separate the abstract notion of a collection of data from its data structure implementation using interfaces. For example, consider a list interface versus a linked list implementation. A simple List might have the following behavior:

    interface List {
      public void add(Object o);
      public boolean includes(Object o);
      public Object elementAt(int index);
      public int length();
    }
    

    whereas a linked list (that can actually play the role of a List collection) would be a class that implements interface List:

    class LList implements List {
      public void add(Object o) {...}
      public boolean includes(Object o) {...}
      public Object elementAt(int index) {...}
      public int length() {...}
    }
    

    Such a list could be used as follows:

    List list = new LList();
    list.add("Frank");
    list.add("Zappa");
    list.add(new Integer(4));
    if ( list.includes("Frank") ) {
      System.out.println("contains Frank");
    }
    

    The key observation here is that a LList implementation was created, but was accessed via the collection interface List.

    Interface Example 3

    Consider the predefined Java interface Enumeration:

    interface Enumeration {
      boolean hasMoreElements();
      Object nextElement();
    }
    

    Any data structure can return an Enumeration of its elements, which should be viewed as a particular "perspective" of the data stored within. To walk any enumeration, the following generic method can be used:

    void walk(Enumeration e) {
      while ( e.hasMoreElements() ) {
        System.out.println(e.nextElement());
      }
    }
    

    The java.util.Vector class has a method called elements() that returns an object (of class VectorEnumerator) that implements the Enumeration behavior. For example:

    Vector v = ...;
    walk(v.elements());     // sends in a VectorEnumerator
    

    This same method, walk(), will work for any Enumeration of any data structure. For example, Hashtable and Dictionary also can return an Enumeration:

    Hashtable h = ... ;
    walk(h.elements());
    Dictionary d = ... ;
    walk(d.elements());
    

    Interface Benefits

    In summary, Java interfaces provide several benefits that allow you to streamline your programming:

    • Interfaces allow you to work with objects by using the interface behavior that they implement, rather than by their class definition.

    • You can send the same message to two unrelated objects that implement the same interface. In C++, the two objects must be related (i.e., inherit from the same parent). The Java language thus provides a great framework for working with distributed objects, even when you do not have the class definition for the target objects!

    Interface Implementation Versus Class Inheritance

    Interface implementation specifies that a class must explicitly define the methods of an interface and, hence, define a portion of the object's behavior. Class inheritance is used to inherit the state and behavior of the superclass. You may consider interface implementation to be a restricted subset of class inheritance. Interface implementation is, in fact, a subset of class inheritance in terms of functionality, except for one important distinction: interface implementation forms another completely independent hierarchy among your classes. In the Java language, grouping by behavior specification is clearly distinct from the grouping of related objects.

    Interfaces are like classes that have nothing but abstract methods and constants (if any). That is, interfaces have no inherent state or behavior, but rather define a protocol for accessing the behavior of objects that implement them.

    Magercise

  • 5. Using Interfaces in Java.
  • 6. Using Static Methods to create a Singleton.
  • 7. Working With Class Information.
  • 8. Stacks and Queues as Interfaces.
  • Dynamic Messages

    The Java compilation and execution sequence has no explicit link phase. Method lookup occurs on-demand at run-time. The first method or variable access to an object actually looks up the member name in the object's class definition to determine the member's position in the object. Once the position has been established (as an integer offset), the byte code that initiated the reference can be rewritten to be a faster, direct access instruction.

    "No-such-method" run-time errors are virtually nonexistent; the Java compiler can catch most such errors at compile time, in contrast to Objective-C or Smalltalk, which have untyped objects.

    Benefits of Dynamic Messages

    C, Pascal, and FORTRAN do not have late binding; once a program is linked, the binding is fixed as all symbols have been resolved to physical addresses. C++ claims to have late binding. However, once a C++ program is linked, the binding is fixed. Java binding occurs later in that the code to execute for a message send is determined immediately before you actually perform the method call. The resulting benefits are:

    • Multiple programmers can work very comfortably on the same Java project since, as long as the interface remains the same, a change in a class definition does not force recompilation of any other class.

      Analogy: To illustrate the difference between Java and C/C++ method lookups, imagine asking your co-worker in the neighboring cubicle to look up Julie's phone number (representing the Java method lookup). To represent a C/C++ lookup, you would instead ask your co-worker to look at the 8th row in the phone list and return that phone number. But what if the order of the phone list changes? The Java lookup is much more flexible at a small cost in speed.

    • Interfaces allow you to send messages to objects for which you do not have the class definition (such as distributed objects) by using type checking.
    • Interfaces allow dynamic loading-if class definitions can be loaded at run-time, they must also be linked at runtime.

    Dynamic Loading

    As we have seen, a Java program is not linked together all at once. The run-time environment first loads only the starting class, followed by other classes as they are needed. You may think of a Java application as a program made up of nothing but DLLs (dynamically loadable libraries).

    The first time a class, say T, is instantiated, the Java interpreter will recognize that the T.class file has not been loaded. The class loader will search for and load the class definition and byte code for the class' methods. (Your CLASSPATH environment variable determines where the run-time system looks for class files.)

    Benefits of Dynamic Loading

    The main benefits of dynamic loading are:

    • Your program loads only the code it needs to run.
    • You grab the latest version of each class at run-time, not at link-time. Note that this can be a two-edged sword; you may not really want the latest version...
    • Java applications can be distributed. They can combine "services" from several sources to form a complete application so you do not end up with a large, hard-to-change (and possibly out-of-date) application.
    • It's easier to work in teams-you do not have to relink your entire project after you (or someone else on your team) change a module.

    Introduction to Java Exceptions

    Java provides a sophisticated error-handling mechanism. Instead of using error return codes as you would in C, you throw exceptions that may be caught elsewhere in your code. Exceptions have a huge advantage over error codes in that exceptions may be not be ignored whereas you can ignore return values.

    An exception is an object whose sole purpose is to inform your program or the operating system that something abnormal has occurred. Exceptions can be thrown by the Java runtime, such as an ArithmeticException that is thrown for a bad division, or your code can explicitly throw an exception.Your program can catch exceptions anywhere along the current call chain. If the currently-executing method does not provide an exception handler for a thrown exception, the current method is immediately exited and the invoking method has a chance to catch the exception. This passing of the exception up the call stack keeps repeating until the exception is caught by a method in the call chain, or, if no method in the call chain provides a handler for it, the Java runtime will handle it. (The Java runtime will usually abort the application and output a rather nasty sounding message.

    Java exceptions are similar to those in C++. Syntactically, both languages specify exceptions via a try block followed by one or more catch blocks. For example, the following Java program prints an error message to standard error indicating that an array index of 50 is out-of-range.

    public class Main {
    public static void main(String[] args) {
      int[] a = new int[10];
      try {
        a[50] = 4; // will cause an exception
      }
      catch (ArrayIndexOutOfBoundsException e) {
        System.err.println("index out of range: " + e.getMessage());
        e.printStackTrace(); // where did we crash?
      }
    }
    

    Common Exceptions

    Common exceptions other than ArrayIndexOutOfBoundsException are:

    • NullPointerException--You tried to access a member of an object that has not been created (a null variable). Example:
        Employee e;
        e.salary = 45000;
        // throws NullPointerException
      
    • ClassCastException--You tried to cast an object to a class that is not a superclass of or the same class as the original. Example:
        try {
          Object o = (Object) new Integer(3);
          String s = (String) o;
          s.length();
        }
        catch (ClassCastException e) {
          System.err.println("illegal cast from " + e.getMessage());
        }
      
    • ArithmeticException--You tried to divide or mod by 0. Example:
        int i = 4 % 0;
      
    • OutOfMemoryError--The new operator will throw this error when no more memory is available.

    A try block may throw many different exceptions, in which case you may want to have multiple catch blocks. The following example catches all exceptions coming from a method call:

    try {
      f();  // assume f() is some method able to throw
            // multiple exceptions
    }
    catch (ArrayIndexOutOfBoundsException e) {
       // code to handle array index problems
    }
    catch (Exception e) { // catch all other exceptions
       // code to handle any other type of exception
       // Note that you may want to take a look at class Exception
       //   to find out how to print the current call stack information
    }
    

    Only one of the catch blocks is executed even if that block throws another exception.

    Magercise

  • 9. Catching Exceptions.
  • Uncaught Exceptions

    Any exception that can be thrown or generated by your method must either have a corresponding catch, or the method must specify that the exception can be generated by the method. For example, you may do either:

    void openData() throws IOException {
      FileOutputStream f=new FileOutputStream("datafile");
    }
    

    and have the caller of openData() catch IOException:

    try { openData() } catch(IOException ioe) {...}
    

    or you can catch the exceptions immediately in openData:

    void openData() {
      try {
        FileOutputStream f = new FileOutputStream("datafile");
      }
      catch (IOException e) {
        System.err.println("can't find datafile");
      }
    }
    

    Finally Block

    A finally block contains code that will be executed regardless of whether an exception occurs. A finally block can be used for clean-up purposes. A catch block, a finally block, or both must follow a try block:

    try {
      ...
    }
    catch (...) {...}
      ...
    catch (...) {...}
    finally {
      System.out.println("Always executed");
    }
    

    C++ has nothing analogous to the finally block.

    Runtime vs. Non-Runtime

    There are exceptions to the statement: Any exception that can be thrown or generated by your method must either have a corresponding catch, or the method must specify that the exception can be generated by the method.

    If an exception is a subclass of RuntimeException, it does not need to be declared in a catch or throws statement. Runtime exceptions, like integer division by zero and array access out of bounds, are types of runtime exceptions. You do not HAVE to catch them. If one is thrown and not caught by a method in the call stack, it will stop the program.

    Defining Your Own Exceptions

    You may define your own exceptions by subclassing Exception. For example,

    class Panic extends Exception {};
    

    To raise or generate an exception, use:

    throw object;
    

    where object is of a type derived from Exception, such as:

    throw new Panic();
    

    Magercises

  • 10. Creating and Throwing Exceptions.
  • 11. And Finally....

  • Copyright © 1996-1997 MageLang Institute. All Rights Reserved.