Putting into a Map<String, ?> - java

So I have a Map that has some values in it being passed into a method:
public String doThis(Map<String, ?> context){
.....
}
And I'm trying to insert an addition attribute to this Map
String abc="123";
context.put("newAttr",abc);
But I am getting this error:
The method put(String, capture#8-of ?) in the type Map is not applicable for the arguments (String, String)
Is there anyway to perform this put without "cloning" the Map?

If you want to put values of type X into a generic Map you need to declare the Map as Map<String, ? super X>. In your example X is String, so:
public String doThis(Map<String, ? super String> context){
.....
}
Map<String, ? super X> means: a map with keys of type String and values of a type which is X or a super-type of X. All such maps are ready to accept String instances as keys and X instances as values.

Remember PECS (Producer Extends, Consumer Super). You have a consumer (putting in), therefore it cannot be extends.

Surprisingly we can convert this map into an easier to use form. Just with this simiple syntax: (Map<String, ObjectOrSth>)unfriendlyMap.
// Let's get this weird map.
HashMap<String, String> mapOrig = new HashMap<String, String>();
Map<String, ?> mapQuestion = (Map<String, ?>)mapOrig;
//mapQuestion.put("key2", "?"); // impossible
// Convert it to almost anything...
Map<String, String> mapStr2 = (Map<String, String>)mapQuestion;
mapStr2.put("key2", "string2");
assertThat(mapOrig.get("key2")).isEqualTo("string2");
Map<String, Object> mapObj = (Map<String, Object>)mapQuestion;
mapObj.put("key3", "object");
assertThat(mapOrig.get("key3")).isEqualTo("object");

Related

why HashMap<String, Object> not accept HashMap<String, List> instance?

I am new in java generics and facing following issues.
I have have method like,
private static void fillDescriptiveData(HashMap<String, Object> output, String attributeMapping) {
for (Map.Entry<String, Object> outputInEntry : output.entrySet()) {
String outputKey = outputInEntry.getKey();
String outputValue = outputInEntry.getValue().toString();
outputValue = getDescriptiveDataForOutput(outputKey, outputValue, attributeMapping);
outputInEntry.setValue(outputValue);
}
}
Now if I call API as below way
HashMap<String, Object> ObjectMap = new HashMap<String, Object>();
HashMap<String, List> listMap = new HashMap<String, List>();
fillDescriptiveData(ObjectMap,"here");
this one working fine.
fillDescriptiveData(listMap,"here");
this call gives error
The method fillDescriptiveData(HashMap, String) in the type CustomAttribute is not applicable for the arguments (HashMap, String)`
why ?
In row to solve this issue I encounter with one more issue,
private static void fillDescriptiveData(HashMap<String, ? extends Object> output, String attributeMapping) {
for (Map.Entry<String, ? extends Object> outputInEntry : output.entrySet()) {
String outputKey = outputInEntry.getKey();
String outputValue = outputInEntry.getValue().toString();
outputValue = getDescriptiveDataForOutput(outputKey, outputValue, attributeMapping);
outputInEntry.setValue(outputValue); /* Error comes at this line */
}
}
HashMap<String, ? extends Object> ObjectMap = new HashMap<String, Object>();
HashMap<String, List> listMap = new HashMap<String, List>();
fillDescriptiveData(ObjectMap,"here");
fillDescriptiveData(listMap,"here");
error at line - outputInEntry.setValue(outputValue);
The method setValue(capture#4-of ? extends Object) in the type
Map.Entry is not applicable for
the arguments (String)
why ?
What is the best way to avoid this issues ?
This is the case when you could use type variables:
private static <T> void fillDescriptiveData(Map<String, T> output,String attributeMapping)
{
for(Map.Entry<String, T> outputInEntry : output.entrySet())
{
String outputKey = outputInEntry.getKey();
String outputValue = outputInEntry.getValue().toString();
outputValue = getDescriptiveDataForOutput(outputKey, outputValue, attributeMapping);
outputInEntry.setValue((T) outputValue);
}
}
More specifically, your second type-parameter in the map is unbounded. Object will not work here as it is specific class. ? extends Object is somewhat nonsense.
Just HashMap<String, ?> would work until you will just read the map, but you will not be able to put something here. So only one way - using type variable.
EDIT: One more thing: please, use interfaces where it's possible. So here instead of HashMap<String, T> better use Map<String, T>. It isn't a mistake, just good and proper style of code.
The error with this line:
outputInEntry.setValue(outputValue);
Is that you're always putting a string into the entry. This will only work if the entry is of type ? super String, or exactly String. So it will not work for a Map<String, Object> or Map<String, List>.
It seems like you just want to map each value to a string. You can do it, but to be type safe, you need to create a new Map<String, String>. Since you're always mapping to a String.
If you for instance pass in a Map<String, List<?>> and (unsafely) replace all the values with strings. Someone could still keep using the Map<String, List<?>> that was passed into the function, but it now contains strings as values instead of lists. When they try to retrieve a List from it they get a class cast exception.
Something like this:
private static Map<String, String> fillDescriptiveData(HashMap<String, ?> input,
String attributeMapping) {
Map<String, String> output = new HashMap<>();
for(Entry<String, ?> e : input.entrySet()) {
String outputKey = e.getKey();
String outputValue = e.getValue().toString();
outputValue
= getDescriptiveDataForOutput(outputKey, outputValue, attributeMapping);
output.put(outputKey, outputValue);
}
return output;
}
Map<String, String> r1 = fillDescriptiveData(ObjectMap, "here");
Map<String, String> r2 = fillDescriptiveData(listMap, "here");

Generics issue with Java

Here is my code snippet:
Map<String, ? extends Object> data = this.aub.getData();
//... some code ...
data.put("ip_macs", new LinkedList<Object>()); //gets error
The error that I get at the marked line is (the message is taken from Eclipse IDE):
The method put(String, capture#3-of ? extends Object) in the type Map is not applicable for the arguments (String, LinkedList)
Does anyone have any idea why that? As long as LinkedList is a subtype of Object, I think that error does not have any reason to appear. Where do I misunderstand this issue?
Specify the generics type as
Map<String, Object> data = this.aub.getData();
data.put("ip_macs", new LinkedList<Object>()); // Compiles
When you say Map<String, ? extends Object> it means a Map whose key is of type String and the value extends Object but its type is unknown ?. Since, the type is not known it's unsafe to insert a LinkedList object there.
Basically, the compiler is trying to prevent this:
Map<String, String> mapOfStrings = new HashMap<String, String>();
mapOfStrings.add("string", "value");
Map<String, ? extends Object> map = mapOfStrings; // Compiles
map.add("string", 1); // ERROR!
If this was allowed, you just circumvented the type safety offered by generics.

Java type captures for wildcards in collections

I would like to do this (minimal repro):
String key = "foo";
Object value = new Bar();
if (target instanceof Map<?,?>) {
Map<?,?> map = (Map<?,?>)target;
map.put(key, value);
...
But I am told this:
The method put(capture#25-of ?, capture#26-of ?) in the type
Map is not applicable for the
arguments (String, Object)
It seems like String and Object should both be okay here. My question has two parts: (1) why? And (2) how can I make something like this work?
The problem is that collections that use unbounded wildcards don't allow elements to be added to them. If they did, you could cast the collection to have more specific type parameters, and all of a sudden the type-safety that generics are supposed to offer is gone:
Map<?,?> map = (Map<?,?>)target;
map.put(key, value); // Not actually allowed
Map<String, String> evilMap = (Map<String, String>)map;
String barAsString = evilMap.get(key); // But it's actually a Bar!
Map<?, ?> really means, Map<? extends Object, ? extends Object> According to Java typesystem, you can only get values of type parameters specialized with ? extends wildcard from method calls. You can't pass it to the methods.
P.S. Probably you want Map<Object, Object>
This blog post provides the answer. In short, the compiler doesn't know if Map<?, ?> is really a Map<String, Object> or, say, a Map<Integer, Double>, so it can't guarantee type safety for you.

Why i can't put string into a field which is something that extends Object

I have a map like the one below
final Map<String, ? extends Object> map
Can anyone tell me why this operation is not possible..?
productMap.put("min", String.valueof(34));
What should be the turnaround...
You can't add any object to a Map because the compiler knows the value is some class which extends Object, but doesn't know which one.
Map<String, ? extends Object> map = new HashMap<String, Integer>();
Object val = map.get("min"); // this is ok.
map.put("min", Integer.toString(34)); // not allowed.
Instead you can use
Map<String, Object> map = new HashMap<String, Object>();
Object val = map.get("min"); // this is ok.
map.put("min", Integer.toString(34)); // is ok.
The wildcard provides flexibility (you can now assign a HashMap<String, String> or a HashMap<String, Integer> to map) in exchange for a condition: you cannot write to map, because it doesn't know what the actual class of the values will be.
See here for a good tutorial.
You've told the compiler that the map values will be some specific subtype of Object. ? could be anything -- you could do:
Map<String,? extends Object> map = new HashMap<String,Integer>();
So String might be invalid.
You probably want the simpler Map which does allow any value.
Or you can "cheat" and do a cast which hides the generic type:
((Map)productMap).put("min", String.valueof(34));
But that trick is not best practice & to be used sparingly if at all.
By declaring the map as Map<String, ? extends Object>, you state that the second type is "anything which inherits from object". This is equivalent to declaring the map as Map<String, ?>. This declaration enables assignments like
final Map<String, ? extends Object> map = new HashMap<String, Integer>();
I think this example makes it clear why the compiler forbids inserting strings into the map: The value type is unspecified in the declaration.

Java Casting Problem

Why isn't a Map<String,List<SomeBean>> castable to Map<String,List<?>>?
What I'm doing now is this:
Map<String, List<SomeBean>> fromMap = new LinkedHashMap<String, List<SomeBean>>();
/* filling in data to fromMap here */
Map<String,List<?>> toMap = new LinkedHashMap<String, List<?>>();
for (String key : fromMap.keySet()) {
toMap.put(key, fromMap.get(key));
}
In my opinion there should be a way around this manual transformation, but I can't figure out how. Any Ideas?
The cast is invalid because in Map<String,List<?>> you can put List<String> and List<WhatEver>, but not in Map<String, List<SomeBean>>.
For instance:
//List<SomeBean> are ok in both lists
fromMap.put("key", new ArrayList<SomeBean>());
toMap.put("key", new ArrayList<SomeBean>());
//List<String> are ok in Map<String,List<?>>, not in Map<String, List<SomeBean>>
fromMap.put("key", new ArrayList<String>()); //DOES NOT COMPILE
toMap.put("key", new ArrayList<String>());
To simplify your code, you may use the appropriate constructor to simplify your code:
Map<String, List<SomeBean>> fromMap = new LinkedHashMap<String, List<SomeBean>>();
Map<String,List<?>> toMap = new LinkedHashMap<String, List<?>>(fromMap);
Not realy an answer to your question, but as an extra: I would not use keyset here... If you want to iterate through all the elements of a map, use the entrySet() method. Its faster because it does not require the key-value lookup for each element.
for (Map.Entry<String, List<SomeBean>> entry : fromMap.entrySet()) {
toMap.put(entry.getKey(), entry.getValue());
}
If you really want to, you could cast to a raw Map (but what you want is not type safe):
Map<String,List<?>> toMap = (Map) new LinkedHashMap<String, List<String>>();
When assigning to a Map, where K and V are not wildcard parameters, the Map being assigned must have exactly the same K and V. In your case, V must be exactly List<?>.
The workaround it to use a wildcard V.
Map<String, ? extends List<?>> map = new LinkedHashMap<String, List<String>>();
Because the V you are assigning to is a wildcard, the V being assigned must only be assignable to V (rather than being exactly V).

Categories

Resources