Does a map implementation exists where one can add alias(es) on existing keys
SuperMap map = .....;
map.put("key1" , object1);
map.addAlias("key1" , "aliaskey"); // <== create alias
Object o = map.get("aliaskey"); // equals object1
Thanks
Not sure what you want to use this for, or whether it's worth the trouble, but the implementations I've seen in this thread so far all have the problem that the alias becomes disconnected from the key it's aliasing. For instance, if you say:
SuperMap<String, String> sm = new SuperMap<String, String>();
sm.put("Elvis", "Presley");
sm.addAlias("Priscilla", "Elvis");
System.out.println(sm.get("Elvis"));
System.out.println(sm.get("Priscilla"));
sm.put("Elvis", "Costello");
System.out.println(sm.get("Elvis"));
System.out.println(sm.get("Priscilla"));
you're going to get:
Presley
Presley
Costello
Presley
(Note: that's with the original implementation of SuperMap, not the edited version.)
If you want a true alias -- if you want to get Presley, Presley, Costello, Costello -- you're going to need something a little more complicated.
The code below is untested (and the whole enterprise seems a little bit insane), but something like it should work:
public class AliasMap<K, V> extends AbstractMap<K, V>
{
private final Map<K, V> backingMap;
private final Map<K, K> aliasToRealKey;
public AliasMap ()
{
this( new HashMap<K, V>() );
}
public AliasMap ( Map<K, V> backingMap )
{
this.backingMap = backingMap;
aliasToRealKey = new HashMap<K, K>();
}
@Override
public Set<Entry<K, V>> entrySet ()
{
return new AliasAwareEntrySet<K, V>( aliasToRealKey, backingMap );
}
@Override
public V put ( K k, V v )
{
if ( aliasToRealKey.containsKey( k ) )
{
throw new IllegalArgumentException( "An alias '" + k + "' already exists in the map" );
}
return backingMap.put( k, v );
}
@Override
public V get ( Object o )
{
V v = backingMap.get( o );
if ( v == null )
{
K realKey = aliasToRealKey.get( o );
if ( realKey == null )
{
return null;
}
return backingMap.get( realKey );
}
return v;
}
public void alias ( K realKey, K alias )
{
if ( backingMap.containsKey( alias ) )
{
throw new IllegalArgumentException( "The key '" + alias + "' already exists in the map" );
}
aliasToRealKey.put( alias, realKey );
}
private static class AliasAwareEntrySet<K, V> extends AbstractSet<Entry<K, V>>
{
private Map<K, K> aliasToRealKey;
private Map<K, V> backingMap;
public AliasAwareEntrySet ( Map<K, K> aliasToRealKey, final Map<K, V> backingMap )
{
this.aliasToRealKey = aliasToRealKey;
this.backingMap = backingMap;
}
@Override
public Iterator<Entry<K, V>> iterator ()
{
return new AliasAwareEntryIterator<K, V>( backingMap, aliasToRealKey );
}
@Override
public int size ()
{
return backingMap.size() + aliasToRealKey.size();
}
}
private static class AliasAwareEntryIterator<K, V> implements Iterator<Entry<K, V>>
{
Set<Entry<K, V>> realEntries;
Set<K> aliasKeys;
Iterator<Entry<K, V>> realIterator;
Iterator<K> aliasIterator;
boolean isRealEntry = true;
private Map<K, V> backingMap;
private Map<K, K> aliasToRealKey;
public AliasAwareEntryIterator ( final Map<K, V> backingMap, Map<K, K> aliasToRealKey )
{
this.realEntries = backingMap.entrySet();
this.aliasKeys = aliasToRealKey.keySet();
realIterator = realEntries.iterator();
aliasIterator = aliasKeys.iterator();
this.backingMap = backingMap;
this.aliasToRealKey = aliasToRealKey;
}
public boolean hasNext ()
{
return realIterator.hasNext() || aliasIterator.hasNext();
}
public Entry<K, V> next ()
{
if ( realIterator.hasNext() )
{
return realIterator.next();
}
isRealEntry = false;
final K alias = aliasIterator.next();
final K realKey = aliasToRealKey.get( alias );
return new AliasAwareEntry( alias, realKey );
}
public void remove ()
{
if ( isRealEntry )
{
realIterator.remove();
}
else
{
aliasIterator.remove();
}
}
private class AliasAwareEntry implements Entry<K, V>
{
private final K alias;
private final K realKey;
public AliasAwareEntry ( K alias, K realKey )
{
this.alias = alias;
this.realKey = realKey;
}
public K getKey ()
{
return alias;
}
public V getValue ()
{
return backingMap.get( realKey );
}
public V setValue ( V v )
{
return backingMap.put( realKey, v );
}
}
}
public static void main ( String[] args )
{
AliasMap<String, String> sm = new AliasMap<String, String>();
sm.put( "Elvis", "Presley" );
sm.alias( "Elvis", "Priscilla" );
System.out.println( sm.get( "Elvis" ) );
System.out.println( sm.get( "Priscilla" ) );
sm.put( "Elvis", "Costello" );
System.out.println( sm.get( "Elvis" ) );
System.out.println( sm.get( "Priscilla" ) );
for ( String s : sm.keySet() )
{
System.out.println(s);
}
for ( Iterator<Entry<String, String>> iterator = sm.entrySet().iterator(); iterator.hasNext(); )
{
Entry<String, String> entry = iterator.next();
System.out.println( entry.getKey() + " : " + entry.getValue() );
if ( "Priscilla".equals( entry.getKey() ) )
{
iterator.remove();
}
}
for ( String s : sm.keySet() )
{
System.out.println(s);
}
}
}
@DavidMoles is the only one who's given you an answer that does true aliasing so far. All of the others just make copies, meaning if you update an entry with an "alias" key the "original" key is not updated.
However, this all seems like a bad design to me. Having multiple keys in your map point to the same key is not the expected semantics of a map. Instead, I'd recommend you do the alias mapping outside of your map, and create a new type to enforce that you always properly unalias before doing a lookup:
// AliasTest.java
import java.util.*;
public class AliasTest {
public static void main(String[] args) {
Map<Aliaser<String>.Key, Integer> map = new HashMap<Aliaser<String>.Key, Integer>();
Aliaser<String> aliaser = new Aliaser<String>();
map.put(aliaser.addKey("A"), 1);
map.put(aliaser.addKey("B"), 2);
map.put(aliaser.addKey("C"), 3);
aliaser.addAlias("A", "X");
map.put(aliaser.lookup("X"), 5);
System.out.println(map.get(aliaser.lookup("A")));
}
}
// Aliaser.java
import java.util.*;
public class Aliaser<T> {
public class Key { }
private Map<T, Key> aliases = new HashMap<T, Key>();
public Key addKey(T alias) {
Key key = new Key();
aliases.put(alias, key);
return key;
}
public void addAlias(T orig, T alias) {
aliases.put(alias, aliases.get(orig));
}
public Key lookup(T alias) {
return aliases.get(alias);
}
}
Note how the map has type Map<Aliaser<String>.Key, Integer> instead of Map<String, Integer>. This helps us by making the Java compiler assert (through type checks) that we always properly unalias all of our String keys before doing a map lookup.
If you were using Scala you could do all of the String->Aliaser<String>.Key conversions automatically with implicit conversions, but in Java you're stuck doing the conversions manually. You could always declare some local methods to make it less verbose though—or just use shorter variable/method names than the ones I used in my example.
I'm not sure such an implementation already exists, but feel free to develop your own:
public class SuperMap<K, V> extends HashMap<K, V> {
private final HashMap<K, K> aliases = new HashMap<K, K>();
public void addAlias(final K alias, final K key) {
aliases.put(alias, key);
}
@Override
public V get(final Object key) {
if (keySet().contains(key)) {
return super.get(key);
} else if (aliases.keySet().contains(key)) {
return super.get(aliases.get(key));
}
return null;
}
}
Simply put the value into the map with different keys:
Map<String, Integer> map = new HashMap<>();
Integer value = 42;
map.put("firstKey", value);
map.put("secondKey", value);
System.out.println(map.get("firstKey") == map.get("secondKey"));
> true
map.put("secondKey", map.get("firstKey")).
key1were to later change, would you also wantaliaskeyto reflect the new value?aliaskeywon't reflect the changed value -- see my answer below. Probably not the most elegant implementation, but it should work.