This has become a Dorothy Dix as I found the root of the matter while I was composing this question. Nevertheless, I decided to post it because it had everyone in our office stumped and I could find nothing helpful in the google or stackoverflow searches. This is a good example of once you ask the right question, you may see the light.
While the title might sounds complicated, it is a really simple problem that seemed to have no answer. At the centre of this is an enum
:
Status
public enum Status
{
INVALID ( "INVALID" ),
ISSUED ( "Issued" ),
CANCELLED ( "Cancelled");
private final String displayName;
private final static Map<String,Status> displayMap = new HashMap( 4 );
private Status( String display ){
this.displayName = display;
mapDisplayName( this.displayName, this );
}
public String getDisplayName(){
return displayName;
}
public static Status parseString( String statusStr ) {
return displayMap.get( statusStr );
}
private static void mapDisplayName( final String displayName, final Status state ){
displayMap.put( displayName, state );
}
}
The idea of course is to use the displayMap
as a reverse-lookup. Nothing to do with the getDisplayName()
method at all.
This enum's getDisplayName()
call is used in a sub-panel to initialise a static array used with a combobox, like:
public class JPanelStatus extends javax.swing.JPanel {
private final String[] STATUS_LABELS = {
Status.ISSUED .getDisplayName(),
Status.CANCELLED .getDisplayName()
};
public JPanelStatus(){
initComponents();
:
jComboBoxStatus.setModel( new DefaultComboBoxModel<>( STATUS_LABELS ) );
:
}
:
}
Which is referenced in the main JPanel. When I view this JPanelStatus
sub-panel in the Netbeans Designer, it works fine. As does the [Preview Design
] function.
However when I load the main form, it fails and the exception show an initialisation failure:
java.lang.NoClassDefFoundError: Could not initialize class au.com.project.State
at au.com.project.client.JPanelStatus.<init>(JPanelStatus.java:35)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
... ... ...
The Netbeans IDE log added the following extra information :-p
INFO: Could not initialize class au.com.project.State
Through a process of elimination -- Commenting-out unrelated code -- I discovered the form will load once I comment-out the HashMap put()
call in the State enum.
"That is interesting.", I say. It looked like a side-effect from the put()
. And in away it is -- A small Spock which quickly gave me the same error from the command line without the JPanel and without Netbeans.
The error is caused by me, trying to use a HashMap from within the Enum constructor. It won't work as written.
So I changed the title to hit at the true problem -- Which is actually, how to use a HashMap to do a reverse-lookup for an enum?
The problem comes from how the HashMap is declared within the
Status
enum due to HOW enums are initialised.The first thing in a Java Enum must be the list of values, here: "INVALID", "ISSUED", and "CANCELLED". The next thing everyone needs to know is that there is a secret Java stage that runs first during Object creation (Class or Enum). Init is dumb, is runs linearly through the declarative code first-come, first-served.
The first 3 x statements of an enum call the constructor -- That means the statement:
Has NOT yet been executed, and
displayMap
is null. Also, astatic { }
block is executed in that same 1-2-3-... sequence and does not work either.Unfortunately none of the Netbeans/Designer stack-trace or IDE log reported a NullPointerException -- The unit test does. Once you have a NPE, it focuses the mind.
displayMap
is uninitialised when the first constructor call is made.Solution: The displayMap cannot be
static final
, because you may not initialise static members in a constructor. It must be initialised on the first call, using some variation of the example shown:Status
And then it all runs quite smoothly.
Caveat:
Do NOT assign
null
in the displayMap declaration -- That was counter productive:if( null == displayMap ){...}
block successfully assigns the HashMap during the first call to the Enum constructor.displayMap = null;
is declared it replaces the populated HashMap, with 3 x values, with a new empty HashMap. grrrPossibly related question: