I can cast simple java objects safely by doing something like:
Object str = "test";
Optional.of(str)
.filter(value -> value instanceof String)
.map(String.class::cast)
.ifPresent(value -> System.out.println("Here is the value: " + value));
But how can I do the same stuff casting object to a generic collection?
For example:
Object entries = /**/;
// this is unsafe
List<Entry<String, String>> subscriptions = (List<Entry<String, String>>) entries;
How should I handle situation like this and is there any library (something like ClassCastUtils) to help with such convertion?
There's not really a good way to do it. The naive approach would be to
Check it's a list/set/collection
Check all the items are Entrys
Check all the keys and values of each Entry are Strings
For example
Map<String, String> map = new HashMap<>();
map.put("a", "b");
Object entries = map.entrySet();
boolean safe = Optional.of(entries)
.filter(Set.class::isInstance)
.map(e -> (Set<?>) e)
.filter(set -> set.stream().allMatch(Map.Entry.class::isInstance))
.map(e -> (Set<Map.Entry<?, ?>>) e)
.filter(set -> set.stream().allMatch(e -> e.getKey() instanceof String && e.getValue() instanceof String))
.isPresent();
However, even this doesn't give enough assurance. Suppose the map was declared as:
Map<String, Object> map = new HashMap<>();
map.put("a", "b");
At the time of our check, this would be seen as safe. It's a set and all the entries are currently strings. However, if some other code were to later mutate the map, for example
map.add("b", 123);
Then you will realise that this cast is still unsafe. You can only work with the current state, so you can't make any guarantees about future state.
You may be able to get around this in a couple of ways:
Deep copy the entire collection after checking. But what about the race condition after having checked before before having copied? You would have to use a lock.
Make the map immutable
How about if it is a collection but it's currently empty? In that case, you can't make any checks against the items.
As you can see, working with such a cast is very hard work and has many places where you could potentially go wrong. Your best bet is to avoid having to make such a cast in the first place.
Related
Given a class Object1
class Object1 {
private Object2 object2;
private List<Object3> object3s;
}
and a List<Object1>
cuurent implementation
Map<Object3, Object2> accountMap = new HashMap<>();
for (Object1 dto : accounts) {
dto.getObject3s()
.forEach(dto -> accountMap.put(dto, dto.getObject2()));
}
How do I create a Map<Object3, Object2> preferably in a stream?
I believe doing this in a stream would look something like this:
Map<Object3, Object2> map = list.stream()
.flatMap(o1 -> o1.object3s.stream().map(o3 -> Map.entry(o3, o1.object2)))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
That is, flatmap your stream of Object1 to a stream of entries of Object3 and Object2; and then collect them to a map.
To achieve this with streams, you need an auxiliary object, that would hold references to Object3 and Object2.
A good practice is to use a Java 16 record for that purpose, if you're using an earlier JDK version you can define a class. Sure a quick and dirty option Map.Entry will do as well, but it reduces the readability of code because methods getKey() and getValue() give no clue what they are returning, it would be even confusing if the source of the stream is entry set and there would be several kinds of entries in the pipeline.
Here's how such a record might look like:
public record Objects3And2(Object3 object3, Object2 object2) {}
In the stream, you need to flatten the data by creating a new instance of record Objects3And2 for every nested Object3. For that, we can use either flatMap() of mapMulti().
And to accumulate the data into a map we can make use of the collector `toMap()
Note that in this implementation (as well as in your code) values of duplicated Object3 would be overridden.
List<Object1> accounts = // initializing the list
Map<Object3, Object2> accountMap = accounts.stream()
.flatMap(o1 -> o1.getObject3s().stream()
.map(o3 -> new Objects3And2(o3, o1.getObject2()))
)
.collect(Collectors.toMap(
Objects3And2::object3, // generating a Key
Objects3And2::object2, // generating a Value
(l, r) -> r // resolving Duplicates
));
If you don't want to lose the data, you need a different structure of the resulting map and different collector, namely groupingBy() in conjunction with mapping() as its downstream.
List<Object1> accounts = // initializing the list
Map<Object3, List<Object2>> accountMap = accounts.stream()
.flatMap(o1 -> o1.getObject3s().stream()
.map(o3 -> new Objects3And2(o3, o1.getObject2()))
)
.collect(Collectors.groupingBy(
Objects3And2::object3,
Collectors.mapping(Objects3And2::object2,
Collectors.toList())
));
I have a Map<String, Integer>, which has some keys and values. I want to associate all keys with the values as the key's length.
I have been able to solve this in pure java and java-8, but somehow I don't think that appending a terminal operation at the end like .collect(Collectors.toList()); which is not required for me in my code.
My code: ( Java ) works fine
Map<String, Integer> nameLength = new HashMap<>();
nameLength.put("John", null);
nameLength.put("Antony", 6);
nameLength.put("Yassir", 6);
nameLength.put("Karein", 6);
nameLength.put("Smith", null);
nameLength.put("JackeyLent",null);
for(Entry<String, Integer> length: nameLength.entrySet()){
if(length.getValue() == null){
nameLength.put(length.getKey(),length.getKey().length());
}
}
Java-8 also works fine but the terminal operation is useless, how I avoid it without using .foreach().
nameLength.entrySet().stream().map(s->{
if(s.getValue() == null){
nameLength.put(s.getKey(),s.getKey().length());
}
return nameLength;
}).collect(Collectors.toList());
System.out.println(nameLength);
Any other way in which I can do the above logic in Java-8 and above??
If you're going to use streams then you should avoid side effects. Functional programming is all about pure operations where the output depends only on the input and functions have no side effects. In other words, create a new map instead of modifying the existing one.
If you do that you might as well just throw away the partially-filled-out map and recompute everything from scratch. Calling String.length() is cheap and it's not really worth the effort to figure out which values are null and which aren't. Recompute all the lengths.
Map<String, Integer> newMap = nameLength.keySet().stream()
.collect(Collectors.toMap(
name -> name,
name -> name.length()
));
On the other hand if you just want to patch up your current map streams don't really buy you anything. I'd just modify it in place without involving streams.
for (Map.Entry<String, Integer> entry: nameLength.entrySet()) {
if (entry.getValue() == null) {
entry.setValue(entry.getKey().length());
}
}
Or, as discussed above, you could simplify matters by replacing all of the lengths:
nameLength.replaceAll((name, __) -> name.length());
(__ signifies a variable that isn't used and so doesn't get a meaningful name.)
You almost there, just use the filter to identify the entries with null values and then use Collectors.toMap to collect them into Map with key length as value
Map<String, Integer> nameLengths = nameLength.entrySet()
.stream()
.filter(entry->entry.getValue()==null)
.collect(Collectors.toMap(Map.Entry::getKey, entry->entry.getKey().length()));
Or more simpler way you have that check in Collectors.toMap
Map<String, Integer> nameLengths = nameLength.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry->entry.getValue() == null ? entry.getKey().length() : entry.getValue()));
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);
I would like to flatten a Map which associates an Integer key to a list of String, without losing the key mapping.
I am curious as though it is possible and useful to do so with stream and lambda.
We start with something like this:
Map<Integer, List<String>> mapFrom = new HashMap<>();
Let's assume that mapFrom is populated somewhere, and looks like:
1: a,b,c
2: d,e,f
etc.
Let's also assume that the values in the lists are unique.
Now, I want to "unfold" it to get a second map like:
a: 1
b: 1
c: 1
d: 2
e: 2
f: 2
etc.
I could do it like this (or very similarly, using foreach):
Map<String, Integer> mapTo = new HashMap<>();
for (Map.Entry<Integer, List<String>> entry: mapFrom.entrySet()) {
for (String s: entry.getValue()) {
mapTo.put(s, entry.getKey());
}
}
Now let's assume that I want to use lambda instead of nested for loops. I would probably do something like this:
Map<String, Integer> mapTo = mapFrom.entrySet().stream().map(e -> {
e.getValue().stream().?
// Here I can iterate on each List,
// but my best try would only give me a flat map for each key,
// that I wouldn't know how to flatten.
}).collect(Collectors.toMap(/*A String value*/,/*An Integer key*/))
I also gave a try to flatMap, but I don't think that it is the right way to go, because although it helps me get rid of the dimensionality issue, I lose the key in the process.
In a nutshell, my two questions are :
Is it possible to use streams and lambda to achieve this?
Is is useful (performance, readability) to do so?
You need to use flatMap to flatten the values into a new stream, but since you still need the original keys for collecting into a Map, you have to map to a temporary object holding key and value, e.g.
Map<String, Integer> mapTo = mapFrom.entrySet().stream()
.flatMap(e->e.getValue().stream()
.map(v->new AbstractMap.SimpleImmutableEntry<>(e.getKey(), v)))
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
The Map.Entry is a stand-in for the nonexistent tuple type, any other type capable of holding two objects of different type is sufficient.
An alternative not requiring these temporary objects, is a custom collector:
Map<String, Integer> mapTo = mapFrom.entrySet().stream().collect(
HashMap::new, (m,e)->e.getValue().forEach(v->m.put(v, e.getKey())), Map::putAll);
This differs from toMap in overwriting duplicate keys silently, whereas toMap without a merger function will throw an exception, if there is a duplicate key. Basically, this custom collector is a parallel capable variant of
Map<String, Integer> mapTo = new HashMap<>();
mapFrom.forEach((k, l) -> l.forEach(v -> mapTo.put(v, k)));
But note that this task wouldn’t benefit from parallel processing, even with a very large input map. Only if there were additional computational intense task within the stream pipeline that could benefit from SMP, there was a chance of getting a benefit from parallel streams. So perhaps, the concise, sequential Collection API solution is preferable.
You should use flatMap as follows:
entrySet.stream()
.flatMap(e -> e.getValue().stream()
.map(s -> new SimpleImmutableEntry(e.getKey(), s)));
SimpleImmutableEntry is a nested class in AbstractMap.
Hope this would do it in simplest way. :))
mapFrom.forEach((key, values) -> values.forEach(value -> mapTo.put(value, key)));
This should work. Please notice that you lost some keys from List.
Map<Integer, List<String>> mapFrom = new HashMap<>();
Map<String, Integer> mapTo = mapFrom.entrySet().stream()
.flatMap(integerListEntry -> integerListEntry.getValue()
.stream()
.map(listItem -> new AbstractMap.SimpleEntry<>(listItem, integerListEntry.getKey())))
.collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
Same as the previous answers with Java 9:
Map<String, Integer> mapTo = mapFrom.entrySet()
.stream()
.flatMap(entry -> entry.getValue()
.stream()
.map(s -> Map.entry(s, entry.getKey())))
.collect(toMap(Entry::getKey, Entry::getValue));
I want to transform keys in a HashMap. The map has lower_underscore keys but an expected map should have camelCase keys. The map may also have null values.
The straightfoward code to do this is here:
Map<String, Object> a = new HashMap<String, Object>() {{
put("foo_bar", 100);
put("fuga_foga", null); // A value may be null. Collectors.toMap can't handle this value.
}};
Map<String, Object> b = new HashMap<>();
a.forEach((k,v) -> b.put(toCamel(k), v));
I want to know the method to do this like Guava's Maps.transformValues() or Maps.transformEntries(), but these methods just transforms values.
Collectors.toMap() is also close, but this method throws NullPointerException when a null value exists.
Map<String, Object> collect = a.entrySet().stream().collect(
Collectors.toMap(x -> toCamel(x.getKey()), Map.Entry::getValue));
If you absolutely want to solve this using streams, you could do it like this:
Map<String, Object> b = a.entrySet()
.stream()
.collect(HashMap::new,
(m, e) -> m.put(toCamel(e.getKey()), e.getValue()),
HashMap::putAll);
But I find the "conventional" way shown in your question easier to read:
Map<String, Object> b = new HashMap<>();
a.forEach((k,v) -> b.put(toCamel(k), v));
This is intended as a comment, but got too long for that.
Wanting something like Guava's Maps.transformValues() or Maps.transformEntries() doesn't make too much sense I think.
Those methods return a view of the original map and when you get some
value using a key then the value is transformed by some function that you specified.
(I could be wrong here because I'm not familiar with Guava but I'm making these assumptions based on documentation)
If you wanted to do "transform" the keys then you could do it by writing a wapper for the map like so:
public class KeyTransformingMap<K, V> implements Map {
private Map<K, V> original;
private Function<K, K> reverseTransformer;
public V get(Object transformedKey) {
K originalKey = reverseTransformer.apply((K) transformedKey);
return original.get(originalKey);
}
// delegate all other Map methods directly to original map (or throw UnsupportedOperationException)
}
In your case where you have a map with snake case keys but want camel case keys,
the reverseTransformer function would take in a camel case string and return a snake case string.
I.e reverseTransformer.apply("snakeCase") returns "snake_case" which you can then use as a key for the original map.
Having said all that I think that the straightforward code you suggested is the best option.