public interface A {
int getA();
}
public class MyObj implements A {
public int getA(){
return 1;
}
}
If have a Map : Map<? extends A, String> aMap = new HashMap<>();
How can I add an MyObj to this aMap ? Or how should be the class MyObj so that it can work in this map
How can i add an MyObj to this aMap ?
You can't, because of the upper bound on the key type.
The reason is that ? extends A could be MyOtherObj implements A. In this case, it would be type-unsafe to be able to put a key of type MyObj into the map:
Map<MyOtherObj, String> anotherMap = new HashMap<>();
Map<? extends A, String> aMap = anotherMap;
aMap.put(new MyObj(), ""); // Can't do this; but if you could...
MyOtherObj obj = anotherMap.keySet().iterator().next(); // ClassCastException!
Remember the acronym PECS (see this question for a lot more detail):
Producer extends
Consumer super
In other words, Map<? extends A, String> can only be used to produce instances of A, it can't consume/accept instances of A.
For example, you can iterate the keys ("producing" the keys):
for (A a : aMap.keySet()) { ... }
The map can only "consume" a literal null:
aMap.put(null, "");
because null can be cast to any type without exception. But there's not much use in a map which only has a single key - you may as well just store the value directly.
The only way to do this type-safely is to put the instance of MyObj into the map via a reference which you know accepts MyObj instances:
Map<MyObj, String> safeMap = new HashMap<>();
safeMap.put(new MyObj(), "");
Map<? extends A, String> aMap = safeMap;
or
Map<A, String> safeMap = new HashMap<>();
safeMap.put(new MyObj(), "");
Map<? extends A, String> aMap = safeMap;
But you should consider not having the wildcard-typed map at all; Map<MyObj, String> or Map<A, String> is easier.
This isn't possible. Your compiler won't allow it.
You have to change your Map to:
Map<A, String> aMap = new HashMap<>();
After this you can use put to add an element to it:
aMap.put(new MyObj(), "myObject");
Related
I am trying to better understand generics in Java and therefore wrote a generic method that merges two maps of collections. (Please ignore for the moment that it creates a hard-coded ArrayList.)
public static <E, K> void mergeMaps(Map<K, Collection<E>> receivingMap, Map<K, Collection<E>> givingMap) {
for (Map.Entry<K, Collection<E>> entry : givingMap.entrySet()) {
Collection<E> someCollection = receivingMap.computeIfAbsent(entry.getKey(), k -> new ArrayList<E>());
someCollection.addAll(entry.getValue());
}
}
My goal is that the mergeMaps function is able to merge maps (of the same type) whose values can be arbitrary collections (ArrayList,LinkedHashMap,...).
However, when I try to merge let's say two instances of Map<Integer, ArrayList<String>> I get a compile-time error but I do not quite understand what the compiler is telling me.
public static void main(String[] args) {
Map<Integer, ArrayList<String>> map1 = new HashMap<>();
Map<Integer, ArrayList<String>> map2 = new HashMap<>();
mergeMaps(map1, map2); // <-- compile error
}
What is wrong here and how can I fix it?
Error:(9, 9) java: method mergeMaps in class CollectionUtil cannot be applied to given types;
required: java.util.Map<K,java.util.Collection<E>>,java.util.Map<K,java.util.Collection<E>>
found: java.util.Map<java.lang.Integer,java.util.ArrayList<java.lang.String>>,java.util.Map<java.lang.Integer,java.util.ArrayList<java.lang.String>>
reason: cannot infer type-variable(s) E,K
(argument mismatch; java.util.Map<java.lang.Integer,java.util.ArrayList<java.lang.String>> cannot be converted to java.util.Map<K,java.util.Collection<E>>)
When the signature of the method is
<E, K> void mergeMaps(Map<K, Collection<E>> receivingMap,
Map<K, Collection<E>> givingMap)
Then a call using Map<Integer, List<String>> as argument types is invalid because Collection is not a generic type parameter of the mergeMaps method.
Why is this a problem? With generics, Map<Integer, List<String>> cannot be assigned to a Map<Integer, Collection<String>> variable (or passed as a method argument in that manner). This is because generic types are invariant (see here for more info. In short, that means List<Integer> is not necessarily compatible with any List<Number>, although a ArrayList<Number> is compatible with List<Number> ).
In other words, the concrete arguments must be of type Map<Integer, Collection<String>>. This leads to your first solution:
//Solution 1: change your arguments to Map<Integer, Collection<String>>:
Map<Integer, Collection<String>> map1 = new HashMap<>();
Map<Integer, Collection<String>> map2 = new HashMap<>();
mergeMaps(map1, map2);
If you want to allow calls with parameters of type Map<Integer, List<String>>, then you have to change your target method to introduce a generic parameter around the map value:
public static <E, K, C extends Collection<E>> void
mergeMaps2(Map<K, C> receivingMap, Map<K, C> givingMap) {
for (Map.Entry<K, C> entry : givingMap.entrySet()) {
Collection<E> someCollection = receivingMap.computeIfAbsent(entry.getKey(),
k -> (C) new ArrayList<E>());
someCollection.addAll(entry.getValue());
}
}
And that can be called with maps where the value is declared as a subtype of Collection<E> (as long as the Collection type is the same in both arguments):
Map<Integer, List<String>> map1 = new HashMap<>();
Map<Integer, List<String>> map2 = new HashMap<>();
mergeMaps2(map1, map2);
Map<Integer, Set<String>> map1 = new HashMap<>();
Map<Integer, Set<String>> map2 = new HashMap<>();
mergeMaps2(map1, map2);
Side note (or digression)
Now, when you compile this, you have a further problem: there's a compiler warning on this line:
Collection<E> someCollection =
receivingMap.computeIfAbsent(entry.getKey(), k -> (C) new ArrayList<E>());
Claiming that (C) new ArrayList<E>() is an unchecked cast. Why this? Let's look at the above example calls (I added the two advisedly):
Call 1:
Map<Integer, List<String>> map1 = new HashMap<>();
Map<Integer, List<String>> map2 = new HashMap<>();
mergeMaps2(map1, map2);
In this example, receivingMap.computeIfAbsent(entry.getKey(), k -> (C) new ArrayList<E>()) means to add an instance of ArrayList<String> as a value to the map. As the actual object is of a type that is compatible with the caller's declared type (List<String>), things are OK.
Now, what do you think this will do?
Call 2:
Map<Integer, Set<String>> map1 = new HashMap<>();
Map<Integer, Set<String>> map2 = new HashMap<>();
mergeMaps2(map1, map2);
In this case too, unfortunately, receivingMap.computeIfAbsent(entry.getKey(), k -> (C) new ArrayList<E>()) will still try to add an ArrayList<String>, which happens to be incompatible with the caller's expected value type (Set<String>).
The compiler can't be sure that the cast (C) new ArrayList<E>() will always be correct in the context of the concrete type arguments. It gives up, but issues a warning to alert the developer.
Dealing with this is actually a tricky problem. You need to know what type to instantiate, but your method parameters won't allow you to do so because you can't just run new C(). Your own requirements and design will determine the correct solution, but I'll end with one possible solution:
public static <E, K, C extends Collection<E>> void
mergeMaps2(Map<K, C> receivingMap,
Map<K, C> givingMap,
Supplier<C> collectionCreator) {
for (Map.Entry<K, C> entry : givingMap.entrySet()) {
Collection<E> someCollection = receivingMap.computeIfAbsent(entry.getKey(),
k -> collectionCreator.get());
someCollection.addAll(entry.getValue());
}
}
The error says java.util.List<java.lang.String>> cannot be converted to java.util.Map<K,java.util.Collection<E>>)
You have to modify your method:
public static <E, K> void mergeMaps(Map<K, List<E>> receivingMap, Map<K, List<E>> givingMap) {
for (Map.Entry<K, List<E>> entry : givingMap.entrySet()) {
Collection<E> someCollection = receivingMap.computeIfAbsent(entry.getKey(), k -> new ArrayList<E>());
someCollection.addAll(entry.getValue());
}
}
I am trying to write a class that has a Map as a field. The field is as follows:
Map<String, Collection<String>> courses;
In the constructor, I have to have the field in the form:
Map<String, Set<String>;
without changing the field at all.
I am getting an error when I try to initialize the field with the set. Can someone tell me why or what to do without altering the original field?
Despite Set<String> is actually a subtype of Collection<String>, a Map<String, Set<String>> is not a subtype of Map<String, Collection<String>>. In fact, they are completely different types and you can't assign one to the other.
Luckily, the Map interface defines the putAll method, which has the following signature:
void putAll(Map<? extends K,? extends V> m)
This means that the putAll method accepts a map whose keys and values might be of types that are subtypes of its own key and value types, respectively.
So, in your example, you could do as follows:
public class YourClass {
private final Map<String, Collection<String>> courses = new HashMap<>();
public YourClass(Map<String, Set<String>> courses) {
this.courses.putAll(courses);
}
}
You only have to make sure that the courses attribute has been instantiated before invoking putAll on it.
I'm not sure what actual question is about, but...
code below is working because of Type erasure at Runtime
public class SimpleTest {
protected Map<String, ? extends Collection<String>> courses;
public SimpleTest(Map<String,Set<String>> setsMap)
{
courses = setsMap;
}
public static void main(String... args) {
Map<String, ? extends Collection<String>> setMap = new HashMap<String, Set<String>>();
SimpleTest stInstance = new SimpleTest((Map<String, Set<String>>) setMap);
String str1 = "Hi";
String str2 = "Hello";
Set<String> stringSet = new HashSet<>();
stringSet.add(str1);
List<String> stringList = new ArrayList<>();
stringList.add(str2);
((Map<String, Collection<String>>)setMap).put("set1", stringSet);
((Map<String, Collection<String>>)setMap).put("list1", stringList);
System.out.println("set1 class: " + stInstance.courses.get("set1").getClass().getName());
System.out.println("list1 class: " + stInstance.courses.get("list1").getClass().getName());
System.out.println("map content: " + stInstance.courses);
}
}
output is:
set1 class:java.util.HashSet
list1 class:java.util.ArrayList
map content:{list1=[Hello], set1=[Hi]}
PS. I do not recommend to use such "technique", at all.
But as experiment it is interesting and funny :-)
I am trying to do this following code:
Map<Node, TreeSet<String>> childrenNodes = new TreeMap<>(getAll());
I put getAllNodesAndEdges method header below:
public Map<Node, Set<String>> getAll() {...}
I need to convert a general map and the set inside of it into both TreeMap and TreeSet for sorted printing. However, the first piece of code has a compile error saying "Cannot infer type arguments for TreeMap<>"
What is the best way to fix this?
Edit:more info below
In Information.java:
public Map<Node, Set<String>> getAll() {
return this.all;
}
However, test1.java needs to use the code
Map<Node, HashSet<String>> all = getAll()
and test2.java needs to use the code
Map<Node, TreeSet<String>> childrenNodes = new TreeMap<Node, TreeSet<String>>(getAll());
but both run type mismatch compile errors
the first:
Type mismatch: cannot convert from Map<Node,Set<String>> to Map<Node,HashSet<String>>
the second:
The construtor TreeMap<Node,TreeSet<String>>(Map<Node,Set<String>>) is undefined
You have to create new objects for the new map's values.
Map<Node, TreeSet<String>> converted = new TreeMap<>();
for(Entry<Node, Set<String>> entry : childrenNodes.entrySet()){
converted.put(entry.getKey(), new TreeSet<>(entry.getValue()));
}
You can not do this in such way.
You can do either:
1) change type of variable childrenNodes:
Map<Node, Set<String>> childrenNodes = new TreeMap<>(getAll());
i.e. not Map<Node, TreeSet<String>>, but Map<Node, Set<String>>
either
2) make getAll() return type
Map<Node, TreeSet<String>> getAll()
The error of compiler tells you exactly this thing: it is impossible infer TreeSet<String> from Set<String>. It is roughly saying the same as trying such kind of assignment: HashMap<String> foo = getMap(); where getMap() returns simply Map<String>.
You can't just cast one type of Set (TreeSet, HashSet) into another. You need to pass a supplier, e.g. TreeSet::new, via method parameter.
private final Map<Node, Set<String>> all;
public <S extends Set<String>, M extends Map<Node, S>> M getAll(Supplier<M> mapFactory, Supplier<S> setFactory) {
return all.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().stream().collect(Collectors.toCollection(setFactory)),
(v1, v2) -> v1,
mapFactory));
}
Then you would call it like this:
Map<Node, HashSet<String>> test1 = getAll(HashMap::new, HashSet::new);
Map<Node, TreeSet<String>> test2 = getAll(TreeMap::new, TreeSet::new);
Or better yet, use the Sorted interfaces rather than implementation classes for your local variable type:
Map<Node, Set<String>> test1 = getAll(HashMap::new, HashSet::new);
SortedMap<Node, SortedSet<String>> test2 = getAll(TreeMap::new, TreeSet::new);
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");
The problem is that the following code can't compile if the generic signature consists of several ? which are the same type.
import java.util.Map;
import java.util.HashMap;
import java.util.function.Function;
public class Test {
private static <T> T findSelfReference(Map<T, T> map) {
for (Map.Entry<T, T> entry : map.entrySet()) {
if (entry.getKey() == entry.getValue()) {
return entry.getKey();
}
}
return null;
}
private static <T> T findSelfReference2(Map<T, T> map) {
for (T key : map.keySet()) {
if (map.get(key) == key) {
return key;
}
}
return null;
}
// Question: How to write the method signature that can ensure compile-time type safety? Both the signatures fail to compile.
// private static <T> String fun(Function<Map<T, T>, T> finder) {
private static String fun(Function<Map<?, ?>, ?> finder) {
Map<Integer, Integer> map1 = new HashMap<>();
// some processing to map1
Integer n = finder.apply(map1); // usage here, compile-time type checking wanted
Map<String, String> map2 = new HashMap<>();
// other processing to map2 depending on n
return finder.apply(finder, map2); // another usage
}
public static void main(String[] args) {
// Please don't change into helper class...
System.out.println(fun(Test::findSelfReference));
System.out.println(fun(Test::findSelfReference2));
}
}
In fun, inside each call to finder.apply() the type T is fixed. But among different calls they use different types. I tried the wildcard capture (ref: here) but no luck.
I don't want to cast the result into Object in which the checking has to be made in runtime. All type checking should be done in compile time.
Is it possible without making O(n) helper classes where n is the number of inline function?
The problem is that you want the finder's apply method to be generic.
The solution is to define your own functional interface with a generic method.
#FunctionalInterface
interface Finder {
<T> T apply(Map<T, T> map);
}
private static String fun(Finder finder) {
// same body
}
If this counts as a "helper class" then I don't know what to tell you. You are trying to pound a square peg in to a round hole. Function#apply isn't a generic method so you can't do this with Function.
What exactly are you trying to achieve with your method fun?
You pass it a Function with either wildcards or with a type parameter T and then you want to apply it on two different, concrete types (Integer and String).
This is not going to work:
Map<Integer, Integer> map1 = new HashMap<>();
// ...
Integer n = finder.apply(map1);
Because here you expect finder to take the type Integer, but you've specified in the declaration of fun that either the type is unknown (if you declare it with ?) or with some unbounded type T (if you declare it with a type parameter T). But if you're going to apply it to Integer you need a Function<Map<Integer, Integer>>, not a Function<Map<?, ?>, ?> or a Function<Map<T, T>, T> for some unbounded type T.
You could write it like this - but the method fun itself is more or less useless.
private static <T> T fun(Map<T, T> map, Function<Map<T, T>, T> finder) {
return finder.apply(map);
}
public static void main(String[] args) {
Map<Integer, Integer> map1 = new HashMap<>();
map1.put(1, 2);
map1.put(3, 4);
map1.put(5, 7);
map1.put(2, 2);
map1.put(8, 8);
Integer n = fun(map1, Test::findSelfReference);
String a = String.valueOf(n + 1);
Map<String, String> map2 = new HashMap<>();
map2.put("1", "2");
map2.put("3", "4");
map2.put("5", "7");
map2.put("3", a);
String s = fun(map2, Test::findSelfReference2);
System.out.println(n);
System.out.println(s);
}
Well you can do something like this:
private static <T> T fun(Function<Map<T, T>, T> finder, Map<T, T> map) {
return finder.apply(map);
}
private static Map<String, String> getStringMap(Integer n) {
String a = String.valueOf(n + 1);
Map<String, String> map2 = new HashMap<>();
map2.put("1", "2");
map2.put("3", "4");
map2.put("5", "7");
map2.put("3", a);
return map2;
}
private static Map<Integer, Integer> getIntMap() {
Map<Integer, Integer> map1 = new HashMap<>();
map1.put(1, 2);
map1.put(3, 4);
map1.put(5, 7);
map1.put(2, 2);
map1.put(8, 8);
return map1;
}
public static void main(String[] args) {
fun(Test::findSelfReference, getIntMap());
fun(Test::findSelfReference, getStringMap(1));
}
I dont see the purpose of fun method, when you can always do this:
Function<Map<Integer, Integer>, Integer> m = Test::findSelfReference;
m.apply(getIntMap());