I have two map arguments: Map<X,Set<Y>> map1
and Map<X,Set<Y>> map2
.
I am looking for a way to write a method that generates a new map containing an Intersection of values for keys that are present in both maps, and a Union of values for keys that are present either in map1
or map2
.
In other words, for any X x
if it is in the domain of map1
but not in the domain of map2
, then its value will be map1.get(x)
. Same in the opposite case. If it is in both of them, then I would like to return a set that is the intersection of map1.get(x)
and map2.get(x)
.
Assuming I know which class X
is, this can be done with the following code:
public Map<X,Set<Y>> unifyAndIntersect(Map<X,Set<Y>> map1, Map<X,Set<Y>> map2) {
Map<X,Set<Y>> combined = new HashMap();
combined.putAll(map1);
for(X x : map2.keySet()){
if(combined.contains(x)){
Set<Y> res = Sets.newSet();
res.addAll(map1.get(x));
res.retainAll(map2.get(x));
combined.put(x,res);
}
else{
combined.put(x,map2.get(x));
}
}
}
However, I would like to make this method generic, in a sense that it will work for any X
and Y
. I have tried using Object
, but get an error converting from my class type to Object
...
Could you advise me on what is the correct way to do this?
CodePudding user response:
In order to declare a generic method, you need to provide generic type parameters <X,Y>
between method modifiers and return type (for more information, see). Without that X
and Y
in the map type Map<X,Set<Y>>
would not be treated as generic "placeholders" for types, but as types itself and the compiler will complain that types X
and Y
are unknown.
Don't forget the diamond <>
on the right side, while instantiating a generic class, new HashMap();
without a diamond creates a map of row type.
There's also an inconsistency in the code you've provided: if a key is present is both maps a new set would be added as a value into the resulting map, but if the key is contained only in one of them according to your code an existing set would be used. I would better to ensure that subsequent modifications of the values of the resulting map will have no impact on the state of map1
and map2
by generating a new set for every value.
public <X,Y> Map<X, Set<Y>> unifyAndIntersect(Map<X,Set<Y>> map1,
Map<X,Set<Y>> map2) {
Map<X, Set<Y>> combined = new HashMap<>();
for(Map.Entry<X, Set<Y>> entry: map1.entrySet()){
Set<Y> union = new HashSet<>(entry.getValue());
if (map2.containsKey(entry.getKey())) {
union.retainAll(map2.get(entry.getKey()));
}
combined.put(entry.getKey(), union);
}
return combined;
}
The same logic can be implemented using Stream API:
public <X,Y> Map<X, Set<Y>> unifyAndIntersect(Map<X,Set<Y>> map1,
Map<X,Set<Y>> map2) {
return map1.entrySet().stream()
.map(entry -> {
Set<Y> set2 = map2.get(entry.getKey());
return set2 == null ? Map.entry(entry.getKey(), new HashSet(entry.getValue())) :
Map.entry(entry.getKey(),
entry.getValue().stream()
.filter(set2::contains)
.collect(Collectors.toSet()));
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}