Michael Bolin's 6.170 Recitation

www.bolinfest.com 
This web site is for students in the recitation that I am teaching for 6.170 Fall 2004. The recitation meets Thursday afternoons from 2-3pm in 34-302 and my office hours are after recitation from 3-5pm in 32-044A.
Recitation Notes
9/16 Recitation 2
9/23 Recitation 3
10/7 Recitation 5
10/14 Recitation 6
10/28 Recitation 8
Recommended Readings
Athena Pocket Reference
The Java IAQ
Programming by Coincidence
Writing a Code Generator
in Java
The Story of Mel
A Comparative Overview of C#
Beating the Averages
J2SE 5.0 New Features

10/28 Recitation 8 - Tying Up Loose Ends

  1. The checkRep() Paradox
  2. The Comparator Contract
  3. Anonymous Classes
  4. MouseAdapter

1. The checkRep() Paradox

Earlier in the semester, we told you when to call checkRep(), but there is a subtle problem with our rules which I like to call The checkRep() Paradox. The Paradox occurs when you have a private method that may be invoked while the RI is broken, but the private method needs to call an innocuous observer method during its execution. For example, suppose we tried to use a checkRep() method in the implementation of Map as follows:
public class ExampleMap implements java.util.Map {

  /*
   * RI =
   *  (1) size is equal to the total number of Entry
   *   objects in, or linked to by members of, the buckets array
   *  (2) size >= (0.25 * buckets.length)
   *  (3) size <= (0.75 * buckets.length)
   */

  private int size;

  private Entry[] buckets;

  private static final boolean CHECKREP_ENABLED = true;

  private void checkRep() {
    if (!CHECKREP_ENABLED) return;
    if (size < (buckets.length / 4)) {
      throw new RuntimeException("number of buckets " + buckets.length +
        " is too large for number of entries: " + size);
    }
    // checks for other conditions of RI
  }

  public ExampleMap() {
    size = 0;
    buckets = new Entry[8];
    checkRep();
  }

  public int size() {
    checkRep();
    return size;
  }

  public Object remove(Object key) {
    checkRep();
    try {
      if (!containsKey(key)) return null;
      Entry oldEntry = removeEntryForKey(key);
      return oldEntry.getValue();
    } finally {
      checkRep();
    }
  }

  private Entry removeEntryForKey(Object key) {
    // removes entry from buckets and returns it
    // side-effect: updates size field if Entry removed
    // calls checkResize() before returning
  }

  private void checkResize() {
    if (size() < (buckets.length / 4)) {
      rehash(buckets.length / 2);
    } else if (size() > (3 * buckets.length / 4)) {
      rehash(buckets.length * 2);
    }
  }

  private void rehash(int newSize) {
    // resizes the number of buckets
    // and reassigns each Entry in the old buckets array
    // to its new bucket
  }

  // other public methods required to implement Map
}
At first, this looks reasonable, but what would happen when we did the following:
Map map = new ExampleMap();
map.put("1", "Mississippi");
map.put("2", "Mississippi");
map.remove("2");
It turns out that we would get a RuntimeException because the RI was violated by removeEntryForKey() before it called checkResize() which invokes size(). Since size() is called while the RI is violated, it will throw a RuntimeException. Obviously this is pretty frustrating because you're aware that there may be a problem with size() – that's why you're calling checkResize() in the first place! So what are the possible solutions?
  1. Temporarily Disable checkRep()

    One option would be to rewrite checkResize() as follows:

      private void checkResize() {
        boolean oldCheckRepValue = CHECKREP_ENABLED;
        try {
          CHECKREP_ENABLED = false;
          if (size() < (buckets.length / 4)) {
            rehash(buckets.length / 2);
          } else if (size() > (3 * buckets.length / 4)) {
            rehash(buckets.length * 2);
          }
        } finally {
          CHECKREP_ENABLED = oldCheckRepValue;
        }
      }
    
    This is verbose and inelegant. Furthermore, if the contents of your try block invoke other methods where it is important that checkRep() is enabled, then you may allow subtle bugs to creep into your code. This is dangerous and is not a good choice.
  2. Use the size Field Directly

    Another option is to refer to size instead of invoking size() to avoid the side effect of invoking checkRep(). This is somewhat undesirable because it eliminates the abstraction that the size() method provides. For example, size() does something simple right now, but if it is changed to do something complicated later, then referencing size directly may miss those effects, which may be important. Furthermore, it is better to use size() to access the field in case someone extends ExampleMap and overrides the size() method to do something important every time the size field is accurate. For something as simple as size, this may not seem so critical, but it is possible to create other scenarios in which this may be a problem.

  3. Create a Private Method to Access size() The best alternative seems to be to create a private accessor method for size that is used by size():
    public int size() {
      checkRep();
      return _size();
    }
    
    private int _size() {
      return size;
    }
    
    private void checkResize() {
      if (_size() < (buckets.length / 4)) {
        rehash(buckets.length / 2);
      } else if (_size() > (3 * buckets.length / 4)) {
        rehash(buckets.length * 2);
      }
    }
    
    This doesn't help those clients who override ExampleMap.size(), but it does allow all accesses to size to be updated in one place within ExampleMap. This is certainly cleaner than toggling the CHECKREP_ENABLED field, and it eliminates duplicate code. This is likely the best solution to the Paradox.
