I started thinking how to support geolocation and i started thinking about having a extension of java.util.Map that would store two keys for longitude and latitude. So i created this.
package se.kentor.demo.dualkey;
import java.util.Collection;
public interface DualKeyMap<K1, K2, V> {
public void clear();
public boolean containsKey(K1 key1, K2 key2);
public boolean containsValue(V value);
public V get(K1 key1, K2 key2);
public boolean isEmpty();
public V put(K1 key1, K2 key2, V value);
public V remove(K1 key1, K2 key2);
public int size();
public Collection<V> values();
}
And the implementation is
package se.kentor.demo.dualkey;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class DualKeyHashMap<K1, K2, V> implements DualKeyMap<K1, K2, V> {
private Map<Pair<K1, K2>, V> map;
public DualKeyHashMap(){
map = new HashMap<Pair<K1, K2>, V>();
}
@Override
public void clear() {
map.clear();
}
@Override
public boolean containsKey(K1 key1, K2 key2) {
Set<Pair<K1, K2>> keys = map.keySet();
Pair<K1, K2> pair = new Pair<K1, K2>(key1, key2);
return keys.contains(pair);
}
@Override
public boolean containsValue(V value) {
return map.containsValue(value);
}
@Override
public V get(K1 key1, K2 key2) {
Pair<K1, K2> pair = new Pair<K1, K2>(key1, key2);
return map.get(pair);
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public V put(K1 key1, K2 key2, V value) {
Pair<K1, K2> pair = new Pair<K1, K2>(key1, key2);
return map.put(pair, value);
}
@Override
public V remove(K1 key1, K2 key2) {
Pair<K1, K2> pair = new Pair<K1, K2>(key1, key2);
return map.remove(pair);
}
@Override
public int size() {
return map.size();
}
@Override
public Collection<V> values() {
return map.values();
}
private class Pair<A extends K1, B extends K2>{
private A key1;
private B key2;
public Pair(A key1, B key2){
if(key1 != null && key2 != null){
this.key1 = key1;
this.key2 = key2;
} else {
throw new IllegalArgumentException("Cannot pass NULL objects as keys!");
}
}
@Override
public boolean equals(Object o){
if(o instanceof Pair){
@SuppressWarnings("unchecked")
Pair<A, B> pair = (Pair<A, B>) o;
return key1.equals(pair.getKey1()) && key2.equals(pair.getKey2());
}
return false;
}
@Override
public int hashCode(){
return key1.hashCode() + key1.hashCode();
}
@Override
public String toString(){
return String.format("{%s, %s}", key1, key2);
}
public K1 getKey1(){
return key1;
}
public K2 getKey2(){
return key2;
}
}
}
I also wrote a test class for this
package se.kentor.demo.dualkey
public class DualKeyMapTest {
public static void main(String[] args) {
DualKeyMap<Double, String, String> map = new DualKeyHashMap<Double, String, String>();
for(int i = 0; i < 1000; i++){
Double key1 = Math.random();
String key2 = ""+Math.random();
String value = "I am value K1: '" + key1 +
"' and K2: '" + key2 + "' with value: '" +
System.currentTimeMillis() + "'";
map.put(key1, key2, value);
}
for( String value : map.values()){
System.out.println(value);
}
}
}
Then i started thinking about other ways of doing this.
I wanted to have something that would support more than long and lat, so i wanted to have a 3D view of a map or why not even an “n” view. Also the implementation of the private Pair class did not appeal to me since i has not really able to retreive the keySet in a nice manner since my objects were stored in an inner and private class.
So i started looking at something nicer, and this is what i came up with…
package se.kentor.demo.multikey;
import java.util.Collection;
import java.util.Set;
public interface NKeyMap<K, V> {
public void clear();
@SuppressWarnings("unchecked")
public boolean containsKey(K... k);
public boolean containsValue(V value);
@SuppressWarnings("unchecked")
public V get(K... k);
public boolean isEmpty();
@SuppressWarnings("unchecked")
public V put(V value, K... k);
@SuppressWarnings("unchecked")
public V remove(K... k);
public int size();
public Collection<V> values();
public Set<Set<K>> keySet();
}
Here is the implementation
package se.kentor.demo.multikey;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class NKeyHashMap<K, V> implements NKeyMap<K, V> {
private Map<Set<K>, V> map;
public NKeyHashMap(){
map = new HashMap<Set<K>, V>();
}
@Override
public void clear() {
map.clear();
}
@Override
@SuppressWarnings("unchecked")
public boolean containsKey(K... k) {
Set<Set<K>> keys = map.keySet();
Set<K> pair = addToSet(k);
return keys.contains(pair);
}
@Override
public boolean containsValue(V value) {
return map.containsValue(value);
}
@Override
@SuppressWarnings("unchecked")
public V get(K... k) {
return map.get(addToSet(k));
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
@SuppressWarnings("unchecked")
public V put(V value, K... k) {
return map.put(addToSet(k), value);
}
@Override
@SuppressWarnings("unchecked")
public V remove(K... k) {
return map.remove(addToSet(k));
}
@Override
public int size() {
return map.size();
}
@Override
public Collection<V> values() {
return map.values();
}
@Override
public Set<Set<K>> keySet() {
return map.keySet();
}
@SuppressWarnings("unchecked")
private Set<K> addToSet(K... k){
Set<K> set = new HashSet<K>();
for(K key : k){
set.add(key);
}
return set;
}
}
And here is the test class
package se.kentor.demo.multikey;
import java.util.Set;
public class MultiKeyMapTest {
public static void main(String[] args) {
NKeyMap<String, String> map = new NKeyHashMap<String, String>();
for(int i = 0; i < 3; i++){
Double key1 = Math.random();
String key2 = ""+Math.random();
String value = "I am multi key value K1: '" + key1 +
"' and K2: '" + key2 + "' with value: '" +
System.currentTimeMillis() + "'";
map.put(value, ""+key1, key2);
}
for( String value : map.values()){
System.out.println(value);
}
for(Set<String> keys : map.keySet()){
System.out.println("-------------- NEXT PAIR -------------");
for(String key : keys){
System.out.println("And my keys are " + key);
}
}
}
}
Now observe how the private inner class is not used any more, i am also able to retreive the keys…
The only drawback of the second solutions comparing to the first one, is that the first one will allow me to use arbitrary key type, but the second solution i need to have the keys of the same type.
As you see in the first solution the Key1 is a Double and key2 is a String, while in the second solution both keys are Strings.