I want to populate a List with generic maps, but my code does not compile. I have prepared the most simplified example for the problem. In the comments above problematic lines I have put the error the line below produces.
void populateList(List<? extends Map<String,?>> list) {
list.clear();
HashMap<String, ?> map;
map = new HashMap<String,String>();
//The method put(String, capture#2-of ?) in the type HashMap<String,capture#2-of ?> is not applicable for the arguments (String, String)
map.put("key", "value"); // this line does not compile
// The method add(capture#3-of ? extends Map<String,?>) in the type List<capture#3-of ? extends Map<String,?>> is not applicable for the arguments (HashMap<String,capture#5-of ?>)
list.add(map); //This line does not compile
}
Why is this so? Is there something I do not understand?
EDIT 1
According to one of the answers below in which he pointed out that ? stands for unknown type and not a descendant of Object. This is a valid point. And also, inside the method I know the type which go into map so I have modified my simple code accordingly.
void populateList(List<? extends Map<String,?>> list) {
list.clear();
HashMap<String, String> map; //known types
map = new HashMap<String,String>();
map.put("key", "value"); // this line now compiles
// The method add(capture#3-of ? extends Map<String,?>) in the type List<capture#3-of ? extends Map<String,?>> is not applicable for the arguments (HashMap<String,capture#5-of ?>)
list.add(map); //This line STILL does not compile. Why is that?
}
The reason I am asking this is because a method form android SDK expects such list and as it seems one cannot populate such lists. How does one do that? Typecast?
EDIT 2
Since there several proposals to change my signature I will add that I cannot do that. Basicaly, I would like to populate lists for SimpleExpandablaListAdapter.
void test() {
ExpandableListView expandableListView.setAdapter(new ArrayAdapterRetailStore(this, R.layout.list_item_retail_store, retailStores));
List<? extends Map<String, ?>> groupData= new ArrayList<HashMap<String,String>>();
populateGroup(groupData)
// child data ommited for simplicity
expandableListView.setAdapter( new SimpleExpandableListAdapter(
this,
groupdata,
R.layout.list_group,
new String[] {"GroupKey"},
new int[] {R.id.tvGroupText},
childData,
R.layout.list_item_child,
new String[] {"ChildKey"},
new int[] {R.id.tvChilText}));
}
// I want populateGroupData() to be generic
void populateGroupData(List<? extends Map<String,?>> groupData) {
groupData.clear();
HashMap<String,String> map;
map = new HashMap<String,String>();
map.put("key", "value");
groupData.add(map); // does not compile
}
From the documentation
When the actual type parameter is ?, it stands for some unknown type. Any parameter we pass to add would have to be a subtype of this unknown type. Since we don't know what type that is, we cannot pass anything in. The sole exception is null, which is a member of every type.
so, you can add only
list.add(null);
Please read this tutorial on Generics Wildcards
here is the working code
//also works with void populateList(List<Map<String,?>> list) {
void populateList(List<? super Map<String,?>> list) {
list.clear();
Map<String, String> map;
map = new HashMap<String,String>();
map.put("key", "value"); // this line now compiles
list.add(map); //This line compiles
}
and why it works:
List<? super Map<String,?>> list or simply List<Map<String,?>> list
// => this ensure you that the list can contains a Map<String,?>.
Map<String, String> map is a Map<String,?>
// =>that can ber inserted to the list, so you don't need any cast
Edit:
The common mistake is that the wildcard "? extends Map" will limit the function call to a list that is "at least" typed with map. This is not what you want, because you could pass a List<TreeMap<String,?>>which can not contain a HashMap for example. Additionnaly you couldn't call your method with a List<Object>
-> To illustrate generics limitation i have added 2 examples with super and extends
void exampleWithExtends(List<? extends Map<String,?>> list) {
}
void exampleWithSuper(List<? super Map<String,?>> list) {
}
void funWithGenerics(){
exampleWithExtends(new ArrayList<TreeMap<String,String>>());
exampleWithExtends(new ArrayList<Map<String,?>>());//works in both cases
//exampleWithExtends(new ArrayList<Object>()); /does not compile
//exampleWithSuper(new ArrayList<TreeMap<String,String>>()); //does not compile
exampleWithSuper(new ArrayList<Map<String,?>>());//works in both cases
exampleWithSuper(new ArrayList<Object>());
}
There is no way you can write Map<String, String> map = getMap("abc"); without a cast
The problem has more to do with easymock and the types returned/expected by the expect and andReturn methods, which I'm not familiar with. You could write
Map<String, String> expected = new HashMap<String, String> ();
Map<?, ?> actual = getMap("someKey");
boolean ok = actual.equals(pageMaps);
//or in a junit like syntax
assertEquals(expected, actual);
Not sure if that can be mixed with your mocking stuff. This would maybe work:
EasyMock.expect((Map<String, String>) config.getMap("sillyMap")).andReturn(pageMaps);
Also note that you can't add anything to a generic collection with a wildcard. So this:
Map<?, ?> map = ...
map.put(a, b);
won't compile, unless a and b are null
Java is type-safe! At least at this point :)
This will do the trick:
HashMap<String, String> map = new HashMap<String,String>();
map.put("key", "value");
((List<Map<String,String>>)groupData).add(map);
Related
How could I add string and int value to a HashMap same time ?
HashMap<String,?> map =new HashMap<>();
map.put("sss", "str");
map.put("sss", 1);
How Android SharedPreferences.getAll() method did this?
Error message
Found 'java.lang.String', required: '?'
You can't add anything (other than literal null) to a Map<String, ?>, because it could be the wrong type. For example:
Map<String, Long> longMap = ...;
Map<String, ?> wildcardMap = longMap;
wildcardMap.put("", ""); // Compiler error!
// but if it did work, the following would be a runtime error:
Long value = longMap.values().iterator().next();
Remember that ? is a shorthand for ? extends Object; and the acronym PECS tells you that something which extends is a producer, not a consumer. So, you can't invoke a consumer method on an instance of it (unless you pass null).
If you want to put heterogeneous values into the map, the value type has to be a common superclass of the types.
Map<String, Object> mapForBoth = ...
mapForBoth.put("key1", "string");
mapForBoth.put("key2", 1);
(Actually, Serializable is a more specific common superclass of String and Integer; it's up to you as to whether that's a better value type)
Try this
HashMap<String,Object> map =new HashMap<>();
map.put("sss", "str");
map.put("sss", 1);
Sorry for the yet another "Java generic method is not applicable" question. I would like to know what am I missing in my understanding:
List<E> is a subtype of Collection<E>
--> meaning, List<String> is a subtype of Collection<String>
Suppose A extends B, List<A> is not a subtype of List<B>
--> but in this case, there's only one type T (or String), so I don't see how the Substitution Principle can explain my problem?
Problem Code:
private <T, K> void genericAddToMapOfLists(HashMap<K, Collection<T>> mapOfLists,
K key, T value) {
if (mapOfLists.containsKey(key)) {
mapOfLists.get(key).add(value);
} else {
List<T> newList = new ArrayList<T>();
newList.add(value);
mapOfLists.put(key, newList);
}
}
private void parseToFruitList(HashMap<String, List<String>> fruit_colors,
String fruitName) {
String color = "";
genericAddToMapOfLists(fruit_colors, fruitName, color);
}
Error:
The method genericAddToMapOfLists(HashMap<K,Collection<T>>, K, T) in the type MyGroceryStore is not applicable for the arguments (HashMap<String,List<String>>, String, String)
The code works when I change the method signature to genericAddToMapOfLists(HashMap<K,List<T>>, K, T).
This is exactly the problem you are explaining in your second point.
Suppose A extends B, List<A> is not a subtype of List<B>
In this case your method expects
HashMap<?, Collection<?>>
but you are giving it
HashMap<?, List<?>>
List extends Collection, but HashMap<?, List> is not a subtype of HashMap<?, Collection>
(I'm not using ? as a wildcard, we just don't care about it right now)
You're right in that "List<String> is a subtype of Collection<String>". And if A extends B, List<A> is not a subtype of List<B>.
Taking that one step further, a HashMap<String, List<String>> is not a HashMap<String, Collection<String>>.
The same reasoning applies, where A is List and B is Collection. If a HashMap<String, List<String>> was a HashMap<String, Collection<String>>, then you could put a Vector<String> into a HashMap<String, List<String>> by assigning it to a HashMap<String, Collection<String>>, even though a Vector isn't a List, and so it's not allowed.
I have made two Lists like
List<LearnerEnrollment> learnerEnrollmentList = new ArrayList<LearnerEnrollment>();
List<LearnerCourseEnrollError> enrollErrorList = new ArrayList<LearnerCourseEnrollError>();
Then i made two Maps like
Map<String, List<LearnerCourseEnrollError>> courseErrorMap = new HashMap<String, List<LearnerCourseEnrollError>>();
Map<String, List<LearnerEnrollment>> courseSuccessMap = new HashMap<String, List<LearnerEnrollment>>();
Then i made another Map to hold the above two Maps like
Map<String, Map<String, List<Object>>> courseMap = new HashMap<String, Map<String, List<Object>>>();
Then i use the following code to add items in lists;
for (com.softech.vu360.lms.model.Course course : courseList) {
Object result = getEnrollmentForCourse(customer, learner, course);
if (result instanceof LearnerEnrollment) {
LearnerEnrollment newEnrollment = (LearnerEnrollment)result;
learnerEnrollmentList.add(newEnrollment);
} else if (result instanceof String) {
String errorMessage = (String)result;
LearnerCourseEnrollError enrollError = new LearnerCourseEnrollError(errorMessage, course);
enrollErrorList.add(enrollError);
}
}
Now i am putting values in the Map
courseSuccessMap.put(learner.getVu360User().getUsername(), learnerEnrollmentList);
courseErrorMap.put(learner.getVu360User().getUsername(), enrollErrorList);
courseMap.put("successfulCoursesMap", courseSuccessMap);
courseMap.put("unSuccessfulCoursesMap", courseErrorMap);
return courseMap;
But i am getting error at these two lines
courseMap.put("successfulCoursesMap", courseSuccessMap);
courseMap.put("unSuccessfulCoursesMap", courseErrorMap);
that
The method put(String, Map<String,List<Object>>) in the type
Map<String,Map<String,List<Object>>> is not applicable for the arguments
(String, Map<String,List<LearnerEnrollment>>)
The method put(String, Map<String,List<Object>>) in the type
Map<String,Map<String,List<Object>>> is not applicable for the arguments
(String, Map<String,List<LearnerCourseEnrollError>>)
Why?
My list type in the Map is List<Object> and List<LearnerEnrollment> is List <Object> because LearnerEnrollment extends Object. Why I am getting these errors ?
If i declare my Map like this
Map<String, Map<String, ?>> courseMap = new HashMap<String, Map<String, ?>>();
Then there is no error. Why i am getting error in first case?
Thanks
You said:
List<LearnerEnrollment> is List<Object>
This is wrong. If it were true, you would be able to do:
List<<LearnerEnrollment> list = new ArrayList<>();
List<Object> objectList = list;
objectList.add("Now what?");
And your type-safe list of LearnerEnrollment would suddenly contain a String.
This is because a List in java is not covariant. A List<LearnerEnrollment> is not a subclass of List<Object>.
See Java covariance for more information.
Here is the scenario:
public static <T> List<T> isTriggeredByBlackList(Map<String, T> params, Class<T> clz) {
System.out.println(clz.getName());
return null;
}
What I want is to pass either String or List<String> to this method.
When it comes to String, it works just fine:
Map<String, String> map1 = new HashMap<String, String>();
map1.put("11", "22");
isTriggeredByBlackList(map1, String.class);
But When I tried to pass a List<String>, it goes wrong:
Map<String, List<String>> map = new HashMap<String, List<String>>();
List<String> l = new ArrayList<String>();
l.add("11");
l.add("22");
map.put("1", l);
isTriggeredByBlackList(map, List.class); //compile error!
With compile error as below:
The method isTriggeredByBlackList(Map<String,T>, Class<T>) in the type CommonTest is not applicable for the arguments (Map<String,List<String>>, Class<List>)
What I need is to write just one method which is suitable to both String type as well as List<String> type.
Could anyone help me out? Thanks a lot!
Change the signature of your method:
public static <T> List<T> isTriggeredByBlackList(Map<String, ? extends T> params, Class<T> clz)
Why does this work?
The expression ? extends T just means that any type that is a subtype of T (and of course T itself) is accepted.
What is a wildcard?
So when method isTriggeredByBlackList is called like that:
isTriggeredByBlackList(map, List.class);
... T is specified to be a (raw) List type, and so the first parameter must be a Map<String, any-type-that-extends-raw-List>, which is true for Map<String, List<String>> (because List<String> is a subtype of (raw) List).
But why is a List a subtype of (raw) List?
Generics are tricky, because polymorphism does not work as expected (on the first sight). A List<String> is-NOT-a List<Object>, although String extends Object! So this won't work:
List<Object> objList = new ArrayList<String>(); // compile error
(Note: It's good that this is not possible, but that's another story)
But a List<String> IS-a (raw) List (and it is-a List<?>). So this works:
List rawList = new ArrayList<String>(); // just compiler warning
List<?> unknownList = new ArrayList<String>();
The reason is: Raw types are still supported to be backwards-compatible! Otherwise old code that does not support generics could not be used nowadays. So any instance of a concrete parametrized type (e.g. ArrayList<String>) can be assigned to a reference of its raw type (e.g. ArrayList) or super types (e.g. List)!
Why are raw types permitted?
Why can't we pass something like List<String>.class?
Because parameterized types have no exact runtime type representation!
Why is there no class literal for concrete parameterized types?
But I want to get a List<List<String>> as return value!
This is no problem! Just define the left side of your assignment to be a List<List<String>>, and java does the rest:
List<List<String>> l = isTriggeredByBlackList(map, List.class);
BUT! This only works, if you slightly modify your method declaration:
public static <T> List<T> isTriggeredByBlackList(
Map<String, ? extends T> params, Class<? super T> clz)
(Otherwise you can't pass the raw List.class as second argument).
If all this modifications and tweaks make sense depends on the task your method should fulfill! Is it only reading from params? How does it make use of the generic type arguments? What is the advantage of using generics here? etc.
Btw.: Methods that start with is... should return a boolean value. Consider renaming your method!
Btw 2.: The return type of your method is List<T>, so in case you're specifying T to be a List, you'll get a List<List>. Is this intended?
There are 3 related points here, with this code.
public static <T> List<T> isTriggeredByBlackList(Map<String, T> params, Class<T> clz) {
System.out.println(clz.getName());
return null;
}
1) The requirement is to support only String or List<String> as parameter in map value.
That is not possible to meet in single (Generic) method.
It will require overloaded methods (as Steve P. mentioned) instead of Generic.
2) If we relax that and use generic method. Then the above definition can be used as it is, if we change the map's type.
Map<String, List> m = new HashMap<String, List>(); // The Raw List.
isTriggeredByBlackList(m, List.class);
3) If the method signature is changed to:
static <T> List<T> isTriggeredByBlackList(Map<String, ? extends T> params, Class<T> clz)
As #isnot2bad has explained quite well, it accepts all Lists as in case of #2.
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