簡體   English   中英

Java - 不可修改的鍵集映射

[英]Java - unmodifiable key set map

我正在尋找一種方法來提供一個預定義的Map (如運行時不可變,而不是編譯時const)常量鍵集,但可修改的值。

JDK提供了Collections.unmodifiableMap工廠方法,它包裝Map並提供它的不可變視圖。

是否有類似的方式來包裝Map以便只有它的鍵是不可變的? 例如, put(K,V)將替換現有鍵的值,但如果鍵不存在則拋出UnsupportedOperationException

使用枚舉作為鍵。 然后人們無需關心他們是否可以添加新密鑰,因為密鑰域是固定且有限的。 實際上,這是Java提供的標准用例java.util.EnumMap<K extends Enum<K>,V> http://docs.oracle.com/javase/8/docs/api/java/util/EnumMap html的

好的,這里建議的所有解決方案都是包裝或擴展Collections.UnmodifiableMap 兩者都不可能,因為最初的實現不允許覆蓋put (和replace等),這正是使其安全的原因......

我看到兩個選擇:

  1. “臭”選項 - 使用反射來獲取原始Map<>並直接調用它的方法。
  2. “丑陋”選項 - 復制java.lang.Collections中某些靜態類的源代碼並修改它們。

如果有人有更好的想法,請告訴我。


以下是2'nd解決方案的初始實現:

import java.io.Serializable;
import java.util.*;
import java.util.function.*;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static java.util.Collections.unmodifiableCollection;
import static java.util.Collections.unmodifiableSet;

/**
 * @serial include
 */
public class UnmodifiableKeySetMap<K,V> implements Map<K,V>, Serializable {

    private final Map<K, V> m;

    /**
     * Returns a view of the specified map with unmodifiable key set. This 
     * method allows modules to provide users with "read-only" access to 
     * internal maps. Query operations on the returned map "read through"
     * to the specified map, and attempts to modify the returned
     * map, whether direct or via its collection views, result in an
     * <tt>UnsupportedOperationException</tt>.<p>
     *
     * The returned map will be serializable if the specified map
     * is serializable.
     *
     * @param <K> the class of the map keys
     * @param <V> the class of the map values
     * @param  m the map for which an unmodifiable view is to be returned.
     * @return an unmodifiable view of the specified map.
     */
    public static <K,V> Map<K,V> unmodifiableKeySetMap(Map<K, V> m) {
        return new UnmodifiableKeySetMap<>(m);
    }

    UnmodifiableKeySetMap(Map<K, V> m) {
        if (m==null)
            throw new NullPointerException();
        this.m = m;
    }

    public int size()                        {return m.size();}
    public boolean isEmpty()                 {return m.isEmpty();}
    public boolean containsKey(Object key)   {return m.containsKey(key);}
    public boolean containsValue(Object val) {return m.containsValue(val);}
    public V get(Object key)                 {return m.get(key);}

    public V put(K key, V value) {
        if (containsKey(key)) {
            return m.put(key, value);
        }
        throw new UnsupportedOperationException();
    }
    public V remove(Object key) {
        throw new UnsupportedOperationException();
    }
    public void putAll(Map<? extends K, ? extends V> m) {
        throw new UnsupportedOperationException();
    }
    public void clear() {
        throw new UnsupportedOperationException();
    }

    private transient Set<K> keySet;
    private transient Set<Map.Entry<K,V>> entrySet;
    private transient Collection<V> values;

    public Set<K> keySet() {
        if (keySet==null)
            keySet = unmodifiableSet(m.keySet());
        return keySet;
    }

    public Set<Map.Entry<K,V>> entrySet() {
        if (entrySet==null)
            entrySet = new UnmodifiableKeySetMap.UnmodifiableEntrySet<>(m.entrySet());
        return entrySet;
    }

    public Collection<V> values() {
        if (values==null)
            values = unmodifiableCollection(m.values());
        return values;
    }

    public boolean equals(Object o) {return o == this || m.equals(o);}
    public int hashCode()           {return m.hashCode();}
    public String toString()        {return m.toString();}

    // Override default methods in Map
    @Override
    @SuppressWarnings("unchecked")
    public V getOrDefault(Object k, V defaultValue) {
        // Safe cast as we don't change the value
        return ((Map<K, V>)m).getOrDefault(k, defaultValue);
    }

    @Override
    public void forEach(BiConsumer<? super K, ? super V> action) {
        m.forEach(action);
    }

