Using optional to populate value in a Map - java

I am new to Java, and still learning Optionals. I understood its used to avoid Null Pointer Exception.
I have a piece of code wherein I want to use optional, which is like this:
final MetadataExtractor<FCAddress> metadataExtractor = t -> {
final Map<String, String> metadata = new HashMap<>();
metadata.put("senderCountryCode", t.getCountryCode());
metadata.put("senderState", t.getState());
metadata.put("senderPostalCode",t.getPostalCode());
return metadata;
};
Here, my use case is, if the SenderState is empty, i.e. t.getState() is empty, I want the map field to to be empty, that is not populated.
I tried something like this:
final MetadataExtractor<FCAddress> metadataExtractor = t -> {
final Map<String, String> metadata = new HashMap<>();
metadata.put("senderCountryCode", t.getCountryCode());
Optional<String> senderState = Optional.of(t.getState());
senderState.ifPresent(metadata.put("senderState", t.getState());
metadata.put("senderPostalCode",t.getPostalCode());
return metadata;
};
But this gives a compilation error, where am I going wrong in this?
Error is: "ifPresent
(java.util.function.Consumer)
in Optional cannot be applied"

You need to pass a Consumer (a lambda) to ifPresent:
senderState.ifPresent(state -> metadata.put("state", state);
// important: the Consumer always receive a parameter.
// An empty value `() ->` is not valid!
The lambda will only get executed in case senderState is present (that means, is not null).
BTW it's very important to construct your senderState by using Optional.ofNullable(...)! Otherwise, it'll throw an NPE.
Check out an in-depth tutorial: https://www.baeldung.com/java-optional

In this particular code the use of Optional is not useful because you don't like to add an Optional to the map, but only skip null values.
The simplest solution that doesn't create new objects is adding a check on value of t.getState() as follow:
final MetadataExtractor<FCAddress> metadataExtractor = t -> {
final Map<String, String> metadata = new HashMap<>();
metadata.put("senderCountryCode", t.getCountryCode());
if (t.getState() != null) {
metadata.put("senderState", t.getState());
}
metadata.put("senderPostalCode",t.getPostalCode());
return metadata;
};
Just for studying purpose the solution of GhostCat works with an Optional:
senderState.ifPresent(() -> metadata.put("senderState", t.getState());
the complete example will be:
final MetadataExtractor<FCAddress> metadataExtractor = t -> {
final Map<String, String> metadata = new HashMap<>();
metadata.put("senderCountryCode", t.getCountryCode());
// Here you create a not useful object that can be replaced with a simple if
Optional<String> senderState = Optional.ofNullable(t.getState());
// Here you create a second not necessary object because a lambda
// espression is an instance of an anonimous class implementing
// the corresponding interface
senderState.ifPresent(() ->
metadata.put("senderState", t.getState()
);
metadata.put("senderPostalCode",t.getPostalCode());
return metadata;
};
Note that this solution will create two unnecessary objects:
The explicit Optional senderState
and another object for the Consumer created as lambda expression inside the ifPresent method

Just to present a totally different approach to the problem:
Put everything into the map, and then remove the null values after:
metadata.values().removeIf(Objects::isNull);
This is a lot neater syntactically, but may or may not be faster because of doing something and then undoing it, as opposed to just not doing it in the first place.

still learning Optionals. I understood its used to avoid Null Pointer Exception.
Not necessarily. They are rather a mean that allows you to have something that represents "nothing".
Regarding your actual compile error:
senderState.ifPresent(metadata.put("senderState", t.getState());
ifPresent() wants a consumer. From the javadoc:
public void ifPresent(Consumer<? super T> consumer)
If a value is present, invoke the specified consumer with the value, otherwise do nothing.
metadata.put() isn't a consumer. That is an ordinary method call on that map! And it will return a the "value" type of your map, so a String. Strings aren't Consumers!
Long story short: if you really want to use ifPresent() here, then you would probably want to pass a lambda expression (see here for some examples).
In your case,
senderState.ifPresent(state -> metadata.put("senderState", state));
should do.

Related

How to add to a casted list in a HashMap?

I have a Map<String, Object>, and one of the values is a List<String>. Currently, I have:
if (!data.containsKey(myVar)) {
List<String> emp = new ArrayList();
data.put(myVar, emp); // myVar is a String
} else {
data.get(myVar).add(otherVar); // "add" gives an error; otherVar is a String
}
My current solution is to do
} else {
#SuppressWarnings("unchecked")
List<String> vals = (List<String>) data.get(myVar);
vals.add(otherVar);
data.put(myVar, vals);
}
Is there a better solution?
Unless you can change Map<String, Object> to Map<String, List<String>>, there's no better solution. You need a cast. You can of course add instanceof checks and extract it to a helper method, but in the end you need a cast.
One thing though: you don't need to do another put into the map — vals.add(otherVar) modifies the List which is already in the map, it doesn't return a new list instance.
Regarding a one-liner (casting to List<String> and doing add in the same line) — this isn't very good since then you would either have to tolerate the "unchecked cast" compiler warning, or you would have to put #SuppressWarnings("unchecked") at the method level, which could suppress other warnings of this type within the method.
EDIT
The Map either has strings or lists as it's values
In this case you may consider changing the data structure to Map<String, List<String>>. A list consisting of a single element is perfectly valid :)
Then the code gets really simple. In Java 8 it's a one-liner:
data.computeIfAbsent(myVar, key -> new ArrayList<>()).add(otherVar);

Java 8 using stream, flatMap and lambda

I have this piece of code and I want to return a list of postCodes:
List<String> postcodes = new ArrayList<>();
List<Entry> entries = x.getEntry(); //getEntry() returns a list of Entry class
for (Entry entry : entries) {
if (entry != null) {
Properties properties = entry.getContent().getProperties();
postcodes.addAll(Arrays.asList(properties.getPostcodes().split(",")));
}
}
return postcodes;
Here's my attempt to use stream() method and the following chained methods:
...some other block of code
List<Entry> entries = x.getEntry.stream()
.filter(entry -> recordEntry != null)
.flatMap(entry -> {
Properties properties = recordEntry.getContent().getProperties();
postCodes.addAll(Arrays.asList(properties.getPostcodes().split(",")));
});
you've got several issues with your code i.e:
postCodes.addAll is a side-effect and therefore you should avoid doing that otherwise when the code is executed in parallel you'll receive non-deterministic results.
flatMap expects a stream, not a boolean; which is what your code currently attempts to pass to flatMap.
flatMap in this case consumes a function that also consumes a value and returns a value back and considering you've decide to use a lambda statement block then you must include a return statement within the lambda statement block specifying the value to return. this is not the case within your code.
stream pipelines are driven by terminal operations which are operations that turn a stream into a non-stream value and your code currently will not execute at all as you've just set up the ingredients but not actually asked for a result from the stream.
the receiver type of your query should be List<String> not List<Entry> as within your current code the call to Arrays.asList(properties.getPostcodes().split(",")) returns a List<String> which you then add to an accumulator with the call addAll.
thanks to Holger for pointing it out, you're constantly failing to decide whether the variable is named entry or recordEntry.
That said here's how I'd rewrite your code:
List<String> entries = x.getEntry.stream()
.filter(Objects::nonNull)
.map(Entry::getContent)
.map(Content::getProperties)
.map(Properties::getPostcodes‌)
.flatMap(Pattern.co‌mpile(",")::splitAsS‌tream)
.collect(Collectors.toList());
and you may want to use Collectors.toCollection to specify a specific implementation of the list returned if deemed appropriate.
edit:
with a couple of good suggestions from shmosel we can actually use method references throughout the stream pipelines and therefore enabling better intent of the code and a lot easier to follow.
or you could proceed with the approach:
List<String> entries = x.getEntry.stream()
.filter(e -> e != null)
.flatMap(e -> Arrays.asList(
e.getContent().getProperties().getPostcodes().split(",")).stream()
)
.collect(Collectors.toList());
if it's more comfortable to you.

How to propagate variables in a stream in java 8

I would be currious to know how to propagate variable into a stream in java 8.
An example is better than a long explaination, so how would you convert the following (abstract) code into streams:
Map<Integer,A> myMap = new HashMap();
for (Entry<Integer,A> entry : myMap)
{
int param1=entry.getValue().getParam1();
List param2=entry.getValue().getParam2();
for (B b : param2)
{
System.out.println(""+entry.getKey()+"-"+param1+"-"+b.toString());
}
}
Knowing that this example is a simplification of the problem (for example, i need "param1" more than once in the next for loop)
So far, the only idea i have is to store all the informations i need into a tuple to finally use the forEach stream method over this tuple.
(Not sure to be very clear....)
Edit:I simplified my example too much. My case is more something like that:
Map<Integer,A> myMap = new HashMap();
for (Entry<Integer,A> entry : myMap)
{
int param1=entry.getValue().getParam1();
CustomList param2=entry.getValue().getParam2();
for (int i = 0; i<param2.size(); i++)
{
System.out.println(""+entry.getKey()+"-"+param1+"-"+param2.get(i).toString());
}
}
I could write something like that with stream:
myMap.entrySet().stream()
.forEach(
e -> IntStream.range(0, e.getValue.getParam2().getSize())
.forEach(
i -> System.out.println(e.getKey()+"-"+e.getValue().getParam1()+"-"+e.getValue.getParam2.get(i))
)
);
However, what i have instead of "e.getValue.getParam2()" in my real case is much more complex (a sequence of 5-6 methods) and heavier than just retrieving a variable (it executes some logic), so i would like to avoid to repeat e.getValue.getParam2 (once in just before the forEach, and once in the forEach)
i know that it's maybe not the best use case for using stream, but I am learning about it and would like to know about the limits
Thanks!
Something like this:
myMap.forEach(
(key, value) -> value.getParam2().forEach(
b -> System.out.println(key+"-"+value.getParam1()+"-"+b)
)
);
That is, for each key/value pair, iterate through value.getParam2(). For each one of those, print out string formatted as you specified. I'm not sure what that gets you, other than being basically what you had before, but using streams.
Update
Responding to updates to your question, this:
myMap.forEach((key, value) -> {
final CustomList param2 = value.getParam2();
IntStream.range(0, param2.getSize()).forEach(
i -> System.out.println(key+"-"+value.getParam1()+"-"+param2.get(i))
)
});
Here we assign the result of getParam2() to a final variable, so it is only calculated once. Final (and effectively final) variables are visible inside lambda functions.
(Thank you to Holger for the suggestions.)
Note that there are more features in the Java 8 API than just streams. Especially, if you just want to process all elements of a collection, you don’t need streams.
You can simplify every form of coll.stream().forEach(consumer) to coll.forEach(consumer). This applies to map.entrySet() as well, however, if you want to process all mappings of a Map, you can use forEach on the Map directly, providing a BiConsumer<KeyType,ValueType> rather than a Consumer<Map.Entry<KeyType,ValueType>>, which can greatly improve the readability:
myMap.forEach((key, value) -> {
int param1 = value.getParam1();
CustomList param2 = value.getParam2();
IntStream.range(0, param2.size()).mapToObj(param2::get)
.forEach(obj -> System.out.println(key+"-"+param1+"-"+obj));
});
It’s worth thinking about adding a forEach(Consumer<ElementType>) method to your CustomList, even if the CustomList doesn’t support the other standard collection operations…

Java 8 Map KeySet Stream not working as desired for use in Collector

I have been trying to learn Java 8's new functional interface features, and I am having some difficulty refactoring code that I have previously written.
As part of a test case, I want to store a list of read names in a Map structure in order to check to see if those reads have been "fixed" in a subsequent section of code. I am converting from an existing Map> data structure. The reason why I am flattening this datastructure is because the outer "String" key of the original Map is not needed in the subsequent analysis (I used it to segregate data from different sources before merging them in the intermediate data). Here is my original program logic:
public class MyClass {
private Map<String, Map<String, Short>> anchorLookup;
...
public void CheckMissingAnchors(...){
Map<String, Boolean> anchorfound = new HashMap<>();
// My old logic used the foreach syntax to populate the "anchorfound" map
for(String rg : anchorLookup.keySet()){
for(String clone : anchorLookup.get(rg).keySet()){
anchorfound.put(clone, false);
}
}
...
// Does work to identify the read name in the file. If found, the boolean in the map
// is set to "true." Afterwards, the program prints the "true" and "false" counts in
// the map
}
}
I attempted to refactor the code to use functional interfaces; however, I getting errors from my IDE (Netbeans 8.0 Patch 2 running Java 1.8.0_05):
public class MyClass {
private Map<String, Map<String, Short>> anchorLookup;
...
public void CheckMissingAnchors(...){
Map<String, Boolean> anchorfound = anchorLookup.keySet()
.stream()
.map((s) -> anchorlookup.get(s).keySet()) // at this point I am expecting a
// Stream<Set<String>> which I thought could be "streamed" for the collector method
// ; however, my IDE does not allow me to select the "stream()" method
.sequential() // this still gives me a Stream<Set<String>>
.collect(Collectors.toMap((s) -> s, (s) -> false);
// I receive an error for the preceding method call, as Stream<Set<String>> cannot be
// converted to type String
...
}
}
Is there a better way to create the "anchorfound" map using the Collection methods or is the vanilla Java "foreach" structure the best way to generate this data structure?
I apologize for any obvious errors in my code. My formal training was not in computer science but I would like to learn more about Java's implementation of functional programming concepts.
I believe what you need is a flatMap.
This way you convert each key of the outer map to a stream of the keys of the corresponding inner map, and then flatten them to a single stream of String.
public class MyClass {
private Map<String, Map<String, Short>> anchorLookup;
...
public void CheckMissingAnchors(...){
Map<String, Boolean> anchorfound = anchorLookup.keySet()
.stream()
.flatMap(s -> anchorlookup.get(s).keySet().stream())
.collect(Collectors.toMap((s) -> s, (s) -> false);
...
}
}
Eran's suggestion of flatMap is a good one, +1.
This can be simplified somewhat by using Map.values() instead of Map.keySet(), since the map's keys aren't used for any other purpose than to retrieve the values. Streaming the result of Map.values() gives a Stream<Map<String,Short>>. Here we don't care about the inner map's values, so we can use keySet() to extract the keys, giving a Stream<Set<String>>. Now we just flatMap these sets into Stream<String>. Finally we send the results into the collector as before.
The resulting code looks like this:
public class MyClass {
private Map<String, Map<String, Short>> anchorLookup;
public void checkMissingAnchors() {
Map<String, Boolean> anchorfound = anchorLookup.values().stream()
.map(Map::keySet)
.flatMap(Set::stream)
.collect(Collectors.toMap(s -> s, s -> false));
}
}

Java map.get(key) - automatically do put(key) and return if key doesn't exist?

I am sick of the following pattern:
value = map.get(key);
if (value == null) {
value = new Object();
map.put(key, value);
}
This example only scratches the surface of the extra code to be written when you have nested maps to represent a multi-dimensional structure.
I'm sure something somewhere exists to avoid this, but my Googling efforts yielded nothing relevant. Any suggestions?
The
java.util.concurrent.ConcurrentMap
and from Java 8
Java.util.Map
has
putIfAbsent(K key, V value)
which returns the existing value, and if that is null inserts given value. So if no value exists for key returns null and inserts the given value, otherwise returns existing value
If you need lazy evaluation of the value there is
computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
Java 8 adds nice method to the Map: compute, computeIfPresent, computeIfAbsent
To achieve what you need:
Object existingOrCreated = map.computeIfAbsent(key, (k) -> new Object());
The problem with this pattern is that you'd have to somehow define the value that should be used in case the get() returns null.
There certainly are libraries out there and IIRC there are also some newer collections that do that, but unfortunately I don't remember which those were.
However, you could write a utility method yourself, provided you have a standard way of creating the new values. Something like this might work:
public static <K, V> V safeGet(K key, Map<K,V> map, Class<V> valueClass) throws /*add exceptions*/ {
V value = map.get(key);
if( value == null ) {
value = valueClass.newInstance();
map.put( key, value );
}
return value;
}
Note that you'd either have to throw the reflection exceptions or handle them in the method. Additionally, this requires the valueClass to provide a no-argument constructor. Alternatively, you could simply pass the default value that should be used.
Java 8 update
It has already been mentioned in other answers but for the sake of completeness I'll add the information here as well.
As of Java 8 there is the default method computeIfAbsent(key, mappingFunction) which basically does the same, e.g. if the value class was BigDecimal it could look like this:
BigDecimal value = map.computeIfAbsent(key, k -> new BigDecimal("123.456"));
The implementation of that method is similar to the safeGet(...) defined above but more flexible, directly available at the map instance and better tested. So when possible I'd recommend using computeIfAbsent() instead.
You can use MutableMap and getIfAbsentPut() from Eclipse Collections which returns the value mapped to the key or inserts the given value and returns the given value if no value is mapped to the key.
You can either use a method reference to create a new Object:
MutableMap<String, Object> map = Maps.mutable.empty();
Object value = map.getIfAbsentPut("key", Object::new);
Or you can directly create a new Object:
MutableMap<String, Object> map = Maps.mutable.empty();
Object value = map.getIfAbsentPut("key", new Object());
In the first example, the object will be created only if there is no value mapped to the key.
In the second example, the object will be created regardless.
Note: I am a contributor to Eclipse Collections.
If in any case you need to get a default data in your map if it's not existing
map.getOrDefault(key, defaultValue);
javadocs
EDIT : Note that the feature mentioned below is long deprecated, and a CacheBuilder should be used instead.
The Guava library has a "computing map", see MapMaker.makeComputingMap(Function).
Map<String, Object> map = new MapMaker().makeComputingMap(
new Function<String, Object>() {
public String apply(Stringt) {
return new Object();
}
});
If you need the Function several times, extract it into a utility class, and then create the Map like this (where MyFunctions.NEW_OBJECT is the static Function instance):
Map<String, Object> map = new MapMaker()
.makeComputingMap(MyFunctions.NEW_OBJECT);
Maybe I'm not seeing the whole problem, but how about using inheritance or composition to add this behavior to the Map object?

Categories