I have a problem with bounded nested wildcards in Java generics.
Here's a common case:
public void doSomething(Set<? extends Number> set) {}
public void callDoSomething() {
Set<Integer> set = new HashSet<Integer>();
doSomething(set);
}
This is standard Java generics, works fine.
However if the wildcard becomes nested, it no longer works:
public void doSomething(Map<String, Set<? extends Number>> map) {}
public void callDoSomething() {
Map<String, Set<Integer>> map = new HashMap<String, Set<Integer>>();
doSomething(map);
}
This leads to a compiler error.
I've tried a variety of casts and wildcard permutations, but I'm unable to get this working. I don't recall seeing this issue before, and I've worked with generics for years. Am I just too tired and missing something obvious?
So the problem is, doSomething could be implemented as:
public void doSomething(Map<String, Set<? extends Number>> map) {
Set<Float> set = ...;
map.put("xyz", set);
}
You need to decide what you actually mean.
Probably something like:
public void doSomething(Map<String, ? extends Set<? extends Number>> map) {}
this will work for you:
public void doSomething(Map<String, ? extends Set<? extends Number>> map) {}
To make code to work Create HashMap as:
Map<String, Set<? extents Number>> map = new HashMap<String, Set<? extents Number>>();
Related
This question already has answers here:
What is PECS (Producer Extends Consumer Super)?
(16 answers)
Closed last year.
Say I have the following two classes:
public class SomethingElse<A, B> {
public List<? extends Something<A, B>> getOneList() {
//doesn't matter
}
public List<? extends Something<A, B>> getAnotherList() {
//doesn't matter
}
}
public class Something<A, B> {
//doesn't matter
}
I would like to merge the results that I get from getOneList() and getAnotherList():
SomethingElse<String, Integer> somethingElse = new SomethingElse<>();
List<? extends Something<String, Integer>> oneList = somethingElse.getOneList();
List<? extends Something<String, Integer>> anotherList = somethingElse.getAnotherList();
anotherList.forEach(e -> oneList.add(e)); //<-- DOESN'T COMPILE
However, the compiler complains that in the .add() method above, it is expecting capture of ? extends Something<String, Integer> but I am providing... well, capture of ? extends Something<String, Integer>:
I feel this has something to see with type erasure but I can't figure out why, even the compiler itself is unable to produce a clear message since it's telling me I'm providing type X but it's expecting type X.
Can anyone explain me technically why the compiler doesn't like this? What are the possible wrong mixes I may be doing into the same list?
Are you really going to subclass those two classes such that you need the extends? You could do it like this, sans the extends. I recommend you read this because using extends vs super dictates whether you can add or remove items from a list.
SomethingElse<String, Integer> somethingElse =
new SomethingElse<>();
List<Something<String, Integer>> oneList =
somethingElse.getOneList();
List<Something<String, Integer>> anotherList =
somethingElse.getAnotherList();
anotherList.addAll(oneList);
class SomethingElse<A, B> {
public List<Something<A, B>> getOneList() {
return null;
}
public List<Something<A, B>> getAnotherList() {
return null;
}
}
class Something<A, B> {
// doesn't matter
}
Here's a visual of the problem:
As can be seen from the visual, the IDE is showing a compile-time error to which it does not allow the class to be inserted into the Map.
Here's a simplified version:
#Override
public <T extends Comparable> void transactPersistentEntityStore(...) {
Map<Class<T>, ComparableBinding> propertyTypeMap = new HashMap<>();
propertyTypeMap.put(EmbeddedArrayIterable.class, EmbeddedEntityBinding.BINDING);
propertyTypeMap.put(EmbeddedEntityIterable.class, EmbeddedEntityBinding.BINDING);
// ...
}
Even if both EmbeddedArrayIterable and EmbeddedEntityIterable implements Comparable
Am I missing or misunderstanding something on generics?
You can simplify the point of the problem to this code snippet:
public <T extends Comparable> void m1(T x) {
Class<? extends Comparable> x1Class = x.getClass();
Class<T extends Comparable> x2Class = x.getClass();
}
Or even to this:
public <T> void m2(T x) {
Class<?> x1Class = x.getClass();
Class<T> x2Class = x.getClass();
}
The line with the variable x2Class has an error in these methods.
This is because the compiler throws away the Generics and thus there is no type T at runtime. T is not reifiable. You cannot obtain the type T at runtime.
Have also a look at this article: Why following types are reifiable& non-reifiable in java?
This question already has answers here:
Java HashMap nested generics with wildcards
(3 answers)
Closed 6 years ago.
I don't understand generic wildcard bounderies ussage.
Could you please explain why processList works pretty well while processMap fails with compilation error in the following example? How should I change signature of processMap to make it work with both Map<String, List<String>> and Map<String, List<Object>>
public void processList(List<? extends Object> list) {
}
public void processMap(Map<String, List<? extends Object>> map) {
}
public void f() {
List<String> list = new ArrayList<>();
Map<String, List<String>> map = new HashMap<>();
processList(list); // OK
processMap(map); // ERROR
}
While moving generic type definition from method argument type to method paramether made the trick
public void processMap(Map<String, List<? extends Object>> map)
public <T extends Object> void processMap(Map<String, List<T>> map)
I would now like to know difference between the two. Moved to another thread.
You can make it work if you eliminate the wildcard. I.e. you create a generic function with a named type: <T extends Object>
public <T extends Object> void processMap(Map<String, List<T>> map) {
}
public void processList(List<? extends Object> list) {
}
public void f() {
List<String> list = new ArrayList<>();
Map<String, List<String>> map = new HashMap<>();
processList(list); // OK
processMap(map); // OK now
processMap(new HashMap<String, List<Integer>>()); // this is OK too
}
Unfortunately, I can't explain why the function with the wildcard doesn't work.
Half an answer: the following code does compile for me.
Missing: a good explanation for why a named T works; but an unnamed ? does not.
public <T> void processMap(Map<String, List<T>> map) {
}
public void f() {
Map<String, List<String>> map = new HashMap<>();
processMap(map);
Map<String, List<Object>> map2 = new HashMap<>();
processMap(map2);
}
Map<String, List<? extends Object>> map = new HashMap<>();
make the above change in f() method and it work.
Java compiler check the type of variable so both should be same, i may wrong.
Hello everybody I try to extend a HashMap<String,String> to enforce a "all-lowercase" rule
public class HttpQueryMap extends HashMap<String,String>
{
...
#Override
public void putAll(Map<? extends String, ? extends String> m)
{
...
Iterator<Map.Entry<String,String>> iterator = m.entrySet().iterator();
...
}
...
}
I get a compile-time error
incompatible types
required: Iterator<Entry<String,String>>
found: Iterator<Entry<CAP#1,CAP#2>>
where CAP#1,CAP#2 are fresh type-variables:
CAP#1 extends String from capture of ? extends String
CAP#2 extends String from capture of ? extends String
The next work-around does the job but it is really ugly:
public class HttpQueryMap extends HashMap<String,String>
{
...
#Override
public void putAll(Map<? extends String, ? extends String> m)
{
...
Map<String,String> m_str=new HashMap<String,String>();
m_str.putAll(m);
Iterator<Map.Entry<String,String>> iterator = m_str.entrySet().iterator();
...
}
...
}
As far as I understand the problem is that the type variable String used in the Iterator<Map.Entry<String,String>> does not extend String (itself) used in the declaration of Map<? extends String, ? extends String> m
Without Iterator
The easiest way is to use a for-each loop. Even in this case, you need the parametrize the Entry with the same wildcards as in the given map. The reason is that Entry<? extends String, ? extends String> is not a subtype of Entry<String, String>. The fact that String is a final class is irrelevant here, because the compiler has no knowledge of that.
for (Entry<? extends String, ? extends String> entry : m.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
}
With Iterator
If you really need an Iterator, the syntax that does compile is a bit baffling:
Iterator<? extends Entry<? extends String, ? extends String>> iterator =
m.entrySet().iterator();
while (iterator.hasNext()) {
Entry<? extends String, ? extends String> entry = iterator.next();
String key = entry.getKey();
String value = entry.getValue();
}
I originally expected the iterator to be only of type Iterator<Entry<? extends String, ? extends String>>, which at first appears to be the return type of iterator() method called on a Set<Entry<? extends String, ? extends String>> which in turns appears to be the return type of entrySet() called on Map<? extends String, ? extends String>.
However, it is a bit more complex than that. I've found a probable answer in here:
http://mail-archives.apache.org/mod_mbox/harmony-dev/200605.mbox/%3Cbb4674270605110156r4727e563of9ce24cdcb41a0c8#mail.gmail.com%3E
The interesting part is this:
The problem is that the entrySet() method is returning a
Set<Map.Entry<capture-of ? extends K, capture-of ? extends V>>,
which is incompatible with the type Set<Map.Entry<? extends K, ? extends V>>.
It's easier to describe why if I drop the extends K and extends V part.
So we have Set<Map.Entry<?, ?> and Set<Map.Entry<capture-of ?, capture-of ?>>.
The first one, Set<Map.Entry<?, ?>> is a set of Map.Entries of different
types - ie it is a heterogeneous collection. It could contain a
Map.Entry<Long, Date> and a Map.Entry<String, ResultSet>> and any other
pair of types, all in the same set.
On the other hand, Set<Map.Entry<capture-of ?, capture-of ?>> is a homogenous
collection of the same (albeit unknown) pair of types. Eg it might be a
Set<Map.Entry<Long, Date>>, so all of the entries in the set MUST be
Map.Entry<Long, Date>.
Wildcards are kind of vague, sometimes we want to turn wildcards into type variables which are more tangible.
The standard way is introducing a method with corresponding type variables
public void putAll(Map<? extends String, ? extends String> m)
{
_putAll(m);
}
<S1 extends String, S2 extends String>
void _putAll(Map<S1, S2> m)
{
Iterator<Map.Entry<S1,S2>> iterator = m.entrySet().iterator();
}
In java8, also try
public void putAll(Map<? extends String, ? extends String> m)
{
m.forEach( (k,v)->
{
...
});
}
The types of (k,v) are inferred to be captured types, just like (S1,S2). However, it is also OK if we fix their types as (String,String), due to the flexibility of the signature of forEach
m.forEach( (String k, String v)->
Why not just avoid the iterator all together as this code seems to work just fine for your implementation of putAll:
for(String s: m.keySet()){
put(s.toLowerCase(), m.get(s));
}
As to why you can't seem to work around that error, I have no idea. I tried multiple variants and nothing seemed to work.
My understanding is this: If it were possible that you could derive from String, say classes called LeftRightString and UpDownString, then
Map<LeftRightString,LeftRightString> is a subtype of Map<? extends String, ? extends String>
Map<String, String> is a subtype of Map<? extends String, ? extends String>
but Map<LeftRightString,LeftRightString> is not a subtype of Map<String,String>
Therefore your iterator type mismatches. If it were allowed then, the following would work when it should not work:
void putAll(Map<? extends String, ? extends String> pm) {
Map<String, String> m = pm;
m.add(new UpDownString(), new UpDownString()); // ooops!! if ? was LeftRightString
}
(Update) I want to add that almost everything I said here is in the Oracle Java tutorials so I'm baffled at why so many people keep commenting that this is wrong. And what is not in the tutorial can be found in the Java Specification. What I haven't done is give a workaround but other answers have.
Here is my problem:
my function in class A:
public void setData(Map<String,? extends ArrayList<? extends SomeInterface>>){...}
my call:
Map<String, ArrayList<ImplementsSomeInterface>> a=...;
instanceOfA.setData(a); //does not compile
instanceOfA.setData((Map<String,? extends ArrayList<? extends SomeInterface>>) a); // works thanks to the Casting.
I don't think this is clean. Is there a way to avoid the casting without droping the wildcard use in the function?
First your setData method should read:
public void setData(Map<String,? extends List<? extends SomeInterface>>)
Your map declaration should read:
Map<String, List<ImplementsSomeInterface>> a = ...;
Which is pretty much what you've got following your edits to the original question. The change I've made from ArrayList to List doesn't effect the behaviour of the code.
Following comments:
public static void main()
{
Map<String, List<Double>> map = new HashMap<String, List<Double>>();
map.put("prices", new ArrayList<Double>(Arrays.asList(1.1, 2.2, 3.3)));
setData(map);
}
public static void setData(Map<String,? extends List<? extends Serializable>> map)
{
}
The problem is that setData takes as an argument a
Map<String,? extends ArrayList<? extends SomeClass>>Map>
whereas in your first call(the one that doesn't compile) you are trying to pass it a Map<String,? extends SomeClass>
Java cannot automatically cast a subclass of
SomeClass
to a subclass of
ArrayList<? extends SomeClass>
because it is not neccesarily an instance of ArrayList.