So I'm working on a plugin for Atlassian Confluence and in my controller for the configuration page I have a HashMap of Type HashMap<Integer, String> that I fill with values from a HTML form. Now after submitting the form, I try to read a value from that HashMap with .get(key) and safe that to a String. I get this typecasting error: java.lang.ClassCastException: [Ljava.lang.String; cannot be cast to java.lang.String. So I looked at the values with a debugger and sure enough my HashMap contains strings wrapped in arrays of length 1 instead of plain simple strings: even tough my HashMap clearly is defined with types Integer->String and the assignment to String works without any explicit typecasting. This is really confusing me. I guess it has to do with the Atlassian stuff automagically deserializing POST-Values; in the past this has already cost me quite a lot of headaches, as there is no proper documentation and the magical background conversion has quite a lot of quirks. What really confuses me tough is the fact that the HashMap can suddenly store values of a different type than defined, I wouldn't have thought it possible with Java putting such a focus on type safety. Is there some reflection foo that can do this, that I'm unaware of? Or am I misunderstanding the nature of HashMaps? Anybody ever experienced something similar? I'm not that experienced at coding in Java.
In case you are creating hashmap as HashMap<Integer, String>() but storing it as HashMap it is indeed possible to store there other types.
For example:
HashMap map = new HashMap<Integer, String>();
map.put(1, new String[]{"1", "2"});
System.out.println(map.get(1));
This code executes without any errors.
So I think what happens is that in you are storing it just as a HashMap reference which is treated as HashMap<Object, Object> and as there is no runtime information about actual generic types you are able to add objects of other types to this collection.
But if you have another reference to the same map with HashMap<Integer, String> then when you call, for example, get(), it would fail with exception you described:
HashMap map = new HashMap<Integer, String>();
map.put(1, new String[]{"1", "2"});
System.out.println(map.get(1));
System.out.println("got here");
HashMap<Integer, String> otherRef = (HashMap<Integer, String>) map;
System.out.println(otherRef.get(1)); //<-ClassCastException exception here
Related
Considering this piece of code, in which I have two maps, a Map<String, Integer> and a Map<String, String>. I assign the second one to an Object and cast this object to a Map so that I can putAll this map to the first one.
Map<String, Integer> map = new HashMap<>();
map.put("one", 1);
Map<String, String> otherMap = new HashMap<>();
otherMap.put("two", "two");
Object obj = otherMap;
map.putAll((Map<String,Integer>)obj);
Assert.assertFalse(map.get("two") instanceof Integer);
Assert.assertEquals("{one=1, two=two}", map.toString());
The first assert ensures that the second element of my Map is not a Integer, so how come the putAll did not fail ?
The second assert is jus here to show that there is no apparent problem to this map.
How can I make sure that the putAll method will fail, when the map is first assigned to an Object ?
Thanks
Generics are a compile-time feature, and are not enforced at runtime. Your code would compile with unchecked warnings telling you exactly this: that your code might behave unexpectedly.
To massively oversimplify, all Maps are treated as a Map<Object, Object> at runtime. (Not really, but sort of.)
You could, if you really wanted, use Collections.checkedMap to wrap the map and enforce the type-safety at runtime. There's an associated performance cost though.
In the Java implementation, I found
transient Entry[] table;
which is initiated in constructor as
table = new Entry[capacity];
I know and understand that creating generic array is not allowed but then what I fail to understand is that how the whole thing works. I mean when we do something like
HashMap<Integer, String> hMap = new HashMap<Integer, String>();
How does above codes leads to creating an Entry array of type <Integer, String>
Well, few people are not able to understand what I am asking. To rephrase what I am asking is what is the point in doing something like
HashMap<Integer, String> hMap = new HashMap<Integer, String>();
When it does not result in
Entry<Integer, String>
Generics are a compile-time safety. At runtime, the map only know about Objects. This is known as type erasure. To scare you even more, the following code will run without problem:
Map<Integer, Integer> safeMap = new HashMap<>();
Map unsafeMap = safeMap;
unsafeMap.put("hello", "world");
You'll get a warning at compile time, because you're using a raw Map instead of a generic one, but at runtime, no check is done at all, because the map is a good old map able of storing any object. Only the compiler prevents you from adding Strings in a map or integers.
The implementation makes an array of Entry<K,V> objects of type
static class Entry<K,V> implements Map.Entry<K,V>
without providing generic type parameters (source). This is allowed, but it comes with understanding that the compiler is no longer guarantees type safety. For example, in other places in code you could write
Entry<K,V> e = table[bucketIndex];
and the compiler will let you do that. If you know for sure that you always set elements of table[] to null or Entry<K,V>, then you know that the assignment is correct.
The reason this works without a problem is that generic types in Java are implemented through type erasure, i.e. there is no difference at runtime between Entry<K,V> objects Entry<Integer,Integer> and Entry<String,Long>.
Try to think of Java Generics this way: type parameters only apply to the static type of reference-typed expressions and do not apply to the type of actual instances being referred to by the reference values at runtime.
I find the above key to developing the proper intuitions when reading Java code. So the next time you see
new HashMap<Integer, String>()
read it as follows: "This is an instance creation expression of the type HashMap<Integer, String>. At runtime this expression will yield a reference to an instance of the HashMap class." As long as the compiler can precisely track what you do with the result of that expression, it can maintain the knowledge that this is indeed a HashMap<Integer, String>, but no further than that.
Now, since the static type system is not powerful enough to track the type parameters on the component type of arrays (the fact that Java's array types are covariant plays strongly here), the code is forced to break out of the static type safety network. The key observation is that on its own, this does not make the code incorrect, it only constrains the power of the compiler to find programming mistakes. This is why Java allows you to make unchecked casts from raw into generic types, although not without a warning which marks the spot where you have left the provinces of static type safety.
In the Java implementation, I found
transient Entry[] table;
which is initiated in constructor as
table = new Entry[capacity];
I know and understand that creating generic array is not allowed but then what I fail to understand is that how the whole thing works. I mean when we do something like
HashMap<Integer, String> hMap = new HashMap<Integer, String>();
How does above codes leads to creating an Entry array of type <Integer, String>
Well, few people are not able to understand what I am asking. To rephrase what I am asking is what is the point in doing something like
HashMap<Integer, String> hMap = new HashMap<Integer, String>();
When it does not result in
Entry<Integer, String>
Generics are a compile-time safety. At runtime, the map only know about Objects. This is known as type erasure. To scare you even more, the following code will run without problem:
Map<Integer, Integer> safeMap = new HashMap<>();
Map unsafeMap = safeMap;
unsafeMap.put("hello", "world");
You'll get a warning at compile time, because you're using a raw Map instead of a generic one, but at runtime, no check is done at all, because the map is a good old map able of storing any object. Only the compiler prevents you from adding Strings in a map or integers.
The implementation makes an array of Entry<K,V> objects of type
static class Entry<K,V> implements Map.Entry<K,V>
without providing generic type parameters (source). This is allowed, but it comes with understanding that the compiler is no longer guarantees type safety. For example, in other places in code you could write
Entry<K,V> e = table[bucketIndex];
and the compiler will let you do that. If you know for sure that you always set elements of table[] to null or Entry<K,V>, then you know that the assignment is correct.
The reason this works without a problem is that generic types in Java are implemented through type erasure, i.e. there is no difference at runtime between Entry<K,V> objects Entry<Integer,Integer> and Entry<String,Long>.
Try to think of Java Generics this way: type parameters only apply to the static type of reference-typed expressions and do not apply to the type of actual instances being referred to by the reference values at runtime.
I find the above key to developing the proper intuitions when reading Java code. So the next time you see
new HashMap<Integer, String>()
read it as follows: "This is an instance creation expression of the type HashMap<Integer, String>. At runtime this expression will yield a reference to an instance of the HashMap class." As long as the compiler can precisely track what you do with the result of that expression, it can maintain the knowledge that this is indeed a HashMap<Integer, String>, but no further than that.
Now, since the static type system is not powerful enough to track the type parameters on the component type of arrays (the fact that Java's array types are covariant plays strongly here), the code is forced to break out of the static type safety network. The key observation is that on its own, this does not make the code incorrect, it only constrains the power of the compiler to find programming mistakes. This is why Java allows you to make unchecked casts from raw into generic types, although not without a warning which marks the spot where you have left the provinces of static type safety.
Eclipse is saying "HashMap is a raw type" When I use the following code
HashMap = new HashMap();
Any idea what could be wrong?
Eclipse will give you that warning when you use a non-Generic HashMap using Java 5 or newer.
See Also: The Generics Lesson in Sun's Java Tutorials.
Edit: Actually, here, I'll give an example too:
Say I want to map someone's name to their Person object:
Map<String, Person> map = new HashMap<String, Person>();
// The map.get method now returns a Person
// The map.put method now requires a String and a Person
These are checked at compile-time; the type information is lost at run-time due to how Java implements Generics.
Nothing wrong exactly, but you are missing out on the wonderful world of generics. Depending on what constraints you want to place on the types used in your map, you should add type parameters. For example:
Map<String, Integer> map = new HashMap<String, Integer>();
That is missing generics, i.e. . If you don't know thise then set the eclipse compiler to java 1.4
Try
HashMap<String,Integer> map = new HashMap<String,Integer>();
instead (obviously replacing the key type (String) and value type (Integer)).
That usually means you're mixing generic code with non-generic code.
But as your example wont even compile its rather hard to tell....
It's missing the generic type. You should specify the key-value generic pair for your map. For instance, the following is a declaration that instantiates a HashMap with String type key and Integer type value.
Map<String, Integer> map = new HashMap<String, Integer>();
All of these are valid answers, you could also use the #SurpressWarnings annotation to get the same result, without having to resort to actual generics. ;)
hashmap is a raw type and hence should be parameterised ie
what ever the data we get through the haspmap function their type must be declared for getting its functions
for example
HashMap<String, Integer> map = new HashMap<String, Integer>();
With the latest Java, you do not have to explicitly mention the variable types in declaration. You can simply put:
= new HashMap<>();
So i want to pass a LinkedHashMap to an intent.
//SEND THE MAP
Intent singlechannel = new Intent(getBaseContext(),singlechannel.class);
singlechannel.putExtra("db",shows1);//perase to
startActivity(singlechannel);
//GET THE MAP
LinkedHashMap<String,String> db = new LinkedHashMap<String,String>();
db=(LinkedHashMap<String,String>) getIntent().getSerializableExtra("db");
This one Worked Like a charm with HashMap.
But with LinkedHashMap i got a problem i got a runtime error here
db=(LinkedHashMap<String,String>) getIntent().getSerializableExtra("db");
I get no error with HashMap.
I also got a warning "Type safety: Unchecked cast from Serializable to LinkedHashMap"
But i had this warning with HashMap too.
Any ideas.Any help is much appreciated
Also I just saw this.
https://issues.apache.org/jira/browse/HARMONY-6498
The root of the problem here is that you are trying to type cast to a generic type. This cannot be done without an unsafe/unchecked type cast.
The runtime types of generic types are raw types; i.e. types in which the actual types of the type parameters are not known. In this case the runtime type will be LinkedHashMap<?, ?>. This cannot be safely typecast to LinkedMashMap<String, String> because the runtime system has no way of knowing that all of the keys and values are actually String instances.
You have two options:
You could add an annotation to tell the compiler to "shut up" about the unchecked cast. This is a bit risky. If for some reason one of the keys or values is not actually a String, your application may later get a ClassCastException at a totally unexpected place; e.g. while iterating the keys or assigning the result of a get.
You could manually copy the deserialised Map<?, ?> into a new LinkedMashMap<String, String>, explicitly casting each key and value to String.
UPDATE
Apparently the real cause of the runtime exception (as distinct from the "unsafe typecast" compilation error) is that Android is passing the serializedExtra between intents as a regular HashMap rather than using the Map class that you provided. This is described here:
Sending LinkedHashMap to intent.
As that Q&A explains, there is no simple workaround. If you want to pass a map preserving the key order, will have to convert the map to an array of pairs and pass that. On the other end, you will then need to reconstruct the map from the passed array.
LinkedHashMap does implement Map interface, here is definition from the source code:
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
The following test is working fine, so I think the problem you have is not related LinkedHashMap.
Map<String, String> map = new LinkedHashMap<String, String>();
map.put("1", "one");
FileOutputStream fos = new FileOutputStream("c://map.ser");
ObjectOutputStream out = new ObjectOutputStream(fos);
out.writeObject(map);
out.close();
FileInputStream fin = new FileInputStream("c://map.ser");
ObjectInputStream in = new ObjectInputStream(fin);
Map<String, String> map2 = (Map<String, String>) in.readObject();
System.out.println(map2);