Приведите исходную карту к общей карте, используя метод, аккуратно и безопасно, в раннем порядке.

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

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

  • (1) Нельзя доверять карте как однородной
  • (2) Редизайн, чтобы избежать необходимости в кастинге или instanceof, нежизнеспособен
  • (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 / super type помочь с (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 будет выброшен естественным образом, и старая ссылка на карту все еще может быть изменена, не затрагивая новую.

Другие вопросы по тегам