The simplest form of question is I have a collection and I want to delete/remove all elements that their value == null. I don't want delete in place (I want to get new Map so my original Map is safe).
public Map<String, String> ADDITIONAL_QUERY = new HashMap<>();
this.ADDITIONAL_QUERY.put("deviceManufacturer", payload.getManufacturer());
this.ADDITIONAL_QUERY.put("deviceModel", payload.getModel());
this.ADDITIONAL_QUERY.put("source", payload.getSource());
this.ADDITIONAL_QUERY.put("adrIMEI", payload.getIMEI());
this.ADDITIONAL_QUERY.put("adrMEID", payload.getMEID());
this.ADDITIONAL_QUERY.put("adrUDID", payload.getUuid() == null ? null : payload.getUuid().toString());
this.ADDITIONAL_QUERY.put("adrID", payload.getAndroidId());
this.ADDITIONAL_QUERY.put("adrSERIAL", payload.getSerial());
I add more elements to ADDITIONAL_QUERY depends of what API I'm calling later.
More details
I just upgraded my Retrofit lib to version 2.0. I have #FieldMap Map<String, String> additionalQuery in majority of my APIs (around 100 APIs). According to their doc regard FieldMap,"Named key/value pairs for a form-encoded request. A null value for the map, as a key, or as a value is not allowed.", as mentioned on java doc (It's so funny that their doc on web says you can https://square.github.io/retrofit/2.x/retrofit/index.html?retrofit2/http/class-use/FieldMap.html).
As APIs are getting called a lot, I'm looking for a way with minor overhead and fast of course.
You can use:
map.values().removeAll(Collections.singleton(null));
This should work:
map.values().removeIf(Objects::isNull);
If you have Guava, you can create a filtered view to avoid copying to a new map:
Map<String, String> filtered = Maps.filterValues(ADDITIONAL_QUERY, new Predicate<String>() {
#Override
public boolean apply(String value) {
return value != null;
}
});
Since you're on Java 8, you can shorten the predicate like this:
Map<String, String> filtered = Maps.filterValues(ADDITIONAL_QUERY, Objects::nonNull);
A possible solution is the following:
public <K, V> Map<K,V> removeNulls(Map<K,V> original) {
return original.entrySet().stream().filter(e -> e.getValue() != null)
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
}
If the map is huge, then you might even use parallelStream() instead of stream()
You can use this :
Map newMap = (HashMap) ADDITIONAL_QUERY.clone();;
newMap.values().remove(null);
Related
How can the following method be written using Java Stream API?
public Map<String, String> getMap() {
Map<String, String> mapB = new HashMap<>();
for (String parameterKey : listOfKeys) {
String parameterValue = mapA.get(parameterKey);
mapB.put(parameterKey, Objects.requireNonNullElse(parameterValue, ""));
}
return ImmutableMap.copyOf(mapB);
}
I tried something like this:
return listOfKeys.stream()
.map(firstMap::get)
.collect( ? )
But I don't know how to continue from here.
Guava
If you need Guava's ImmutableMap, you can make use of the Collector returned by ImmutableMap.toImmutableMap() available since Guava's version 21.0.
Note: the minimum JDK version required by the Guava 21.0 release is Java 8 (see), hence below is a valid Java 8 compliant solution.
public Map<String, String> getMap() {
return listOfKeys.stream()
.collect(ImmutableMap.toImmutableMap(
Function.identity(), // keyMapper - generating keys
str -> mapA.getOrDefault(str, ""), // valueMapper - generating values
(left, right) -> left // mergeFunction - resolving duplicates
));
}
In case if you're using Guava's version earlier than 21.0, then you can generate the resulting map using standard JDK collector toMap() and wrap it with an ImmutableMap by the means of collectingAndThen().
This approach would be cleaner, cramming the stream as an argument of the copyOf() method as suggested in another answer:
public Map<String, String> getMap() {
return istOfKeys.stream()
.collect(Collectors.collectingAndThen(
Collectors.toMap(
Function.identity(), // keyMapper - generating keys
str -> mapA.getOrDefault(str, ""), // valueMapper - generating values
(left, right) -> right), // mergeFunction - resolving duplicates
ImmutableMap::copyOf
));
}
Note:
If all elements contained in listOfKeys are guaranteed to be unique, then you can remove mergeFunction (the third argument of the collector).
I've replaced Objects.requireNonNullElse() with Map.getOrDefault() which would guard against the cases when a key is not present in the mapA. If you also want you protect against situations when the value associated with the key is null, then replace it with requireNonNullElse(). But you need to be aware that it's a sign of a faulty design if null values are being stored in the map because they have some special meaning in your business logic (and therefore you can't get rid of them).
Standard JDK
Otherwise, you can use collector Collectors.toUnmodifiableMap()
It internally uses a combination of collectors toMap(), accumulates stream data into a map, and collectingAndThen()) which performs the final transformation into a so-called unmodifiable map created via Map.ofEntries().
public Map<String, String> getMap() {
return listOfKeys.stream()
.collect(Collectors.toUnmodifiableMap(
Function.identity(), // keyMapper - generating keys
str -> mapA.getOrDefault(str, ""), // valueMapper - generating values
(left, right) -> left // mergeFunction - resolving duplicates
));
}
I have a flux of response form below responses as Flux.<Response>fromIterable(responses). I want to convert this to Mono of map as follows:
Mono< Map< String, Map< String, Collection< Response>>>> collectMap = ?
where company is first key for which another map of response will be generated with category as key.
List< Response> responses = new ArrayList(){
{
add(Response.builder().company("Samsung").category("Tab").price("$2000").itemName("Note").build());
add(Response.builder().company("Samsung").category("Phone").price("$2000").itemName("S9").build());
add(Response.builder().company("Samsung").category("Phone").price("$1000").itemName("S8").build());
add(Response.builder().company("Iphone").category("Phone").price("$5000").itemName("Iphone8").build());
add(Response.builder().company("Iphone").category("Tab").price("$5000").itemName("Tab").build());
}
};
Though I am able to achieve initial map as follow
Mono<Map<String, Collection<Response>>> collect = Flux.<Response>fromIterable( responses )
.collectMultimap( Response::getCompany );
Do someone has an idea how I can achieve my goal here.
I don't think collectMultiMap or collectMap helps you directly in this case:
The collectMultiMap (and its overloads) only can return Map<T, Collection<V> which is clearly different than what you want. Of course, you can process the resulting value set (namely the Collection<V> part of the map) with a O(n) complexity.
On the other hand collectMap (and its overloads) look a bit more promising, if you provide the value function. However, you don't have access to other V objects, which forbids you to build the Collection<V>.
The solution I came up with is using reduce; though the return type is:
Mono<Map<String, Map<String, List<Response>>>> (mind the List<V> instead of Collection<V>)
return Flux.<Response>fromIterable( responses )
.reduce(new HashMap<>(), (map, user) -> {
map.getOrDefault(user.getId(), new HashMap<>())
.getOrDefault(user.getEmail(), new ArrayList<>())
.add(user);
return map;
});
The full type for the HashMap in reduce is HashMap<String, Map<String, List<AppUser>>>, thankfully Java can deduce that from the return type of the method or the type of the assigned variable.
I am trying to collect into a ListMultiMap using java 8 without using the forEach operation.
If I were to write the code in Java 7, it will be something like this:
ListMultimap<String, String> result = ArrayListMultimap.create();
for(State state: states) {
for(City city: state.getCities()) {
result.put(state.getName(), city.getName());
}
}
I found online a website that talks about creating your own collectors to use in scenarios such as this one.
I used this implementation for the collector. I then wrote the following code:
ListMultimap<String, String> result = states
.stream()
.flatMap(state -> state.getCities().stream()
.map(city -> {
return new Pair(state, city);
}))
.map(pair -> {
return new Pair(pair.first().getName(), pair.second().getName()));
})
.collect(MultiMapCollectors.listMultimap(
Pair::first,
Pair::second
)
);
But at the collect level, I can only pass just one parameter, and I can seem to find a way to pass two parameters.
Following the example from the website, I understood that to use both, I need to store a "pair" in the multimap such as the following:
ArrayListMultimap<String, Pair> testMap = testObjectList.stream().collect(MultiMapCollectors.listMultimap((Pair p) -> p.first().getName()));
However this is not what I'm looking for, I want to collect into ListMultimap using the state's name and the city's name using java 8's collector (and no forEach).
Can someone help me with that ?
Thank you!
ImmutableListMultimap.flatteningToImmutableListMultimap
return states.stream()
.collect(flatteningToImmutableListMultimap(
State::getName,
state -> state.getCities().stream().map(City::getName)));
You could create a custom collector for that (note that Louis Wasserman's answer will do a forEachOrdered internally, you can't really escape a forEach either internally or externally).
ListMultimap<String, String> list = states.collect(Collector.of(ArrayListMultimap::create, (multimap, s) -> {
multimap.putAll(s.getName(), s.getCities().stream().map(City::getName).collect(Collectors.toList()));
}, (multi1, multi2) -> {
multi1.putAll(multi2);
return multi1;
}));
What I want to do is shown below in 2 stream calls. I want to split a collection into 2 new collections based on some condition. Ideally I want to do it in 1. I've seen conditions used for the .map function of streams, but couldn't find anything for the forEach. What is the best way to achieve what I want?
animalMap.entrySet().stream()
.filter(pair-> pair.getValue() != null)
.forEach(pair-> myMap.put(pair.getKey(), pair.getValue()));
animalMap.entrySet().stream()
.filter(pair-> pair.getValue() == null)
.forEach(pair-> myList.add(pair.getKey()));
Just put the condition into the lambda itself, e.g.
animalMap.entrySet().stream()
.forEach(
pair -> {
if (pair.getValue() != null) {
myMap.put(pair.getKey(), pair.getValue());
} else {
myList.add(pair.getKey());
}
}
);
Of course, this assumes that both collections (myMap and myList) are declared and initialized prior to the above piece of code.
Update: using Map.forEach makes the code shorter, plus more efficient and readable, as Jorn Vernee kindly suggested:
animalMap.forEach(
(key, value) -> {
if (value != null) {
myMap.put(key, value);
} else {
myList.add(key);
}
}
);
In most cases, when you find yourself using forEach on a Stream, you should rethink whether you are using the right tool for your job or whether you are using it the right way.
Generally, you should look for an appropriate terminal operation doing what you want to achieve or for an appropriate Collector. Now, there are Collectors for producing Maps and Lists, but no out of-the-box collector for combining two different collectors, based on a predicate.
Now, this answer contains a collector for combining two collectors. Using this collector, you can achieve the task as
Pair<Map<KeyType, Animal>, List<KeyType>> pair = animalMap.entrySet().stream()
.collect(conditional(entry -> entry.getValue() != null,
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue),
Collectors.mapping(Map.Entry::getKey, Collectors.toList()) ));
Map<KeyType,Animal> myMap = pair.a;
List<KeyType> myList = pair.b;
But maybe, you can solve this specific task in a simpler way. One of you results matches the input type; it’s the same map just stripped off the entries which map to null. If your original map is mutable and you don’t need it afterwards, you can just collect the list and remove these keys from the original map as they are mutually exclusive:
List<KeyType> myList=animalMap.entrySet().stream()
.filter(pair -> pair.getValue() == null)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
animalMap.keySet().removeAll(myList);
Note that you can remove mappings to null even without having the list of the other keys:
animalMap.values().removeIf(Objects::isNull);
or
animalMap.values().removeAll(Collections.singleton(null));
If you can’t (or don’t want to) modify the original map, there is still a solution without a custom collector. As hinted in Alexis C.’s answer, partitioningBy is going into the right direction, but you may simplify it:
Map<Boolean,Map<KeyType,Animal>> tmp = animalMap.entrySet().stream()
.collect(Collectors.partitioningBy(pair -> pair.getValue() != null,
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
Map<KeyType,Animal> myMap = tmp.get(true);
List<KeyType> myList = new ArrayList<>(tmp.get(false).keySet());
The bottom line is, don’t forget about ordinary Collection operations, you don’t have to do everything with the new Stream API.
The problem by using stream().forEach(..) with a call to add or put inside the forEach (so you mutate the external myMap or myList instance) is that you can run easily into concurrency issues if someone turns the stream in parallel and the collection you are modifying is not thread safe.
One approach you can take is to first partition the entries in the original map. Once you have that, grab the corresponding list of entries and collect them in the appropriate map and list.
Map<Boolean, List<Map.Entry<K, V>>> partitions =
animalMap.entrySet()
.stream()
.collect(partitioningBy(e -> e.getValue() == null));
Map<K, V> myMap =
partitions.get(false)
.stream()
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
List<K> myList =
partitions.get(true)
.stream()
.map(Map.Entry::getKey)
.collect(toList());
... or if you want to do it in one pass, implement a custom collector (assuming a Tuple2<E1, E2> class exists, you can create your own), e.g:
public static <K,V> Collector<Map.Entry<K, V>, ?, Tuple2<Map<K, V>, List<K>>> customCollector() {
return Collector.of(
() -> new Tuple2<>(new HashMap<>(), new ArrayList<>()),
(pair, entry) -> {
if(entry.getValue() == null) {
pair._2.add(entry.getKey());
} else {
pair._1.put(entry.getKey(), entry.getValue());
}
},
(p1, p2) -> {
p1._1.putAll(p2._1);
p1._2.addAll(p2._2);
return p1;
});
}
with its usage:
Tuple2<Map<K, V>, List<K>> pair =
animalMap.entrySet().parallelStream().collect(customCollector());
You can tune it more if you want, for example by providing a predicate as parameter.
I think it's possible in Java 9:
animalMap.entrySet().stream()
.forEach(
pair -> Optional.ofNullable(pair.getValue())
.ifPresentOrElse(v -> myMap.put(pair.getKey(), v), v -> myList.add(pair.getKey())))
);
Need the ifPresentOrElse for it to work though. (I think a for loop looks better.)
This question already has answers here:
Java Map, filter with values properties
(6 answers)
Closed 6 years ago.
Following map, having both key-value pair as String, Write a logic to filter all the null values from Map without using any external API's ?
Is there any other approach than traversing through whole map and filtering out the values (Traversing whole map and getting Entry Object and discarding those pair) ?
Map<String,String> map = new HashMap<String,String>();
map.put("1", "One");
map.put("2", "Two");
map.put("3", null);
map.put("4", "Four");
map.put("5", null);
//Logic to filer values
//Post filtering It should print only ( 1,2 & 4 pair )
You can use the Java 8 method Collection.removeIf for this purpose:
map.values().removeIf(Objects::isNull);
This removed all values that are null.
Online demo
This works by the fact that calling .values() for a HashMap returns a collection that delegated modifications back to the HashMap itself, meaning that our call for removeIf() actually changes the HashMap (this doesn't work on all java Map's)
If you are using pre-Java 8, you can use:
Collection<String> values = map.values();
while (values.remove(null)) {}
This works because HashMap.values() returns a view of the values, and:
The collection [returned by HashMap.values()] supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Set.remove, removeAll, retainAll, and clear operations
An alternative way that might be faster, because you don't have to keep re-iterating the collection to find the first null element:
for (Iterator<?> it = map.values().iterator();
it.hasNext();) {
if (it.next() == null) {
it.remove();
}
}
Or you can do it without the explicit iteration:
values.removeAll(Collections.singleton(null));
that will work
Map<String, String> result = map.entrySet()
.stream()
.filter(e -> e.getValue() != null)
.collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));