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());
}
}
Related
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);
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");
I would like to have a method that maps a List to a NavigableMap. The method call expects an parameter that is used as map key. This parameter is an attribute of the list objects.
Something like this, so both calls are ok:
List<MyObject> list = new ArrayList<>();
NavigableMap<String, MyObject> stringKeyMap = asNavMap(list, MyObject:.getString());
NavigableMap<Date, MyObject> dateKeyMap = asNavMap(list, MyObject::getDate());
I dont know how to define the second parameter (MyObject::getDate()). Do I have to use a lambda expression (p -> p.getDate()) or something like Predicate or Function?
I've tried to derive a solution from Approach 8 (or simular) from http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html, but I don't know how to do.
This is what I have done so far:
The concrete implementation:
public class ConcreteConverter {
public static NavigableMap<Integer, Pair<Integer, String>> asNavMap(List<Pair<Integer, String>> pairs) {
NavigableMap<Integer, Pair<Integer, String>> navMap = new TreeMap<>();
for (Pair<Integer, String> pair : pairs) {
navMap.put(pair.getKey(), pair);
}
return navMap;
}
public static void main(String[] args) {
List<Pair<Integer, String>> pairs = new ArrayList<>();
pairs.add(new Pair<Integer, String>(1, "one"));
NavigableMap<Integer, Pair<Integer, String>> map = ConcreteConverter.asNavMap(pairs);
}
}
class Pair<K, V> {
K key;
V val;
// constructor, getter, setter
}
Here I stuck (??? is an attribute of the Pair object):
public static <K, V> NavigableMap<K, V> asNavMap(List<V> items, ???) {
NavigableMap<K, V> navMap = new TreeMap<>();
for (V item : items) {
navMap.put(???, item);
}
return navMap;
}
Please notice I have barely experiences writing generic methods or using lambda functions/interfaces.
Any help is appreciated.
Edit 1
As Nick Vanderhofen mentioned I didn't clarify the search for a generic solution.
You can do that with a Function. You keep the code you wanted:
List<MyObject> list = new ArrayList<>();
NavigableMap<String, MyObject> stringKeyMap = asNavMap(list, MyObject::getKey);
The method asNavMap can then take a Function:
private NavigableMap<String,MyObject> asNavMap(List<MyObject> list, Function<MyObject, String> getKey) {
//the actual mapping goes here
}
The getKey method you are specifying can either be a simple getter on the MyObject:
public String getKey(){
return key;
}
Or you could create a static method to get the same result:
public static String getKey(MyObject myObject){
return myObject.getKey();
}
To apply the function you can just use the apply method:
String key = getKey.apply(someObject);
For the actual mapping implementation you can keep your for loop, or you could rewrite it using java 8 and re-use the Function that you got as a parameter in the collector. However, since you want a TreeMap, the syntax is quite verbose:
items.stream().collect(Collectors.toMap(mapper, Function.identity(), (a,b) -> a, TreeMap::new));
Just figured out a working solution!
Still reading http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#approach7 I've tried to use Function, and now this is my solution:
public static <K, V> NavigableMap<K, V> asNavigableMap(List<V> items, Function<V, K> mapper) {
NavigableMap<K, V> navMap = new TreeMap<>();
for (V item : items)
navMap.put(mapper.apply(item), item);
return navMap;
}
And these calls work:
List<Pair<Integer, String>> pairs = new ArrayList<>();
pairs.add(new Pair<Integer, String>(1, "one"));
NavigableMap<Integer, Pair<Integer, String>> navI2P1 = GenericConverter.asNavigableMap(pairs, Pair::getKey);
NavigableMap<String, Pair<Integer, String>> navI2P2 = GenericConverter.asNavigableMap(pairs, Pair::getVal);
It was hard for me to understand the Function functional interface and the apply method.
Thanks to anyone!
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());
I am trying to sort the values of two LinkedHashMap. I can compile it and run the code just fine, but it tells me to use -Xlint option during compiling because it is unsafe code. It has something to do with type casting stuff, but I am royally confused on how to do it. I got this class which I put inbedded in my class:
static class MyComparator implements Comparator {
public int compare(Object obj1, Object obj2){
int result=0;
Map.Entry e1 = (Map.Entry)obj1 ;
Map.Entry e2 = (Map.Entry)obj2 ;//Sort based on values.
Integer value1 = (Integer)e1.getValue();
Integer value2 = (Integer)e2.getValue();
if(value1.compareTo(value2)==0){
String word1=(String)e1.getKey();
String word2=(String)e2.getKey();
//Sort String in an alphabetical order
result=word1.compareToIgnoreCase(word2);
} else {
//Sort values in a descending order
result=value2.compareTo( value1 );
}
return result;
}
}
I tried to call it in one of my functions with:
ArrayList myArrayList=new ArrayList(this.map_freq_by_date.entrySet());
Collections.sort(myArrayList, new MyComparator());
Iterator itr=myArrayList.iterator();
Note: this.map_freq_by_date is defined as follows:
Map<String,Integer> map_freq_by_date = new LinkedHashMap<String,Integer>();
The error I get with -Xlint option:
unchecked call to ArrayList(java.util.Collection<? extends E>) as a member of the raw type java.util.ArrayList
ArrayList myArrayList=new ArrayList(this.map_freq_by_date.entrySet());
unchecked conversion
found LogGrep.MyComparator
required: java.util.Comparator(? super T>
Collections.sort(myArrayList, new MyComparator());
unchecked method invocation: <T>sort(java.util.List<T>,java.util.Comparator<? super T> in java.util.Collections is applied to (java.util.ArrayList,LogGrep.MyComparator)
Collections.sort(myArrayList, new MyComparator());
Help with how to fix these would be appreciated. I looked online and tried all kinds of things shown, but I can't seem to get it right.
Note: if I put ArrayList<Object> myArrayList = new ArrayList<Object>... the error changes to:
unchecked method invocation <T>sort(java.util.List<T>,java.util.Comparator<> super T?) in java.util.Collections is applied ot (java.util.ArraList<java.lang.Object>,LogGrep.MyComparator)
Collections.sort(myArrayList, new MyComparator());
Comparator is a Generic Interface. Do it like this:
static class MyComparator implements Comparator<Map.Entry<String, Integer>> {
public int compare(Map.Entry<String, Integer> obj1, Map.Entry<String, Integer> obj2){
...
}
}
and define your list as
List<Map.Entry<String, Integer>> myArrayList = new ArrayList<Map.Entry<String, Integer>>()
And the compiler will be happy again.
Read the Generics Tutorial for more info. Or Angelika Langer's Generics FAQ.
Btw, unless your Comparator needs runtime parameters or has mutable state, you should define it as a Constant instead of creating a New instance for every call
You should use Comparator<T> interface not a raw Comparator.
Read this article.
You can do it in a type safe way as follows:
Map<String, Integer> map = new LinkedHashMap<String, Integer>();
map.put("four", 4);
map.put("one", 1);
map.put("five", 5);
map.put("three", 3);
map.put("two", 2);
System.out.println(map);
List<Map.Entry<String, Integer>> entryList = new ArrayList<Map.Entry<String, Integer>>(map.entrySet());
Collections.sort(entryList, new Comparator<Map.Entry<String, Integer>>() {
#Override
public int compare(Map.Entry<String, Integer> e1, Map.Entry<String, Integer> e2) {
return e1.getValue().compareTo(e2.getValue());
}
});
map.clear();
for(Map.Entry<String, Integer> e : entryList) {
map.put(e.getKey(), e.getValue());
}
System.out.println(map);
Output:
{four=4, one=1, five=5, three=3, two=2}
{one=1, two=2, three=3, four=4, five=5}