A Developer’s Comparison of Open Social and Facebook platforms A rejoinder to the alternative SafeHashMap
Feb 15

Eric Redmond in his post A Safe HashMap for Java describes a SafeHashMap

Here’s a suggestion to extend the capabilities of the same. Lets look at the code.

Define an interface to create an instance.

First, I create a new interface (We’ll get to know why very soon).

public interface InstanceProvider<K,V>
{
	public V createNew(K k);
}

The SafeMap class itself

Here’s the proposed class. The main distinction is that instead of caching an instance and using the clone method to clone, this implementation stores away a reference to the instance provider and triggers it when required.

public class SafeMap<K, V> extends HashMap<K, V>
{
	// Here's where we cache away the provider
	private InstanceProvider<K,V> provider;

	// Note that the provider is now 
	// passed to all the constructors
	
	public SafeMap(
			int initialCapacity, 
			float loadFactor, 
			InstanceProvider<K,V> provider)
	{
		super(initialCapacity,loadFactor);
		this.provider = provider;
	}

	public SafeMap(
			int initialCapacity, 
			InstanceProvider<K,V> provider)
	{
		this.provider = provider;
	}

	public SafeMap(
			InstanceProvider<K,V> provider)
	{
		this.provider = provider;
	}

	public SafeMap(
			Map<? extends K, ? extends V> m, 
			InstanceProvider<K,V> provider)
	{
		super(m);
		this.provider = provider;
	}

	@Override
	@SuppressWarnings("unchecked")
	public V get(Object key)
	{
		V value = super.get(key);
		if (value == null)
		{
			// use the provider here
			value = 
				provider.createNew(
						(K) key);
		}
		return value;
	}	
}

Test Case demonstrating usage.

public class TestSafeMap
{
	@Test
	public void testGetObject()
	{
		// Am using an anonymous class here. 
		// If additional parameters are required
		// to be passed to constructor, one could 
		// create an abstract class with the constructor
		// and pass the necessary arguments 
		Map<String, List<String>> myMap = 
			new SafeMap<String, List<String>>(
					new InstanceProvider<String, List<String>>()
					{
						public List<String> createNew(String string)
						{
							List<String> list = new ArrayList<String>();
							list.add(string);
							return list;
						}
					}
		);
		
		String key = "hello world";
		assertEquals(
				"List size should've been one",
				1,
				myMap.get(key).size());
		assertEquals(
				"The only element in the list should've been : " + key,
				key,
				myMap.get(key).get(0));
	}
	
	@Test
	public void testArrayInstantiation()
	{
		Map<String, String[]> myMap = 
			new SafeMap<String, String[]>(
					new InstanceProvider<String, String[]>()
					{
						public String[] createNew(
								String string)
						{
							return new String[] {string};
						}
					}
			);
			
		String key = "hello world";
		assertEquals(
				"List size should've been one",
				1,
				myMap.get(key).length);
		assertEquals(
				"The only element in the list should've been : " + key,
				key,
				myMap.get(key)[0]);
	}
}

This way I get a finer level of control on the instantiation of the default instance. There are multiple reasons why one might want that such as :

  • The default instance needs to be configured in some way depending upon the key value (shown in the example above)
  • The default instance needs to be configured based on some constructor parameters passed to it. In this case create an abstract class which implements the interface, declare a constructor with the necessary arguments, use the abstract class during instantiation, and allow the createNew method to behave appropriately based on the values of the arguments.
  • There are some situations such as where the type above is a String[] where the clone method does not work (as in the second test case above). Perhaps the code to conduct the cloning could be modified above to create an array, but I am not too sure (since I’ve often faced difficulties working with instantiation of array types when using generics).


Trackbacks

blog comments powered by Disqus