2

Map<String,String>Javaでは、値を変更可能に保ちながら、変更不可能なキーを持つを作成するにはどうすればよいですか。

Map<String,String>他の誰かが Map 値を追加/変更できるようにインターフェースを介してこれを渡したいのですが、Map キーを変更することはできません。

より高いレベルの問題の背景は、変数名のリスト/セット (ツリーのような構造を持つ) (Java 文字列として表される) を持っていることです。文字列) を各変数名に使用します。このインターフェースを複数実装して、ツリー階層の命名をさまざまな状況に合わせてさまざまな方法でエイリアスにできるようにしたいと考えています。インターフェイスの実装に、Map<String,String>すでに設定されている一連のキー (値のデフォルトが含まれている可能性があります) を入力し、値 (キーではなく) を変更できるようにするのが最善の方法のようです。名前とエイリアスの間のマッピングを作成しているのでMap<>、理にかなっています。

低レベルの問題に戻ります。コードを次のようにしたい:

    public class MyClass
    {
        public interface IMyMapper
        {
            void build(Map<String,String> mapping);
        }

        IMyMapper mapper;

        // How I'd like to use it
        void work()
        {
            Map<String,String> map ;
            // Magic something like Collections unmodifiableMap, but only for keys
            // Maybe my question should be how this magic for UnmodifiableMap works, so I could reproduce it??
            mapper.build(map); 
            // Because Maps<> are by reference, changed they made (to the values) would be reflected here
        }
    }

    public class TheirClass implements MyClass.IMyMapper
    {
        @Override
        public void build(Map<String,String> mapping)
        {
            // use mapping like Map<String,String> without extra/foreign classes
            // but not be able to modify the Map keys
            // only be able to change the Map values
            // Should be able to use all of the awesome Map stuff, like foreach, values, compute
        }
    }

あることは知っていますがCollections unmodifiableMap(Map<> m)、それによって値が変更できなくなります。私の値が変更可能なオブジェクトである場合、それらを変更することはできますが、私は固執したいと思いますStrings(単一の String メンバーの set/get を使用して Class を作成したり、public String メンバーを使用して構造のようなクラスを作成したりすることは避けます)。

AKA、私は独自の変更可能なクラス値を作成することを避けCollections unmodifiableMap()、キーをvalue references変更不可能にするために使用したいと思います:

    // mutable reference to a String
    public class ExtraWorkForForEveryone
    {
        public String value;

        public void setValue(String value) { ... }
        public String getValue() { ... }
    }

    // and then use:
    void work()
    {            
        Map<String,ExtraWorkForEveryone> map;
        map = Collections.unmodifiableMap( ... );
        // because Collections.unmodifiableMap() only stops them from changing the Map references,
        // the interfacer could still change the ExtraWorkForEveryone internals.
        // so they could not change keys refs or value refs, but they could change value data.
        mapper.build(map); 
        // Because Maps<> are by reference, changed they made (to the values) would be reflected here
    }

独自の Map を拡張または実装してから、( how のようにCollections unmodifiableMap()) キー throw を変更できるすべてのメソッドをオーバーライドできますUnsupportedOperationException。しかし、Java 8 では、Lambda 関数を使用して追加された多数のメソッドがあり、キーを変更できない限り、インターフェイスの実装者がアクセスできると便利です。

別名、この長くてエラーが発生しやすい手法は避けたいと思います。

    public final class FinalHashMap extends HashMap
    {
        @Override // anything that might be able to change the Map Keys
        so_many_methods_and_edge_cases()
        { throws UnsupportedOperationException }
    }

の値のデータの変更のみを許可する既存のインターフェイスはありますMaps<>か?

変更不可能なキーMap<String,String>を持ち、変更可能な値を持つa似たものを作成するための他のオプションは何ですか? 可能であれば、適切なコーディング プラクティスに関心があります。

4

2 に答える 2

4

Proxy Patternを探しているようです。


詳細な回答:

アイデアは、プロキシと呼ばれるものを使用してマップを操作することです。プロキシは、マップへのすべての呼び出しをインターセプトします。プロキシを介してのみマップと対話できるようにする必要があります。これは、クライアントとマップの間のインターフェイスとして機能します。

プロキシは、「ラップ」しているもののスケルトンです。マップのプロキシを作成しているため、プロキシは次のMapインターフェースを実装する必要があります。

class ImmutableMap<K, V> implements Map<K, V> {
    private Map<K, V> map;

    public ImmutableMap(Map<K, V> map) {
        this.map = new HashMap<>(map); // detach reference
    }

    //implement methods from Map
}

ほとんどのメソッドは単純に にテレスコープしmapます。キーの削除やマップへの新しいキーの追加を防ぐために必要なメソッドを変更します ( 、 、putなどputAll) remove

final class ImmutableMap<K, V> implementsMap<K, V> {
    private Map<K, V> map;

    public ImmutableMap(Map<K, V> map) {
        this.map = new HashMap<>(map);
    }

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public boolean isEmpty() {
        return map.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return map.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return map.containsValue(value);
    }

    @Override
    public V get(Object key) {
        return map.get(key);
    }

    @Override
    public V put(K key, V value) {
        if(!map.containsKey(key)) {
            throw new IllegalArgumentException("Cannot add new keys!");
        }

        return map.put(key, value);
    }

    @Override
    public V remove(Object key) {
        throw new UnsupportedOperationException("You cannot remove entries from this map!");
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> map) {
        for(K key : map.keySet()) {
            if(!this.map.containsKey(key)) {
                throw new IllegalArgumentException("Cannot add new keys to this map!");
            }
        }

        this.map.putAll(map);
    }

    @Override
    public void clear() {
        throw new UnsupportedOperationException("You cannot remove entries from this map!");
    }

    @Override
    public Set<K> keySet() {
        return Collections.unmodifiableSet(map.keySet());
    }

    @Override
    public Collection<V> values() {
        return Collections.unmodifiableSet(map.values()); //prevebt changing values to null
    }

    @Override
    public Set<Map.Entry<K, V>> entrySet() {
        //to allow modification of values, create your own ("immutable") entry set and return that
        return Collections.unmodifiableSet(map.entrySet()); 
    }
}

基調講演:

  1. Collections.unmodifiableSetマップからセットを返すときに使用する必要があります。これにより、マップから返されたセットを変更しようとすると、UnsupportedOperationException

  2. Mapコンストラクターに渡されたマップの値を含むnew を作成すると、クライアントImmutableMapは渡されたマップを使用して を変更できなくなります。

于 2015-04-09T20:14:22.640 に答える