Java - 不可修改的键集映射

时间:2017-05-16 09:51:29

标签: java dictionary key immutability

我正在寻找一种方法来为Map提供预定义的(如运行时不可变,而不是编译时const)常量键集,但是可修改的值。

JDK提供Collections.unmodifiableMap工厂方法,它包装Map并提供不可变的视图。

是否有类似的方法来包装Map,以便只有它的密钥是不可变的?例如,put(K,V)将替换现有密钥的值,但如果密钥不存在则抛出UnsupportedOperationException

5 个答案:

答案 0 :(得分:5)

使用枚举作为键。然后人们无需关心他们是否可以添加新密钥,因为密钥域是固定且有限的。实际上,这是Java提供的标准用例java.util.EnumMap<K extends Enum<K>,V> http://docs.oracle.com/javase/8/docs/api/java/util/EnumMap.html

答案 1 :(得分:2)

好的,这里建议的所有解决方案都是包装或扩展Collections.UnmodifiableMap。两者都不可能,因为原始实现不允许覆盖put(和replace等),这正是使其安全的原因......

我看到两个选项:

  1. &#34;臭&#34;选项 - 使用反射来获取原始Map<>并直接调用它的方法。
  2. &#34;丑陋&#34;选项 - 复制java.lang.Collections中某些静态类的源并修改它们。
  3. 如果有人有更好的想法,请告诉我。

    以下是2&lt;解决方案的初步实施:

    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);
        }
    }
    

答案 2 :(得分:1)

1)代理

我会将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的情况下写了这个 < / p>

2)实施

没有什么能阻止您只是从UnmodifiableMap获取Collections的代码并根据您的需要进行调整。从这里,您将看到根据需要创建自己的实现非常简单。

相信我,这个课程经过测试和评审;)

您需要调整put以更新现有值(与上述代码相同)和UnmodifiableEntry.setValue以接受条目集中的更新。

答案 3 :(得分:1)

我知道问题并不严格,但我认为这种做法值得考虑。

如果需要,使用Google Guava的ImmutableMap并使您的值类型变为可变 - 使用包装类型。

在继续之前,了解这一点非常重要:地图级别的不变性意味着您无法重新分配对象。没有什么可以阻止你在地图上已有的对象上调用mutator方法。有了这种见解,上述方法可能是显而易见的,但让我通过一个例子来解释。

对我来说,值类型是AmtomicInteger-已经完全以我需要的正确方式变异,所以我不需要改变任何东西。但是,一般来说,假设你有一个类型T,并且你想要这种类型的部分可变映射。好吧,你几乎可以通过一个可变映射来实现这一点,它的值是该类的包装类型。这是一个非常基本的包装器类型,您可能希望通过一些封装来增强它:

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

然后你只需要以正常方式创建一个常规的ImmutableMap,除了用Wrapper的对象填充它而不是T的对象。使用这些值时,你需要从包装器中取消它们。

同样,我知道这不是所要求的,并且从包装器中取消装箱可能对某些用例来说太痛苦了,但我想这会在很多情况下起作用。当然对我来说,它帮助我意识到我已经有了一个可变类型,它基本上是一个int的包装器,因此ImmmutableMap很好。

答案 4 :(得分:-1)

我相信你想做这样的事情。

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();
}

}