I have a nullable Map<String, String> myMap in my Java 8 class.
I need to fetch a value from the map based on the following condition.
get keyA;
if not present, get keyB;
if not present, get keyC.
I understand that Java 8's Optional<T> brings the required behavior. Is there a way to use Optional to elegantly get the data instead of using containsKey checks?
Building on Alexander Ivanchenko's answer, you could get the first non-null value from the map based on a series of alternate keys:
public static Optional<String> getValue(Map<String, String> map,
String keyA, String... keys) {
Optional<String> result = Optional.ofNullable(map.get(keyA));
for (String key : keys) {
if (result.isPresent()) break;
result = result.or(() -> Optional.ofNullable(map.get(key)));
}
return result;
}
You could use the getOrDefault method of map, but something like
map.getOrDefault("keyA", map.getOrDefault("keyB", map.get("keyC")))
seems overly specific and complicated, and you still have to deal with the fact that neither keyA nor keyB nor keyC might be present, so you might still get null, and this has the performance penalty of looking up all three values from the map no matter which is returned.
It also only works for three keys.
No. There is no special integration of Optional with Maps.
More to the point, Optional does not support null values. Assuming that by "nullable map" you mean "a map that can contain null values", you can't possibly use Optional to distinguish between "null values that are in your map" and "values that aren't in your map at all." There is absolutely no way to use Optional helpfully in this scenario.
You can get an Optional result of retrieving the value corresponding to one of the given keys from a Map containing nullable values by using Stream.ofNullable() and Optional.or().
Method Optional.or() expects a supplier of Optional which will be utilized only if this method was invoked on an empty optional.
public static Optional<String> getValue(Map<String, String> myMap,
String keyA, String keyB, String keyC) {
return Stream.ofNullable(myMap) // precaution for myMap == null
.flatMap(map -> Stream.ofNullable(map.get(keyA)))
.findFirst()
.or(() -> Optional.ofNullable(myMap.get(keyB))) // evaluated only if keyA is not present
.or(() -> Optional.ofNullable(myMap.get(keyC))); // evaluated only if both keyA and keyB are not present
}
Note: the method above is meant to accommodate the safe fetching (as the question title states) and will return an empty Optional if all the given keys are absent or if one or more of them is mapped to a null value.
Related
I have a map as below
Map<String, String> myMap = new HashMap<>();
myMap.put("a", "Something");
myMap.put("b", null);
myMap.put("c", "more");
and a list,
List<String> myList = Arrays.asList("a","b");
I want to check, whether all the values in myMap with keys in myList are null
I have created a method as follows and it works fine. I wanted to check whether we can achieve the same in one line of code using stream
myMap.values().removeIf(Objects::isNull);
Map<String, String> resultMap = myList.stream().filter(myMap::containsKey).collect(Collectors.toMap(Function.identity(), myMap::get));
if(!resultMap.isEmpty()){
// Atleast one not null value is present in myMap with key in myList
}
Sure, simply check if all elements in the list match a non-null value from the map:
myList.stream().allMatch(x -> myMap.containsKey(x) && myMap.get(x) == null);
// or (more overhead, but you might prefer its expressivness):
myList.stream()
.filter(myMap::containsKey)
.map(myMap::get)
.allMatch(Objects::isNull);
Or, if you consider "missing keys" to be equivalent to "having null":
myList.stream().map(myMap::get).allMatch(Objects:isNull);
Map.get specifies that keys that aren't present return null. So you can filter out keys mapped to null or not mapped at all with just one null check.
Map<String, String> resultMap = myList.stream()
.filter(key -> myMap.get(key) != null)
.collect(Collectors.toMap(Function.identity(), myMap::get));
If you don't need a resultMap it's even shorter with anyMatch
myList.stream().allMatch(key -> myMap.get(key) != null)
Unlike myMap.values().removeIf(Objects::isNull) this will not modify the original map.
So, you've removed the entries having null values with this line:
myMap.values().removeIf(Objects::isNull);
Fine, since keeping null-references in the collections an antipattern because these elements can't provide any useful information. So I consider this was your intention unrelated to checking if all the string in myList were associated with null (or not present).
Now to check whether myMap contain any of the elements from myList (which would automatically imply that the value mapped to such element is non-null) you can create a stream over the contents of myList and check each element against the key-set of myMap:
boolean hasNonNullValue = myList.stream().anyMatch(myMap.keySet()::contains);
I suspect you might to perform some actions with such keys (if any). If so, then instead of performing a check provided above would make sense to generate a list of these keys:
List<String> keysToExamine = myList.stream()
.filter(myMap.keySet()::contains)
.toList(); // for JDK versions earlier then 16 use .collect(Collectors.toList()) instead of toList()
Note: check elements of the list against the key-set, not the opposite, otherwise you might cause performance degradation.
Stream#findAny and Optional#ifPresent
It seems you want to perform some action if at least one non-null value is present in myMap with the corresponding key in myList. If yes, this combination meets your requirement perfectly.
myMap.keySet()
.stream()
.filter(k -> myMap.get(k) != null && myList.contains(k))
.findAny()
.ifPresent(
// Atleast one not null value is present in myMap with key in myList
System.out::println // A sample action
);
Demo
Consider the following:
for (Boolean i: x.values()) {
if (i == false) {
return // KEY;
}
}
In the above code, I am trying to iterate through the HashMap. And I want to return a Key when the value is false.
How can I do it?
You need to loop through the HashMap's entrySet:
for (Map.Entry<Object, Boolean> entry : x.entrySet()) {
if(entry.getValue() == false){
return entry.getKey();
}
}
In order to find first a key that is mapped to a particular value you need to iterate either over the key set or over the entry set.
The latter option is preferred because at each iteration step you will have a key-value pair on your hands. Therefore, it would be slightly faster than interrogating the map at every iteration (remainder: there could be collisions in a real hashmap, and finding the target node can take more time than accessing the value on a map-entry).
But you definitely can't use a collection of map values returned by a method values() like you're doing in the code-snippet you've posted. There's no fast way of retrieving a key when you have only a value on your hands.
In the example below, ill show how to obtain a key from a map of arbitrary type that matches the condition (a Predicate) provided dynamically at runtime using Stream API.
There's one important thing to consider: target value might not be present in the map. And the code should handle this case. We can provide a default value (either directly or via Supplier), throw an exception (if according to the application logic the given value is always expected to be present), or by utilizing methods ifPresent(), ifPresentOrElse() provided by the Optional class. In the example below, I've chosen to provide a default value.
Take a look at these tutorials for more information on lambda expressions and streams
public static void main(String[] args) {
Map<String, Boolean> sourceMap =
Map.of("A", true, "B", false, "C", true);
String result = getFirstKey(sourceMap, entry -> !entry.getValue(), "");
System.out.println(result);
}
public static <K, V> K getFirstKey(Map<K, V> map,
Predicate<Map.Entry<K, V>> condition,
K defaultKey) {
return map.entrySet().stream()
.filter(condition) // Stream<Map.Entry<K, V>>
.findFirst() // Optional<Map.Entry<K, V>>
.map(Map.Entry::getKey) // Optional<K>
.orElse(defaultKey); // or apply .orElseThrow() depending on your needs
}
Output
B // key assosiated with a value of `false`
I'm new to streams and I am trying to filter through this map for the first true value in a key/value pair, then I want to return the string Key, and replace the Value of true with false.
I have a map of strings/booleans:
Map<String, Boolean> stringMap = new HashMap<>();
//... added values to the map
String firstString = stringMap.stream()
.map(e -> entrySet())
.filter(v -> v.getValue() == true)
.findFirst()
//after find first i'd like to return
//the first string Key associated with a true Value
//and I want to replace the boolean Value with false.
That is where I am stuck--I might be doing the first part wrong too, but I'm not sure how to both return the string value and replace the boolean value in the same stream? I was going to try to use collect here to deal with the return value, but I think if I did that it would maybe return a Set rather than the string alone.
I could work with that but I would prefer to try to just return the string. I also wondered if I should use Optional here instead of the String firstString local variable. I've been reviewing similar questions but I can't get this to work and I'm a bit lost.
Here are some of the similar questions I've checked by I can't apply them here:
Sort map by value using lambdas and streams
Modify a map using stream
Map doesn't have a stream() method, also your .map() doesn't really make sense. What is entrySet() in that context? And at last, findFirst() returns an Optional so you'd either change the variable, or unwrap the Optional.
Your code could look something like this:
String first = stringMap.entrySet().stream()
.filter(Map.Entry::getValue) // similar to: e -> e.getValue()
.map(Map.Entry::getKey) // similar to: e -> e.getKey()
.findFirst()
.orElseThrow(); // throws an exception when stringMap is empty / no element could be found with value == true
Please also note that the "first" element doesn't really make sense in the context of maps. Because a normal map (like HashMap) has no defined order (unless you use SortedMap like TreeMap).
At last, you shouldn't modify the input map while streaming over it. Find the "first" value. And then simply do:
stringMap.put(first, false);
Optional<String> firstString = stringMap.entrySet().stream()
.filter( v-> v.getValue() == true )
.map( e -> e.getKey())
.findFirst();
Your ordering of the operations seems to be off.
stringMap.entrySet().stream()
On a map you could stream the key set, or the entry set, or the value collection. So make sure you stream the entry set, because you'll need access to both the key for returning and the value for filtering.
.filter( v-> v.getValue() == true )
Next filter the stream of entries so that only entries with a true value remain.
.map( e -> e.getKey())
Now map the stream of entries to just the String value of their key.
.findFirst();
Find the first key whose value is true. Note that the entries in a hash map are in no particular order. The result of the find first operation is as you already mentioned an optional value.
As for now I am doing :
Map<Item, Boolean> processedItem = processedItemMap.get(i);
Map.Entry<Item, Boolean> entrySet = getNextPosition(processedItem);
Item key = entrySet.getKey();
Boolean value = entrySet.getValue();
public static Map.Entry<Item, Boolean> getNextPosition(Map<Item, Boolean> processedItem) {
return processedItem.entrySet().iterator().next();
}
Is there any cleaner way to do this with java8 ?
I see two problems with your method:
it will throw an exception if the map is empty
a HashMap, for example, has no order - so your method is really more of a getAny() than a getNext().
With a stream you could use either:
//if order is important, e.g. with a TreeMap/LinkedHashMap
map.entrySet().stream().findFirst();
//if order is not important or with unordered maps (HashMap...)
map.entrySet().stream().findAny();
which returns an Optional.
Seems like you need findFirst here
Optional<Map.Entry<Item, Boolean>> firstEntry =
processedItem.entrySet().stream().findFirst();
Obviously a HashMap has no order, so findFirst might return a different result on different calls. Probably a more suitable method would be findAny for your case.
I have a stream of words and I would like to sort them according to the occurrence of same elements (=words).
e.g.: {hello, world, hello}
to
Map<String, List<String>>
hello, {hello, hello}
world, {world}
What i have so far:
Map<Object, List<String>> list = streamofWords.collect(Collectors.groupingBy(???));
Problem 1: The stream seems to lose the information that he is processing Strings, therefore the compiler forces me to change the type to Object, List
Problem 2: I don't know what to put inside the parentesis to group it by the same occurrence. I know that I am able to process single elements within th lambda-expression but I have no idea how to reach "outside" each element to check for equality.
Thank You
To get a Map<String, List<String>>, you just need to tell to the groupingBy collector that you want to group the values by identity, so the function x -> x.
Map<String, List<String>> occurrences =
streamOfWords.collect(groupingBy(str -> str));
However this a bit useless, as you see you have the same type of informations two times. You should look into a Map<String, Long>, where's the value indicates the occurrences of the String in the Stream.
Map<String, Long> occurrences =
streamOfWords.collect(groupingBy(str -> str, counting()));
Basically instead of having a groupingBy that return values as List, you use the downstream collector counting() to tell that you want to count the number of times this value appears.
Your sort requirement should imply that you should have a Map<Long, List<String>> (what if different Strings appear the same number of times?), and as the default toMap collector returns an HashMap, it has no notions of ordering, but you could store the elements in a TreeMap instead.
I've tried to summarize a bit what I've said in the comments.
You seems to have troubles with how str -> str can tell whether "hello" or "world" are different.
First of all str -> str is a function, that is, for an input x yields a value f(x). For example, f(x) = x + 2 is a function that for any value x returns x + 2.
Here we are using the identity function, that is f(x) = x. When you collect the elements from the pipeline in the Map, this function will be called before to obtain the key from the value. So in your example, you have 3 elements for which the identity function yields:
f("hello") = "hello"
f("world") = "world"
So far so good.
Now when collect() is called, for every value in the stream you'll apply the function on it and evaluate the result (which will be the key in the Map). If a key already exists, we take the currently mapped value and we merge in a List the value we wanted to put (i.e the value from which you just applied the function on) with this previous mapped value. That's why you get a Map<String, List<String>> at the end.
Let's take another example. Now the stream contains the values "hello", "world" and "hey" and the function that we want to apply to group the elements is str -> str.substring(0, 2), that is, the function that takes the first two characters of the String.
Similarly, we have:
f("hello") = "he"
f("world") = "wo"
f("hey") = "he"
Here you see that both "hello" and "hey" yields the same key when applying the function and hence they will be grouped in the same List when collecting them, so that the final result is:
"he" -> ["hello", "hey"]
"wo" -> ["world"]
To have an analogy with mathematics, you could have take any non-bijective function, such as x2. For x = -2 and x = 2 we have that f(x) = 4. So if we grouped integers by this function, -2 and 2 would have been in the same "bag".
Looking at the source code won't help you to understand what's going on at first. It's useful if you want to know how it's implemented under the hood. But try first to think of the concept with a higher level of abstraction and then maybe things will become clearer.
Hope it helps! :)
The KeyExtractor you are searching for is the identity function:
Map<String, List<String>> list = streamofWords.collect(Collectors.groupingBy(Function.identity()));
EDIT added explanation:
Function.identity() retuns a 'Function' with one method that does nothing more than returning the argument it gets.
Collectors.groupingBy(Function<S, K> keyExtractor) provides a collector, which collects all elements of the stream to a Map<K, List<S>>. It is using the keyExtractor implementation it gets to inspect the stream's objects of type S and deduce a key of type K from them. This key is the map's key used to get (or create) the list in the result map the stream element is added to.
If you want to group by some fields of an object, not a whole object and you don't want to change your equals and hashCode methods I'd create a class holding a set of keys for grouping purposes:
import java.util.Arrays;
#Getter
public class MultiKey {
public MultiKey(Object... keys) {
this.keys = keys;
}
private Object[] keys;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MultiKey multiKey = (MultiKey) o;
return Arrays.equals(keys, multiKey.keys);
}
#Override
public int hashCode() {
return Arrays.hashCode(keys);
}
}
And the groupingBy itself:
Map<MultiKey, List<VhfEventView>> groupedList = list
.stream()
.collect(Collectors.groupingBy(
e -> new MultiKey(e.getGroupingKey1(), e.getGroupingKey2())));