Java 8 - Map(Enhancements of existing methods) and Annotations
Hi, I am Malathi Boggavarapu working at Volvo Group and i live in Gothenburg, Sweden. I have been working on Java since several years and had vast experience and knowledge across various technologies.
Map - Enhancements of existing methods
forEach() - This is the same kind of method that is defined on iterable interface which is available for all instances of Collection. forEach is also available for Map and takes BiConsumer as parameter. BiConsumer is a regular Consumer which takes two parameters instead of one and those two parameters are ofcourse Key and value pair of the Map
Map<String, Person> map = ...;
map.forEach((key, person) -> System.out.println(key + " " +person));
getOrDefault() - get() method in earlier versions may return sometimes null value. null value will be returned if either the key is not present in the Map or the key is associated with null value.
In java8, getorDefault method is used to specify the default value to be returned in case when the Map does not contain any value for the key.
Map<String, Person> map = ...;
Person defaultPErson = PErson.DEFAULT_PERSON;
Person p = map.getOrDefault(key, defaultPerson);
putIfAbsent() - This is the newer version of put method in java8. In earlier versions, If put method is called on the key, the existing value for the key will be erased and replaced with the new value.
In java8, putIfAbsent() does not erase the existing value in the map and put the value if and only if the key does not have any value.
Map<String, Person> map = ...;
map.put(key, person);
map.putIfAbsent(key, person);
replace(key, oldValue, newValue) - replace the value for the key with newValue only if the oldValue is mapped to the key.
replaceAll() - Takes a BiFunction that takes key and value and that will produce a new value.
remove(key, value) - remove the key associated with the value specified in the method
compute(), computeIfAbsent() and computeIfPresent()
Often we fetch a value from a map, make some calculations on it and put the value back into the map. This can be hard to get right when concurrency involved. With java8, we can pass a BiFunction to the methods to handle it properly.
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
// Compute a new value for the existing key
System.out.println(map.compute("A",
(k, v) -> v == null? 42: v + 41));
System.out.println(map);
// This will add a new (key, value) pair
System.out.println(map.compute("X",
(k, v) -> v == null? 42: v + 41));
System.out.println(map);
computeIfPresent()
the key and the value associated should be present in the map otherwise the BifFunction is not called and does not compute a new value.
computeIfAbsent()
We just provide a key and if this key is not present in map then BiFunction executed and return a new value for the key.
computeIfAbsent is used to compute Bimaps. It is a map in which a value is itself an another map.
Map<String, Map<Integer, Person>> biMap = ...;
Person p = ..;
bimap.computeIfAbsent(key1, key -> new HashMap<>().put(key2, p));
Case 1 : If key1 is already present in the map, then the function expressed as lambda expression is not evaluated at all but returns existing hashMap that has been put as value associated with key1.
So we can directly call put on this existing hashMap and associated key2 with person p.
Case 2: Now key1 is not present, then lambda expression will be executed and new HashMap will be built and associate it to the key1 key in the first map and then this new empty hashmap will also be returned by the method and this time we call put on this new hashMap and associate person to the new key key2.
This basically allows us to create bimaps in java with just one line of code.
merge() - This method assumes that key is present in the map or associated to a null value. when we call merge, we pass key and person as a parameter, the key should be present in the map and person should match the person already associated to the key in the map and then we compute a new person with lambda expression passed as a parameter. This lambda expression is just the implementation of BiFunction that takes the key and value of the map as a parameters and builds a new value out of those parameters.
Map<String, Person> map = ...;
map.merge(key, person, (key ,person)) -> newPerson);
Example:
People.txt
Malathi 34 F
Neelima 27 F
Naresh 34 M
public class MergingMaps{
public static void main(String args[]){
List<Person> persons = new ArrayList<Person>();
BufferedReader reader = new BufferedReader(new InputStreamReader(MergingMaps.class.getResourceAsStream("People.txt")));
Stream<String> stream = reader.lines();
stream.map(line -> {
String s[] = line.split(" ");
Person p = new Person(s[0], Integer.parseInt(s[1]), s[2]);
persons.add(p);
return p;
}).forEach(System.out::println);
List<Person> list1 = persons.subList(0,10);
List<Person> list2 = persons.subList(10, persons.size());
Map<Integer, List<Person>> map1 = mapByAge(list1);
map1.forEach((age, list) -> System.out.println("age : " + age + "list " + list));
Map<Integer, List<Person>> map2 = mapByAge(list2);
map1.forEach((age, list) -> System.out.println("age : " + age + "list " + list));
map2.entrySet().stream().forEach(entry -> {
map1.merge(entry.getKey(), entry.getValue(), (l1, l2) -> {
l1.addAll(l2);
return l1;
}) ;
});
map1.forEach((age, list) -> System.out.println("age : " + age + "list " + list));
}
private static Map<Integer, List<Person>> mapByAge(List<Person> list){
Map<Integer, List<Person>> map = list.stream().collect(Collectors.groupingBy(Person:getAge));
return map;
}
}
In the above code we read the People.txt content to the lists list1 and list2. Later we created two maps map1 and map2 using the method mapByAge(). This method returns map with age as key and value as List which holds the list of people of the same age. Thus we create two maps for two given lists. Later we are merging map1 and map2 using merge method as shown above.
In the example above, for each entry of map2, we are checking against with map1 entries. If key of map2 matches with the key of map1 then we return list of combined map1 and map2 list values. Hence the final result will be map1 which holds list of persons grouped by age.
Hope it is clear. Try the above example. Dont forget ot create Person DAO :)
Annotations
Java8 brings the concept of multiple annotations. So what is the problem with java7 annotations??
If we see the below example, suppose if we want to test the case with multiple parameters, we need to wrap the annotations with another annotation. Here we wrap a list of annotations using TestCases annotation.
@TestCases({
@TestCase(param=1, expected = false),
@TestCase(param=2, expected = true),
})
public boolean even(int param){
return param % 2 == 0
}
In java8 it is made simple. we no need to wrap the annotations using TestCases. See below
@TestCase(param=1, expected = false),
@TestCase(param=2, expected = true)
public boolean even(int param){
return param % 2 == 0
}
How does it work? The wrapping annotation is automatically added for us.
First create the annotations as usual
@Repeatable(TestCases.class)
@interface TestCase{
int param();
boolean expected();
}
@interface TestCases{
TestCase[] value;
}
we just provide the wrapping annotation; in our case TestCases as an attribute to the Repeatable annotation. So the compiler will automatically add wrapping annotation - TestCases for us
2) Java8 allows annotations to be put on types
Example 1: To declare that a variable should not be null
private @NonNull List<Person> persons = ...;
Example 2 To declare that a list should not be null, and should not contain null values
private @NonNull List<@NonNull Person> persons = ...;
forEach() - This is the same kind of method that is defined on iterable interface which is available for all instances of Collection. forEach is also available for Map and takes BiConsumer as parameter. BiConsumer is a regular Consumer which takes two parameters instead of one and those two parameters are ofcourse Key and value pair of the Map
Map<String, Person> map = ...;
map.forEach((key, person) -> System.out.println(key + " " +person));
getOrDefault() - get() method in earlier versions may return sometimes null value. null value will be returned if either the key is not present in the Map or the key is associated with null value.
In java8, getorDefault method is used to specify the default value to be returned in case when the Map does not contain any value for the key.
Map<String, Person> map = ...;
Person defaultPErson = PErson.DEFAULT_PERSON;
Person p = map.getOrDefault(key, defaultPerson);
putIfAbsent() - This is the newer version of put method in java8. In earlier versions, If put method is called on the key, the existing value for the key will be erased and replaced with the new value.
In java8, putIfAbsent() does not erase the existing value in the map and put the value if and only if the key does not have any value.
Map<String, Person> map = ...;
map.put(key, person);
map.putIfAbsent(key, person);
replace(key, oldValue, newValue) - replace the value for the key with newValue only if the oldValue is mapped to the key.
replaceAll() - Takes a BiFunction that takes key and value and that will produce a new value.
remove(key, value) - remove the key associated with the value specified in the method
compute(), computeIfAbsent() and computeIfPresent()
Often we fetch a value from a map, make some calculations on it and put the value back into the map. This can be hard to get right when concurrency involved. With java8, we can pass a BiFunction to the methods to handle it properly.
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
// Compute a new value for the existing key
System.out.println(map.compute("A",
(k, v) -> v == null? 42: v + 41));
System.out.println(map);
// This will add a new (key, value) pair
System.out.println(map.compute("X",
(k, v) -> v == null? 42: v + 41));
System.out.println(map);
Comments
Post a Comment