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);
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 convert a map into multivalue map, but I am getting the below compilation exception:
Wrong 1st argument type. Found: java.util.Map<java.lang.String,java.util.List<java.lang.String>>, required: org.springframework.util.MultiValueMap<java.lang.String,java.lang.String> less... Inspection info:
Here is the structure:
Map<String, List<String>> tradersTradeMap - > MultiValueMap<String, String>tradersTradeMap
class Trade {
public String getTraderNameAfterProcesing (MultiValueMap<String, String>
tradersTradeMap){
..... // SOme code goes here
}
}
class Customer {
private Trade trade;
public String Method1(){
Map<String, List<String>> traderTradeMap = new HashMap<>();
traderTradeMap.put("TraderA", Arrays.asList("SPOT","BLOCK","FORWARD"));
traderTradeMap.put("TraderB", Arrays.asList("SPOT","BLOCK"));
trade = new Trade();
trade.getTraderNameAfterProcesing(traderTradeMap); // This line is giving exception
}
}
Is there any simple way to do it?
If you don't care about which MultiValueMap type you use, the easiest way to do it is to use LinkedMultiValueMap's copy constructor which takes a Map<K, List<V>>
One problem in your example is that you're trying to give the original map and the MultiValueMap the same variable name. So, if you instead did something like this:
MultiValueMap<String, String> TradersTradeMVMap = new LinkedMultiValueMap<>(TradersTradeMap);
Yes, Spring provides a handy wrapper in the form of CollectionUtils.toMultiValueMap(), which preserves the original Map used.
The following code won't compile:
public static Map<String, Collection<String>> convert(Collection<Foo> foos) {
return foos == null ? Maps.newHashMap()
: foos.stream().collect(
Collectors.groupingBy(
f -> f.getSomeEnum().name(),
Collectors.mapping(Foo::getVal, Collectors.toList())));
}
Unless I change the return type to List:
public static Map<String, List<String>> convert(Collection<Foo> foos) {
Normally I believe I should be able to, but maybe there is some ambiguity introduced by generics?
The exact error:
Incompatible types. Required Map<String, Collection<String>> but 'collect' was inferred to R: no instance(s) of type variable(s) A, A, A, A, A, A, D, K, R, R, T, T, T, U exist so that List<T> conforms to Collection<String>
I don't think the details are relevant but just in case:
Foo is like this:
public class Foo {
private MyEnumType someEnum;
private String val;
public MyEnumType getSomeEnum();
public String getVal();
}
and I am trying to convert a list of Foos to a map of Foos, vals grouped by someEnum.
Map<String, List<String>> is not a subtype of Map<String, Collection<String>>, see this for more.
You can declare the return type as Map<String, ? extends Collection<String>>
The method signatures of groupingBy and mapping have no variance regarding the result type of the downstream collector. Hence, you end up with the result type List<T> of the toList() collector. In contrast, toCollection has variance in its type parameter C extends Collection<T>, but even without, there wouldn’t be any problem to assign ArrayList::new to a Supplier<Collection<…>>:
public static Map<String, Collection<String>> convert(Collection<Foo> foos) {
return foos == null? new HashMap<>(): foos.stream().collect(
Collectors.groupingBy(f -> f.getSomeEnum().name(),
Collectors.mapping(Foo::getVal, Collectors.toCollection(ArrayList::new))));
}
This does exactly the same as toList(), given the current implementation. But it wouldn’t benefit from future improvements specifically made to the toList() collector. An alternative would be to keep using toList(), but chain a type conversion:
public static Map<String, Collection<String>> convert(Collection<Foo> foos) {
return foos == null? new HashMap<>(): foos.stream().collect(
Collectors.groupingBy(f -> f.getSomeEnum().name(),
Collectors.collectingAndThen(
Collectors.mapping(Foo::getVal, Collectors.toList()),
c -> c)));
}
Since this is a widening conversion, the conversion function is as simple as c -> c. Unfortunately, the underlying implementation doesn’t know about the triviality of this function and will iterate over the result map’s values to replace each of them with the result of applying this function.
This can be solved with a special widening collector:
public static <T,A,R extends W,W> Collector<T,A,W> wideningResult(Collector<T,A,R> original) {
return Collector.of(original.supplier(), original.accumulator(), original.combiner(),
original.finisher().andThen(t -> t),
original.characteristics().toArray(new Collector.Characteristics[0]));
}
This does basically the same as Collectors.collectingAndThen(original, t -> t), chaining the trivial widening conversion function. But it keeps the original collector’s characteristics, so if the original collector has the IDENTITY_FINISH characteristic, we will still have it, which allows to skip the finishing operation, which in case of groupingBy implies that it doesn’t need to iterate over the map to apply the function.
Applying it to the actual use case yields
public static Map<String, Collection<String>> convert(Collection<Foo> foos) {
return foos == null? new HashMap<>(): foos.stream().collect(
Collectors.groupingBy(f -> f.getSomeEnum().name(),
wideningResult(Collectors.mapping(Foo::getVal, Collectors.toList()))));
}
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 make a method call like this,
public class GenericsTest<T> {
public static <T> Map<String, T> createMap(Class<? extends Map<String, T>> clazz) {
return null;
}
public static void main(String[] argv) {
Map<String, Integer> result = createMap(TreeMap.class);
}
}
But I am getting this error,
<T>createMap(java.lang.Class<? extends java.util.Map<java.lang.String,T>>) in test.GenericsTest<T> cannot be applied to (java.lang.Class<java.util.TreeMap>)
How to fix this problem?
Map<String, Integer> instance = new TreeMap<String, Integer>();
#SuppressWarnings("unchecked")
Map<String, Integer> map =
createMap((Class<? extends Map<String, Integer>>)instance.getClass());
map.put("x", 1);
System.out.println("THIS IS x: " + map.get("x"));
This will appropriately print out 1. The implementation of the method is most likely
try
{
return clazz.newInstance();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
A better implementation of their API would be for them to ask you for the type, T, and for them to give back a Map of their choosing instead of asking you for all of the details. Otherwise, as long as they are not filling in the Map with any data, you can instantiate a Map with the generic type argument yourself like so:
public static <T> Map<String, T> getMap()
{
return new TreeMap<String, T>();
}
You can then access that without a warning:
// note the lack of type arguments, which are inferred
Map<String, Integer> instance = getMap();
// alternatively, you could do it more explicitly:
// Map<String, Integer> instance = ClassName.<Integer>getMap();
There's really no reason for them to ask you for the Class type of your Map except to give you back an exact match to the implementation (e.g., if you stick in a HashMap, then you will get back a HashMap, and if you stick in a TreeMap, then you will get back a TreeMap). However, I suspect that the TreeMap will lose any Comparator that it was constructed with, and since that is an immutable (final) field of TreeMap, then you cannot fix that; that means that the Map is not the same in that case, nor is it likely to be what you want.
If they are filling in the Map with data, then it makes even less sense. You could always pass in an instance of a Map to fill, or have them return a Map that you can simply wrap (e.g., new TreeMap<String, Integer>(instance);), and they should know which Map offers the most utility to the data.