I need to use a Map with a List inside :
Map<String, List<String>> keyToGroup = new HashMap<String, ArrayList<String>>();
I am getting compiler error on this line in eclipse.
The only working thing seem to be changing the inside List in the Map to ArrayList
Map<String, ArrayList<String>> keyToGroup = new HashMap<String, ArrayList<String>>();
I had to change the signature of many interfaces' methods, but I still don't get it; why isn't the first definition work?
Isn't it the same, should not
Map<String, List<String>> keyToGroup
&
Map<String, ArrayList<String>>
be the same?
No, they're not. Consider this:
Map<String, List<String>> keyToGroup = new HashMap<String, ArrayList<String>>();
keyToGroup.put("foo", new LinkedList<String>());
The second line is fine, because a LinkedList<String> is a List<String> - but it's not logically fine in terms of adding it to a HashMap<String, ArrayList<String>>, because a LinkedList<String> is not an ArrayList<String>.
To make it clearer:
Map<String, ArrayList<String>> map1 = new HashMap<String, ArrayList<String>>();
Map<String, List<String>> map2 = map1; // This is invalid
map2.put("foo", new LinkedList<String>());
ArrayList<String> oops = map1.get("foo"); // Because this would be broken
This isn't just the case with collections as the type argument. It's even simpler to see with normal inheritance:
List<Banana> bunchOfBananas = new ArrayList<Banana>();
List<Fruit> fruitBowl = bunchOfBananas; // Invalid!
fruitBowl.add(new Apple());
Banana banana = bunchOfBananas.get(0);
Even though every banana is a fruit, so a "collection of bananas" is a "collection of fruit* in the sense of fetching them, not every fruit is a banana.
You can use wildcard parameterized types to help in some cases, but it depends on exactly what you're trying to achieve.
Ask yourself a question if you need particular list implementation in your Map or any List?
In case of particular implementation you can use your last example:
Map<String, ArrayList<String>> keyToGroup = new HashMap<String, ArrayList<String>>();
In case of any list just use:
Map<String, List<String>> keyToGroup = new HashMap<String, List<String>>();
keyToGroup.put("arraylist", new ArrayList<String());
keyToGroup.put("linkedlist", new LinkedList<String());
BTW the second option usually is better from design point of view so if you don't know exactly for now - try using second option first.
No they are not. Generics are not covariant in Java.
If they are covariant you can logically put any type of List instead of ArrayList which defeats the purpose of having generics.
Consider reading Effective Java (2nd Edition) Chapter 5: Generics which has very good explanation of Generics.
Another good read is http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html
Related
I have HashMap which generic type <Integer, String> i.e. key should be an Integer and value should be String for this HashMap.
I wrote bellow code which put String and getting no compilation and runtime error. Why?
Map map = new HashMap<Integer, String>();
map.put("a", "one");
System.out.println(map);
OUTPUT:
{a=one}
I have HashMap which generic type <Integer, String> ...
No you do not!
Map map = new HashMap<Integer, String>();
Means you have just a Map (because of Map map =). If you want Map<Integer, String> you must use:
Map<Integer, String> map = new HashMap<Integer, String>();
or, in later versions of Java
Map<Integer, String> map = new HashMap<>();
Added
The reason for this is that the right-hand-side of the assignment is a separate process and is evaluated first. In your case it creates a HashMap<Integer, String>.
Next the assignment happens, the compiler checks that HashMap<Integer, String> can be cast to Map (which is equivalent to Map<Object,Object> BTW) and the assignment is performed. From then on all references to map treat it as type Map<Object,Object> and can therefore hold any type for key or value.
You are adding content to Map map declared without specifying any generics types.
If you declare the map this way the compilator doesn't know how to check the map content.
If you change your map declaration to
Map<Integer, String> map = new HashMap<>();
Then you will have a compilation error.
Map map = new HashMap<Integer, String>();
Here your definition is type specified, however declaration is not. So, you are able to add any type to map.
The proper way for generic map declaration is
Map<Integer, String> map = new HashMap<Integer, String>();
or in new versions of Java, you can skip type in defintion.
Map<Integer, String> map = new HashMap<>();
Defining generics on the right side is more or less obsolete (grey font).
Following code wouldn't compile:
Map<Integer, String> map = new HashMap<>();
map.put("a", "one");
System.out.println(map);
with this explanation:
Wrong 1st argument type. Found: 'java.lang.String', required: 'java.lang.Integer'
in java, what is wrong with this assignment:
Map<String, Object> mObj = new HashMap<String, String[]>();
I get:
error: incompatible types: HashMap<String,String[]> cannot be converted to Map<String,Object>
Since String[] is an Object, that should work.
If I cast to an unparameterized Map like this: Map<String, Object> mObj = (Map) new HashMap<String, String[]>();, it is working but of course, I get a warning and it is dirty.
Further more, I feel that my first assignment should work.
Thank you !
PS: I cannot simply change new HashMap<String, String[]>(); to new HashMap<String, Object>(); because in reality, I call a method that returns a Map<String, String[]>(); and of course, I cannot change this method. Thank you again.
The error is is because generics do not support subtyping. Number a = new Integer(5) is valid case. But once you put generics it gives compilation error ArrayList<Number> a = new ArrayList<Integer>() is not allowed. See if this link helps https://dzone.com/articles/5-things-you-should-know-about-java-generics to understand some guidelines on Generics.
Lets' see what could happen if what you wrote would be possible:
HashMap<String, String[]> foo = new HashMap<String, String[]>();
Map<String, Object> bar = foo;
bar.put("key",new Object());
String[] baz = foo.get("key"); // <-- ClassCastException
See the problem? A series of normal, mundane operations will cause ClassCastException in a place where you would expect it wouldn't be possible to emit one.
Edit: To summarize, Map<String, Object> is not a supertype of Map<String, String>, Map<String, Object[]> or Map<String, String[]>, so the assignment will not work, as the types aren't compatible. This is where wildcards come in; see this answer and this one too
Do this:
Map<String, ? extends Object> mObj = new HashMap<String, String[]>();
But I can't tell you why your solution doesn't work.
Can I do something like this in Java?
HashMap<String, Child> childMap=new HashMap<String, Child>();
HashMap<String, childMap.typeName> parentMap=new HashMap<String, childMap.typeName>();
//instead of
HashMap<String, HashMap<String, Child>> parentMap=new HashMap<String, HashMap<String, Child>>();
or something like this
HashMap<String, HashMap<String, Child>> parent1=new HashMap<String, HashMap<String, Child>>();
parent1.typeName parent2=new parent1.typeName;
Because some time, if the map level is too deep or too complex, it is very hard to write and read.
Abbreviations are possible by defining a subclass:
class Str2Child extends HashMap<String, Child>>{}
class Str2Map extends HashMap<String,Str2Child>{}
Str2Map parent1 = new Str2Map();
No but you could shorten it if you're using Java 7 or higher. The compiler can infer the type parameters from the left side of the assignment and you can skip them altogether while creating the object HashMap<String, HashMap<String, Child>> parentMap = new HashMap<>();
In older versions of Java, you could resord to Guava's Maps class and its newHashMap method. HashMap<String, HashMap<String, Child>> parentMap = Maps.newHashMap();
Another thing you could possibly do is create a type that implements a certain specification of the generic HashMap.
public class HashMapStringChild extends HashMap<String, Child> {
}
and then use it as a type parameter
HashMap<String, HashMapStringChild> parent2 = new HashMap<>();
but personally, I find this a bit of a stretch. I certainly wouldn't overuse it and I'd be careful extending the collection classes.
Addendum
You should also note that you're effectively binding your API to a specific implementation of the Map interface (HashMap), or even worse, in case of introducing the new class (HashMapStringChild), to a specific, non-standard implementation.
What if at some point, you decide to keep your Child objects sorted at all times? You could do this by switching to a TreeMap but that would mean a big deal of refactoring.
You would be better off basing your API on a more general interface. This way you could switch from
Map<String, Map<String, Child>> map = new HashMap<String, HashMap<String, Child>>();
to
Map<String, Map<String, Child>> map = new HashMap<String, TreeMap<String, Child>>();
or
Map<String, Map<String, Child>> map = new TreeMap<String, TreeMap<String, Child>>();
or any other implementation without a hassle.
If you really want to make the map of String to Child a specific type, you could introduce an interface
public interface MapStringToChild extends Map<String, Child> {
}
Then you could keep your reference types general and use HashMap<String, Child>, TreeMap<String, Child>, HashMapStringChild or literally any other implementation mapping a String to a Child interchangeably, while keeping the code short.
Is there any difference between the following declarations -
List<String> list = new ArrayList<String>();
and
List<String> list = new ArrayList<>();
In both cases anyhow , list will have elements of type String only.
There is no difference. However, the first one is legal in Java <= 7 whereas the second one is legal only in Java 7 and was introduced as a short-hand notation*. The compiler will infer the generic type from the declaration.
*It was basically introduced to remove redundant information and reduce code-noise. So you now have:
Map<String, List<String>> myMap = new HashMap<>();
versus:
Map<String, List<String>> myMap = new HashMap<String, List<String>>();
The first one is a lot easier on the eyes.
Consider my custom extended hashmap:
public class CustomHashMap extends HashMap<String, Object> {
...
}
Why doesn't this work since CustomHashMap is child of HashMap?
Map<String, HashMap<String, Object>> customs = new LinkedHashMap<String, CustomHashMap>();
But this works:
Map<String, HashMap<String, Object>> customs = new LinkedHashMap();
And also it works when adding (put) an CustomHashMap into the customs Map.
customs.put("test", new CustomHashMap());
It seems weird that not specifying the generics at initialization works, but it doesn't otherwise.
This statement is not working
Map<String, HashMap<String, Object>> customs = new LinkedHashMap<String, CustomHashMap>();
because customs is of type Map<String, HashMap<String, Object>> and you are assigning a LinkedHashMap which is of type <String, CustomHashMap>, where CustomHashMap is a sub class of HashMap<String, Object>.
Generics are invariant: for any two distinct types T1 and T2, HashMap<String, T1> is neither a subtype nor a supertype of HashMap<String, T2>. So, LinkedHashMap<String, CustomHashMap> cannot be assigned to Map<String, HashMap<String, Object>>. On the other hand, arrays are covariant, which means below statement will compile without any error or warning. But, it might fail at run time (which might cause more harm) if you put any other subtype of HashMap<String, Object> into it other than CustomHashMap :
HashMap<String, Object>[] mapArray = new CustomHashMap[1];
mapArray[0] = new CustomHashMap_1();// this will throw java.lang.ArrayStoreException
Now, if you want to assign LinkedHashMap<String, CustomHashMap> to Map<String, HashMap<String, Object>> , change the statement to this:
Map<String, ? extends HashMap<String, Object>> customs = new LinkedHashMap<String, CustomHashMap>();
Some additional information about this approach is nicely explained by #Seelenvirtuose , which is the accepted answer.
When working with generics, you should always keep type erasure in mind. At runtime an objct of type Map does not know its type parameters anymore. The consequence: A LinkedHashMap<String, CustomHashMap> is not a sub-type of Map<String, HashMap<String, Object>>.
If you want to have somthing sub-type related you must do it the following way:
Map<String, ? extends HashMap<String, Object>> customs = new LinkedHashMap<String, CustomHashMap>();
This is called an upper-bounded wildcard and exists exactly for that case: To get a sub-type relationship. Please refer to the Java tutorial about generics for more information.
An additional info as per the comment:
The upper-bounded version has a disadvantage on how to use the customs map. You cannot put instances anymore into that map. The only value allowed is null. The reason is, that you could have another class extending Map<String, HashMap> and try to put an instance of that into your customs map. But this is a problem, as the variable customs refers to a map that was parameterized with CustomHashMap.
When working with bounded wildcards, you should always remind PECS. PECS stands for "producer extends, consumer super". This is valuable for method parameters. If you write a method that only needs to read values from such a map, you could type the parameter as Map<String, ? extends Map<String, Object>>. This is called a producer. If you only need to write to that map, use the keyword super. If you need both - read and write - you cannot do either.
From the java tutorial on oracle's site
List<String> ls = new ArrayList<String>(); // 1
List<Object> lo = ls; // 2
Line 1 is certainly legal. The trickier part of the question is line 2. This boils down to the question: is a List of String a List of Object. Most people instinctively answer, "Sure!"
Well, take a look at the next few lines:
lo.add(new Object()); // 3
String s = ls.get(0); // 4: Attempts to assign an Object to a String!
Here we've aliased ls and lo. Accessing ls, a list of String, through the alias lo, we can insert arbitrary objects into it. As a result ls does not hold just Strings anymore, and when we try and get something out of it, we get a rude surprise.
The Java compiler will prevent this from happening of course. Line 2 will cause a compile time error.
this link would help you to learn generics and subtyping