Notes on Tim Budd Book, Chapters 5-7

CSE 341

These notes cover some additional material from Chapters 5-7 of the text Understanding Object-Oriented Programming with Java, beyond what is in the slides.

Chapter 5: Ball Worlds

Java AWT and graphics model: the BallWorld class is a subclass of Frame (provided in the AWT library). We override the inherited paint method. The text uses a hack, in which the animation is provided by this paint method.

The paint method takes an argument g of type Graphics. (We saw Graphics already in the applet example in the slides.) We can invoke methods such as setColor and fillOval on g to achieve the desired graphical effects. A Graphics object encapsulates state information needed for the basic rendering operations. It includes:

If you will be doing any graphics in your project you should browse through the online documentation for Graphics to see what is available. Design decisions in Java's AWT: package up all the graphics output parameters (color, font family, size, etc) into a Graphics object, rather than providing a large number of parameters to various drawing methods. Another design decision: to draw a circle, we invoke the fillOval of Graphics -- an alternative would be to have a draw method for a Circle class.

Chapter 6: Cannon Game

This chapter develops a simple cannon game. In the first version, there is a command line interface that fires a single cannon ball; then a version with a slider that controls the angle and a button to fire a cannon ball.

Frame: a top-level window with a title and a border. Examples of use: CannonGame and CannonWorld in Chapter 6, as well as BallWorld, MultiBallWorld in Chapter 5. These are all subclasses of Frame.

Note use of static (class) messages in the following:

Input model: a stream of events. Most modern window managers, including Java's, use an event model. Classes: EventObject and various subclasses, e.g. MouseEvent. Since each event is an object, we can ask it things such as the timestamp of when the event occurred, the x and y positions of the mouse when the event occurred, which buttons were pressed at that time, etc.

Historical comparison: mouse handling on the Xerox Alto. There were methods available to the programmer to query the current state of the mouse (position, buttons up or down). Problems: need to do polling, which makes it more difficult to write systems that support multiple concurrent activities; mouse button state and mouse position sampled at slightly different times.

Java also provides higher-level events, such as ActionEvent (which extends AWTEvent).

From the online documentation for ActionEvent:

In Java, we add various input widgets to the frame -- for example, a scrollbar and a "fire" button in CannonWorld. A layout manager positions these within the frame. The Java runtime takes care of distributing the events to the appropriate widget.

However, we need to supply code that is to be run when the button is pushed or the scrollbar is adjusted. This is done by supplying an implementation of ActionListener or AdjustmentListener respectively. These listeners in the CannonWorld are activated only when the user presses a button or finishes moving a slider. There are also listeners (for example MouseMotionListener) that support actions such as dragging.

Another point of interest: the use of inner classes for FireButtonListener and ScrollBarListener. The inner class is lexically scoped inside the enclosing class -- so the name FireButtonListener isn't known outside CannonWorld. The code in the inner class can access the fields defined by the enclosing class. Note that these fields of the enclosing class will always be meaningful, since we can't access the inner class to instantiate it without going through an instance of the enclosing class.

This is analogous to lexical scoping in Scheme and other languages, albeit with classes and objects.

Chapter 7: Pinball Game Construction Kit

The pinball game allows several balls to be moving at once. These are dynamically created while playing the game, so we don't want to use a fixed size array. The book uses the class Vector from the java.util package. This class includes methods to add, remove, and access objects in the vector. The vector will expand dynamically if necessary.

The declaration for the elementAt method in Vector says it just returns an Object -- so we have to put in a runtime cast to tell Java that the elements are balls:

    for (int i = 0; i < balls.size(); i++) {
      Ball aBall = (Ball) balls.elementAt(i);
      aBall.paint(g);
      }
This is an annoying weakness of the current Java design; there are a bunch of competing proposals for fixing it, so that we could for example declare balls to be of type Vector(Ball).