    @Override
    public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
        throw new UnsupportedOperationException();
    }

    @Override
    public V putIfAbsent(K key, V value) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean remove(Object key, Object value) {
        throw new UnsupportedOperationException();
    }

    @Override
    public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
        throw new UnsupportedOperationException();
    }

    @Override
    public V computeIfPresent(K key,
                              BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        throw new UnsupportedOperationException();
    }

    @Override
    public V compute(K key,
                     BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        throw new UnsupportedOperationException();
    }

    @Override
    public V merge(K key, V value,
                   BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
        throw new UnsupportedOperationException();
    }

    /**
     * @serial include
     */
    static class UnmodifiableSet<E> extends UnmodifiableCollection<E>
            implements Set<E>, Serializable {
        private static final long serialVersionUID = -9215047833775013803L;

        UnmodifiableSet(Set<? extends E> s)     {super(s);}
        public boolean equals(Object o) {return o == this || c.equals(o);}
        public int hashCode()           {return c.hashCode();}
    }

    /**
     * @serial include
     */
    static class UnmodifiableCollection<E> implements Collection<E>, Serializable {
        private static final long serialVersionUID = 1820017752578914078L;

        final Collection<? extends E> c;

        UnmodifiableCollection(Collection<? extends E> c) {
            if (c==null)
                throw new NullPointerException();
            this.c = c;
        }

        public int size()                   {return c.size();}
        public boolean isEmpty()            {return c.isEmpty();}
        public boolean contains(Object o)   {return c.contains(o);}
        public Object[] toArray()           {return c.toArray();}
        public <T> T[] toArray(T[] a)       {return c.toArray(a);}
        public String toString()            {return c.toString();}

        public Iterator<E> iterator() {
            return new Iterator<E>() {
                private final Iterator<? extends E> i = c.iterator();

                public boolean hasNext() {return i.hasNext();}
                public E next()          {return i.next();}
                public void remove() {
                    throw new UnsupportedOperationException();
                }
                @Override
                public void forEachRemaining(Consumer<? super E> action) {
                    // Use backing collection version
                    i.forEachRemaining(action);
                }
            };
        }

        public boolean add(E e) {
            throw new UnsupportedOperationException();
        }
        public boolean remove(Object o) {
            throw new UnsupportedOperationException();
        }

        public boolean containsAll(Collection<?> coll) {
            return c.containsAll(coll);
        }
        public boolean addAll(Collection<? extends E> coll) {
            throw new UnsupportedOperationException();
        }
        public boolean removeAll(Collection<?> coll) {
            throw new UnsupportedOperationException();
        }
        public boolean retainAll(Collection<?> coll) {
            throw new UnsupportedOperationException();
        }
        public void clear() {
            throw new UnsupportedOperationException();
        }

        // Override default methods in Collection
        @Override
        public void forEach(Consumer<? super E> action) {
            c.forEach(action);
        }
        @Override
        public boolean removeIf(Predicate<? super E> filter) {
            throw new UnsupportedOperationException();
        }
        @SuppressWarnings("unchecked")
        @Override
        public Spliterator<E> spliterator() {
            return (Spliterator<E>)c.spliterator();
        }
        @SuppressWarnings("unchecked")
        @Override
        public Stream<E> stream() {
            return (Stream<E>)c.stream();
        }
        @SuppressWarnings("unchecked")
        @Override
        public Stream<E> parallelStream() {
            return (Stream<E>)c.parallelStream();
        }
    }

    /**
     * We need this class in addition to UnmodifiableSet as
     * Map.Entries themselves permit modification of the backing Map
     * via their setValue operation.  This class is subtle: there are
     * many possible attacks that must be thwarted.
     *
     * @serial include
     */
    static class UnmodifiableEntrySet<K,V>
            extends UnmodifiableSet<Entry<K,V>> {
        private static final long serialVersionUID = 7854390611657943733L;

        @SuppressWarnings({"unchecked", "rawtypes"})
        UnmodifiableEntrySet(Set<? extends Map.Entry<? extends K, ? extends V>> s) {
            // Need to cast to raw in order to work around a limitation in the type system
            super((Set)s);
        }

        static <K, V> Consumer<Entry<K, V>> entryConsumer(Consumer<? super Entry<K, V>> action) {
            return e -> action.accept(new UnmodifiableKeySetMap.UnmodifiableEntrySet.UnmodifiableEntry<>(e));
        }

        public void forEach(Consumer<? super Entry<K, V>> action) {
            Objects.requireNonNull(action);
            c.forEach(entryConsumer(action));
        }

        static final class UnmodifiableEntrySetSpliterator<K, V>
                implements Spliterator<Entry<K,V>> {
            final Spliterator<Map.Entry<K, V>> s;

            UnmodifiableEntrySetSpliterator(Spliterator<Entry<K, V>> s) {
                this.s = s;
            }

            @Override
            public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
                Objects.requireNonNull(action);
                return s.tryAdvance(entryConsumer(action));
            }

            @Override
            public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
                Objects.requireNonNull(action);
                s.forEachRemaining(entryConsumer(action));
            }

            @Override
            public Spliterator<Entry<K, V>> trySplit() {
                Spliterator<Entry<K, V>> split = s.trySplit();
                return split == null
                        ? null
                        : new UnmodifiableKeySetMap.UnmodifiableEntrySet.UnmodifiableEntrySetSpliterator<>(split);
            }

            @Override
            public long estimateSize() {
                return s.estimateSize();
            }

            @Override
            public long getExactSizeIfKnown() {
                return s.getExactSizeIfKnown();
            }

            @Override
            public int characteristics() {
                return s.characteristics();
            }

            @Override
            public boolean hasCharacteristics(int characteristics) {
                return s.hasCharacteristics(characteristics);
            }

            @Override
            public Comparator<? super Entry<K, V>> getComparator() {
                return s.getComparator();
            }
        }

        @SuppressWarnings("unchecked")
        public Spliterator<Entry<K,V>> spliterator() {
            return new UnmodifiableKeySetMap.UnmodifiableEntrySet.UnmodifiableEntrySetSpliterator<>(
                    (Spliterator<Map.Entry<K, V>>) c.spliterator());
        }

        @Override
        public Stream<Entry<K,V>> stream() {
            return StreamSupport.stream(spliterator(), false);
        }

        @Override
        public Stream<Entry<K,V>> parallelStream() {
            return StreamSupport.stream(spliterator(), true);
        }

        public Iterator<Map.Entry<K,V>> iterator() {
            return new Iterator<Map.Entry<K,V>>() {
                private final Iterator<? extends Map.Entry<? extends K, ? extends V>> i = c.iterator();

                public boolean hasNext() {
                    return i.hasNext();
                }
                public Map.Entry<K,V> next() {
                    return new UnmodifiableKeySetMap.UnmodifiableEntrySet.UnmodifiableEntry<>(i.next());
                }
                public void remove() {
                    throw new UnsupportedOperationException();
                }
            };
        }

        @SuppressWarnings("unchecked")
        public Object[] toArray() {
            Object[] a = c.toArray();
            for (int i=0; i<a.length; i++)
                a[i] = new UnmodifiableKeySetMap.UnmodifiableEntrySet.UnmodifiableEntry<>((Map.Entry<? extends K, ? extends V>)a[i]);
            return a;
        }

        @SuppressWarnings("unchecked")
        public <T> T[] toArray(T[] a) {
            // We don't pass a to c.toArray, to avoid window of
            // vulnerability wherein an unscrupulous multithreaded client
            // could get his hands on raw (unwrapped) Entries from c.
            Object[] arr = c.toArray(a.length==0 ? a : Arrays.copyOf(a, 0));

            for (int i=0; i<arr.length; i++)
                arr[i] = new UnmodifiableKeySetMap.UnmodifiableEntrySet.UnmodifiableEntry<>((Map.Entry<? extends K, ? extends V>)arr[i]);

            if (arr.length > a.length)
                return (T[])arr;

            System.arraycopy(arr, 0, a, 0, arr.length);
            if (a.length > arr.length)
                a[arr.length] = null;
            return a;
        }

        /**
         * This method is overridden to protect the backing set against
         * an object with a nefarious equals function that senses
         * that the equality-candidate is Map.Entry and calls its
         * setValue method.
         */
        public boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            return c.contains(
                    new UnmodifiableKeySetMap.UnmodifiableEntrySet.UnmodifiableEntry<>((Map.Entry<?,?>) o));
        }

        /**
         * The next two methods are overridden to protect against
         * an unscrupulous List whose contains(Object o) method senses
         * when o is a Map.Entry, and calls o.setValue.
         */
        public boolean containsAll(Collection<?> coll) {
            for (Object e : coll) {
                if (!contains(e)) // Invokes safe contains() above
                    return false;
            }
            return true;
        }
        public boolean equals(Object o) {
            if (o == this)
                return true;

            if (!(o instanceof Set))
                return false;
            Set<?> s = (Set<?>) o;
            if (s.size() != c.size())
                return false;
            return containsAll(s); // Invokes safe containsAll() above
        }

        /**
         * This "wrapper class" serves two purposes: it prevents
         * the client from modifying the backing Map, by short-circuiting
         * the setValue method, and it protects the backing Map against
         * an ill-behaved Map.Entry that attempts to modify another
         * Map Entry when asked to perform an equality check.
         */
        private static class UnmodifiableEntry<K,V> implements Map.Entry<K,V> {
            private Map.Entry<? extends K, ? extends V> e;

            UnmodifiableEntry(Map.Entry<? extends K, ? extends V> e)
            {this.e = Objects.requireNonNull(e);}

            public K getKey()        {return e.getKey();}
            public V getValue()      {return e.getValue();}
            public V setValue(V value) {
                throw new UnsupportedOperationException();
            }
            public int hashCode()    {return e.hashCode();}
            public boolean equals(Object o) {
                if (this == o)
                    return true;
                if (!(o instanceof Map.Entry))
                    return false;
                Map.Entry<?,?> t = (Map.Entry<?,?>)o;
                return eq(e.getKey(),   t.getKey()) &&
                        eq(e.getValue(), t.getValue());
            }
            public String toString() {return e.toString();}
        }
    }

    /**
     * Returns true if the specified arguments are equal, or both null.
     *
     * NB: Do not replace with Object.equals until JDK-8015417 is resolved.
     */
    static boolean eq(Object o1, Object o2) {
        return o1==null ? o2==null : o1.equals(o2);
    }
}

