Core Java Technologies Tech Tips Tips, Techniques, and Sample Code Welcome to the Core Java Technologies Tech Tips for February 11, 2006. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE). This issue covers: * Using WeakHashMap for Listener Lists * Catching Uncaught Exceptions These tips were developed using Java 2 Platform, Standard Edition Development Kit 5.0 (JDK 5.0). You can download JDK 5.0 at http://java.sun.com/j2se/1.5.0/download.jsp. This issue of the Core Java Technologies Tech Tips is written by John Zukowski, president of JZ Ventures, Inc. (http://www.jzventures.com). You can view this issue of the Tech Tips on the Web at http://java.sun.com/developer/JDCTechTips/2006/tt0211.html See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USING WEAKHASHMAP FOR LISTENER LISTS The May 11, 1999 Tech Tip titled "Reference Objects" (http://java.sun.com/developer/TechTips/1999/tt0511.html#tip2). introduced the concept of reference objects, but didn't go into much depth. In this tip, you'll learn more about this topic. Basically, reference objects provide a way to indirectly reference the memory needed by objects. The reference objects are maintained in a reference queue (class ReferenceQueue), which monitors the reference objects for reachability. Based on the type of reference object, the garbage collector can free memory at times when a normal object reference might not be released. In the Java platform, there are four types of references to objects. Direct references are the type you normally use, as in: Object obj = new Object() You can think of direct references as strong references that require no extra coding to create or access the object. The remaining three types of references are subclasses of the Reference class found in the java.lang.ref package. Soft references are provided by the SoftReference class, weak references by the WeakReference class, and phantom references by PhantomReference. Soft references act like a data cache. When system memory is low, the garbage collector can arbitrarily free an object whose only reference is a soft reference. In other words, if there are no strong references to an object, that object is a candidate for release. The garbage collector is required to release any soft references before throwing an OutOfMemoryException. Weak references are weaker than soft references. If the only references to an object are weak references, the garbage collector can reclaim the memory used by an object at any time. There is no requirement for a low memory situation. Typically, memory used by the object is reclaimed in the next pass of the garbage collector. Phantom references relate to cleanup tasks. They offer a notification immediately before the garbage collector performs the finalization process and frees an object. Consider it a way to do cleanup tasks within an object. As a simple demonstration of using a WeakHashMap, the following WeakTest program creates a WeakHashMap with a single element in it. It then creates a second thread that waits for the map to empty out, requesting that the garbage collector run every 1/2 second. The main thread waits for this second thread to finish. import java.util.*; public class WeakTest { private static Map map; public static void main (String args[]) { map = new WeakHashMap(); map.put(new String("Scott"), "McNealey"); Runnable runner = new Runnable() { public void run() { while (map.containsKey("Scott")) { try { Thread.sleep(500); } catch (InterruptedException ignored) { } System.out.println("Checking for empty"); System.gc(); } } }; Thread t = new Thread(runner); t.start(); System.out.println("Main joining"); try { t.join(); } catch (InterruptedException ignored) { } } } Since there are no strong references to the only key in the map, the entry in the map should be garbage collected at the earliest convenience of the system. Running the program generates the following results: Main joining Checking for empty There are two important things to point out in this example. First, normally a call to System.gc() would not be required. Because the WeakTest program is lightweight and doesn't use much memory an explicit call to the garbage collector is necessary. Second, notice the call to new String("Scott"). You might ask why call new String() when the end result is the same "Scott" string? The answer is that if you don't call new String(), the reference for the map key will be to the system's string constant pool. This never goes away, so the weak reference will never be released. To get around this, new String("Scott") creates a reference to the reference in the string constant pool. The string contents are never duplicated. They stay in the constant pool. This simply creates a separate pointer to the string constant in the pool. A more complicated use of WeakHashMap is to manage a listener list for a Swing component or data model. This would not be a general purpose listener list. Instead it would be a weak listener list. In this case, as long as a strong reference is kept outside the component or data model, that object will continue to notify the listener/observer. This helps garbage collection in the sense that the listener doesn't prevent the source object (which registered the listener) from being garbage collected. By default, listener references are strong references and are not garbage collected when your method returns. You must remember to remove the listener when you finish monitoring the situation. To demonstrate, lets create a WeakListModel that offers a ListModel. The ListModel relies on a WeakHashMap for storing ListDataListener objects. Aside from the imports, the start of the class definition comprises the class and local variable declarations. public class WeakListModel implements ListModel, Serializable { private Map listenerList = Collections.synchronizedMap( new WeakHashMap()); private final Object present = new Object(); private ArrayList delegate = new ArrayList(); The listener list is a WeakHashMap. Its access is synchronized. Although some implementations of listener lists rely on making copies to avoid synchronized access, weak references can be removed at any time. So making a copy provides two places for the weak reference to be dropped with no added value. Because the listener list is maintained in a Map, you need both a key and a value. The value associated with every key in the map is the 'present' object. Theoretically, you only need a WeakHashSet. However, the class is not in the predefined set of collections, so a WeakHashMap is used instead. The last variable, delegate, manages the ListModel contents. Most of the ListModel interface implementation simply passes the request to the delegate to perform the operation, hence the name delegate. The first set of methods for the WeakListModel are related to sizing or querying the model structure. In all cases, these pass the call to the delegate: public int getSize() { return delegate.size(); } public Object getElementAt(int index) { return delegate.get(index); } public void trimToSize() { delegate.trimToSize(); } public void ensureCapacity(int minCapacity) { delegate.ensureCapacity(minCapacity); } public int size() { return delegate.size(); } public boolean isEmpty() { return delegate.isEmpty(); } public Enumeration elements() { return Collections.enumeration(delegate); } public boolean contains(Object elem) { return delegate.contains(elem); } public int indexOf(Object elem) { return delegate.indexOf(elem); } public int lastIndexOf(Object elem) { return delegate.lastIndexOf(elem); } public Object elementAt(int index) { return delegate.get(index); } public Object firstElement() { return delegate.get(0); } public Object lastElement() { return delegate.get(delegate.size()-1); } public String toString() { return delegate.toString(); } The next set of methods have to do with adding and removing elements. In addition to accessing the delegate for the storage operation, the set of listeners need to be notified. These methods call fireXXX() methods that will be shown shortly. These methods do the bulk of the work in the class. public void setElementAt(Object obj, int index) { delegate.set(index, obj); fireContentsChanged(this, index, index); } public void removeElementAt(int index) { delegate.remove(index); fireIntervalRemoved(this, index, index); } public void insertElementAt(Object obj, int index) { delegate.add(index, obj); fireIntervalAdded(this, index, index); } public void addElement(Object obj) { int index = delegate.size(); delegate.add(obj); fireIntervalAdded(this, index, index); } public boolean removeElement(Object obj) { int index = indexOf(obj); boolean rv = delegate.remove(obj); if (index >= 0) { fireIntervalRemoved(this, index, index); } return rv; } public void removeAllElements() { int index1 = delegate.size()-1; delegate.clear(); if (index1 >= 0) { fireIntervalRemoved(this, 0, index1); } } The listener list itself deals with the add and remove listener calls. Again, the present object is not used itself. A "map" just needs a key and value. This is the same pattern used by TreeSet, which is backed by a TreeMap. public synchronized void addListDataListener( ListDataListener l) { listenerList.put(l, present); } public synchronized void removeListDataListener( ListDataListener l) { listenerList.remove(l); } public EventListener[] getListeners(Class listenerType) { Set set = listenerList.keySet(); return set.toArray(new EventListener[0]); } The most interesting set of methods are the fireXXX() methods. For all three of these methods, fireContentsChanged(), fireIntervalAdded(), and fireIntervalRemoved(), a new Set is created with the keys from the WeakHashMap. This is done to ensure that if the set changes in the middle of notification, the original set is still notified. protected synchronized void fireContentsChanged( Object source, int index0, int index1) { ListDataEvent e = null; Set set = new HashSet(listenerList.keySet()); Iterator iter = set.iterator(); while (iter.hasNext()) { if (e == null) { e = new ListDataEvent( source, ListDataEvent.CONTENTS_CHANGED, index0, index1); } ListDataListener ldl = iter.next(); ldl.contentsChanged(e); } } protected synchronized void fireIntervalAdded( Object source, int index0, int index1) { ListDataEvent e = null; Set set = new HashSet(listenerList.keySet()); Iterator iter = set.iterator(); while (iter.hasNext()) { if (e == null) { e = new ListDataEvent( source, ListDataEvent.INTERVAL_ADDED, index0, index1); } ListDataListener ldl = iter.next(); ldl.intervalAdded(e); } } protected synchronized void fireIntervalRemoved( Object source, int index0, int index1) { ListDataEvent e = null; Set set = new HashSet(listenerList.keySet()); Iterator iter = set.iterator(); while (iter.hasNext()) { if (e == null) { e = new ListDataEvent( source, ListDataEvent.INTERVAL_REMOVED, index0, index1); } ListDataListener ldl = iter.next(); ldl.intervalRemoved(e); } } Add the imports and close the braces and you have your class definition: import java.io.Serializable; import java.util.*; import java.lang.ref.*; import javax.swing.*; import javax.swing.event.*; ... } To test the ListModel, the following program, TestListModel, defines a ListDataListener and associates it with the created WeakListModel. Notice that adding and removing the names of Sun's founders as elements to the model notifies the listener. As soon as the strong reference to the listener is gone, the WeakListModel can release the weak listener reference. Further operations on the model then do not notify the listener. import javax.swing.*; import javax.swing.event.*; public class TestListModel { public static void main (String args[]) { ListDataListener ldl = new ListDataListener() { public void intervalAdded(ListDataEvent e) { System.out.println("Added: " + e); } public void intervalRemoved(ListDataEvent e) { System.out.println("Removed: " + e); } public void contentsChanged(ListDataEvent e) { System.out.println("Changed: " + e); } }; WeakListModel model = new WeakListModel(); model.addListDataListener(ldl); model.addElement("Scott McNealy"); model.addElement("Bill Joy"); model.addElement("Andy Bechtolsheim"); model.addElement("Vinod Khosla"); model.removeElement("Scott McNealy"); ldl = null; System.gc(); model.addElement("Scott McNealy"); System.out.println(model); } } Compile and run the TestListModel program. It should generate the following results: > java TestListModel Added: javax.swing.event.ListDataEvent[type=1,index0=0,index1 =0] Added: javax.swing.event.ListDataEvent[type=1,index0=1,index1 =1] Added: javax.swing.event.ListDataEvent[type=1,index0=2,index1 =2] Added: javax.swing.event.ListDataEvent[type=1,index0=3,index1 =3] Removed: javax.swing.event.ListDataEvent[type=2,index0=0,inde x1=0] [Bill Joy, Andy Bechtolsheim, Vinod Khosla, Scott McNealy] See the java.lang.ref package documentation http://java.sun.com/j2se/1.5.0/docs/api/java/lang/ref/package-summary.html for additional information about reference objects, notification, and reachability. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CATCHING UNCAUGHT EXCEPTIONS The March 16, 2004 Tech Tip "Best Practices in Exception Handling" (http://java.sun.com/developer/JDCTechTips/2004/tt0316.html#2 ) described several best practices for handling exceptions. In this tip you'll learn about an additional way of handling exceptions, that is, through the UncaughtExceptionHandler, which was added in J2SE 5.0. As its name implies, the UncaughtExceptionHandler handles uncaught exceptions. More specifically, it handles uncaught runtime exceptions. The java compiler requires all non-runtime exceptions to be handled, otherwise the program won't compile. Here "handled" means that exceptions are declared in the throws clause of the declaring method or caught in the catch clause of a try-catch block. To demonstrate, lets look at two exceptions: FileNotFoundException and ArithmeticException. Calling the constructor for FileReader with either a String or File argument throws a FileNotFoundException if the provided location does not point to a valid regular file. The compiler requires that when you call either of these constructors, you handle the thrown exception: FileReader input; String filename = ...; try { input = new FileReader(filename); } catch (FileNotFoundException e) { processMissingFile(filename, e); } By comparison, an ArithmeticException is a type of runtime exception. The Java programming language specification (and so too the compiler) doesn't require that runtime exceptions be handled. So the following loop, which divides by 100 the numbers from 10-to-0, throws an ArithmeticException on the final pass through the loop: for (int i=10; i >= 0; i--) { int div = 100 / i; } Exception in thread "main" java.lang.ArithmeticException: / by zero at Test.main(Test.java:4) Printing the stack trace is what the default uncaught exception handler does. Typically, this default behavior is sufficient, but sometimes it isn't. Imagine that you want to show the stack trace in a popup window, instead of dumping the trace to the system console. By installing your own default uncaught exception handler, you can get this behavior. There are at least three ways to install an uncaught exception handler. First, you can call the setUncaughtExceptionHandler() method of Thread. This allows you to customize the behavior for a specific thread. Second, you can define your own ThreadGroup and change the behavior of any threads created in the group by overriding their uncaughtException() method. Third, you can set the default behavior for all threads by calling the static setDefaultUncaughtExceptionHandler() method of Thread. Both the setUncaughtExceptionHandler() and setDefaultUncaughtExceptionHandler() methods of Thread accept an implementation of the UncaughtExceptionHandler interface an argument. The interface is an inner interface of the Thread class so its full name is Thread.UncaughtExceptionHandler. This interface has a single method: void uncaughtException(Thread t, Throwable e) You can get the customized behavior by providing an implementation of the uncaughtException method, either as part of your custom implementation of the interface or as an overridden method of ThreadGroup. To demonstrate, here's an implementation of UncaughtExceptionHandler that shows a window with the stack trace appended to end of a text area whenever a runtime exception is encountered. You can close the window between exceptions. The window will reappear in front of other windows when the next exception happens. import java.awt.*; import java.io.*; import javax.swing.*; public class StackWindow extends JFrame implements Thread.UncaughtExceptionHandler { private JTextArea textArea; public StackWindow( String title, final int width, final int height) { super(title); setSize(width, height); textArea = new JTextArea(); JScrollPane pane = new JScrollPane(textArea); textArea.setEditable(false); getContentPane().add(pane); } public void uncaughtException(Thread t, Throwable e) { addStackInfo(e); } public void addStackInfo(final Throwable t) { EventQueue.invokeLater(new Runnable() { public void run() { // Bring window to foreground setVisible(true); toFront(); // Convert stack dump to string StringWriter sw = new StringWriter(); PrintWriter out = new PrintWriter(sw); t.printStackTrace(out); // Add string to end of text area textArea.append(sw.toString()); } }); } } To test the handler, you need a program that installs the handler and then throws some runtime exceptions. The following program, DumpTest, does that. For simplicity, DumpTest only generates two exceptions. Feel free to add more troublesome code to the program, with more exceptions thrown. The program pauses between exceptions to show that you can close the exception dump stack window between exceptions. import java.io.*; public class DumpTest { public static void main(final String args[]) throws Exception { Thread.UncaughtExceptionHandler handler = new StackWindow("Show Exception Stack", 400, 200); Thread.setDefaultUncaughtExceptionHandler(handler); new Thread() { public void run() { System.out.println(1 / 0); } }.start(); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Press Enter for next exception"); br.readLine(); new Thread() { public void run() { System.out.println(args[0]); } }.start(); System.out.print("Press Enter to end"); br.readLine(); System.exit(0); } } Compile StackWindow and DumpTest. When you run DumpTest, you should see the follow in your console: > java DumpTest Press Enter for next exception You'll also see a window with the stack trace for an exception in the text area. Press Enter, and you should see the following in your console: Press Enter to end You should also see another stack trace appended to the text area in the window. Although you might think that this is all there is to handling uncaught exceptions, there is more. Modal dialogs require their own event thread and because of that, their own uncaught handler. The system property sun.awt.exception.handler does cover all cases, but is not well documented. An RFE (request for enhancement) has been submitted to expand the property into a formal API. In addition to the Best Practices tip on exception handling (http://java.sun.com/developer/JDCTechTips/2004/tt0316.html#2), the May 2002 tip on StackTraceElements (http://java.sun.com/developer/JDCTechTips/2002/tt0507.html#tip2) is another useful point of reference. See also the lesson "Handling Errors Using Exceptions" (http://java.sun.com/docs/books/tutorial/essential/exceptions/) in The Java Tutorial. . . . . . . . . . . . . . . . . . . . . . . . IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies: http://www.sun.com/share/text/termsofuse.html http://www.sun.com/privacy/ http://developers.sun.com/dispatcher.jsp?uid=6910008 * FEEDBACK Comments? Please enter your feedback on the Tech Tips at: http://developers.sun.com/contact/feedback.jsp?category=sdn * SUBSCRIBE/UNSUBSCRIBE Subscribe to other Java developer Tech Tips: - Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the Java 2 Platform, Enterprise Edition (J2EE). - Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME). To subscribe to these and other JDC publications: - Go to the Sun Developer Network - Subscriptions page, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), choose the newsletters you want to subscribe to and click "Submit". - To unsubscribe, go to the Subscriptions page, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), uncheck the appropriate checkbox, and click "Submit". - To use our one-click unsubscribe facility, see the link at the end of this email: - ARCHIVES You'll find the Core Java Technologies Tech Tips archives at: http://java.sun.com/developer/TechTips/index.html - COPYRIGHT Copyright 2006 Sun Microsystems, Inc. All rights reserved. 4150 Network Circle, Santa Clara, California 95054 USA. This document is protected by copyright. For more information, see: http://java.sun.com/developer/copyright.html Core Java Technologies Tech Tips February 11, 2006 Trademark Information: http://www.sun.com/suntrademarks/ Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.