Sun Java Solaris Communities My SDN Account Join SDN
 
Article

SwingWorker Update Fixes Subtle Bug

 

SwingWorker Update Fixes Subtle Bug

An updated version of SwingWorker (which we'll call SwingWorker 3) is now available. Here is the source file:

SwingWorker.java
This article is aimed at people who've used earlier versions of SwingWorker. It tells you how to convert to SwingWorker 3 and then discusses the bug that SwingWorker 3 fixes. For more information on SwingWorker, see the following two articles, which have been updated to use SwingWorker 3: Updated coverage is also available in The Java Tutorial, in the section Using the SwingWorker Class.

How to Convert to SwingWorker 3

To convert code that uses previous versions of SwingWorker, you need only one change: After creating a SwingWorker object, invoke the new start() method on it. For example:
SwingWorker worker = new SwingWorker() {
    public Object construct() {
	return "Hello From: " + Thread.currentThread();
    }
};
worker.start();  // Added for SwingWorker 3
System.out.println(worker.get().toString());

The Bug and Its Fix

With previous versions of SwingWorker, instances of SwingWorker subclasses sometimes throw a null pointer exception in their construct method. The error message looks something like this:
java.lang.NullPointerException
        at frame3$1.construct(frame3.java:354)
        at SwingWorker$2.run(SwingWorker.java:102)
        at java.lang.Thread.run(Thread.java:484)
The null pointer exception happens when the worker thread starts before the worker's constructors have finished running. This is a special case of the problem illustrated by the following snippet:
class P {
    Object f;
    void m() {
    }
    P() {
	m();
	f = "Not Null";
    }
}

class C extends P {
    void m() {
	f.toString();
    }
}

Given these two classes, C.new() causes a null pointer exception. That's because the field f isn't initialized by the constructor for class P until after method m is called. This is harmless in the superclass, since method m doesn't do anything, but it fails in the subclass because the m method assumes that f has been initialized.

This example demonstrates why you must think carefully about calling non-private methods from a constructor. Subclasses that override such methods will find their methods being invoked before the superclass's constructor has finished initializing the object.

A variant of this problem can occur when the constructor for an inner class refers to a field in its outer class. Here's an example:

abstract class Worker {
    abstract Object construct();
    Worker() {
	construct();
    }
}

public class Foo {
    Object f;
    class MyWorker extends Worker {
	Object construct() {
	    return Foo.this.f;
	}
    }
    Foo() {
	new MyWorker();
    }
}

Once again we've created a superclass, Worker, whose constructor calls a non-private method, construct, that will be overridden in a subclass. In this case that subclass is the inner class Foo.MyWorker. The construct method in MyWorker seems to harmlessly refer to the f field of its outer class Foo. Note that we've used the fully qualified reference to f just to help make the point; typically you wouldn't use the "outerclass.this." prefix.

Constructing an instance of the MyWorker class causes a null pointer exception because (here's the tricky part) the synthetic Foo.this field in MyWorker isn't initialized until after the superclass's constructor returns. So calling Foo.new() causes a null pointer exception. Although we didn't include a constructor in MyWorker, there's an implicit one that calls super (that's the Worker constructor) and then initializes the synthetic Foo.this field. The construct method refers to Foo.this.f before Foo.this has been initialized, and that produces a rather obscure null pointer exception.

This is precisely what can happen with SwingWorker. The only difference is that in the case of SwingWorker the construct method runs on a separate thread, which means that often (but not, unfortunately, always) it's called after the SwingWorker constructors have finished running.

The fix for the SwingWorker bug is simple: Don't start the worker thread in the SwingWorker constructor. This fix prevents the construct method from running before the SwingWorker constructor finishes. SwingWorker 3 contains this fix and a new method, start, that you must invoke to start the worker thread.

Oracle is reviewing the Sun product roadmap and will provide guidance to customers in accordance with Oracle's standard product communication policies. Any resulting features and timing of release of such features as determined by Oracle's review of roadmaps, are at the sole discretion of Oracle. All product roadmap information, whether communicated by Sun Microsystems or by Oracle, does not represent a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. It is intended for information purposes only, and may not be incorporated into any contract.