How to facilitate Netbeans Designer to load JPanel

2019-08-01 01:36发布

问题:

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?

回答1:

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:

    private final static  Map<String,Status>  displayMap = new HashMap( 4 );

Has NOT yet been executed, and displayMap is null. Also, a static { } 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

    public enum Status
    {
        INVALID     ( "INVALID"  ),
        ISSUED      ( "Issued"   ),
        CANCELLED   ( "Cancelled");

        private static    Map<String,Status>   displayMap;

        private Status( String display  ){
            this.displayName = display;
            mapDisplayName( this.displayName, this );
        }
          :

        private static void mapDisplayName( final String displayName, final Status state ){
            if( null ==  displayMap  ){
                displayMap = new HashMap( 7 );
            }
            displayMap.put( displayName,  state );
        }
    }

And then it all runs quite smoothly.

Caveat:

Do NOT assign null in the displayMap declaration -- That was counter productive:

  • The if( null == displayMap ){...} block successfully assigns the HashMap during the first call to the Enum constructor.
  • After all the enum values declarations are processed.
  • Init will call any initialises for declared variables.
  • If displayMap = null; is declared it replaces the populated HashMap, with 3 x values, with a new empty HashMap. grrr

Possibly related question:

  • Reverse lookup for Java Enums with more than one value per key/constant? ... I felt that a HashMap would deliver the reverse-lookup of multiple strings a lot more easily.