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