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.
|