Today, I discovered that one is able to put
an object that in an existing Map
even if the object cannot be cast to the right type.
First, let me start with a simple example:
Map<Integer, String> myMap = new HashMap<>(); //plain old hashmap
myMap.put(9,"star"); //no problem
myMap.put(10, 1.2); //Incompatible type, the compiler yells
Map<Integer, Double> aMap = (Map<Integer, Double>) myMap; //Cannot cast, the compiler yells
So far, everything is expected, as you should not be able to put an object of inconsistent type into an already constructed Map. Now let's consider this:
public class NoRulesForMe {
static Object theRing;
public static void main(String[] args){
Map<Integer, String> myMap = new HashMap<>();
myMap.put(9,"star");
Map<Integer, Double> myMapMorphed = castWildly(myMap);
myMapMorphed.put(99, 3.14);
System.out.println(myMapMorphed.get(9)); //"star", as we put in
System.out.println(myMapMorphed.get(99)); //3.14, as we put in
}
public static <T> T castWildly(Object value){
theRing = value;
T morphed = (T) theRing;
return morphed;
}
}
I'm surprised that this didn't cause a run-time error - how does Map achieve this, and is this behavior specified in the JLS or API and thus can be depended upon?
Reason I ask is that I saw a (more involved) version of this in production code, and I wonder, even if this may be befuddling and smelly, can it be guaranteed to work functionality-wise. Any input would be appreciated.
This type of coding is very risky!! Although it will compile, you will notice that the compiler complains with a warning:
Note: NoRulesForMe.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
These warnings, especially since you are using generics, should never be ignored nor just suppressed. You must absolutely be sure (logically following the code) that the cast is safe and won't be causing some problems later on. It is best to always code in such a way that errors are discovered and picked up at compiler time instead of run time. The warning given by the compiler here is telling you that things may go wrong.
You are passing your myMap
as an Object
to the method castWildly
, and when you are casting you are casting from Object
to a Map
.
The compiler can infer that T
in your code has type target of Map<String, Double>
, and thus can infer this. However, when casting, it has no information about what (sub)type the Object value
(or Object theRing
) is. So it has no way in checking that the cast is safe (specifically type safe).
The problem with this code comes in when you retrieve values out of your map. The following code has one extra added line, and the code compiles (with the same warning as above). This is because when retrieving a value as a Double
out of a map that is declared as Map<String, Double>
is absolutely valid when the compiler does type checking... at run time however, your code will crash (run time crash error shown below). This is very dangerous way to code, especially in production code. You'd rather have your compiler give you errors than deploying production code that compiles and have your product crash when live.
public class NoRulesForMe {
static Object theRing;
public static void main(String[] args){
Map<Integer, String> myMap = new HashMap<>();
myMap.put(9,"star");
Map<Integer, Double> myMapMorphed = castWildly(myMap);
myMapMorphed.put(99, 3.14);
System.out.println(myMapMorphed.get(9)); //"star", as we put in
System.out.println(myMapMorphed.get(99)); //3.14, as we put in
// added to show why this style of coding causes problems
Double testValue1 = myMapMorphed.get(9);
}
public static <T> T castWildly(Object value){
theRing = value;
T morphed = (T) theRing;
return morphed;
}
}
Run time error when running the above code:
star
3.14
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Double at NoRulesForMe.main(NoRulesForMe.java:19)
For further information, read Effective Java, by Joshua Bloch; Item 24: Eliminate unchecked warnings. (This item is under the heading Generics).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With