I have Map<String, Map<String, String>> myMap in my Java 8 class. I need to navigate to a leaf String like myMap['keyA']['keyB'], returning null if either 'keyA' or 'keyB' does not exist in the correlating Map.
In groovy I would use myMap?.keyA?.keyB and be done with it. I understand that Java 8's Optional<T> brings similar behavior into java. Is there a way to use this new behavior to concisely mimic the groovy functionality? If not, is there another concise way to get this behavior in Java 8, or am I still stuck with elaborate procedural code?
String valueOrNull = Optional.ofNullable(myMap.get("keyA"))
.map(x -> x.get("keyB"))
.orElse(null);
First, it wraps the results of the first lookup in an Optional, which acts as a monad. If you add a third layer (myMap.?keyA.?keyB.?keyC), it would look like this:
String valueOrNull = Optional.ofNullable(myMap.get("keyA"))
.map(x -> x.get("keyB"))
.map(x -> x.get("keyC"))
.orElse(null);
You can use Optional's ofNullable method to create an Optional that may or may not represent a null value. Then you can use the map method that will, with a Function, map the result to a new value if the value wasn't already null.
Here, I supply a Function as a lambda expression to get the value from the second Map using the second key.
Optional<String> result = Optional.ofNullable(myMap.get("keyA")).map(m -> m.get("keyB"));
From there you can see if the Optional has a value with isPresent(), and if so, get it with get().
Testing:
public static Optional<String> method(Map<String, Map<String, String>> map,
String key1, String key2)
{
return Optional.ofNullable(map.get(key1)).map(m -> m.get(key2));
}
Calling Code:
Map<String, Map<String, String>> myMap = new HashMap<>();
Map<String, String> inner = new HashMap<>();
inner.put("one", "two");
myMap.put("three", inner);
System.out.println(method(myMap, "three", "one"));
System.out.println(method(myMap, "three", "dne"));
System.out.println(method(myMap, "dne", "dne"));
Output:
Optional[two]
Optional.empty
Optional.empty
Interesting question.
You can consider using recursion.
/**
* Finds the value of a node in nested maps.
* #return leaf value or null if none
*/
public <K, V> V getValueFromKeys(Map<K, V> map, K... keys) {
V value = map.getOrDefault(keys[0], null);
if (keys.length == 1) return value;
if (value instanceof Map) {
K[] remainingKeys = Arrays.copyOfRange(keys, 1, keys.length);
return getValueFromKeys((Map<K, V>) value, remainingKeys);
}
return null;
}
This will work with Java >= 8 (you can easily adapt it to previous versions).
Bonus (needs Guava):
#Test
public void getValueFromKeys_level1() {
Map<String, String> mapLevel1 = ImmutableMap.of("key1", "value1");
assertEquals("value1", getValueFromKeys(mapLevel1, "key1"));
assertNull(getValueFromKeys(mapLevel1, null));
assertNull(getValueFromKeys(mapLevel1, ""));
assertNull(getValueFromKeys(mapLevel1, "wrong"));
assertNull(getValueFromKeys(mapLevel1, "key1", "wrong"));
}
#Test
public void getValueFromKeys_level2() {
Map<String, Map<String, String>> mapLevel2 = ImmutableMap.of("key1", ImmutableMap.of("subkey1", "value1"));
assertEquals("value1", getValueFromKeys(mapLevel2, "key1", "subkey1"));
assertNull(getValueFromKeys(mapLevel2, null));
assertNull(getValueFromKeys(mapLevel2, ""));
assertNull(getValueFromKeys(mapLevel2, "wrong"));
assertNull(getValueFromKeys(mapLevel2, "key1", "wrong"));
assertNull(getValueFromKeys(mapLevel2, "key1", "subkey1", "wrong"));
assertTrue(getValueFromKeys(mapLevel2, "key1") instanceof Map);
}
#Test
public void getValueFromKeys_level3() {
Map<String, Map<String, Map<String, String>>> mapLevel3 = ImmutableMap.of("key1", ImmutableMap.of("subkey1", ImmutableMap.of("subsubkey1", "value1")));
assertEquals("value1", getValueFromKeys(mapLevel3, "key1", "subkey1", "subsubkey1"));
assertNull(getValueFromKeys(mapLevel3, null));
assertNull(getValueFromKeys(mapLevel3, ""));
assertNull(getValueFromKeys(mapLevel3, "wrong"));
assertNull(getValueFromKeys(mapLevel3, "key1", "wrong"));
assertNull(getValueFromKeys(mapLevel3, "key1", "subkey1", "wrong"));
assertNull(getValueFromKeys(mapLevel3, "key1", "subkey1", "subsubkey1", "wrong"));
}
Related
I have a class named ConfigKey
public class ConfigKey {
String code;
String key;
String value;
//omit setter and getter
}
I want to convert List<ConfigKey> to Map<String, Map<String, Object>>, here is my method definition
public Map<String, Map<String, Object> convert (List<ConfigKey> list) {
return list.stream().collect(Collectors.groupingBy(ConfigKey::getCode,
Collectors.toMap(ConfigKey::getKey, ConfigKey::getValue)));
}
however I want to do some changes, for each ConfigKey put another key to the map, e.g.
{ "code": "code1","key", "key1", "value": "value1"}
to Map
{"code1": {"key1":"value1", "prefix_key1": "value1" }
is there any API to do it like bellow:
public Map<String, Map<String, Object> convert (List<ConfigKey> list) {
return list.stream().collect(Collectors.groupingBy(ConfigKey::getCode,
Collectors.toMap("prefix_" + ConfigKey::getKey, ConfigKey::getValue))
Collectors.toMap(ConfigKey::getKey, ConfigKey::getValue)));
}
You can make use of the Collector.of() factory method, which allows you to create your own collector:
public Map<String, Map<String, Object> convert (List<ConfigKey> list) {
return list.stream().collect(Collectors.groupingBy(ConfigKey::getCode, Collector.of(
HashMap::new, (m, c) -> {
m.put(c.getKey(), c.getValue());
m.put("prefix_" + c.getKey(), c.getValue());
}, (a, b) -> {
a.putAll(b);
return b;
}
)));
}
But honestly that seems a bit messy, and maybe a normal loop would've been better. The streams intention was to provide an api which does things in a more readable manner, but when you have to hackaround that construct, by introducing some extremely unreadable logic then it is almost always the better option to just do it the old way:
public Map<String, Map<String, Object> convert (List<ConfigKey> list) {
Map<String, Map<String, Object>> map = new HashMap<>();
for (ConfigKey ck : list) {
Map<String, Object> inner = map.computeIfAbsent(ck.getCode(), k -> new HashMap<>());
inner.put(ck.getKey(), ck.getValue());
inner.put("prefix_" + ck.getKey(), ck.getValue());
}
return map;
}
You can first add the new entries to the map and then group them:
private Map<String, Map<String, Object>> convert(List<ConfigKey> list) {
new ArrayList<>(list).stream().map(configKey -> new ConfigKey(configKey.getCode(), "prefix_" + configKey.getKey(), configKey.getValue())).forEachOrdered(list::add);
return list.stream().collect(Collectors.groupingBy(ConfigKey::getCode,
Collectors.toMap(ConfigKey::getKey, ConfigKey::getValue)));
}
I cloned the list (in order to prevent ConcurrentModificationException), then changed the keys to the "new" ones (with map) and added them to the original list - forEachOrdered(list::add).
Because the 'code' field was not changed, both entries will use it which results in 2 entries in the map
Say I have a HashMap and I want to insert the same value to a list of keys. How can I do this with Java 8 without iterating through all the keys and inserting the value? This is more of a Java Streams question.
Here is the straight forward way of doing it. This is a sample code that I wrote to demonstrate what I wanted to achieve.
public void foo(List<String> keys, Integer value) {
Map<String, Integer> myMap = new HashMap<>();
for (String key : keys) {
myMap.put(key, value);
}
}
Is there a simpler way of doing the above using Java 8 streams? How can I avoid the for loop using Java 8 streams. Thanks!
[Edit-1] A better code snippet below.
public void foo() {
Map<String, Integer> myMap = new HashMap<>();
List<String> keys = getKeysFromAnotherFunction();
Integer value = getValueToBeInserted(); // Difficult to show my actual use case. Imagine that some value is getting computed which has to be inserted for the keys.
for (String key : keys) {
myMap.put(key, value);
}
List<String> keys2 = getNextSetOfKeys();
Integer newValue = getValueToBeInserted();
for (String key : keys2) {
myMap.put(key, newValue);
}
}
Using collector, something like:
Map<String, Integer> myMap = keys.stream()
.collect(Collectors.toMap(key -> key,
val -> value, (a, b) -> b));
I think that your question is about factoring out some piece of code more than converting traditional for loops into stream constructs.
Suppose you have the following generic utility method:
public static <K, V, M extends Map<K, V>> M fillMap(
Supplier<List<K>> keysFactory,
Supplier<V> singleValueFactory,
Supplier<M> mapFactory) {
M map = mapFactory.get();
List<K> keys = keysFactory.get();
V singleValue = singleValueFactory.get();
keys.forEach(k -> map.put(k, singleValue));
return map;
}
Then, you could use the above method as follows:
Map<String, Integer> myMap = fillMap(() -> getKeysFromAnotherFunction(),
() -> getValueToBeInserted(),
HashMap::new); // create HashMap
myMap = fillMap(() -> getNextSetOfKeys(),
() -> getValueToBeInserted(),
() -> myMap); // use previously created map
There are variants for the code above, i.e., the method could receive a Map<K, V> instance instead of a Supplier<Map<K, V>>, or it might even be overloaded to support both variants.
I have a Map<String, Double>, and want to multiply all the values in the map by 2, say, but keep the nulls as nulls.
I can obviously use a for loop to do this, but was wondering if there was a cleaner way to do so?
Map<String, Double> someMap = someMapFunction();
Map<String, Double> adjustedMap = new Hashmap<>();
if (someMap != null) {
for (Map.Entry<String,Double> pair : someMap.entryset()) {
if (pair.getValue() == null) {
adjustedMap.put(pair.getKey(), pair.getValue());
} else {
adjustedMap.put(pair.getKey(), pair.getValue()*2)
}
}
}
Also sometimes the map returned by someMapFunction is an immutable map, so this can't be done in place using Map.replaceAll. I couldn't come up with a stream solution that was cleaner.
My first instinct was to suggest a Stream of the input Map's entrySet which maps the values to new values and terminates with collectors.toMap().
Unfortunately, Collectors.toMap throws NullPointerException when the value mapper function returns null. Therefore it doesn't work with the null values of your input Map.
As an alternative, since you can't mutate your input Map, I suggest that you create a copy of it and then call replaceAll:
Map<String, Double> adjustedMap = new HashMap<>(someMap);
adjustedMap.replaceAll ((k,v) -> v != null ? 2*v : null);
As an alternative to streaming and/or copying solutions, the Maps.transformValues() utility method exists in Google Guava:
Map<String, Double> adjustedMap = Maps.transformValues(someMap, value -> (value != null) ? (2 * value) : null);
This returns a lazy view of the original map that does not do any work on its own, but applies the given function when needed. This can be both a pro (if you're unlikely to ever need all the values, this will save you some computing time) and a con (if you'll need the same value many times, or if you need to further change someMap without adjustedMap seeing the changes) depending on your usage.
There already are many answers. Some of them seem a bit dubious to me. In any case, most of them inline the null-check in one form or the other.
An approach that takes one step up the abstraction ladder is the following:
You want to apply an unary operator to the values of the map. So you can implement a method that applies an unary operator to the values of the map. (So far, so good). Now, you want a "special" unary operator that is null-safe. Then, you can wrap a null-safe unary operator around the original one.
This is shown here, with three different operators (one of them being Math::sin, for that matter) :
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.UnaryOperator;
public class MapValueOps
{
public static void main(String[] args)
{
Map<String, Double> map = new LinkedHashMap<String, Double>();
map.put("A", 1.2);
map.put("B", 2.3);
map.put("C", null);
map.put("D", 4.5);
Map<String, Double> resultA = apply(map, nullSafe(d -> d * 2));
System.out.println(resultA);
Map<String, Double> resultB = apply(map, nullSafe(d -> d + 2));
System.out.println(resultB);
Map<String, Double> resultC = apply(map, nullSafe(Math::sin));
System.out.println(resultC);
}
private static <T> UnaryOperator<T> nullSafe(UnaryOperator<T> op)
{
return t -> (t == null ? t : op.apply(t));
}
private static <K> Map<K, Double> apply(
Map<K, Double> map, UnaryOperator<Double> op)
{
Map<K, Double> result = new LinkedHashMap<K, Double>();
for (Entry<K, Double> entry : map.entrySet())
{
result.put(entry.getKey(), op.apply(entry.getValue()));
}
return result;
}
}
I think this is clean, because it nicely separates the concerns of applying the operator and performing the null-check. And it is null-safe, because ... the method name says so.
(One could argue to pull the call to wrap the operator into a nullSafe one into the apply method, but that's not the point here)
Edit:
Depending on the intended application pattern, one could do something similar and apply the transformation in place, without creating a new map, by calling Map#replaceAll
You can achieve that by converting into a stream, with something like:
someMap.entrySet()
.forEach(entry -> {
if (entry.getValue() != null) {
adjustedMap.put(entry.getKey(), someMap.get(entry.getKey()) * 2);
} else {
adjustedMap.put(entry.getKey(), null);
}
});
which can be shortened to:
someMap.forEach((key, value) -> {
if (value != null) {
adjustedMap.put(key, value * 2);
} else {
adjustedMap.put(key, null);
}
});
So, if you have a map with:
Map<String, Double> someMap = new HashMap<>();
someMap.put("test1", 1d);
someMap.put("test2", 2d);
someMap.put("test3", 3d);
someMap.put("testNull", null);
someMap.put("test4", 4d);
You will get this output:
{test4=8.0, test2=4.0, test3=6.0, testNull=null, test1=2.0}
It can be done like that
someMap.entrySet().stream()
.filter(stringDoubleEntry -> stringDoubleEntry.getValue() != null) //filter null values out
.forEach(stringDoubleEntry -> stringDoubleEntry.setValue(stringDoubleEntry.getValue() * 2)); //multiply values which are not null
In case you need a second map where just values in which are not null just use the forEach to put them into your new map.
How about this?
Map<String, Double> adjustedMap = new HashMap<>(someMap);
adjustedMap.entrySet().forEach(x -> {
if (x.getValue() != null) {
x.setValue(x.getValue() * 2);
}
});
You can do this with this code:
Map<String, Double> map = new HashMap<>();
map.put("1", 3.0);
map.put("3", null);
map.put("2", 5.0);
Map<String, Double> res =
map.entrySet()
.stream()
.collect(
HashMap::new,
(m,v)->m.put(v.getKey(), v.getValue() != null ? v.getValue() * 2 : null),
HashMap::putAll
);
System.out.println(res);
and the output will be:
{1=6.0, 2=10.0, 3=null}
It will allow you to keep null values in the map.
To keep null values you can use something as simple as :
someMap.keySet()
.stream()
.forEach(key -> adjustedMap.put(key, (someMap.get(key)) == null ? null : someMap.get(key) * 2));
Edit in response to Petr Janeček comment: you could apply the proposed on a copy of someMap:
adjustedMap.putAll(someMap);
adjustedMap.keySet()
.stream()
.forEach(key -> adjustedMap.put(key, (adjustedMap.get(key)) == null ? null : adjustedMap.get(key) * 2));
Yet another way:
Map<String, Double> someMap = someMapFunction();
int capacity = (int) (someMap.size() * 4.0 / 3.0 + 1);
Map<String, Double> adjustedMap = new HashMap<>(capacity);
if (someMap != null) someMap.forEach((k, v) -> adjustedMap.put(k, v == null ? v : v * 2));
Note that I'm building the new map with the default load factor (0.75 = 3.0 / 4.0) and an initial capacity that is always greater than size * load_factor. This ensures that adjustedMap is never resized/rehashed.
If you are OK with Optional values the following may work for you:
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import static java.util.stream.Collectors.toMap;
public static Map<String, Optional<Double>> g(Map<String, Double> map, Function<Double, Double> f) {
return map.entrySet().stream().collect(
toMap(
e -> e.getKey(),
e -> e.getValue() == null ? Optional.empty() : Optional.of(f.apply(e.getValue()))
));
}
and then:
public static void main(String[] args) throws Exception {
Map<String, Double> map = new HashMap<>();
map.put("a", 2.0);
map.put("b", null);
map.put("c", 3.0);
System.out.println(g(map, x -> x * 2));
System.out.println(g(map, x -> Math.sin(x)));
}
prints:
{a=Optional[4.0], b=Optional.empty, c=Optional[6.0]}
{a=Optional[0.9092974268256817], b=Optional.empty, c=Optional[0.1411200080598672]}
This is fairly clean with creation of the new map delegated to Collectors and the added benefit of the return type Map<String, Optional<Double>> clearly indicating the possibility nulls and encouraging users to handle them.
Try something like this with java 8 steam api
Map<String, Double> newMap = oldMap.entrySet().stream()
.collect(Collectors.toMap(x -> x.getKey(), x -> x.getValue() == null ? null: x.getValue()*2));
Use like this.
Map<String, Double> adjustedMap = map.entrySet().stream().filter(x -> x.getValue() != null)
.collect(Collectors.toMap(x -> x.getKey(), x -> 2*x.getValue()));
//printing
adjustedMap.entrySet().stream().forEach(System.out::println);
How Concatenate 2 Java Map<String, Object> with same keys without override
Hi, I'm trying to concatenate 2 maps in java, and try with putAll() but this method override values with same key
Example initial maps:
{Foo: "A", Bar: "B"}
{Foo: "C", Bar: "D"}
I want some like this:
{ Foo0: "A", Bar0: "B", Foo1: "C", Bar1: "D" }
It's not entirely clear what your requirements are, as #JohnBollinger pointed out. But if we take your example very simplistically, you just want to append an index to the end of each key, in which case you can do something like this:
static Map<String, Object> merge(Map<String, Object> map1, Map<String, Object> map2) {
Map<String, Object> result = new HashMap<>();
map1.forEach((k, v) -> result.put(k + "0", v));
map2.forEach((k, v) -> result.put(k + "1", v));
return result;
}
I advise you considering the following structure: Map<String, List<String>>.
In that case, you needn't override a key, just put a value to the end of the list by this key. Also, you don't have to think about how to name new keys (Bar0, Bar1). Finally, if a new map appears (third, fourth), its values will be added without issues.
Some pseudo-code for you:
map.put(key,
map.get(key) == null ? newListAddValueReturnList :
getListAddValueReturnList);
Well, the code also is written for you:
String v = map.get(key);
if(v == null)
map.put(key, new ArrayList<String>() {{ add(value); }});
else
v.add(value);
Recently I have conversation with a colleague about what would be the optimal way to convert List to Map in Java and if there any specific benefits of doing so.
I want to know optimal conversion approach and would really appreciate if any one can guide me.
Is this good approach:
List<Object[]> results;
Map<Integer, String> resultsMap = new HashMap<Integer, String>();
for (Object[] o : results) {
resultsMap.put((Integer) o[0], (String) o[1]);
}
With java-8, you'll be able to do this in one line using streams, and the Collectors class.
Map<String, Item> map =
list.stream().collect(Collectors.toMap(Item::getKey, item -> item));
Short demo:
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Test{
public static void main (String [] args){
List<Item> list = IntStream.rangeClosed(1, 4)
.mapToObj(Item::new)
.collect(Collectors.toList()); //[Item [i=1], Item [i=2], Item [i=3], Item [i=4]]
Map<String, Item> map =
list.stream().collect(Collectors.toMap(Item::getKey, item -> item));
map.forEach((k, v) -> System.out.println(k + " => " + v));
}
}
class Item {
private final int i;
public Item(int i){
this.i = i;
}
public String getKey(){
return "Key-"+i;
}
#Override
public String toString() {
return "Item [i=" + i + "]";
}
}
Output:
Key-1 => Item [i=1]
Key-2 => Item [i=2]
Key-3 => Item [i=3]
Key-4 => Item [i=4]
As noted in comments, you can use Function.identity() instead of item -> item, although I find i -> i rather explicit.
And to be complete note that you can use a binary operator if your function is not bijective. For example let's consider this List and the mapping function that for an int value, compute the result of it modulo 3:
List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6);
Map<String, Integer> map =
intList.stream().collect(toMap(i -> String.valueOf(i % 3), i -> i));
When running this code, you'll get an error saying java.lang.IllegalStateException: Duplicate key 1. This is because 1 % 3 is the same as 4 % 3 and hence have the same key value given the key mapping function. In this case you can provide a merge operator.
Here's one that sum the values; (i1, i2) -> i1 + i2; that can be replaced with the method reference Integer::sum.
Map<String, Integer> map =
intList.stream().collect(toMap(i -> String.valueOf(i % 3),
i -> i,
Integer::sum));
which now outputs:
0 => 9 (i.e 3 + 6)
1 => 5 (i.e 1 + 4)
2 => 7 (i.e 2 + 5)
List<Item> list;
Map<Key,Item> map = new HashMap<Key,Item>();
for (Item i : list) map.put(i.getKey(),i);
Assuming of course that each Item has a getKey() method that returns a key of the proper type.
Just in case this question isn't closed as a duplicate, the right answer is to use Google Collections:
Map<String,Role> mappedRoles = Maps.uniqueIndex(yourList, new Function<Role,String>() {
public String apply(Role from) {
return from.getName(); // or something else
}});
Short and sweet.
Using Java 8 you can do following :
Map<Key, Value> result= results
.stream()
.collect(Collectors.toMap(Value::getName,Function.identity()));
Value can be any object you use.
Alexis has already posted an answer in Java 8 using method toMap(keyMapper, valueMapper). As per doc for this method implementation:
There are no guarantees on the type, mutability, serializability, or
thread-safety of the Map returned.
So in case we are interested in a specific implementation of Map interface e.g. HashMap then we can use the overloaded form as:
Map<String, Item> map2 =
itemList.stream().collect(Collectors.toMap(Item::getKey, //key for map
Function.identity(), // value for map
(o,n) -> o, // merge function in case of conflict with keys
HashMap::new)); // map factory - we want HashMap and not any Map implementation
Though using either Function.identity() or i->i is fine but it seems Function.identity() instead of i -> i might save some memory as per this related answer.
Since Java 8, the answer by #ZouZou using the Collectors.toMap collector is certainly the idiomatic way to solve this problem.
And as this is such a common task, we can make it into a static utility.
That way the solution truly becomes a one-liner.
/**
* Returns a map where each entry is an item of {#code list} mapped by the
* key produced by applying {#code mapper} to the item.
*
* #param list the list to map
* #param mapper the function to produce the key from a list item
* #return the resulting map
* #throws IllegalStateException on duplicate key
*/
public static <K, T> Map<K, T> toMapBy(List<T> list,
Function<? super T, ? extends K> mapper) {
return list.stream().collect(Collectors.toMap(mapper, Function.identity()));
}
And here's how you would use it on a List<Student>:
Map<Long, Student> studentsById = toMapBy(students, Student::getId);
A List and Map are conceptually different. A List is an ordered collection of items. The items can contain duplicates, and an item might not have any concept of a unique identifier (key). A Map has values mapped to keys. Each key can only point to one value.
Therefore, depending on your List's items, it may or may not be possible to convert it to a Map. Does your List's items have no duplicates? Does each item have a unique key? If so then it's possible to put them in a Map.
There is also a simple way of doing this using Maps.uniqueIndex(...) from Google guava libraries
Universal method
public static <K, V> Map<K, V> listAsMap(Collection<V> sourceList, ListToMapConverter<K, V> converter) {
Map<K, V> newMap = new HashMap<K, V>();
for (V item : sourceList) {
newMap.put( converter.getKey(item), item );
}
return newMap;
}
public static interface ListToMapConverter<K, V> {
public K getKey(V item);
}
Using java-8 streams
Map<Integer, String> map = results.stream().collect(Collectors.toMap(e -> ((Integer) e[0]), e -> (String) e[1]));
Without java-8, you'll be able to do this in one line Commons collections, and the Closure class
List<Item> list;
#SuppressWarnings("unchecked")
Map<Key, Item> map = new HashMap<Key, Item>>(){{
CollectionUtils.forAllDo(list, new Closure() {
#Override
public void execute(Object input) {
Item item = (Item) input;
put(i.getKey(), item);
}
});
}};
like already said, in java-8 we have the concise solution by Collectors:
list.stream().collect(
groupingBy(Item::getKey)
)
and also, you can nest multiple group passing an other groupingBy method as second parameter:
list.stream().collect(
groupingBy(Item::getKey, groupingBy(Item::getOtherKey))
)
In this way, we'll have multi level map, like this: Map<key, Map<key, List<Item>>>
Many solutions come to mind, depending on what you want to achive:
Every List item is key and value
for( Object o : list ) {
map.put(o,o);
}
List elements have something to look them up, maybe a name:
for( MyObject o : list ) {
map.put(o.name,o);
}
List elements have something to look them up, and there is no guarantee that they are unique: Use Googles MultiMaps
for( MyObject o : list ) {
multimap.put(o.name,o);
}
Giving all the elements the position as a key:
for( int i=0; i<list.size; i++ ) {
map.put(i,list.get(i));
}
...
It really depends on what you want to achive.
As you can see from the examples, a Map is a mapping from a key to a value, while a list is just a series of elements having a position each. So they are simply not automatically convertible.
Here's a little method I wrote for exactly this purpose. It uses Validate from Apache Commons.
Feel free to use it.
/**
* Converts a <code>List</code> to a map. One of the methods of the list is called to retrive
* the value of the key to be used and the object itself from the list entry is used as the
* objct. An empty <code>Map</code> is returned upon null input.
* Reflection is used to retrieve the key from the object instance and method name passed in.
*
* #param <K> The type of the key to be used in the map
* #param <V> The type of value to be used in the map and the type of the elements in the
* collection
* #param coll The collection to be converted.
* #param keyType The class of key
* #param valueType The class of the value
* #param keyMethodName The method name to call on each instance in the collection to retrieve
* the key
* #return A map of key to value instances
* #throws IllegalArgumentException if any of the other paremeters are invalid.
*/
public static <K, V> Map<K, V> asMap(final java.util.Collection<V> coll,
final Class<K> keyType,
final Class<V> valueType,
final String keyMethodName) {
final HashMap<K, V> map = new HashMap<K, V>();
Method method = null;
if (isEmpty(coll)) return map;
notNull(keyType, Messages.getString(KEY_TYPE_NOT_NULL));
notNull(valueType, Messages.getString(VALUE_TYPE_NOT_NULL));
notEmpty(keyMethodName, Messages.getString(KEY_METHOD_NAME_NOT_NULL));
try {
// return the Method to invoke to get the key for the map
method = valueType.getMethod(keyMethodName);
}
catch (final NoSuchMethodException e) {
final String message =
String.format(
Messages.getString(METHOD_NOT_FOUND),
keyMethodName,
valueType);
e.fillInStackTrace();
logger.error(message, e);
throw new IllegalArgumentException(message, e);
}
try {
for (final V value : coll) {
Object object;
object = method.invoke(value);
#SuppressWarnings("unchecked")
final K key = (K) object;
map.put(key, value);
}
}
catch (final Exception e) {
final String message =
String.format(
Messages.getString(METHOD_CALL_FAILED),
method,
valueType);
e.fillInStackTrace();
logger.error(message, e);
throw new IllegalArgumentException(message, e);
}
return map;
}
A Java 8 example to convert a List<?> of objects into a Map<k, v>:
List<Hosting> list = new ArrayList<>();
list.add(new Hosting(1, "liquidweb.com", new Date()));
list.add(new Hosting(2, "linode.com", new Date()));
list.add(new Hosting(3, "digitalocean.com", new Date()));
//example 1
Map<Integer, String> result1 = list.stream().collect(
Collectors.toMap(Hosting::getId, Hosting::getName));
System.out.println("Result 1 : " + result1);
//example 2
Map<Integer, String> result2 = list.stream().collect(
Collectors.toMap(x -> x.getId(), x -> x.getName()));
Code copied from:
https://www.mkyong.com/java8/java-8-convert-list-to-map/
You can leverage the streams API of Java 8.
public class ListToMap {
public static void main(String[] args) {
List<User> items = Arrays.asList(new User("One"), new User("Two"), new User("Three"));
Map<String, User> map = createHashMap(items);
for(String key : map.keySet()) {
System.out.println(key +" : "+map.get(key));
}
}
public static Map<String, User> createHashMap(List<User> items) {
Map<String, User> map = items.stream().collect(Collectors.toMap(User::getId, Function.identity()));
return map;
}
}
For more details visit: http://codecramp.com/java-8-streams-api-convert-list-map/
I like Kango_V's answer, but I think it's too complex. I think this is simpler - maybe too simple. If inclined, you could replace String with a Generic marker, and make it work for any Key type.
public static <E> Map<String, E> convertListToMap(Collection<E> sourceList, ListToMapConverterInterface<E> converterInterface) {
Map<String, E> newMap = new HashMap<String, E>();
for( E item : sourceList ) {
newMap.put( converterInterface.getKeyForItem( item ), item );
}
return newMap;
}
public interface ListToMapConverterInterface<E> {
public String getKeyForItem(E item);
}
Used like this:
Map<String, PricingPlanAttribute> pricingPlanAttributeMap = convertListToMap( pricingPlanAttributeList,
new ListToMapConverterInterface<PricingPlanAttribute>() {
#Override
public String getKeyForItem(PricingPlanAttribute item) {
return item.getFullName();
}
} );
Apache Commons MapUtils.populateMap
If you don't use Java 8 and you don't want to use a explicit loop for some reason, try MapUtils.populateMap from Apache Commons.
MapUtils.populateMap
Say you have a list of Pairs.
List<ImmutablePair<String, String>> pairs = ImmutableList.of(
new ImmutablePair<>("A", "aaa"),
new ImmutablePair<>("B", "bbb")
);
And you now want a Map of the Pair's key to the Pair object.
Map<String, Pair<String, String>> map = new HashMap<>();
MapUtils.populateMap(map, pairs, new Transformer<Pair<String, String>, String>() {
#Override
public String transform(Pair<String, String> input) {
return input.getKey();
}
});
System.out.println(map);
gives output:
{A=(A,aaa), B=(B,bbb)}
That being said, a for loop is maybe easier to understand. (This below gives the same output):
Map<String, Pair<String, String>> map = new HashMap<>();
for (Pair<String, String> pair : pairs) {
map.put(pair.getKey(), pair);
}
System.out.println(map);
If you use Kotlin, there is an example:
listOf("one", "two").mapIndexed { i, it -> i to it }.toMap()
public class EmployeeDetailsFetchListToMap {
public static void main(String[] args) {
List<EmployeeDetailsFetch> list = new ArrayList<>();
list.add(new EmployeeDetailsFetch(1L, "vinay", 25000F));
list.add(new EmployeeDetailsFetch(2L, "kohli", 5000000F));
list.add(new EmployeeDetailsFetch(3L, "dhoni", 20000000F));
//adding id as key and map of id and student name
Map<Long, Map<Long, String>> map1 = list.stream()
.collect(
Collectors.groupingBy(
EmployeeDetailsFetch::getEmpId,
Collectors.toMap(
EmployeeDetailsFetch::getEmpId,
EmployeeDetailsFetch::getEmployeeName
)
)
);
System.out.println(map1);
//converting list into map of Student
//Adding id as Key and Value as Student into a map
Map<Long, EmployeeDetailsFetch> map = list.stream()
.collect(
Collectors.toMap(
EmployeeDetailsFetch::getEmpId,
EmployeeDetailsFetch -> EmployeeDetailsFetch
)
);
for(Map.Entry<Long, EmployeeDetailsFetch> m : map.entrySet()) {
System.out.println("key :" + m.getKey() + " Value : " + m.getValue());
}
}
}