Generics issue with Java - 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.

Related

Java: cast Object to HashMap<String, Object>

I am trying to cast an Object to HashMap<String, Object> in a neat, robust way. So far, every way I tried produces compiler warnings or errors. What is the proper way to do it? I have checked the internet and tried the following:
HashMap<String, Object> map = (HashMap<String, Object>) object;
The code above gives an unchecked conversion warning.
HashMap<String, Object> map = new HashMap<>();
if (object instanceof Map<String, Object>){
map = (Map<String, Object>) object;
}
The code above gives an error, which says that objects cannot be compared to parameterized collections.
HashMap<String, Object> map = new HashMap<>();
if (object instanceof Map){
Map genericMap = (Map) object;
for (Object key : genericMap.keySet()){
if (key instanceof String){
map.put((String) key, genericMap.get(key));
}
else{
throw new KeyException();
}
}
}
The code above yields a warning that "Map is a raw type. References to generic type Map<K,V> should be parameterized."
So what would be the proper way to do this? Thank you in advance!
I am trying to cast an Object to HashMap<String, Object> in a neat, robust way. So far, every way I tried produces compiler warnings or errors. What is the proper way to do it?
There is no proper way to do it, supposing that "proper" implies both useful and type safe. Casting is the antithesis of type safety. Other than casts for arithmetic purposes, a safe cast is an unnecessary one.
There is not enough information to determine how to achieve what ultimately you are after, but generally speaking, that sort of thing revolves around writing true generic code instead of using type Object to funnel objects of unrelated type into the same methods, using instanceof to determine what you actually have, or casting.
Just add #SuppressWarnings("unchecked") to your method
#SuppressWarnings("unchecked")
public void myMethod(){
...
}
and you should be able to use
HashMap<String, Object> map = (HashMap<String, Object>) object;

Cast Map<String, ? extends Object> to Map<String, Object>

In Java 8, I am trying to cast Map<String, ? extends Object> to Map<String, Object>. I though that it would be safe given the constraint I put on the input Type (all ? must implement Object), but I get an unchecked cast warning.
Any idea where I reason wrong ? Any clean solution ? Thanks for your help !
This cast is not safe. In particular:
Map<String, ? extends Object> before;
before.put("foo", "example"); // <-- illegal
Map<String, Object> after;
after.put("foo", "example"); // <-- legal
Observe that String is not a subclass of ? extends Object because you don't know ? but it is a subclass of the more general Object thus the second call is okay.

Type safety warning when casting to generic type

(When researching this, all I found were SO posts and websites saying you cannot simply cast to a generic type due to type erasure, I found nothing on point.)
Can someone explain to me why Eclipse prompts me with this warning?
public <T> T getFromBag(String key, Class<T> clazz) {
Object val = null;
T typeCastValue = null;
if (commonDataBag.containsKey(key)) {
val = commonDataBag.get(key);
typeCastValue = clazz.cast(val);
}
return typeCastValue;
}
Usage:
Map<String, Foo> stuff= myService().<Map>getFromBag(key, Map.class);
Warning:
Type safety: The expression of type Map needs unchecked conversion to conform to Map<String,Foo>
Can someone explain to me why Eclipse prompts me with this warning?
In myService().<Map>getFromBag(key, Map.class), you provide an explicit type witness (<Map>) for the type variable T. Therefore, the return type of the call is T, which is Map. Then you assign that (of type Map) to type Map<String, Foo>, which is an unchecked implicit conversion from a raw type to a parameterized type.
Your return value is of class HashMap, there is not way to be sure that it's HashMap, so you get a warning.
Imagine if you have code like this:
Map<String, String> map = new HashMap<>();
map.put("foo", "bar");
commonDataBag.put("42", map);
Then you call:
Map<String, Foo> stuff= myService().<Map>getFromBag("42", Map.class);
Foo foo = stuff.get("foo");
At this point you'll get a classcastexception because your map doesn't contain Foo values, it contains String values.
This is what prompts the warning.

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.

Categories

Resources