Enum dictionary issue: will this work?

B

Ben Phillips

Here's the code. The enum constants should get added to a private map
with a public, unmodifiable view as they are created. It definitely
won't work if the map put is right in the constructor. Will this sort of
thing work as written, with a static method called that initializes the
map if it's null? Or will the map just get clobbered back to null after
the enum constants are all constructed? And if not, will the
unmodifiable view be constructed correctly?

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

package thing;

public enum Thing () {

FOO ("foo"),

BAR ("bar");

private String name;

private static Map<String,Thing> thingMap;

public static final Map<String,Thing> things =
Collections.unmodifiableMap(thingMap);

Thing (String name) {
this.name = name;
put(name, this);
}

private static void put (String name, Thing thing) {
if (thingMap == null) {
thingMap = new HashMap<String,Thing>();
}
thingMap.put(name, thing);
}
}
 
J

John B. Matthews

Ben Phillips said:
Here's the code. The enum constants should get added to a private map
with a public, unmodifiable view as they are created. It definitely
won't work if the map put is right in the constructor. Will this sort of
thing work as written, with a static method called that initializes the
map if it's null? Or will the map just get clobbered back to null after
the enum constants are all constructed? And if not, will the
unmodifiable view be constructed correctly?

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

package thing;

public enum Thing () {

FOO ("foo"),

BAR ("bar");

private String name;

private static Map<String,Thing> thingMap;

public static final Map<String,Thing> things =
Collections.unmodifiableMap(thingMap);

Thing (String name) {
this.name = name;
put(name, this);
}

private static void put (String name, Thing thing) {
if (thingMap == null) {
thingMap = new HashMap<String,Thing>();
}
thingMap.put(name, thing);
}
}

I don't see why not. I did something similar, but I kept the Map private
and exposed a static lookup() method. I also used isEmpty() as a cue to
rehash and an EnumSet to optimize the map size:

<http://robotchase.svn.sourceforge.net/viewvc/robotchase/trunk/src/org/gc
s/robot/Key.java?revision=27&view=markup>
 
R

Roland de Ruiter

Here's the code. The enum constants should get added to a private map
with a public, unmodifiable view as they are created. It definitely
won't work if the map put is right in the constructor. Will this sort of
thing work as written, with a static method called that initializes the
map if it's null? Or will the map just get clobbered back to null after
the enum constants are all constructed? And if not, will the
unmodifiable view be constructed correctly?

Interesting case. I've rewritten your example so it is compilable:

<SSCCE>
import java.util.*;

public enum Thing {
FOO("foo"),
BAR("bar");

private static Map<String, Thing> thingMap;

public static final Map<String, Thing> things = Collections
.unmodifiableMap(thingMap);

private String name;

private Thing(String name) {
this.name = name;
put(name, this);
}

private static void put(String name, Thing thing) {
if (thingMap == null) {
thingMap = new HashMap<String, Thing>();
}
thingMap.put(name, thing);
}

public static void main(String[] args) {
System.out.println(things);
}
}
</SSCCE>

It will work "correctly" only because the static "thingMap" field does
not have an initializing expression.

When the enum class Thing loads and gets initialized, the following will
happen and in this order:

Note, that all static fields are null upon class initialization.

1) the enum constant fields are initialized

1a) the enum constant FOO is created: the constructor gets called and as
a side effect also assigns a new map object to the static thingMap
field. This newly created enum object gets stored in the thingMap.

1b) the enum constant BAR is created: constructor is called and this
enum object also gets stored in the thingMap.

2) the static fields are initialized.

2a) Since there isn't an initializing expression for the static
"thingMap" field, the thingMap field will still refer to the map
instance which was created in step 1a.

2b) The static "things" field is assigned with the result of its
initializing expression
Collections.unmodifiableMap(thingMap)
And because the thingMap field still refers to the map object created in
step 1a, the "things" field refers to an unmodifiable view on that object.



It will go wrong, if the static "thingMap" field would have an
initializing expression, e.g.
(A) private static Map<String, Thing> thingMap = null;
or
(B) private static Map<String, Thing> thingMap = new HashMap<String,
Thing>();

Then step 2a would execute the initializing expression, assigning an new
object to "thingMap", and discarding the map created in step 1a.
Step 2b then would throw a NullPointer exception in case of
initializing expression (A). In case (B), the "things" field would refer
to an (unmodifiable) empty map.
 
R

Roland de Ruiter

Here's the code. The enum constants should get added to a private map
with a public, unmodifiable view as they are created. It definitely
won't work if the map put is right in the constructor. Will this sort
of thing work as written, with a static method called that initializes
the map if it's null? Or will the map just get clobbered back to null
after the enum constants are all constructed? And if not, will the
unmodifiable view be constructed correctly?
[...]
When the enum class Thing loads and gets initialized, the following will
happen and in this order:

[...]
Order according to the Java Language Specification, see
<http://java.sun.com/docs/books/jls/third_edition/html/execution.html#12.4>


Personally I would have written it differently, e.g. like the class below.

Since the thingMap is only an auxiliary object, I don't think it
shouldn't be a field of the enum class. Using the values() method, the
"things" map can easily be created after all enum constants have been
initialized.

import java.util.*

public enum Thing {
FOO("foo"),
BAR("bar");

public static final Map<String, Thing> things = createThings();

private String name;

private Thing(String name) {
this.name = name;
}

private static Map<String, Thing> createThings() {
Map<String, Thing> thingMap = new HashMap<String, Thing>();
for (Thing thing : values()) {
thingMap.put(thing.name, thing);
}
return Collections.unmodifiableMap(thingMap);
}

public static void main(String[] args) {
System.out.println(things);
}
}
 
S

softwarepearls_com

Here's the code. The enum constants should get added to a private map
with a public, unmodifiable view as they are created. It definitely
won't work if the map put is right in the constructor. Will this sort of
thing work as written, with a static method called that initializes the
map if it's null? Or will the map just get clobbered back to null after
the enum constants are all constructed? And if not, will the
unmodifiable view be constructed correctly?

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

package thing;

public enum Thing () {

     FOO ("foo"),

     BAR ("bar");

     private String name;

     private static Map<String,Thing> thingMap;

     public static final Map<String,Thing> things =
             Collections.unmodifiableMap(thingMap);

     Thing (String name) {
         this.name = name;
         put(name, this);
     }

     private static void put (String name, Thing thing) {
         if (thingMap == null) {
             thingMap = new HashMap<String,Thing>();
         }
         thingMap.put(name, thing);
     }

}

I've been using more and more static init blocks in enums to do stuff
like that. Typically I would have enum constants that require too many
parameter combinations to provide a concise set of overloaded
constructors. In that case, I would provide just a few constructors,
and provide plenty of private setters allowing a static init block to
"complete the initialization" of my constants.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top