1)代理

我會將SemiMutableMap的范圍SemiMutableMap到類似的范圍

interface ISemiMutableMap<U, V> {

    V get(U key);
    V set(U key, V value) throws Exception; //create your own maybe ?
}

這將減少訪問的可能性,但讓您完全控制它。

然后像代理一樣實現它

public class SemiMutableMap<U, V> implements ISemiMutableMap<U,V>{

    private Map<U, V> map;

    public SemiMutableMap(Map<U, V> map){ //get the predefine maps
        this.map = map;
    }

    public V get(U key){
        return map.get(U);
    }

    public V set(U key, V value) throws Exception{
        if(!map.containsKey(key)){
            throw new Exception();
        }

        return map.put(key,value);
    }
}

你可以在課程中添加你喜歡的方法。

請注意,這不是完全正確的,構造函數應該克隆地圖而不是使用相同的引用,但我有點懶惰;)並且我在沒有IDE的情況下寫了這個

2)實施

沒有什么能阻止您從Collections簡單地獲取UnmodifiableMap的代碼並使其適應您的需求。 從這里,您將看到根據需要創建自己的實現非常簡單。

相信我,這個課程已經過測試和審查;)

您需要調整put以便能夠更新現有值(與上面相同的代碼)和UnmodifiableEntry.setValue以接受來自條目集的更新。

