The following class demonstrates which
Map
implementations and which nested Map
classes are Serializable
and which are not for several popular standard Map
implementations.package dustin.examples; import java.io.Serializable; import java.util.Collection; import java.util.EnumMap; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import static java.lang.System.out; /** * This class demonstrates that the nested classes for various types of Java * maps are not Serializable. */ public class NonSerializableCollectionsInnerClassesDemonstrator { final String NEW_LINE = System.getProperty("line.separator"); /** Enum describing types of Maps used in this demonstration. */ private enum MapTypeEnum { CONCURRENT_HASH { public Map<Long, String> newSingleEntryMapInstance() { final Map<Long, String> map = new ConcurrentHashMap<Long, String>(); map.put(1L, "One"); return map; } }, HASH { public Map<Long, String> newSingleEntryMapInstance() { final Map<Long, String> map = new HashMap<Long, String>(); map.put(2L, "Two"); return map; } }, HASH_TABLE { public Map<Long, String> newSingleEntryMapInstance() { final Map<Long, String> map = new Hashtable<Long, String>(); map.put(3L, "Three"); return map; } }, LINKED_HASH { public Map<Long, String> newSingleEntryMapInstance() { final Map<Long, String> map = new LinkedHashMap<Long, String>(); map.put(4L, "Four"); return map; } }, TREE { public Map<Long, String> newSingleEntryMapInstance() { final Map<Long, String> map = new TreeMap<Long, String>(); map.put(5L, "Five"); return map; } }, WEAK_HASH { public Map<Long, String> newSingleEntryMapInstance() { final Map<Long, String> map = new WeakHashMap<Long, String>(); map.put(6L, "Six"); return map; } }; public abstract Map<Long, String> newSingleEntryMapInstance(); } /** * Indicate where the provided class defines a class that implements the * java.io.Serializable interface. * * @param candidateClass Class whose serializable status is desired. * @return {@code true} if the provided class is Serializable. */ public static boolean isSerializable(final Object candidateClass) { return candidateClass instanceof Serializable; } /** * Print (to stdout) a simple message describing if the provided object * is serializable or not. * * @param object Object whose class's status as Serializable or not is to be * printed to stdout. */ public static void printSerializableStatus(final Object object) { final Class clazz = object.getClass(); out.println( clazz.getName() + " is " + (isSerializable(object) ? "" : "NOT ") + "Serializable."); } /** * Process a provided Map instance to indicate if the Map itself and its * nested classes are Serializable. * * @param header Header to be printed before Serializable results are printed. * @param map Map to be evaluated for Serializability or it and its nested classes. */ private void processSpecificMapInstance( final String header, final Map<Long, String> map) { out.println(NEW_LINE + "===== " + header + " ====="); printSerializableStatus(map); final Set<Long> mapKeySet = map.keySet(); printSerializableStatus(mapKeySet); final Iterator<Long> mapKeySetIterator = mapKeySet.iterator(); printSerializableStatus(mapKeySetIterator); final Collection<String> mapValues = map.values(); printSerializableStatus(mapValues); for (final Map.Entry<Long, String> mapEntrySet : map.entrySet()) { printSerializableStatus(mapEntrySet); } } /** * Method for explicitly creating EnumMap to check it and its nested classes * for Serializability. */ private void processAndDemonstrateEnumMap() { out.println(NEW_LINE + "===== ENUMMAP ====="); final Map<MapTypeEnum, String> map = new EnumMap(MapTypeEnum.class); map.put(MapTypeEnum.HASH, "HashMap"); printSerializableStatus(map); final Set<MapTypeEnum> mapKeySet = map.keySet(); printSerializableStatus(mapKeySet); final Iterator<MapTypeEnum> mapKeySetIterator = mapKeySet.iterator(); printSerializableStatus(mapKeySetIterator); final Collection<String> mapValues = map.values(); printSerializableStatus(mapValues); for (final Map.Entry<MapTypeEnum, String> mapEntrySet : map.entrySet()) { printSerializableStatus(mapEntrySet); } } /** * Demonstrate Serializability status of different Map implementations and * the Serializability of those Map instance's nested classes. */ private void demonstrateMapNestedClassesSerializability() { final Map<Long, String> hashMap = MapTypeEnum.HASH.newSingleEntryMapInstance(); processSpecificMapInstance("HASHMAP", hashMap); final Map<Long, String> linkedHashMap = MapTypeEnum.LINKED_HASH.newSingleEntryMapInstance(); processSpecificMapInstance("LINKEDHASHMAP", linkedHashMap); final Map<Long, String> concurrentHashMap = MapTypeEnum.CONCURRENT_HASH.newSingleEntryMapInstance(); processSpecificMapInstance("CONCURRENTHASHMAP", concurrentHashMap); final Map<Long, String> weakHashMap = MapTypeEnum.WEAK_HASH.newSingleEntryMapInstance(); processSpecificMapInstance("WEAKHASHMAP", weakHashMap); final Map<Long, String> treeMap = MapTypeEnum.TREE.newSingleEntryMapInstance(); processSpecificMapInstance("TREEMAP", treeMap); final Map<Long, String> hashTable = MapTypeEnum.HASH_TABLE.newSingleEntryMapInstance(); processSpecificMapInstance("HASHTABLE", hashTable); processAndDemonstrateEnumMap(); } /** * Main executable function to run the demonstrations. * * @param arguments Command-line arguments; none expected. */ public static void main(final String[] arguments) { final NonSerializableCollectionsInnerClassesDemonstrator instance = new NonSerializableCollectionsInnerClassesDemonstrator(); instance.demonstrateMapNestedClassesSerializability(); } }
The above class runs through several popular
Map
implementations (EnumMap, HashMap, LinkedHashMap, TreeMap, Hashtable, WeakHashMap, ConcurrentHashMap) and prints out whether each Map
implementation and its nested classes are Serializable
. The output it generates is shown next.===== HASHMAP ===== java.util.HashMap is Serializable. java.util.HashMap$KeySet is NOT Serializable. java.util.HashMap$KeyIterator is NOT Serializable. java.util.HashMap$Values is NOT Serializable. java.util.HashMap$Entry is NOT Serializable. ===== LINKEDHASHMAP ===== java.util.LinkedHashMap is Serializable. java.util.HashMap$KeySet is NOT Serializable. java.util.LinkedHashMap$KeyIterator is NOT Serializable. java.util.HashMap$Values is NOT Serializable. java.util.LinkedHashMap$Entry is NOT Serializable. ===== CONCURRENTHASHMAP ===== java.util.concurrent.ConcurrentHashMap is Serializable. java.util.concurrent.ConcurrentHashMap$KeySet is NOT Serializable. java.util.concurrent.ConcurrentHashMap$KeyIterator is NOT Serializable. java.util.concurrent.ConcurrentHashMap$Values is NOT Serializable. java.util.concurrent.ConcurrentHashMap$WriteThroughEntry is Serializable. ===== WEAKHASHMAP ===== java.util.WeakHashMap is NOT Serializable. java.util.WeakHashMap$KeySet is NOT Serializable. java.util.WeakHashMap$KeyIterator is NOT Serializable. java.util.WeakHashMap$Values is NOT Serializable. java.util.WeakHashMap$Entry is NOT Serializable. ===== TREEMAP ===== java.util.TreeMap is Serializable. java.util.TreeMap$KeySet is NOT Serializable. java.util.TreeMap$KeyIterator is NOT Serializable. java.util.TreeMap$Values is NOT Serializable. java.util.TreeMap$Entry is NOT Serializable. ===== HASHTABLE ===== java.util.Hashtable is Serializable. java.util.Collections$SynchronizedSet is Serializable. java.util.Hashtable$Enumerator is NOT Serializable. java.util.Collections$SynchronizedCollection is Serializable. java.util.Hashtable$Entry is NOT Serializable. ===== ENUMMAP ===== java.util.EnumMap is Serializable. java.util.EnumMap$KeySet is NOT Serializable. java.util.EnumMap$KeyIterator is NOT Serializable. java.util.EnumMap$Values is NOT Serializable. java.util.EnumMap$EntryIterator is NOT Serializable.
The output shown above leads to several interesting observations. First, and most importantly from this post's perspective, is the fact that most (all but
WeakHashMap
) of the Map
implementations are themselves Serializable
, but most of them (all but Hashtable
) have nested classes (for keyset, values, and entryset) that are NOT Serializable
.There are several approaches that can be used if the data from one of these classes nested within Map need to be distributed. The previously referenced bug reports provide an obvious "work around." One can copy the returned key set into its own new (and Serializable) Set:
Copy the values of the Set returned byThis blog post has attempted to demonstrate thatkeySet()
,entrySet()
orvalues()
into a newHashSet
orTreeSet
object:Set obj = new HashSet(myMap.keySet());
Serializable
cannot be taken for granted. This is particularly true when dealing with nested classes in Map
implementations.
4 comments:
A very informative post for a Java novice. Thanks.
In the post you say that Hashtable does NOT have nested implementations of ketset and values. Does this mean that Hashtable does not suffer from this serialisation problem?
Cluggas,
Although Hashtable has neither keyset nor values, its nested Enumerator and Entry are similarly non-Serializable, meaning one needs to be careful with passing either the Enumerator or the Entry to remote objects. That being said, its SynchronizedSet and SynchronizedCollection appear to be Serializable.
Dustin
Is it just me or does it seem crazy that an implementation (especially one provided by Java itself) would implement Serializable if it can't actually be serialized? Am I missing something? I thought that was the whole point of needing to explicitly implement the interface - it puts some responsibility on the developer to ensure their class truly is serializable.
Yes, you are missing something. The fact that a data-structure returned by a map, when queried in a very specific way, is not serializable does not mean that the map as a whole is not...
The interface just ensures that the map itself is serializable (save as a whole, read as a whole) and not all the data-structures involved in all the methods the map exposes.
Post a Comment