既存のキーにエイリアスを追加できるマップ実装は存在しますか
SuperMap map = .....;
map.put("key1" , object1);
map.addAlias("key1" , "aliaskey"); // <== create alias
Object o = map.get("aliaskey"); // equals object1
ありがとう
これを何に使用したいのか、またはトラブルに見合う価値があるのかどうかはわかりませんが、これまでにこのスレッドで見た実装にはすべて、エイリアスがエイリアス化されているキーから切断されるという問題があります。たとえば、次のように言うとします。
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"));
あなたは得るつもりです:
Presley
Presley
Costello
Presley
(注: これはSuperMapの元の実装であり、編集されたバージョンではありません。)
真のエイリアスが必要な場合 (取得したい場合)Presley, Presley, Costello, Costello
は、もう少し複雑なものが必要になります。
以下のコードはテストされていません (そして、エンタープライズ全体が少し狂っているように見えます) が、次のようなものは動作するはずです:
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は、これまでに真のエイリアシングを行う回答を提供した唯一の人です。他のすべてはコピーを作成するだけです。つまり、「エイリアス」キーでエントリを更新しても、「元の」キーは更新されません。
ただし、これはすべて悪い設計のように思えます。マップ内の複数のキーが同じキーを指すようにすることは、マップの予期されるセマンティクスではありません。代わりに、マップの外でエイリアス マッピングを行い、新しいタイプを作成して、ルックアップを行う前に常に適切にエイリアスを解除することをお勧めします。
// 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);
}
}
マップがMap<Aliaser<String>.Key, Integer>
ではなくtype を持っていることに注意してくださいMap<String, Integer>
。String
これは、マップ ルックアップを実行する前にすべてのキーのエイリアスを常に適切に解除することを Java コンパイラに (型チェックを通じて) アサートさせることで役立ちます。
Scala を使用していた場合は、すべてのString
->Aliaser<String>.Key
変換を暗黙的な変換で自動的に行うことができますが、Java では変換を手動で行う必要があります。ただし、ローカル メソッドを宣言して冗長性を軽減することもできます。または、例で使用したものよりも短い変数/メソッド名を使用することもできます。
エイリアスを、同じ値を指す別のキー エントリにすることができます。
map.put("key1", object1);
map.put("aliaskey", map.get("key1")); // <== create alias
Object o = map.get("aliaskey"); // equals object1
そのような実装が既に存在するかどうかはわかりませんが、自由に独自の実装を開発してください。
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;
}
}
異なるキーを使用して値をマップに入れるだけです。
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