Внесите необработанную карту на общую карту, используя метод, чисто и безопасно в случае сбоя

Кастинг, instanceof и @SuppressWarnings ( "unchecked" ) являются шумными. Было бы неплохо набить их в метод, в котором им не нужно будет смотреть. CheckedCast.castToMapOf() - попытка сделать это.

castToMapOf() делает некоторые предположения:

  • (1) На карту нельзя доверять однородный
  • (2) Редизайн для избежать необходимости кастинга или экземпляра не является жизнеспособным
  • (3) Обеспечение безопасности типа в fail early является более важным, чем производительность
  • (4) Возврат Map<String,String> является достаточным (вместо возврата HashMap<String, String>)
  • (5) Аргументы типа и значения типа не являются общими (например, HashMap<String, ArrayList<String>>)

(1), (2) и (3) являются симптомами моей рабочей среды, вне моего контроля. (4) и (5) являются компромиссами, которые я сделал, потому что я еще не нашел хороших способов их преодоления.

(4) Трудно преодолеть, потому что даже если a 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)

    }
}

Некоторое чтение я нашел полезным:

Общий factory с неизвестными классами реализации

Общие и параметризованные типы

Мне также интересно, если TypeReference/токены супертекста могут помочь с (4) и (5) и стать лучшим способом для решения этой проблемы. Если вы так думаете, отправьте пример.

Ответ 1

Код выглядит неплохо, но я бы добавил предположение: (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 будет выбрасываться естественным образом, а старую ссылку на карту можно изменить, не затрагивая новую.