Hopefully you will not encounter this problem, but when you do, it is a puzzling one, so hopefully this solution will help you out.

2. The Comparator Contract

3. Anonymous Classes

Below is the code for a panel called SurprisePanel that will display a message to the user when she clicks on it:
public class SurpriseComponent extends JComponent {

  private String message;

  SurpriseComponent(final String secretMessage) {
    addMouseListener(new MouseListener() {
      public void mouseClicked(MouseEvent e) {
	JOptionPane.showMessageDialog(getParent(), secretMessage);
      }
      public void mousePressed(MouseEvent e) {}
      public void mouseReleased(MouseEvent e) {}
      public void mouseEntered(MouseEvent e) {}
      public void mouseExited(MouseEvent e) {}
    });
  }

}
The code shown in boldface constitutes what is called an anonymous class. It creates an object that implements the MouseListener interface without having a named, concrete class that explicitly implements the interface. Since interfaces are not classes, it may seem odd that we can write new MouseListener(), but this turns out to be a convenient construct:

1. It eliminates the need to create arbitrary classes.
Suppose Java did not have anonymous classes. In that case, every time we wanted to add a MouseListener to a GUI widget, we would have to create a new class that implements the MouseListener interface.

2. It minimizes the scope of local variables (as Bloch advocates).
If there were no anonymous classes, and small helper classes had to be created to implement listener interfaces all over your code, then it would be possible to instantiate those helper classes from parts of your code that have no business instantiating that helper class. Using an anonymous class limits the scope of the class, making it impossible to instantiate outside the block in which it is created. This also makes your code easier to maintain because it is one less private class whose API he needs to be kept up to date.

Note that the parameter to the SurpriseComponent is declared final (the code would not compile otherwise). Normally, the secretMessage parameter would fall out of scope once the end of the block is reached; however, the anonymous MouseListener still exists when the end of the block is reached (because SurpriseComponent has a reference to it), so a reference to secretMessage needs to be persisted in some way. One way to resolve this problem is to declare the parameter final.

Alternatively, secretMessage could be assigned to the private field message and then message could be used as the parameter to showMessageDialog. Though this does not involve the use of the final keyword, the reference to message is persisted because it is a field of SurprisePanel. Another option would be to create a final local variable:

  SurpriseComponent(String secretMessage) {
    final String m = secretMessage;
    addMouseListener(new MouseListener() {
      public void mouseClicked(MouseEvent e) {
	JOptionPane.showMessageDialog(getParent(), m);
      }
      ...
Doing something like this may be necessary if the anonymous class needs to make a reference to an object that is created inside the method rather than to an object that is passed as a parameter to the method.

4. MouseAdapter

The code above could be simplified greatly by the use of the MouseAdapter class:
public class SurpriseComponent extends JComponent {

  SurpriseComponent(final String secretMessage) {
    addMouseListener(new MouseAdapter() {
      public void mouseClicked(MouseEvent e) {
	JOptionPane.showMessageDialog(getParent(), secretMessage);
      }
    });
  }

}
MouseAdapter trivially implements the MouseListener interface; that is, it implements MouseListener by creating an empty method body for each method in MouseListener. Thus, you can create a MouseListener by first extending MouseAdapter, and then overriding only the methods of MouseListener that interest you. As you can see in the example above, this saves us the trouble of listing a number of methods that do not get used.

This trick is used often in the java.awt.event package: KeyAdapter, MouseAdapter, MouseMotionAdapter, and WindowAdapter are just a few examples of classes with trivial interface implementations. Each adapter has an analogous listener interface in the same package that it implements.

Ironically, this is not a true use of the Adapter design pattern, as these adapters do not convert the interface of one class to that of another.



©2004 Michael Bolinwww.bolinfest.com