使用方法将原始映射转换为通用映射,以早期失败的方式干净、安全



Casting、instanceof和@SuppressWarnings("unchecked")有噪声。把它们塞进一个不需要查看的方法中会很好。CheckedCast.castToMapOf()就是这样做的尝试。

castToMapOf()正在做一些假设:

  • (1) 不能相信地图是同质的
  • (2) 重新设计以避免强制转换或实例化是不可行的
  • (3) 在故障早期确保类型安全比性能打击更重要
  • (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)
    }
}

一些我觉得有用的读物:

具有未知实现类的通用工厂

通用和参数化类型

我还想知道TypeReference/超类型令牌是否有助于解决(4)和(5)问题,并且是解决这个问题的更好方法。如果你这么认为,请贴一个例子。

代码看起来不错,但我要添加一个假设:(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将被自然抛出,并且在不影响新映射的情况下仍然可以修改旧映射引用。

相关内容

  • 没有找到相关文章

最新更新