Over a million developers have joined DZone.

Maps With 2 Keys: Inspire Yourself to Think Differently

· Java Zone

Navigate the Maze of the End-User Experience and pick up this APM Essential guide, brought to you in partnership with CA Technologies

Thinking about a Map with 2 keys immediately compelled me to use a user-defined key, and that would probably be a Class. Following is the key Class:

public class MapKey {
	private Object key1;
	private Object key2;

	public Object getKey1() {
		return key1;
	}

	public void setKey1(Object key1) {
		this.key1 = key1;
	}

	public Object getKey2() {
		return key2;
	}

	public void setKey2(Object key2) {
		this.key2 = key2;
	}
}

The Main Class as shown below includes a Map with the above Class as its key, and a List of Objects as its value.

public class MapWithTwoKeys {
	private static final Map<MapKey, List<Object>> mapWithTwoKeys = new HashMap<MapKey, List<Object>>();

	public static void main(String[] args) {

		// Create first map entry with key <A,B>.
		MapKey mapKey1 = new MapKey();
		mapKey1.setKey1("A");
		mapKey1.setKey2("B");

		List<Object> list1 = new ArrayList<Object>();
		list1.add("List1 Entry");

		mapWithTwoKeys.put(mapKey1, list1);

		// Create second map entry with key <A,B>, append value.
		MapKey mapKey2 = new MapKey();
		mapKey2.setKey1("A");
		mapKey2.setKey2("B");

		List<Object> list2 = new ArrayList<Object>();
		list2.add("List2 Entry");

		mapWithTwoKeys.put(mapKey2, list2);

		// Create third map entry with key <A,>.
		MapKey mapKey3 = new MapKey();
		mapKey3.setKey1("A");
		mapKey3.setKey2("");

		List<Object> list3 = new ArrayList<Object>();
		list3.add("List3 Entry");

		mapWithTwoKeys.put(mapKey3, list3);

		// Create forth map entry with key <,>.
		MapKey mapKey4 = new MapKey();
		mapKey4.setKey1("");
		mapKey4.setKey2("");

		List<Object> list4 = new ArrayList<Object>();
		list4.add("List4 Entry");

		mapWithTwoKeys.put(mapKey4, list4);

		// Create forth map entry with key <,B>.
		MapKey mapKey5 = new MapKey();
		mapKey5.setKey1("");
		mapKey5.setKey2("B");

		List<Object> list5 = new ArrayList<Object>();
		list5.add("List5 Entry");

		mapWithTwoKeys.put(mapKey5, list5);

		for (Map.Entry<MapKey, List<Object>> entry : mapWithTwoKeys.entrySet()) {
			System.out.println("MapKey Key: <" + entry.getKey().getKey1() + ","
					+ entry.getKey().getKey2() + ">");
			System.out.println("Value: " + entry.getValue());
			System.out.println("---------------------------------------");
		}
	}
}

Great going 'til now!

Let’s check the output of the above code:

MapKey Key: <,B>
Value: [[List5 Entry]]
---------------------------------------
MapKey Key: <,>
Value: [[List4 Entry]]
---------------------------------------
MapKey Key: <A,>
Value: [[List3 Entry]]
---------------------------------------
MapKey Key: <A,B>
Value: [[List2 Entry]]
---------------------------------------
MapKey Key: <A,B>
Value: [[List1 Entry]]
---------------------------------------

Do you notice something that did not work as per the Map basics? Each key should have only one entry. The last 2 entries in the output! Aren’t the keys the same in this case? So there should have been a single entry for this key, and List2 should replace List1 as the value.

So why did we have 2 entries in the Map even though the keys are same? If you look at how Map behaves for put, it would be very obvious. Map first checks for the hashcode of the key, and if the hashcode for 2 keys is same, then it does an equals comparison. Let’s try overriding these methods to achieve our target. Here’s how the MapKey class looks after overriding those 2 methods.

public class MapKey {
	private Object key1;
	private Object key2;

	@Override
	public boolean equals(Object object) {
		if (((MapKey) object).key1 == null && ((MapKey) object).key2 == null) {
			return true;
		}
		if (((MapKey) object).key1 == null
				&& ((MapKey) object).key2.equals(this.key2)) {
			return true;
		}
		if (((MapKey) object).key1.equals(this.key1)
				&& ((MapKey) object).key2 == null) {
			return true;
		}
		if (((MapKey) object).key1.equals(this.key1)
				&& ((MapKey) object).key2.equals(this.key2)) {
			return true;
		}
		return false;
	}
	
	@Override
	public int hashCode() {
		int hashCode = this.key1 == null ? 0 : this.key1.hashCode();
		hashCode = hashCode + (this.key2 == null ? 0 : this.key2.hashCode());
		return hashCode;
	}

	public Object getKey1() {
		return key1;
	}

	public void setKey1(Object key1) {
		this.key1 = key1;
	}

	public Object getKey2() {
		return key2;
	}

	public void setKey2(Object key2) {
		this.key2 = key2;
	}
}

The overridden hashCode() method will always return the addition of hashCodes of both key1 and key2, and we do the equals comparison to check if both key1 and key2 have a match. Let’s see the output of the Class MapWithTwoKeys again after this modification.

MapKey Key: <,B>
Value: [[List5 Entry]]
---------------------------------------
MapKey Key: <,>
Value: [[List4 Entry]]
---------------------------------------
MapKey Key: <A,>
Value: [[List3 Entry]]
---------------------------------------
MapKey Key: <A,B>
Value: [List2 Entry]
---------------------------------------

Wow! We got only one entry for the key <A,B> this time, and List2 has replaced List1 as the value to this key.

Let’s make the requirement more complex and sort the Map by the insertion order. Think!

NOTE: There are few open source APIs (like…  TableMultikeyMap, etc.) that have already created Map-like interfaces that will accept multiple keys. These are good to explore.

Thrive in the application economy with an APM model that is strategic. Be E.P.I.C. with CA APM.  Brought to you in partnership with CA Technologies.

Topics:

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}