'How to add an entry with an Integer value into Map<String, ?>
I have to place an integer value into the map below.
Map<String,?> map
map.put("key",2000);
When I run the above code I'm getting the following error:
incompatible types: java.lang.Integer cannot be converted to capture#1 of ?
Solution 1:[1]
Unbounded generic collections (Collection<?>
) are not writable. Let's explore why.
Unknown type - <?>
A question mark in angle brackets <?>
is called the unknown type. For the compiler, that means that the actual type can't be predicted and at runtime it could appear to be pretty match anything: an Object
, a String
, a Cat
. Therefore, everything that you retrieve from the unbounded collection will be treated as an Object
:
Collection<?> items = new ArrayList<>();
Object item = items.iterator().next(); // this assignment is safe and will compile
But don't confuse an unbounded collection Collection<?>
with a collection of objects Collection<Object>
. They are incompatible.
Collection<Object>
has invariant behavior, i.e. it expects only another collection of type Object
to be assigned to it. Meanwhile, we can assign a collection of any type to Collection<?>
.
Collection<?> items = new ArrayList<String>(); // OK
Collection<Object> objects1 = items; // that will not compile
Collection<Object> objects2 = new ArrayList<String>(); // that will not compile
Collection<Object> objects3 = new ArrayList<Object>(); // OK - compiler will not complain here
So, unknown type could be represented by anything that extends Object
. And you might think about Collection<?>
as if it is a upper-bounded collection Collection<? extends Object>
. They are absolutely compatible.
Collection<?> items = new ArrayList<>();
Collection<? extends Object> upperBounded = items; // no issues
items = upperBounded; // no issues
Modifying an unbounded collection
Wild-cards like <?>
or <? extends Object>
are not actual types. They are just a way to tell the compiler that we don't know what the type will be at runtime.
Then what will be the actual type of Collection<?>
?
Since it's already clear that unknown type (<?>
) implies that it could be any type that extends Object
, hence under the hood of a Collection<?>
could appear an ArrayList<Object>
, an ArrayList<String>
, a HashSet<BigDecimal>
, etc.
Remainder: parameterized collection that will appear in the guise of Collection<?>
at runtime is invariant, which means that ArrayList<String>
is expected to contain only String
s and HashSet<BigDecimal>
is expected to store only instances of BigDecimal
. But that there's no way to ensure that at runtime because during the compilation generic parameters get erased. Therefore, the only way to provide type-safety while utilizing unbounded generic collection is to disallow to add anything into it.
For example, since it's illegal to add an Integer
or an Object
into an ArrayList<String>
, or to add an instance of String
into a HashSet<BigDecimal>
compiler will prevent any attempt to add anything to the Collection<?>
. Because there's no type compatible with the unknown type.
There's only one exception, null
is a valid value for any type of object, and therefore it could be added into an unbounded as well as into an upper-bounded collection.
Collection<?> items = new ArrayList<>();
items.add(null);
System.out.println(items);
items.remove(null);
System.out.println(items.isEmpty());
Will give an output:
[null] - null-element was successfuly added
true - i.e. isEmpty
Note: that there are no restrictions on removal of elements for both unbounded and upper-bounded collections.
Alternatives
Hence, there's no way to add new entries to the unbounded map you can either copy its entries one by one performing an instanceOf
check into the regular generic map Map<String, Integer>
get rid of generics at all by assign the map to a variable of row type, and then add type-casts manually when need to retrieve a value like in Java 1.4.
That how you can copy the contents of unbounded map to a regular and utilize the advantages of generics.
Map<String, ?> unboundedMap = new HashMap<>()
Map<String, Integer> writableMap = new HashMap<>();
for (Map.Entry<String, ?> entry: unboundedMap.entrySet()) {
if (entry.getValue() instanceof Integer) {
writableMap.put(entry.getKey(), (Integer) entry.getValue());
}
}
The same thing by using the Stream API
Map<String, Integer> writableMap =
unboundedMap.entrySet().stream()
.filter(entry -> entry.getValue() instanceof Integer)
.collect(Collectors.toMap(Map.Entry::getKey,
entry -> (Integer) entry.getValue()));
Solution 2:[2]
You can assign many things to a a variable of type Map<String, ?>
. For example:
Map<String, ?> map;
map = new HashMap<String, Object>(); // works
map = new HashMap<String, String>(); // works
map = new HashMap<String, Number>(); // works
map = new HashMap<String, Integer>(); // works
That all works. So, let's say that map = new HashMap<String, String>()
was assigned to it. And then you attempt to run .put("key", 2000);
on it.
That, obviously, makes no sense. Hence why that code doesn't compile.
I'm assuming that your eyeballs and human brain looked at this code and determined that the map
you have there couldn't possibly be pointing at anything other than either a Map<String, Object>
or a Map<String, Number>
or a Map<String, Integer>
or even a Map<String, Serializable>
- types for which .put("key", 2000)
would be sensible.
But that's not how java works. Part of the point of types is to restrict yourself and give yourself the freedom to assign other stuff later.
It is exactly analogous to this:
Object x = "hello";
x.toLowerCase();
The above does not compile: The Object
type doesn't have a toLowerCase()
method. Yes, yes, our eyeballs and brain look at this and go: That's stupid, of course it does! Look, x
is clearly pointing at a string!
But in such cases, just.. write String x = "hello"
.
So, the same applies for your scenario. Just write Map<String, Integer>
instead.
Unfortunately....
sometimes you have dynamic code that wants to check what a thing is and act accordingly. For example:
void foo(Object o) {
if (o instanceof String s) return s.toLowerCase();
throw new SomeException();
}
But you can't do that for generics - generics cannot be instanceof
checked. You can't do something like: IF the object my map
variable is pointing at has Integer
as value type (or any supertype of Integer), then do map.put("key", 2000)
, otherwise don't.
That's just not how generics works in java. There is simply no way to do this.
The only thing you can do is just decree it. Write code that works fine if it is true, but messes everything up if it is not true: You can FORCIBLY run .put("key", 2000)
on the map, which will put that in the map, and if that is a map declared as, say, Map<String, String>
, then any code that interacts with this map will start throwing ClassCastExceptions
left and right even though they never cast anything. Because you shoved an integer into a map of strings.
Hence why the compiler yells at when you force this, and why you shouldn't be doing this. The only point of generics is to make your programming life easier, to clarify APIs, and to catch bugs, and you're.. doing the opposite of it by forcing it. But, hey, if you really want to do this (highly unlikely):
void example(Map<String, ?> map) {
Map forceIt = map; // raw assignment
forceIt.put("key", 2000); // compiles
}
The above compiles and does what you asked for - but will warn you that this is a very bad idea.
of (o instanceof String) return ((String) o).toLowerCase();
Solution 3:[3]
Map<String, Integer> map = new Map<String, Integer>;
map.put("key",2000);
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | rzwitserloot |
Solution 3 | Zikria Azimi |