Casting, instanceof, and @SuppressWarnings("unchecked") are noisy. It would be nice to stuff them down into a method where they won't need to be looked at. CheckedCast.castToMapOf()
is an attempt to do that.
castToMapOf()
is making some assumptions:
- (1) The map can't be trusted to be homogeneous
- (2) Redesigning to avoid need for casting or instanceof is not viable
- (3) Ensuring type safety in an fail early manner is more important than the performance hit
- (4) Returning
Map<String,String>
is sufficient (rather than returningHashMap<String, String>
) - (5) The key and value type args are not generic (like
HashMap<ArrayList<String>, String>
)
(1), (2) and (3) are symptoms of my work environment, beyond my control. (4) and (5) are compromises I've made because I haven't found good ways to overcome them yet.
(4) Is difficult to overcome because even if a HashMap.class
was passed into a Class<M>
I haven't been able to figure out how to return a M<K, V>
. So I return a Map<K, V>
.
(5) Is probably an inherent limitation of using Class<T>
. I'd love to hear alternative ideas.
Despite those limitations can you see any problems with this java 1.5 code? Am I making any assumptions I haven't identified? Is there a better way to do this? If I'm reinventing the wheel please point me to the wheel. :)
Usage code block:
public class CheckedCast {
public static final String LS = System.getProperty("line.separator");
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)
}
Methods code block:
/** 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
);
}
}
Some reading I found helpful:
Generic factory with unknown implementation classes
Generic And Parameterized Types
I'm also wondering if a TypeReference / super type tokens might help with (4) and (5) and be a better way to approach this problem. If you think so please post an example.
Map<String,String>
is sufficient (rather than returningHashMap<String, String>
" This is usually expected, we should program to interfaces, not implementations. I will be quite concerned if there are methods expecting aHashMap
instead of aMap
... \$\endgroup\$Pair
? If it's really just something that holds two other "things", full stop, then you do realize the semantic differences between that and aMap
hmms? (we can move this to a chat room if you want to) \$\endgroup\$