Guava provides us with great factory methods for Java types, such as Maps.newHashMap().
But are there also builders for java Maps?
HashMap<String,Integer> m = Maps.BuildHashMap.
put("a",1).
put("b",2).
build();
There is no such thing for HashMaps, but you can create an ImmutableMap with a builder:
final Map<String, Integer> m = ImmutableMap.<String, Integer>builder().
put("a", 1).
put("b", 2).
build();
And if you need a mutable map, you can just feed that to the HashMap constructor.
final Map<String, Integer> m = Maps.newHashMap(
ImmutableMap.<String, Integer>builder().
put("a", 1).
put("b", 2).
build());
Not quite a builder, but using an initializer:
Map<String, String> map = new HashMap<String, String>() {{
put("a", "1");
put("b", "2");
}};
Since Java 9 Map interface contains:
Map.of(k1,v1, k2,v2, ..)
Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ..).
Limitations of those factory methods are that they:
can't hold nulls as keys and/or values (if you need to store nulls take a look at other answers)
produce immutable maps
If we need mutable map (like HashMap) we can use its copy-constructor and let it copy content of map created via Map.of(..)
Map<Integer, String> map = new HashMap<>( Map.of(1,"a", 2,"b", 3,"c") );
This is similar to the accepted answer, but a little cleaner, in my view:
ImmutableMap.of("key1", val1, "key2", val2, "key3", val3);
There are several variations of the above method, and they are great for making static, unchanging, immutable maps.
Here is a very simple one ...
public class FluentHashMap<K, V> extends java.util.HashMap<K, V> {
public FluentHashMap<K, V> with(K key, V value) {
put(key, value);
return this;
}
public static <K, V> FluentHashMap<K, V> map(K key, V value) {
return new FluentHashMap<K, V>().with(key, value);
}
}
then
import static FluentHashMap.map;
HashMap<String, Integer> m = map("a", 1).with("b", 2);
See https://gist.github.com/culmat/a3bcc646fa4401641ac6eb01f3719065
A simple map builder is trivial to write:
public class Maps {
public static <Q,W> MapWrapper<Q,W> map(Q q, W w) {
return new MapWrapper<Q, W>(q, w);
}
public static final class MapWrapper<Q,W> {
private final HashMap<Q,W> map;
public MapWrapper(Q q, W w) {
map = new HashMap<Q, W>();
map.put(q, w);
}
public MapWrapper<Q,W> map(Q q, W w) {
map.put(q, w);
return this;
}
public Map<Q,W> getMap() {
return map;
}
}
public static void main(String[] args) {
Map<String, Integer> map = Maps.map("one", 1).map("two", 2).map("three", 3).getMap();
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
}
You can use:
HashMap<String,Integer> m = Maps.newHashMap(
ImmutableMap.of("a",1,"b",2)
);
It's not as classy and readable, but does the work.
HashMap is mutable; there's no need for a builder.
Map<String, Integer> map = Maps.newHashMap();
map.put("a", 1);
map.put("b", 2);
Using java 8:
This is a approach of Java-9 Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ...)
public class MapUtil {
import static java.util.stream.Collectors.toMap;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Stream;
private MapUtil() {}
#SafeVarargs
public static Map<String, Object> ofEntries(SimpleEntry<String, Object>... values) {
return Stream.of(values).collect(toMap(Entry::getKey, Entry::getValue));
}
public static SimpleEntry<String, Object> entry(String key, Object value) {
return new SimpleEntry<String, Object>(key, value);
}
}
How to Use:
import static your.package.name.MapUtil.*;
import java.util.Map;
Map<String, Object> map = ofEntries(
entry("id", 1),
entry("description", "xyz"),
entry("value", 1.05),
entry("enable", true)
);
There's ImmutableMap.builder() in Guava.
I had a similar requirement a while back. Its nothing to do with Guava but you can do something like this to be able to cleanly construct a Map using a fluent builder.
Create a base class that extends Map.
public class FluentHashMap<K, V> extends LinkedHashMap<K, V> {
private static final long serialVersionUID = 4857340227048063855L;
public FluentHashMap() {}
public FluentHashMap<K, V> delete(Object key) {
this.remove(key);
return this;
}
}
Then create the fluent builder with methods that suit your needs:
public class ValueMap extends FluentHashMap<String, Object> {
private static final long serialVersionUID = 1L;
public ValueMap() {}
public ValueMap withValue(String key, String val) {
super.put(key, val);
return this;
}
... Add withXYZ to suit...
}
You can then implement it like this:
ValueMap map = new ValueMap()
.withValue("key 1", "value 1")
.withValue("key 2", "value 2")
.withValue("key 3", "value 3")
Here's one I wrote
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class MapBuilder<K, V> {
private final Map<K, V> map;
/**
* Create a HashMap builder
*/
public MapBuilder() {
map = new HashMap<>();
}
/**
* Create a HashMap builder
* #param initialCapacity
*/
public MapBuilder(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
/**
* Create a Map builder
* #param mapFactory
*/
public MapBuilder(Supplier<Map<K, V>> mapFactory) {
map = mapFactory.get();
}
public MapBuilder<K, V> put(K key, V value) {
map.put(key, value);
return this;
}
public Map<K, V> build() {
return map;
}
/**
* Returns an unmodifiable Map. Strictly speaking, the Map is not immutable because any code with a reference to
* the builder could mutate it.
*
* #return
*/
public Map<K, V> buildUnmodifiable() {
return Collections.unmodifiableMap(map);
}
}
You use it like this:
Map<String, Object> map = new MapBuilder<String, Object>(LinkedHashMap::new)
.put("event_type", newEvent.getType())
.put("app_package_name", newEvent.getPackageName())
.put("activity", newEvent.getActivity())
.build();
You can use the fluent API in Eclipse Collections:
Map<String, Integer> map = Maps.mutable.<String, Integer>empty()
.withKeyValue("a", 1)
.withKeyValue("b", 2);
Assert.assertEquals(Maps.mutable.with("a", 1, "b", 2), map);
Here's a blog with more detail and examples.
Note: I am a committer for Eclipse Collections.
This is something I always wanted, especially while setting up test fixtures. Finally, I decided to write a simple fluent builder of my own that could build any Map implementation -
https://gist.github.com/samshu/b471f5a2925fa9d9b718795d8bbdfe42#file-mapbuilder-java
/**
* #param mapClass Any {#link Map} implementation type. e.g., HashMap.class
*/
public static <K, V> MapBuilder<K, V> builder(#SuppressWarnings("rawtypes") Class<? extends Map> mapClass)
throws InstantiationException,
IllegalAccessException {
return new MapBuilder<K, V>(mapClass);
}
public MapBuilder<K, V> put(K key, V value) {
map.put(key, value);
return this;
}
public Map<K, V> build() {
return map;
}
Underscore-java can build hashmap.
Map<String, Object> value = U.objectBuilder()
.add("firstName", "John")
.add("lastName", "Smith")
.add("age", 25)
.add("address", U.arrayBuilder()
.add(U.objectBuilder()
.add("streetAddress", "21 2nd Street")
.add("city", "New York")
.add("state", "NY")
.add("postalCode", "10021")))
.add("phoneNumber", U.arrayBuilder()
.add(U.objectBuilder()
.add("type", "home")
.add("number", "212 555-1234"))
.add(U.objectBuilder()
.add("type", "fax")
.add("number", "646 555-4567")))
.build();
// {firstName=John, lastName=Smith, age=25, address=[{streetAddress=21 2nd Street,
// city=New York, state=NY, postalCode=10021}], phoneNumber=[{type=home, number=212 555-1234},
// {type=fax, number=646 555-4567}]}
Related
I am learning some cool stuff about Java StreamAPI and got stuck'd into one problem:
I have a use case where I want to return newly create hashmap using stream. I am using the traditional way of defining a HashMap in the function and adding up values to it.
I was more interested in knowing some better ways to achieve so
public Map<String,String> constructMap(List<CustomObject> lists){
Map<String,String> newMap = new HashMap<>();
lists.stream().filter(x->x!=null).forEach(map -> newMap.putAll(map.getSomeMapping(studentId));
return newMap;
}
Can I achieve this using reduceAPI or any other way without having to create a custom hashmap (directly return the stream one liner)?
Edit:
for Example:
CustomObject c1 = new CustomObject("bookId1", "book1");
CustomObject c2 = new CustomObject("bookId2", "book2");
List<CustomObject> lists = new ArrayList();
lists.add(c1); lists.add(c2);
The getter in class CustomObject is: getSomeMapping(input)
which return Map<BookID, Book>
Expected output:
{"bookId1" : "book1", "bookId2" : "book2"}
Edit2:
One more thing to clarify, the CustomObject class does not have any other getters defined. The only function I have access to is getSomeMapping(input) which returns a mapping
thank you for any help.
Assuming CustomObject has the following structure and getter getSomeMapping which returns a map:
class CustomObject {
private Map<String, String> someMapping;
public CustomObject(String key, String value) {
this.someMapping = new HashMap<>();
someMapping.put(key, value);
}
public Map<String, String> getSomeMapping() {
return someMapping;
}
}
Then constructMap will use already mentioned Collectors.toMap after flattening the entries in someMapping:
public static Map<String, String> constructMap(List<CustomObject> list) {
return list.stream()
.filter(Objects::nonNull)
.map(CustomObject::getSomeMapping)
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(v1, v2) -> v1, // merge function to handle possible duplicates
LinkedHashMap::new
));
}
Test
CustomObject c1 = new CustomObject("bookId1", "book1");
CustomObject c2 = new CustomObject("bookId2", "book2");
List<CustomObject> lists = Arrays.asList(c1, c2);
Map<String, String> result = constructMap(lists);
System.out.println(result);
Output:
{bookId1=book1, bookId2=book2}
You can use Collectors#toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier) to create a LinkedHashMap using the bookId as the key, and bookName as the value.
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
class CustomObject {
private String bookId;
private String bookName;
public CustomObject(String bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
public String getBookId() {
return bookId;
}
public String getBookName() {
return bookName;
}
// Other stuff e.g. equals, hashCode etc.
}
public class Main {
public static void main(String[] args) {
List<CustomObject> list = List.of(new CustomObject("bookId1", "book1"), new CustomObject("bookId2", "book2"));
System.out.println(constructMap(list));
}
public static Map<String, String> constructMap(List<CustomObject> list) {
return list.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(CustomObject::getBookId, CustomObject::getBookName, (a, b) -> a, LinkedHashMap::new));
}
}
Output:
{bookId1=book1, bookId2=book2}
Note: The mergeFunction, (a, b) -> a resolves the collision between values associated with the same key e.g. in this case, we have defined it to select a out of a and b having the same key. If the order of elements does not matter, you can use Collectors#toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper) as shown below:
public static Map<String, String> constructMap(List<CustomObject> list) {
return list.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(CustomObject::getBookId, CustomObject::getBookName));
}
A sample output:
{bookId2=book2, bookId1=book1}
To turn a stream into a map you're better off using collect(). For instance:
public Map<String,String> toMap(List<Entry<String,String>> entries) {
return entries.stream().collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
Or if your keys are non-unique and you want the values to be combined as a list:
public Map<String,List<CustomObject>> toMap(List<CustomObject> entries) {
return entries.stream().collect(Collectors.groupingBy(CustomObject::getKey));
}
Look into [Collectors.toMap()] 1. This can return the items as a new Map.
lists.stream().filter(x->x!=null).collect(Collectors.toMap(CustomObject::getMapKey(), CustomObject::getMapValue()));
getMapKey and getMapValue are here methods returning the key and value of the CustomObject for the map. Instead of using simple getters it might also be necessary to execute some more advanced logic.
lists.stream().filter(x->x!=null).collect(Collectors.toMap(l -> {...; return key;}, l -> { ...; return value;}));
Let's assume your CustomObject class has getters to retrieve a school id with a name. You could do it like this. I declared it static as it does not appear to depend on instance fields.
public static Map<String,String> constructMap(List<CustomObject> lists){
return lists.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(CustomObject::getName, CustomObject::getID));
}
This presumes that names and Id's are one-to-one, as this does not handle duplicate keys.
I'm reviewing an old code of an project and got a datastructure as bellow using Map of Map of Map(3-Layered Map):
// data structure
Map<String, Map<String, Map<String, List<String>>>> tagTree
= new HashMap<String, Map<String,Map<String,List<String>>>>();
And fetch the values from Map (I think this is the nice part)
// fetch at tag values
List<String> tagList1 = tagTree.get("Java").get("Active").get("Tags");
List<String> tagList2 = tagTree.get("Java").get("Latest").get("SubTags");
Put the values in Map (little bit complex and error-prone)
// put values
Map<String, Map<String, List<String>>> javaLangMap = new HashMap<String, Map<String, List<String>>>();
Map<String, List<String>> javaStatusMap = new HashMap<String, List<String>>();
List<String> javaTagList = new ArrayList<String>();
javaTagList.add("Java-OOP");
javaTagList.add("Java-Variables");
// put tag list
javaStatusMap.put("Tags", javaTagList);
// put status-wise tag
javaLangMap.put("Active", javaStatusMap);
// put language-wise tag
tagTree.put("Java", javaLangMap);
Currently this is serving to maintain following structure
TagLanguage -> TagStatus -> TagType -> TagList
I'm planning to refactor this Map because it's hard to read for other developers.
Please share your Idea How to do it by considering following cases:
All four layer may be changed during runtime.
All Level should
accessible
In-memory solution required i.e. dont use Database table hierarchy .
If you only ever wanted to access the last level of your data structure, you could use a Multimap<Triple<String,String,String>,String>. Multimap<K,V> is a data structure from Guava which basically is a nicer Map<K,Collection<V>>. Triple<L,M,R> is a 3-elements tuple data structure from Apache Commons Lang3 which is Comparable and implements equals.
You could declare your tag tree like this:
Multimap<Triple<String, String, String>, String> tagTree = HashMultimap.create();
And then fill it like this:
tagTree.put(Triple.of("Java", "Active", "Tags"), "Java-OOP");
tagTree.put(Triple.of("Java", "Active", "Tags"), "Java-Variables");
Or:
tagTree.putAll(Triple.of("Java", "Active", "Tags"), Arrays.asList("Java-OOP", "Java-Variables"));
And then get your values from it like this:
Set<String> values = tagTree.get(Triple.of("Java", "Active", "Tags"));
Here is another rough solution that may suit you which enables to get with 1, 2 or 3 keys:
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
public class ThreeLevelMap<K1, K2, K3, V> {
private Map<K1, Map<K2, Multimap<K3, V>>> firstLevelMap = new HashMap<>();
private Map<Pair<K1, K2>, Multimap<K3, V>> secondLevelMap = new HashMap<>();
private Multimap<Triple<K1, K2, K3>, V> thirdLevelMap = HashMultimap.create();
public void put(K1 key1, K2 key2, K3 key3, V value) {
thirdLevelMap.put(Triple.of(key1, key2, key3), value);
final Pair<K1, K2> secondLevelKey = Pair.of(key1, key2);
Multimap<K3, V> secondLevelContainer = secondLevelMap.get(secondLevelKey);
if (secondLevelContainer == null) {
secondLevelContainer = HashMultimap.create();
secondLevelMap.put(secondLevelKey, secondLevelContainer);
}
secondLevelContainer.put(key3, value);
Map<K2, Multimap<K3, V>> firstLevelContainer = firstLevelMap.get(key1);
if (firstLevelContainer == null) {
firstLevelContainer = new HashMap<>();
firstLevelMap.put(key1, firstLevelContainer);
}
firstLevelContainer.put(key2, secondLevelContainer);
}
public Collection<V> get(K1 key1, K2 key2, K3 key3) {
return thirdLevelMap.get(Triple.of(key1, key2, key3));
}
public Multimap<K3, V> get(K1 key1, K2 key2) {
return secondLevelMap.get(Pair.of(key1, key2));
}
public Map<K2, Multimap<K3, V>> get(K1 key1) {
return firstLevelMap.get(key1);
}
}
You can use it this way:
ThreeLevelMap<String, String, String, String> tlm = new ThreeLevelMap<>();
tlm.put("Java", "Active", "Tags", "Java-OOP");
tlm.put("Java", "Active", "Tags", "Java-Variables");
Map<String, Multimap<String, String>> firstLevelMap = tlm.get("Java");
Multimap<String, String> secondLevelMap = tlm.get("Java", "Active");
Collection<String> tags = tlm.get("Java", "Active", "Tags");
I say it is rough because:
the maps that the get methods return are modifiable
I didn't implement remove methods
I didn't test it a lot
I don't think that this is such a bad solution.
It's just a tree representation, for a tree where every leaf is at level 3.
If this was not the case (different leaf levels, etc.) you would have to build up a tree class structure.
But what I would change is to put everything in a class, with a get and set method, including null-checks.
In the following code, the add method takes care of the error-prone handling of the intermediate level maps, while get checks for null values in the intermediate levels:
public class TreeStructure {
Map<String, Map<String, Map<String, List<String>>>> tagTree
= new HashMap<String, Map<String,Map<String,List<String>>>>();
// ... Constructor ...
// This method adds all intermediate levels if not existing
public void add(String level1, String level2, String level3) {
String l1 = tagTree.get(level1);
if(l1 == null)
tagTree.put(level1, new HashMap<String, Map<String, List<String>>>());
l1 = tagTree.get(level1);
String l2 = l1.get(level2),
if(l2 == null)
tagTree.put(level2, new Map<String, List<String>>(););
l2 = l1.get(level2);
String l3 = l2.get(level3);
if(l3 == null) l2.add(level3, new ArrayList<>());
}
// This method checks, if every intermediate level existed
// Otherwise, get() returns null, and the next get() would fail
public String get(String level1, String level2, String level3) {
String l1 = tagTree.get(level1);
if(l1 == null)
return null;
String l2 = l1.get(level2),
if(l2 == null)
return null;
l2 = l1.get(level2);
String l3 = l2.get(level3);
return l3;
}
}
(Code untested)
You can create classes witch will hold the data structure
public class A {
Map<String, List<String>> map;
}
public class B {
Map<String, A> map;
}
Map<String, B> tagTree;
Use a Map of 3-Tuples:
class Tuple3<A,B,C> {
private A a;
private B b;
private C c;
// getters, setters, constructor
// make sure equals() and hashCode() are okay
}
Note, however, that the Map of Map of Maps can tell you in O(1) whether there are entries for some element by just looking in the outer map. Whereas, with the tuple solution, you can only work with full keys.
I think there is no reason for refactor this Map structure. But if possible, it is probably a good idea to encapsulate this Map in another class and give other developers a clean interface.
...
public void addTag(String language, String status, String tag)
public void removeTag(String language, String status, String tag)
public List<String> getTags(String language, String status)
...
I like most of the solutions suggested above. I think the simpler and efficient the design is - more supportable it will be.
Hence, I used basics - plain object composition to refactor your code.
package design;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class JavaTag{
private String tags;
JavaTag(String tags){
this.tags = tags;
}
}
class JavaTagStatusList{
private ArrayList<JavaTag> tagList = new ArrayList<JavaTag>();
JavaTagStatusList(){
}
public void addJavaTag (JavaTag tagObj){
if (tagObj != null){
tagList.add(tagObj);
}
}
}
class JavaTagStatusMap {
private HashMap<String, JavaTagStatusList> tagStatusMap = new HashMap<String, JavaTagStatusList>();
JavaTagStatusMap(){
}
public void addTagStatusEntry(String status, JavaTag obj){
if (tagStatusMap.containsKey(status)){
tagStatusMap.get(status).addJavaTag(obj);
}
else {
JavaTagStatusList statusList = new JavaTagStatusList();
statusList.addJavaTag(obj);
tagStatusMap.put(status, statusList);
}
}
}
Main:
public class MapofMapRefactor {
public static void main(String[] args) {
JavaTag tag1 = new JavaTag("Java-OOP");
JavaTag tag2 = new JavaTag("Java-Variables");
JavaTagStatusMap statusMap = new JavaTagStatusMap();
statusMap.addTagStatusEntry("Active", tag1);
statusMap.addTagStatusEntry("Active", tag2);
// HashMap of Java Lang Map
HashMap<String, JavaTagStatusMap> javaLanguageMap = new HashMap<String, JavaTagStatusMap>();
javaLanguageMap.put("Java", statusMap);
}
}
I have a unit test that needs to check for a nested map value. I can get my assertion to work by pulling out the entry and matching the underlying Map, but I was looking for a clear way to show what the assertion is doing. Here is a very simplified test:
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasEntry;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
public class MapContainsMapTest {
#Test
public void testMapHasMap() {
Map<String, Object> outerMap = new HashMap<String, Object>();
Map<String, Object> nestedMap = new HashMap<String, Object>();
nestedMap.put("foo", "bar");
outerMap.put("nested", nestedMap);
// works but murky
assertThat((Map<String, Object>) outerMap.get("nested"), hasEntry("foo", "bar"));
// fails but clear
assertThat(outerMap, hasEntry("nested", hasEntry("foo", "bar")));
}
}
It seems the problem is the outer map is being compared using hasEntry(K key, V value) while what I want to use is hasEntry(Matcher<? super K> keyMatcher, Matcher<? super V> valueMatcher). I am not sure how to coerce the assertion to use the second form.
Thanks in advance.
If you only want to put Map<String, Object> as values in your outerMap adjust the declaration accordingly. Then you can do
#Test
public void testMapHasMap() {
Map<String, Map<String, Object>> outerMap = new HashMap<>();
Map<String, Object> nestedMap = new HashMap<String, Object>();
nestedMap.put("foo", "bar");
outerMap.put("nested", nestedMap);
Object value = "bar";
assertThat(outerMap, hasEntry(equalTo("nested"), hasEntry("foo", value)));
}
Object value = "bar"; is necessary for compile reasons. Alternatively you could use
assertThat(outerMap,
hasEntry(equalTo("nested"), Matchers.<String, Object> hasEntry("foo", "bar")));
If You declare outerMap as Map<String, Map<String, Object>> you don't need the ugly cast. Like this:
public class MapContainsMapTest {
#Test
public void testMapHasMap() {
Map<String, Map<String, Object>> outerMap = new HashMap<>();
Map<String, Object> nestedMap = new HashMap<>();
nestedMap.put("foo", "bar");
outerMap.put("nested", nestedMap);
assertThat(outerMap.get("nested"), hasEntry("foo", "bar"));
}
}
I would probably extend a new Matcher for that, something like that (beware, NPEs lurking):
class SubMapMatcher extends BaseMatcher<Map<?,?>> {
private Object key;
private Object subMapKey;
private Object subMapValue;
public SubMapMatcher(Object key, Object subMapKey, Object subMapValue) {
super();
this.key = key;
this.subMapKey = subMapKey;
this.subMapValue = subMapValue;
}
#Override
public boolean matches(Object item) {
Map<?,?> map = (Map<?,?>)item;
if (!map.containsKey(key)) {
return false;
}
Object o = map.get(key);
if (!(o instanceof Map<?,?>)) {
return false;
}
Map<?,?> subMap = (Map<?,?>)o;
return subMap.containsKey(subMapKey) && subMap.get(subMapKey).equals(subMapValue);
}
#Override
public void describeTo(Description description) {
description.appendText(String.format("contains %s -> %s : %s", key, subMapKey, subMapValue));
}
public static SubMapMatcher containsSubMapWithKeyValue(String key, String subMapKey, String subMapValue) {
return new SubMapMatcher(key, subMapKey, subMapValue);
}
}
Try like this :
assertThat(nestedMap).contains(Map.entry("foo", "bar"));
assertThat(outerMap).contains(Map.entry("nested", nestedMap));
In Java, I want to add a getOrAdd method to a regular map, just like putIfAbsent on a ConcurrentHashMap.
Furthermore, for a certain key I want to store a list of items. Here's my attempt:
public class ListMap<K, V> extends HashMap<K, V> {
private HashMap<K, List<V>> map;
public ListMap() {
map = new HashMap<K, List<V>>();
}
public List<V> getOrAdd(K key) {
if (map.containsKey(key)) {
return map.get(key);
} else {
List<V> l = new ArrayList<V>();
map.put(key, l);
return l;
}
}
}
However, if someone wanted to iterate over a ListMap, he would need to cast the values explictly.
ListMap<Integer, MyClass> listMap = new ListMap<Integer, MyClass>();
for (Map.Entry<Integer, MyClass> entry : listMap.entrySet()) {
List<MyClass> val = (List<MyClass>) entry.getValue();
}
Is there a way of extending the HashMap class by some methods without creating a subclass? ( I've seen this in C#)
How can the ListMap class be modified such that one can get a ListMaps's value (List) without casting?
Instance of your class will be also HashMap so you don't need to, or even shouldn't add another field just to support getOrAdd method because other inherited and not overridden methods will not be referring to map field but to this instance.
So instead of adding separate field
private HashMap<K, List<V>> map;
change extending type of your ListMap to
public class ListMap<K, V> extends HashMap<K, List<V>>
^^^^^^^
and change your getOrAdd method to not use map field but this
public List<V> getOrAdd(K key) {
if (containsKey(key)) {
return get(key);
} else {
List<V> l = new ArrayList<V>();
put(key, l);
return l;
}
}
This change will let you use your map like
ListMap<Integer, String> listMap = new ListMap<Integer, String>();
for (Map.Entry<Integer, List<String>> entry : listMap.entrySet()) {
List<String> val = entry.getValue();//NO CASTING NEEDED
}
You can just extend HashMap like this:
public class ListMap<K, V> extends HashMap<K, List<V>> {
...
}
I want to use MultiKeyMap from Apache Collection, because I need a HashMap with two keys and a value.
To put elements I do this:
private MultiKeyMap multiKey = new MultiKeyMap();
multiKey.put("key1.1", "key2.1", "value1");
And for get element I do this:
String s = multiKey.get("key1.1");
But the String s cames null... If I pass the two keys, like that:
String s = multiKey.get("key1.1", "key2.1");
The String s cames with values value1...
How can I extend the MultiKeyMap to get the right value when I pass only one of the two keys?
If you need only one key to get a value you have a plain old HashMap.
private Map<String, String> map = new HashMap<>();
map.put("key1.1", "value1");
map.put("key2.1", "value1");
And for get element you can do this:
String s = map.get("key1.1"); // s == "value1"
MultiKeyMap is required when both keys must be provided.
If you specify a value with two keys, you are going to need both keys to get it back. The hash function is not designed to return all the possible values that are associated with only one of the two keys. You may need to find a different data structure to do this.
MultiKeyMap is about using tuples as keys, not about matching one value to more than one key. Use a normal map and just put your value twice, with different keys.
Some more caution is needed when removing values. When you remove a value for the first key, do you want to automatically remove other keys with the same value? If so, you need either to loop over all keys and remove those with same value by hand, which could be inefficient, or keep some kind of reverse map to quickly find keys for specific value.
I don't know exact solution to your problem. But I suggest you to implement it like:
Map<K2, K1> m2;
Map<K1, V> m1;
And see: How to implement a Map with multiple keys?
It seems that you just do not need MultiKeyMap. You need regular map. Using it you can associate the same value with as many keys as you want.
Map<String, String> map = new HashMap<String, String>();
Object value = .....
map.put("key1", value);
map.put("key2", value);
..................
if(map.get("key1") == map.get("key2")) {
System.out.println("the same value stored under 2 different keys!");
}
You just can't since it's not the way a MultiKeyMap works. Put the value with separate keys and than try getting it with each key at a time.
Instead of that you can use table data stature from guava.
I would suggest to create a separate class for multiple keys:
public class Test {
Map<Shape, Book> test1 = new HashMap<>();
Book book = new Book("A");
test1.put(Shape, book);
private class Shape {
String id1;
String id2;
public Shape(String id1, String id2) {
this.id1 = id1;
this.id2 = id2;
}
#Override
public boolean equals(Object o) {//}
#Override
public int hashCode() {//}
}
}
Here is a simple MultiKeyMap implementation that worked for me.
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
public class MultiMap<K, V> implements Map<K, V>
{
private class MultiMapEntery implements java.util.Map.Entry<K, V>
{
private final K key;
private V value;
public MultiMapEntery(K key, V value)
{
this.key = key;
this.value = value;
}
#Override
public K getKey()
{
return key;
}
#Override
public V getValue()
{
return value;
}
#Override
public V setValue(V value)
{
V oldValue = this.value;
this.value = value;
return oldValue;
}
};
private final Map<K, String> keyMap = new HashMap<K, String>();
private final Map<String, Set<K>> inverseKeyMap = new HashMap<String, Set<K>>();
private final Map<String, V> valueMap = new HashMap<String, V>();
#Override
public void clear()
{
keyMap.clear();
inverseKeyMap.clear();
valueMap.clear();
}
#Override
public boolean containsKey(Object key)
{
return keyMap.containsKey(key);
}
#Override
public boolean containsValue(Object value)
{
return valueMap.containsValue(value);
}
#Override
public Set<java.util.Map.Entry<K, V>> entrySet()
{
Set<java.util.Map.Entry<K, V>> entries = new HashSet<>();
for(K key : keyMap.keySet())
{
V value = valueMap.get(key);
entries.add(new MultiMapEntery(key, value));
}
return entries;
}
#Override
public V get(Object key)
{
return valueMap.get(keyMap.get(key));
}
#Override
public boolean isEmpty()
{
return valueMap.isEmpty();
}
#Override
public Set<K> keySet()
{
return keyMap.keySet();
}
#Override
public V put(K key, V value)
{
String id = keyMap.get(key);
if(id == null)
{
id = UUID.randomUUID().toString();
}
keyMap.put(key, id);
Set<K> keys = inverseKeyMap.get(id);
if(keys == null)
{
keys = new HashSet<>();
}
keys.add(key);
inverseKeyMap.put(id, keys);
valueMap.put(id, value);
return value;
}
public V put(Set<K> keys, V value)
{
String id = null;
for(K key : keys)
{
id = keyMap.get(key);
if(id != null) // one of the keys already exists
{
break;
}
}
if(id == null)
{
id = UUID.randomUUID().toString();
}
for(K key : keys)
{
keyMap.put(key, id);
}
inverseKeyMap.put(id, keys);
valueMap.put(id, value);
return value;
}
#Override
public void putAll(Map<? extends K, ? extends V> map)
{
for(java.util.Map.Entry<? extends K, ? extends V> entry : map.entrySet())
{
put(entry.getKey(), entry.getValue());
}
}
#Override
public V remove(Object key)
{
String id = keyMap.get(key);
keyMap.remove(key);
Set<K> keys = inverseKeyMap.get(id);
keys.remove(key);
V value = valueMap.get(id);
if(keys.size() == 0) // it was the last key, now remove the value
{
valueMap.remove(id);
}
return value;
}
#Override
public int size()
{
return valueMap.size();
}
#Override
public Collection<V> values()
{
return valueMap.values();
}
public static void main(String[] args)
{
MultiMap<String, String> m = new MultiMap<>();
m.put("a", "v1");
Set<String> s = new HashSet<>();
s.add("b");
s.add("c");
s.add("d");
m.put(s, "v2");
System.out.println("size:" + m.size());
System.out.println("keys:" + m.keySet());
System.out.println("values:" + m.values().toString());
System.out.println("a:" + m.get("a"));
System.out.println("b:" + m.get("b"));
System.out.println("c:" + m.get("c"));
System.out.println("d:" + m.get("d"));
m.remove("a");
System.out.println("size:" + m.size());
System.out.println("keys:" + m.keySet());
System.out.println("values:" + m.values().toString());
System.out.println("a:" + m.get("a"));
System.out.println("b:" + m.get("b"));
System.out.println("c:" + m.get("c"));
System.out.println("d:" + m.get("d"));
s.add("a");
m.put(s, "v3");
System.out.println("size:" + m.size());
System.out.println("keys:" + m.keySet());
System.out.println("values:" + m.values().toString());
System.out.println("a:" + m.get("a"));
System.out.println("b:" + m.get("b"));
System.out.println("c:" + m.get("c"));
System.out.println("d:" + m.get("d"));
}
}
A little late, but you probably mean to get every result from the map, that matches the first element only, even though it contains multiple results, ignoring the second key (wildcard effect). Apache's MultiKeyMap is not suitable for this.
You could solve this by creating your own filter functionality using the MultiKey of MultiKeyMap. First, filter out only the relevant MultiKeys (which you get from yourMultiKeyMap.keySet() ) . The following method takes those multiKeys, and the first keys you want to filter on:
private Set<MultiKey<? extends String>> filterMultiKeys(Set<MultiKey<? extends String>> multiKeys, final String... keys) {
final List<String> givenKeys = Arrays.asList(keys);
return multiKeys.stream().filter(multiKey -> {
final Object[] actualKeys = multiKey.getKeys();
if (actualKeys.length < givenKeys.size()) {
// Lesser keys, so never a match
return false;
}
final List<Object> trimmedKeys = Arrays.asList(actualKeys).subList(0, givenKeys.size());
return trimmedKeys.equals(givenKeys);
}).collect(Collectors.toSet());
}
Then, use the resulting MultiKeys to get the results:
final Set<String> results = filteredKeys.stream().map(multiKey -> yourMultiKeyMap.get(multiKey)).collect(Collectors.toSet());
For bonus points, one could extend or decorate MultiKeyMap and create MyMultiKeyMap , having a method like match(keys...) using the filter functionality.