The collection classes (which include Vector) are in java.util.*. (These have been substantially improved in JDK 1.2; the edition of the book we're using includes these.)

There are (arguably) better ways to iterate through the elements in a collection than with a for loop as is done in the text -- I'd use an iterator.

Example:

import java.util.*;


    Iterator i = balls.iterator(); 
    while (i.hasNext()) {
      Ball aBall = (Ball) i.next();
      aBall.paint(g);
    }

Some collection interfaces of interest: Collection, Comparator, Map, Iterator. Some collection classes of interest: ArrayList, HashSet, HashMap, TreeMap. These all support iterators, either directly or indirectly. Examples:

   ArrayList al;
   HashSet hs;
   HashMap hm;
   TreeMap tm;
   Iterator i;
   ...
   i = al.iterator();   // iterates through elements of the array list
   i = hs.iterator();   // iterates through elements of the array list

   i = hm.keySet().iterator();  // iterate through the keys
   i = hm.values().iterator();  // iterate through the values

   i = tm.keySet().iterator(); 
   i = tm.values().iterator(); 

Thus the iterator interface encapsulates the behavior needed to iterate through the elements of collections of all kinds, providing a common interface and allowing one kind of collection to be substituted for another.

Also except when you need synchronization because of multiple threads, I suggest using ArrayList instead of Vector. (But Vector is probably a better choice still for the pinball game.)

MouseListener: an interface for listening for mouse events. Events: mouseClicked, mouseEntered, mouseExited, mousePressed, mouseReleased. Since MouseListener is an interface, not a class, any class that implements this interface must provide implementations for each of these methods. The class MouseKeeper is a class that implements MouseListener, with no-op methods for each event -- you can then just override the ones you need to.

Notice how nicely the object-oriented paradigm lets us specify the desired behavior for different events.

In case you are wondering what the difference is between mouseClicked and mousePressed, a mousePressed event is generated when the user first presses the mouse button. A mouseReleased event is generated when the user releases the button, and also a mouseClicked event (so clicking is the combination press/release). (See the program ~borning/java/MouseTest.java on tahiti if you want to poke around with this too.)

In the BallWorld examples, the paint method included code to do some simple animation, which was not a very clean way to accomplish this. The pinball game handles this correctly, using multiple threads of control. There is one thread that handles events. Each ball gets its own animation thread. The animation threads include a call sleep(10) after each move. This suspends the animation thread for at least 10 milliseconds. (I say "at least" since other thread scheduling, or the granularity and accuracy of the system clock, might interfere.)

This use of threads accomplishes two things: first, it cleans up the code, moving the animation part out of the paint method; and second, it makes the animation rate more independent of the speed of the machine running the Java program.

Threads can have priorities, and higher priority threads will pre-empt lower priority ones. Generally, the continuously running part of your application should run at a lower priority than the thread dealing with rarer events such as user input. This allows good response to user inputs. (This isn't a problem in the pinball game because the sleep command allows the event thread to get enough cycles; but better style would have been to make the animation threads have lower priority.)

The addScore method includes yet another modifier: synchronized. This ensures that two different ball threads aren't both executing the method at once and interfering with each other.

The pinball game allows multiple kinds of targets. In good object-oriented style, we define an interface PinBallTarget that defines methods required of targets of all kinds, and then define a series of classes that implement this interface. Each of these classes (for example Spring) can of course have multiple instances.

Design decision: how should targets and balls interface? Should there be a hitBy method for targets, or a hits method for balls? Or should there be a separate Collision class? Choice in book: a hitBy method for targets. This is better than a hits method for balls, since there are different kinds of targets but only one kind of ball, so that the functionality can be distributed to the different implementations of hitBy.

What if there were different kinds of balls as well? If the behavior can be divided neatly between the targets and the balls, this still works out well. For example, suppose we have ordinary balls and exploding balls. Then the hitBy method in targets could (as part of the code) send the message hit to the ball, which could then either do nothing or explode.

It's possible to get carried away with this decomposition, and end up with code that is hard to understand because the functionality is scattered in many methods in many different classes. This problem becomes particularly acute when it interacts with inheritance, and we bounce up and down the inheritance hierarchy a lot (the so-called yo-yo problem).

Suppose the behavior can't be divided neatly -- that essentially we want to do something different for each possible combination of class of target and ball. Then the code will get uglier. There are other object-oriented languages that use multiple dispatch, in which the method is selected based on the type of all arguments. In such a language we could define different methods

  collide(t: Wall, b: ExplodingBall)
  collide(t: Wall, b: Ball)
  collide(t: Spring, b: ExplodingBall)
  collide(t: Spring, b: Ball)
  ... etc
This comes at a cost of more language complexity and the loss of the intuition behind objects that receive messages. Examples of such languages are CLOS (Common Lisp Object System) and Cecil.