我知道這不是嚴格的問題,但我認為這種方法值得考慮。

如果需要,使用Google Guava的ImmutableMap並使您的值類型變為可變 - 使用包裝類型。

在繼續之前,理解這一點很重要:地圖級別的不變性意味着您無法重新分配對象。 沒有什么可以阻止你在地圖上已有的對象上調用mutator方法。 有了這種見解,上述方法可能是顯而易見的,但讓我通過一個例子來解釋。

對我來說,值類型是AmtomicInteger-已經完全以我需要的正確方式變化,所以我不需要改變任何東西。 但是,一般來說,假設你有一個類型T,並且你想要這種類型的部分可變映射。 好吧,你幾乎可以通過一個可變映射來實現這一點,它的值是該類的包裝類型。 這是一個非常基本的包裝器類型,您可能希望通過一些封裝來增強它:

public class Wrapper<T>{
  public T value;
  Wrapper(T t){value = t;}
}

然后你只需要以正常的方式創建一個常規的ImmutableMap,除了用Wrapper的對象填充它而不是T的對象。使用這些值時,你需要從包裝器中取消它們。

同樣,我知道這不是所要求的,並且從包裝器中取消裝箱可能對某些用例來說太痛苦了,但我想這會在很多情況下起作用。 當然對我來說,它幫助我意識到我已經有一個可變類型,它基本上是一個int的包裝器,所以ImmmutableMap很好。

我相信你想做這樣的事情。

public class UnmodifiableKeyMap{

static enum MYKeySet {KEY1, KEY2, KEY3, KEY4};
private Map myUnmodifiableHashMap = new HashMap();

public boolean containsKey(Object key) {
    return this.containsKey(key);
}


public Object get(Object key) {

    if(this.containsKey(key)){
        return this.myUnmodifiableHashMap.get(key);
    }else {
        return null;
    }
}

public Object put(Object key, Object value) throws Exception {

    if(this.containsKey(key)){
        this.myUnmodifiableHashMap.put(key, value);
    }

    throw new Exception("UnsupportedOperationException");
}


public Set keySet() {

    Set mySet = new HashSet(Arrays.asList(MYKeySet.values()));
    return mySet;
}

public Collection values() {

    return this.myUnmodifiableHashMap.values();
}

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM