简体   繁体   English

Java - 不可修改的键集映射

[英]Java - unmodifiable key set map

I am looking for a way to provide a Map with pre-defined (as in runtime immutable, not compile time const) constant key set, but modifiable values. 我正在寻找一种方法来提供一个预定义的Map (如运行时不可变,而不是编译时const)常量键集,但可修改的值。

The JDK provides Collections.unmodifiableMap factory method, which wraps a Map and provides an immutable view of it. JDK提供了Collections.unmodifiableMap工厂方法,它包装Map并提供它的不可变视图。

Is there a similar way to wrap a Map so that only it's keys are immutable? 是否有类似的方式来包装Map以便只有它的键是不可变的? For instance, put(K,V) will replace the value of existing keys, but throw UnsupportedOperationException if the key does not exist. 例如, put(K,V)将替换现有键的值,但如果键不存在则抛出UnsupportedOperationException

Use an enum as the key. 使用枚举作为键。 Then one needn't care if they can add a new key since the key domain is fixed and finite. 然后人们无需关心他们是否可以添加新密钥,因为密钥域是固定且有限的。 In fact, that's such a standard use case that Java provides java.util.EnumMap<K extends Enum<K>,V> http://docs.oracle.com/javase/8/docs/api/java/util/EnumMap.html 实际上,这是Java提供的标准用例java.util.EnumMap<K extends Enum<K>,V> http://docs.oracle.com/javase/8/docs/api/java/util/EnumMap html的

Ok, all the solutions suggested here were to wrap or extend Collections.UnmodifiableMap . 好的,这里建议的所有解决方案都是包装或扩展Collections.UnmodifiableMap Both impossible since the original implementation would not allow to override put (and replace etc.), Which is exactly what makes it secure... 两者都不可能,因为最初的实现不允许覆盖put (和replace等),这正是使其安全的原因......

I see two options: 我看到两个选择:

  1. The "smelly" option - Using reflection to get hold of the original Map<> and directly call it's methods. “臭”选项 - 使用反射来获取原始Map<>并直接调用它的方法。
  2. The "ugly" option - Copy the source of some of the static classes in java.lang.Collections and modify them. “丑陋”选项 - 复制java.lang.Collections中某些静态类的源代码并修改它们。

If anyone has a better idea, please let me know. 如果有人有更好的想法,请告诉我。


Here is an initial implementation of the 2'nd solution: 以下是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) The proxy 1)代理

I would reduce the scope of your SemiMutableMap to something like 我会将SemiMutableMap的范围SemiMutableMap到类似的范围

interface ISemiMutableMap<U, V> {

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

This will reduce the possibilities of access but give you the full control of it. 这将减少访问的可能性,但让您完全控制它。

And then implements it simply like a proxy 然后像代理一样实现它

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

And you can add the methods you like to it off course. 你可以在课程中添加你喜欢的方法。

Note that this is not complety true, The constructor should clone the map instead of using the same reference but I am a bit lazy ;) and I've writen this without an IDE 请注意,这不是完全正确的,构造函数应该克隆地图而不是使用相同的引用,但我有点懒惰;)并且我在没有IDE的情况下写了这个

2) The implementation 2)实施

Nothing prevent you to simply get the code of the UnmodifiableMap from the Collections and adapt it to your needs. 没有什么能阻止您从Collections简单地获取UnmodifiableMap的代码并使其适应您的需求。 From this, you will see it is quite simple to create your own implementation to your need. 从这里,您将看到根据需要创建自己的实现非常简单。

Trust me, this class as been tested and reviewed ;) 相信我,这个课程已经过测试和审查;)

You will need to adapt put to be able to update an existing value (same code as above) and UnmodifiableEntry.setValue to accept an update from the entry set. 您需要调整put以便能够更新现有值(与上面相同的代码)和UnmodifiableEntry.setValue以接受来自条目集的更新。

I know this isn't strictly what was asked in the question, but I think this approach is worth considering. 我知道这不是严格的问题,但我认为这种方法值得考虑。

Use Google Guava's ImmutableMap and make your value type mutable- with a wrapper type if necessary. 如果需要,使用Google Guava的ImmutableMap并使您的值类型变为可变 - 使用包装类型。

Before moving on, it's important to understand this: immutability at the map level only means you can't re-assign objects. 在继续之前,理解这一点很重要:地图级别的不变性意味着您无法重新分配对象。 There's nothing stopping you from calling mutator methods on the objects already in the map. 没有什么可以阻止你在地图上已有的对象上调用mutator方法。 With this insight, the above approach might be obvious, but let me explain via an example. 有了这种见解,上述方法可能是显而易见的,但让我通过一个例子来解释。

For me, the value type was AmtomicInteger- already perfectly mutable in exactly the right way I needed, so I didn't need to change anything. 对我来说,值类型是AmtomicInteger-已经完全以我需要的正确方式变化,所以我不需要改变任何东西。 However, in general, let's say you have a type T, and you want a partially mutable map for this type. 但是,一般来说,假设你有一个类型T,并且你想要这种类型的部分可变映射。 Well, you can almost do this via a mutable map whose values are wrapper types for that class. 好吧,你几乎可以通过一个可变映射来实现这一点,它的值是该类的包装类型。 Here's a really basic wrapper type, which you might want to enhance with some encapsulation: 这是一个非常基本的包装器类型,您可能希望通过一些封装来增强它:

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

Then you simply create a regular ImmutableMap in the normal way, except fill it with objects of Wrapper instead of objects of T. When using the values, you need to unbox them from the wrapper. 然后你只需要以正常的方式创建一个常规的ImmutableMap,除了用Wrapper的对象填充它而不是T的对象。使用这些值时,你需要从包装器中取消它们。

Again, I know this isn't what was asked, and unboxing from a wrapper might be too painful for some use cases, but I'd imagine this would work in a lot of cases. 同样,我知道这不是所要求的,并且从包装器中取消装箱可能对某些用例来说太痛苦了,但我想这会在很多情况下起作用。 Certainly for me, it helped me realise that I already had a mutable type that was basically a wrapper for an int, and so the ImmmutableMap was fine. 当然对我来说,它帮助我意识到我已经有一个可变类型,它基本上是一个int的包装器,所以ImmmutableMap很好。

I believe you want to do something like this. 我相信你想做这样的事情。

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