代码之家  ›  专栏  ›  技术社区  ›  candied_orange Elliott Frisch

使用方法将原始映射转换为通用映射,以故障早期的方式干净且安全

  •  1
  • candied_orange Elliott Frisch  · 技术社区  · 10 年前

    Casting , instanceof @SuppressWarnings("unchecked") 噪音很大。把它们塞进一个不需要被关注的方法中会很好。 CheckedCast.castToMapOf() 是一种尝试。

    castToMapOf() 正在进行一些假设:

    • (1) 无法信任该地图 homogeneous
    • (2) 重新设计为 avoid need for casting or instanceof 不可行
    • (3) 确保 fail early 态度比表现更重要
    • (4) 返回 Map<String,String> 足够了(而不是返回 HashMap<String, String> )
    • (5) 键和值类型参数不是泛型的(如 HashMap<String, ArrayList<String>> )

    (1) 、(2)和(3)是我工作环境的症状,超出了我的控制范围。(4) 和(5)是我做出的妥协,因为我还没有找到克服它们的好方法。

    (4) 很难克服,因为即使 HashMap.class 被传递到 Class<M> 我还不知道如何返回 M<K, V> 。所以我返回 Map<K, V> .

    (5) 可能是使用的固有限制 Class<T> 。我很想听听其他想法。

    尽管有这些限制,你能看到这个代码有什么问题吗?我是否做出了任何我尚未确定的假设?有更好的方法吗?如果我正在重新发明轮子,请指向轮子。:)

    public class CheckedCast {
    
        public static final String LS = System.getProperty("line.separator");
    
        /** Check all contained items are claimed types and fail early if they aren't */
        public static <K, V> Map<K, V> castToMapOf( 
                Class<K> clazzK,    
                Class<V> clazzV,
                Map<?, ?> map) {
    
            for ( Map.Entry<?, ?> e: map.entrySet() ) {
                checkCast( clazzK, e.getKey() );            
                checkCast( clazzV, e.getValue() );            
            }
    
            @SuppressWarnings("unchecked")
            Map<K, V> result = (Map<K, V>) map;        
            return result; 
        }
    
        /** Check if cast would work */
        public static <T> void checkCast(Class<T> clazz, Object obj) {
            if ( !clazz.isInstance(obj) ) {
                throw new ClassCastException(
                    LS + "Expected: " + clazz.getName() +
                    LS + "Was:      " + obj.getClass().getName() +
                    LS + "Value:    " + obj
                );
            }
        }
    
        public static void main(String[] args) {
    
            // -- Raw maps -- //
    
            Map heterogeneousMap = new HashMap();
            heterogeneousMap.put("Hmm", "Well");
            heterogeneousMap.put(1, 2); 
    
            Map homogeneousMap = new HashMap();
            homogeneousMap.put("Hmm", "Well");
    
            // -- Attempts to make generic -- //
    
            //Unsafe, will fail later when accessing 2nd entry
            @SuppressWarnings("unchecked") //Doesn't check if map contains only Strings
            Map<String, String> simpleCastOfHeteroMap = 
                        (Map<String, String>) heterogeneousMap;  
    
            //Happens to be safe.  Does nothing to prove claim to be homogeneous.
            @SuppressWarnings("unchecked") //Doesn't check if map contains only Strings
            Map<String, String> simpleCastOfHomoMap = 
                        (Map<String, String>) homogeneousMap;  
    
            //Succeeds properly after checking each item is an instance of a String
            Map<String, String> checkedCastOfHomoMap = 
                        castToMapOf(String.class, String.class, homogeneousMap);
    
            //Properly throws ClassCastException
            Map<String, String> checkedCastOfHeteroMap = 
                        castToMapOf(String.class, String.class, heterogeneousMap); 
            //Exception in thread "main" java.lang.ClassCastException: 
            //Expected: java.lang.String
            //Was:      java.lang.Integer
            //Value:    1
            //    at checkedcast.CheckedCast.checkCast(CheckedCast.java:14)
            //    at checkedcast.CheckedCast.castToMapOf(CheckedCast.java:36)
            //    at checkedcast.CheckedCast.main(CheckedCast.java:96)
    
        }
    }
    

    我发现一些阅读很有用:

    Generic factory with unknown implementation classes

    Generic And Parameterized Types

    我也想知道 TypeReference / super type tokens 可能有助于(4)和(5),并且是解决这个问题的更好方法。如果你这么认为,请发布一个例子。

    1 回复  |  直到 7 年前
        1
  •  2
  •   WilQu    10 年前

    代码看起来不错,但我要补充一个假设:(6)原始引用将不再使用。因为如果你把你的 Map Map<String, String> ,然后在原始映射中添加一个整数,您可能会得到惊喜。

    Map raw = new HashMap();
    raw.put("Hmm", "Well");
    Map<String, String> casted = castToMapOf(String.class, String.class, raw); // No error
    raw.put("one", 1);
    String one = casted.get("one"); // Error
    

    我将创建一个新地图(可能是一个 LinkedHashMap 为了保持顺序),在将每个对象添加到新地图时投射它们。这样 ClassCastException 将被自然抛出,并且旧的地图引用仍然可以被修改而不影响新的地图引用。