I want to provide the user defined method to merge the map in java 8? The method you create should accept two maps and “merging” behavior.
public <T> Map<? super T, ? super T> mergeMaps( Map<? super T, ? super
T> map1, Map<? super T, ? super T> map2 ) {
// merging code here
}
but I want something like this
public <T> Map<? super T, ? super T> mergeMaps( Map<? super T, ? super T>
map1, Map<? super T, ? super T> map2 , MergeTwoMaps<T, U> mergeTwoMaps)
{
// merging code here
}
Something like this. Java generic merge
Map<String, Integer> map1 = new HashMap<>();
Map<String, Integer> map2 = new HashMap<>();
map1.put(“key1”, 20);
map1.put(“key2”, 30);
map2.put(“key3”, 40);
map2.put(“key1”, 50);
mergeMaps(map1,map2,mergeBehaviour)
Output should be
map.get("key1")--> 70
Map<String, String> map1 = new HashMap<>();
Map<String, String> map2 = new HashMap<>();
map1.put(“key1”, "Hello");
map1.put(“key2”, "Hi");
map2.put(“key3”, "Ok");
map2.put(“key1”, "world");
mergeMaps(map1,map2,mergeBehaviour)
Output should be
map.get("key1")--> Helloworld
You should rethink your desired method signature. To cite the guidelines:
Using a wildcard as a return type should be avoided because it forces programmers using the code to deal with wildcards.
In other words, having wildcards in the return types forces the caller to spread wildcards into every code using the returned result.
The method should rather look like:
public static <K, V> Map<K, V> mergeMaps(
Map<? extends K, ? extends V> map1, Map<? extends K, ? extends V> map2,
BinaryOperator<V> mergeFunction) {
Map<K, V> result = new HashMap<>(map1);
map2.forEach((k,v) -> result.merge(k, v, mergeFunction));
return result;
}
It allows the caller to pick arbitrary key and value types, as long as the input argument are compatible, which is always the case if the input maps’ key and value types are the same as the output map’s or subtypes of it. Of course, this signature also allows the use case where key and value types are the same, but it does not require it.
The implementation of the merge operation itself is trivial.
You can raise the method’s flexibility even more by using
public static <K, V> Map<K, V> mergeMaps(
Map<? extends K, ? extends V> map1, Map<? extends K, ? extends V> map2,
BiFunction<? super V, ? super V, ? extends V> mergeFunction) {
Map<K, V> result = new HashMap<>(map1);
map2.forEach((k,v) -> result.merge(k, v, mergeFunction));
return result;
}
though this is rarely needed, because it’s unnecessary when passing method references or lambda expressions. This only helps when reusing already existing BiFunction instances.
Related
I have a generic function which accepts Collection<? extends T> ts.
I'm also passing:
Function<? extends T, ? extends K> classifier which maps each item T to a key K (possible to have duplicates)
Function<? extends T, Integer> evaluator which gives an integer value for the item.
The function itself has a built-in calculation ("int to int") for every produced Integer (could be something like squaring for our example)
Finally, I'd like to sum all of the values for each key.
So the end result is: Map<K, Integer>.
For example,
Let's say we have the list ["a","a", "bb"] and we use Function.identity to classify, String::length to evaluate and squaring as the built-in function. Then the returned map will be: {"a": 2, "b": 4}
How can I do that? (I guess that preferably using Collectors.groupingBy)
Here's one way to do it:
public static <T,K> Map<K,Integer> mapper (
Collection<T> ts,
Function<T, K> classifier,
Function<T, Integer> evaluator,
Function<Integer,Integer> calculator)
{
return
ts.stream()
.collect(Collectors.groupingBy(classifier,
Collectors.summingInt(t->evaluator.andThen(calculator).apply(t))));
}
The output for:
System.out.println (mapper(Arrays.asList("a","a","bb"),Function.identity(),String::length,i->i*i));
is
{bb=4, a=2}
Or another approach:
private static <K, T> Map<K, Integer> map(Collection<? extends T> ts,
Function<? super T, ? extends K> classifier,
Function<? super T, Integer> evaluator,
Function<Integer, Integer> andThen) {
return ts.stream()
.collect(Collectors.groupingBy(
classifier,
Collectors.mapping(evaluator.andThen(andThen),
Collectors.reducing(0, Integer::sum))
));
}
And use it with:
public static void main(String[] args) {
System.out.println(map(
Arrays.asList("a", "a", "bb"),
Function.identity(),
String::length,
x -> x * x));
}
Assume I have a method with the following signature:
<T, U extends Comparable<? super U>> Comparator<T> method(Map<String, Function<? super T, ? extends U>> comparatorFunctionMap)
The method accepts a map of functions (with string keys) and creates a Comparator<T> as a result (it isn't important how). Map values are instances of Function<? super T, ? extends U>, so that they can be directly passed to Comparator.comparing().
How do I populate this map in a type-safe way? Say I have a class Person with attributes name and age (and getters for them).
When I do the following:
Map<String, Function<? super Person, ? extends Comparable>> map1 = new HashMap<>();
map1.put("name", Person::getName);
method(map1);
I get a warning on lines 1 and 3. If I try this instead, for example:
Map<String, Function<? super Person, ? extends Comparable<?>>> map2 = new HashMap<>();
map2.put("name", Person::getName);
method(map2);
The third line is a compile error.
Is there a way to do this type-safely?
If you want to be able to add both Person::getName and Person::getAge in the map, you won't be able to use the method signature you propose because there is no U that is a supertype of both String, Integer and Comparable.
Essentially, your map is a Map<String, Function<T, Comparable<?>> since the comparables are not related to each other type-wise.
I don't think you can get around it without using raw types, which could look like below. Note that you still have type safety (you must pass a Comparable to the map).
static void m() {
Map<String, Function<Person, Comparable<?>>> map = new HashMap<>();
map.put("name", Person::getName);
map.put("age", Person::getAge);
Comparator<Person> c = method(map);
}
#SuppressWarnings(value = {"unchecked", "rawtypes"})
static <T> Comparator<T> method(String name, Map<String, Function<T, Comparable<?>>> comparatorFunctionMap) {
Function f = (Function) comparatorFunctionMap.get("age");
Comparator<T> c = Comparator.comparing(f);
return c;
}
The only (I think) limitation of this is that technically, one could pass a weird class say Weird implements Comparable<String> that would probably cause a runtime error.
In Function.class from Java8, we have:
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
Compose accepts:
Function<? super V, ? extends T> before
Rather than:
Function<V, ? extends T> before
Is there any plausible situation in which the fact that "V" is lower bounded matters?
The ? super allows the returned Function's input type (V) to be different from the arguments input type.
For example, this compiles with the ? super version but not the alternate one.
Function<Object, String> before = Object::toString;
Function<String, Integer> after = Integer::parseInt;
Function<Integer, Integer> composed = after.compose(before);
So I'm trying to sort a HashMap that contains the person's name (key) and their age and height in cm. The HashMap is set up like this:
Map<String, List<Integer>> s = new HashMap<>();
List<Integer> l1, l2, l3, l4;
l1 = new ArrayList<>();
l2 = new ArrayList();
l3 = new ArrayList();
l4 = new ArrayList();
l1.add(22); l1.add(177); //age then height
l2.add(45); l2.add(162);
l3.add(19); l3.add(182);
l4.add(38); l4.add(174);
s.put("John", l1);
s.put("Eric", l2);
s.put("Darren", l3);
s.put("Carter", l4);
Then I want to sort the Map by the person's height using a generic function.
This is what I tried:
static <K, V extends List<? extends Comparable<? super V>>> Map<K, V> specialSort(Map<K, V> map) {
Map<K, V> result = new LinkedHashMap<>();
Stream<Entry<K, V>> st = map.entrySet().stream();
st.sorted(Comparator.comparing(e -> e.getValue().get(0))).
forEach(e -> result.put(e.getKey(), e.getValue()));
return result;
}
However I get this error:
incompatible types: inferred type does not conform to upper bound(s)
inferred: CAP#1
upper bound(s): Comparable<? super CAP#1>,V,Object
where V,K are type-variables:
V extends List<? extends Comparable<? super V>> declared in method <K,V>specialSort(Map<K,V>)
K extends Object declared in method <K,V>specialSort(Map<K,V>)
where CAP#1 is a fresh type-variable:
CAP#1 extends Comparable<? super V> from capture of ? extends Comparable<? super V>
The base function I'm using is from this thread: https://stackoverflow.com/a/2581754
This is the function:
public static <K, V extends Comparable<? super V>> Map<K, V>
sortByValue( Map<K, V> map )
{
Map<K,V> result = new LinkedHashMap<>();
Stream <Entry<K,V>> st = map.entrySet().stream();
st.sorted(Comparator.comparing(e -> e.getValue()))
.forEach(e ->result.put(e.getKey(),e.getValue()));
return result;
}
I've been trying to get this to work for about an hour and a half now and I've almost given up. Please help!
Ok, like mentioned in the comments to your question, it is not really an object oriented approach
But it's fine for practicing with generics and lambdas.
It will work, when you also declare the List-type.
public static <K, V extends Comparable<? super V>> Map<K, List<V>> sortByValue( Map<K, List<V>> map ) {
Map<K,List<V>> result = new LinkedHashMap<>();
Stream <Entry<K,List<V>>> st = map.entrySet().stream();
st.sorted(Comparator.comparing(e -> e.getValue().get(1)))
.forEach(e -> result.put(e.getKey(), e.getValue()));
return result;
}
Alternatively, you can use the sorted method in this way:
st.sorted((e1,e2)->{return e1.getValue().get(1).compareTo(e2.getValue().get(1));})
.forEach(e -> result.put(e.getKey(), e.getValue()));
And check the results with:
result.forEach( (name, list)-> System.out.println(""+name+":" + list.get(1).toString()));
In this answer I attempted to create a static utility method to make a List into a Map:
public static <K, T> Map<K, T> toMapBy(List<T> list,
Function<? super T, ? extends K> mapper) {
return list.stream().collect(Collectors.toMap(mapper, Function.identity()));
}
It works just fine. However, I found that the method cannot be used in all the same contexts as the list.stream().collect(...) expression. The method isn't as flexible.
List<Student> students = Arrays.asList();
Map<Long, Student> studentsById1 = students.stream()
.collect(Collectors.toMap(Student::getId, Function.identity()));
Map<Long, Student> studentsById2 = toMapBy(students, Student::getId);
Map<Long, Person> peopleById1 = students.stream()
.collect(Collectors.toMap(Student::getId, Function.identity()));
Map<Long, Person> peopleById2 = toMapBy(students, Student::getId); // compile error!
In this example, Student is a subtype of Person and has a getId method that returns a Long.
The last statement fails with incompatible types: inference variable T has incompatible bounds ... (JDK 1.8.0_25). Is there a way to define the type parameters so that the static method will work in the same contexts as the expression it contains?
You could add a type parameter for the values of the map so they can be different from T:
public static <K, V, T extends V> Map<K, V> toMapBy(List<T> list,
Function<? super T, ? extends K> mapper) {
return list.stream().collect(Collectors.toMap(mapper, Function.identity()));
}
Your last line calls the method toMapBy in which the compiler infers the type Student for T. So it obviously returns a List<Long, Student>.
But generics aren't covariant!
That means, you cannot assign a List<Long, Student> to a variable of type List<Long, Person>, because they are not in a subtype relationship.
The solution is to use the subtype form:
Map<Long, ? extends Person> peopleById2 = toMapBy(students, Student::getId); // no compiler error
With this bit:
Map<Long, Person> peopleById1 = students.stream()
.collect(Collectors.toMap(Student::getId, Function.identity()));
Notice that you do not provide arguments to Function.identity(). The compiler is free to infer it as Function.<Person>identity() to resolve the difference imposed by the return value assignment.
This should be good enough for your purpose:
public static <K, T> Map<K, T> toMapBy(
List<? extends T> list, // <- note
Function<? super T, ? extends K> mapper
) {
...
}
Now the elements of the List can be a subtype of the Map values. Or you can define a third parameter like #Alex has suggested.