Java Generic Advanced Usage - java

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());

Related

Merge map of collections

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());
}
}

Type mismatch involving sets and collections

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 :-)

How can I add an object to <? extends Interface>?

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");

How to use an objects attribute as method parameter to set a map key?

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!

Generic 0 cannot be cast to java.lang.Short

I have two maps in my class (I am new to generics)
private Map<Integer, Integer> aMap = new ConcurrentHashMap<Integer, Integer>();
private Map<Integer, Short> bMap = new HashMap<Integer, Short>();
If key does not exist in map I want to get a zero value. So I have made this wrapper method to minimize typing containsKey(key)
#SuppressWarnings("unchecked")
private <T extends Number> T getValue (Map<Integer, T> map, Integer key) {
return (T) ((map.containsKey(key)) ? map.get(key) : 0);
}
I call it like
Integer a = getValue(aMap, 15); //okay in any case
Short b = getValue(bMap, 15); //15 key does not exist
For second case it gives me:
ClassCastException: java.lang.Integer cannot be cast to java.lang.Short
So probably I would need to do something like : new Number(0), but Number is abstract.
How can I fix it?
EDIT:
My idea is to do arithmetic operations without additional ifs:
Integer a = getValue(aMap, 15);
a = a + 10;
One way is to supply the default value as an argument to your function:
private <T extends Number> T getValue (Map<Integer, T> map, Integer key, T dflt) {
return (T) ((map.containsKey(key)) ? map.get(key) : dflt);
}
public static void main(String[] args) {
Integer a = getValue(aMap, 15, 0); //okay in any case
Short b = getValue(bMap, 15, (short)0); //15 key does not exist
}
Well, you can't do much about that without also providing T in a way that code can look at.
The simplest approach at that point would probably be to keep a map of 0 values:
private static Map<Class<?>, Number> ZERO_VALUES = createZeroValues();
private static Map<Class<?>, Number> createZeroValues() {
Map<Class<?>, Number> ret = new HashMap<Class<?>, Number>();
ret.put(Integer.class, (int) 0);
ret.put(Short.class, (short) 0);
ret.put(Long.class, (long) 0);
// etc
}
Then:
private <T extends Number> T getValue (Map<Integer, T> map, Integer key, Class<T> clazz) {
return clazz.cast(map.containsKey(key) ? map.get(key) : ZERO_VALUES.get(clazz));
}
Then you'd unfortunately have to call it as:
Short b = getValue(bMap, 15, Short.class);
Basically this is a limitation of Java generics :(
In situations like this, I just override the get() method:
private Map<Integer, Short> bMap = new HashMap<Integer, Short>() {
#Override
public Short get(Object key) {
return containsKey(key) ? super.get(key) : new Short(0);
}
};
Then you can just use it anywhere and it will behave as you specified.
Cast 0 to short explicitly as it will be int by default. And then convert to Short wrapper. In java u cannot directly convert from primitive to a Wrapper of a different type.

Categories

Resources