![]() Princeton University |
Computer Science 441 |
When an error occurs in Java, either your program or the system can throw an exception which includes information on what went wrong. If you wish to throw an exception you need to create an object of a class which extends Exception. There are a number of such classes already built into Java, such as IOException and its subclass EOFException. If you don't like what you find, you can always define your own subclass of extension and make sure it includes whatever information you want the exception handler to know about.
If you want to raise an exception, simply create an object of the appropriate exception type and throw it:
throw new EOFException();Of course, any constructor for the class will work fine.
If you throw an exception in a method, but don't handle it there, you must advertise the fact:
public void openFile(String name) throws IOException{...}Now any method that includes a call to openFile must either be prepared to handle an exception of that type or itself advertise that it throws that kind of exception (or a superclass of that exception). The point of this is that if you write code sending a message to an object, you should be aware of (at compile time) what exceptions may be raised in that message send, because you likely don't want a raised exception to crash your program.
Code to handle exceptions has the following form:
try { //code that might raise an exception } catch (ExceptionType1 exc) { //code to execute if this type of exception raised } catch (ExceptionType2 exc) { //code to execute if this type of exception raised } ... } finally {// code to be executed whether or not exceptions are raised}The finally block is optional for the try block. If the code in the tryp block executes without raising any exceptions then it will execute the code in the finally block (if there is any) and then continue with the statement after the try block.
If an exception is raised, it will stop executing the code in the try block and check the exception's type against those found in the catch parameters successively until it finds one that matches, and then execute that code. After executing that code it executes the finally block (if any) and then continues to the next statement. If there is no catch block with an exception type which matches the one thrown then the code in the finally block is executed before going to the caller and looking for an appropriate catch clause there. next statement.
Thus, no matter what happens inside the try block, Java guarantees that the code in the finally block (if any) will be executed. this is very handy if the try block allocates resources (e.g., opens a file) that must be disposed of before proceeding. Of course it is always possible for a catch block to either throw again the same exception it caught again (e.g., you try to fix things up, but fail, so ask someone else to take care of it) or even raise a brand new exception. Thus you can dispose of resources in the catch clause and then re-raise the exception. However in most cases it is a bit simpler to take care of things that always must be done by putting them in the finally clause.
Incidentally, Java has some exceptions that need not be declared in a throws clause in method headers. These are typically exceptions from subclasses of Error (representing internal compiler errors - which in theory should never occur!) and RuntimeException which includes things like array out of bounds exceptions, division by zero, etc. These would be a pain in the neck to always advertise, but if they can ever happen you should certainly handle them.
The package java.util contains the following interface:
public interface Enumeration { public boolean hasMoreElements(); // pre: Associated structure, S, is unchanged // post: true iff element of S has yet to be traversed. public Object nextElement(); // pre: S has more unenumerated elements // post: returns another element of S, marks it enumerated }A data structure can create an object of type enumeration, which can be used to cycle through the elements. For example, the built-in Java class java.util.Vector has a method: public Enumeration elements() with this, you can print out the elements of Vector v as follows:
for (Enumeration enum=v.elements(); enum.hasMoreElement(); ) System.out.println(enum.nextElement());If you don't like this style (I personally prefer more methods in my iterators) you can roll your own. Basically, iterators should not be a problem in object-oriented languages.
Event handling is built into Java 1.1 and beyond (it was sort of there in Java 1.0, but done rather poorly). As a result the programmer need not provide either the loop or queue. Instead events are delivered to objects which handle them.
Java event handling is based on a delegation model. Objects which can generate events have an associated collection of registered "listeners". Every time an event occurs, all registered listeners are notified. Each event type has an associated listener type and there is an associated method for attaching listeners to each component. As an example, we discuss events generated by the user clicking on a Java button in a window. Java buttons generate ActionEvents when a user clicks on them. Listeners of type ActionListener are added to buttons by sending the addActionListener(ActionListener listener) message to a button with the listener as an arguement. Now whenever the user clicks on the button, an actionPerformed method, with the event as a parameter, will be sent to all of that buttons registered listeners.
Each listener must implement the interface ActionListener. The interface includes only method:
void actionPerformed(ActionEvent e) Thus each action listener must implement this method. The ActionEvent can contain information that the listener can use. For example if you send the message getActionCommand() to an ActionEvent you will get back a string with the label on the button.Here is a very simple example of creating a Frame (window) with some buttons and adding some listeners. Most of the commands involving creating the window and laying out the buttons on it. ButtonListener is a class written by the programmer to respond to button clicks on the start and stop buttons on the window.
public class ButtonDemo extends Frame { protected Button startButton, stopButton; public ButtonDemo() { super("Button demo"); // calls Frame constructor // which adds title to window setSize(400,200); // sets the size of the window // sets layout so objects added go from left to right // until fill up row and then go to next row setLayout(new FlowLayout()); // create two new buttons w/labels start and stop startButton = new Button("Start"); stopButton = new Button("Stop"); // set backgrounds of buttons startButton.setBackground(Color.green); stopButton.setBackground(Color.red); add(startButton); // add buttons to frame add(stopButton); // create an object to listen to both buttons: ButtonListener myButtonListener = new ButtonListener(); // tell buttons that myButtonListener should be notified startButton.addActionListener(myButtonListener); stopButton.addActionListener(myButtonListener); } ... }In the example above we have the same listener listening to two separate buttons. As a result the actionPerformed method of ButtonListener will have to send the getActionCommand() to its event parameter in order to figure out which button was pushed. Alternatively the programmer could associate different listeners to each button so they will know without asking which button must have been pressed.Any class implementing the interface ActionListener can be a listener. You can create a specialized class to be a listener, or you can have another class created for another purpose also play the role of a listener. To do so, it need only implement the interface and include the actionPerformed method.
For example, we could make the ButtonDemo class itself be an ActionListener. We need only make the following modifications:
public class ButtonDemo extends Frame implements ActionListener { protected Button startButton, stopButton; public ButtonDemo() { ... same as above // no longer need to create an object to listen to both buttons: // tell buttons that to notify me! startButton.addActionListener(self); stopButton.addActionListener(self); } ... public void actionPerformed(ActionEvent evt){...} }All that was necessary was to add the implementation, use the addActionListener method to add the frame itself as the listener, and include the actionPerformed method.There is another alternative to the two given above, and that is to create an "inner" class to be the action listener.
Inner classes in Java
Java 1.1 now allows the programmer to nest classes. That is, completely enclosed in the definition of one class, you can define another class. Why might you want to create inner classes?The first two reasons are pretty straightforward. Normally an object of one class cannot get access to the instance variables of another, but sometimes you want one object to have exclusive access to the instance variables of another. If that object is only going to be used inside the methods of the outer class, hiding it as an inner class avoids polluting the name space and keeps other objects from having access to the same instance variables.
- An object of an inner class has access to the instance variables of the object of the outer class that created it.
- Inner classes can be hidden from other classes in the same package. E.g., if declared to be private, they can only be accessed from the enclosing class.
- They work well with the new event model.
With the Java 1.1 event model, the programmer will often want separate listener objects for each of the buttons of the window, so it doesn't make sense to have the window itself as the listener (beside strong arguments can be made that the code executed as a response to the event should be separate from the code creating and handling the interface). Thus one would like to create a separate listener class. However, sometimes that listener class needs to access items in the window (e.g., fields which can record the results of handling the events).
One can pass those along as parameters to the constructor for the listeners, so that they can store those objects and be able to send them messages when necessary. However, sometimes it is just more convenient to nest the listener classes inside the frame. This has the added advantage that the listener classes are not publicly visible and can't generate naming conflicts.
Implementation of object-oriented languages
The implementation of objects in object-oriented languages is relatively straightforward. In most OO languages, an object is represented by a pointer to a record which includes slots for all of the instance variables of the object and a pointer to what is called a virtual method table in C++. This table simply consists of pointers to the actual code to be executed. Notice that the virtual method table is exactly the same for every object generated from the same class.Because the layout of instance variables and methods is exactly the same for every object from a class, the class can keep track of the offsets for every instance variable in the class as well as the offsets for methods in the virtual method table. This makes it easy to compile the code in methods to efficiently find instance variables and methods.
Because subclasses may extend the collection of instance variables and/or methods, these new entries are placed after all of the entries for instance variables and methods in the superclass. Notice that if a method gets overridden in a subclass, its offset in the virtual method table remains exactly the same, though the actual code pointed to changes. Thus if an object o has static type C, then performing a message send by using the offsets corresponding to C will be correct even if the actual value held in o was generated by a subclass.
Unfortunately, life is not as easy if the object's static type is given by an interface. Classes whose method offsets differ dramatically may implement the same interface. Thus message sends to object whose type is an interface need an extra lookup in order to find the appropriate offset for the corresponding method.
Semantics and Types for OO languages
The next segment of the course investigates the semantics and type systems of object-oriented languages. Since this involves discussing extensions of the typed lambda calculus, pure html is inadequate to accurately render the lecture notes. As a result these notes will be provided in postscript form. If you computer can display postscript (e.g., using ghostview), you can click on the notes and either read them on-line or print them out.
CS441 | CS Department | Princeton University