Suppressing Warnings when using a dynamic class reference - java

Background
An existing system creates a plethora of HashMap instances via its Generics class:
import java.util.Map;
import java.util.HashMap;
public class Generics {
public static <K,V> Map<K, V> newMap() {
return new HashMap<K,V>();
}
public static void main( String args[] ) {
Map<String, String> map = newMap();
}
}
This is the single point of creation for all instances of classes that implement the Map interface. We would like the ability to change the map implementation without recompiling the application. This would allow us to use Trove's THashMap, for example, to optimize the application.
Problem
The software cannot be bundled with Trove's THashMap due to licensing conditions. As such, it would be great if there was a way to specify the name of the map to instantiate at runtime (for those people who have no such licensing restrictions). For example:
import java.util.Map;
import java.util.HashMap;
import gnu.trove.map.hash.THashMap;
public class Generics {
private String mapClassName = "java.util.HashMap";
#SuppressWarnings("unchecked")
public <K,V> Map<K,V> newMap() {
Map<K,V> map;
try {
Class<? extends Map<K,V>> c = (Class<Map<K,V>>)Class.forName(
getMapClassName() ).asSubclass( Map.class );
map = c.newInstance();
}
catch( Exception e ) {
map = new HashMap<K,V>();
}
return map;
}
protected String getMapClassName() {
return this.mapClassName;
}
protected void setMapClassName( String s ) {
this.mapClassName = s;
}
public static void main( String args[] ) {
Generics g = new Generics();
Map<String, String> map = g.newMap();
System.out.printf( "Class = %s\n", map.getClass().toString() );
g.setMapClassName( "gnu.trove.map.hash.THashMap" );
map = g.newMap();
System.out.printf( "Class = %s\n", map.getClass().toString() );
}
}
Question
Is there a way to avoid the #SupressWarnings annotation when compiling with -Xlint and still avoid the warnings?

Is there a way to avoid the #SuppressWarnings annotation when compiling with -Xlint and still avoid the warnings?
No. Class.forName returns a Class<?>. The only way to assign it to Class<? extends Map<K, V>> is to do an unchecked cast. Sometimes unchecked casts are necessary, and so using #SuppressWarnings("unchecked") is acceptable here (provided you document the reason well).
IMHO it would be more correct to keep a reference to Class<? extends Map<?, ?>> and then do an unchecked cast of the result of newInstance to Map<K,V>. I only say this because a Class object is a canonical representation of a raw type, so a type like Class<? extends Map<K, V>> is slightly misleading.
This is outside the scope of the question, but here's a suggested alternative for your solution:
public interface MapFactory {
<K, V> Map<K, V> newMap() throws Exception;
}
public enum HashMapFactory implements MapFactory {
INSTANCE;
#Override
public <K, V> Map<K, V> newMap() {
return new HashMap<K, V>();
}
}
public static final class DynamicMapFactory implements MapFactory {
private final Constructor<? extends Map<?, ?>> constructor;
private DynamicMapFactory(Constructor<? extends Map<?, ?>> constructor) {
this.constructor = constructor;
}
#Override
//Impl note: these checked exceptions could also be wrapped in RuntimeException
public <K, V> Map<K, V> newMap() throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
#SuppressWarnings("unchecked") //this is okay because the default ctor will return an empty map
final Map<K, V> withNarrowedTypes = (Map<K, V>)constructor.newInstance();
return withNarrowedTypes;
}
public static DynamicMapFactory make(String className) throws ClassNotFoundException, NoSuchMethodException, SecurityException {
#SuppressWarnings("unchecked") //Class<? extends Map> can safely be cast to Class<? extends Map<?, ?>>
final Class<? extends Map<?, ?>> type =
(Class<? extends Map<?, ?>>)Class.forName(className).asSubclass(Map.class);
final Constructor<? extends Map<?, ?>> constructor = type.getDeclaredConstructor();
return new DynamicMapFactory(constructor);
}
}
public static void main(String[] args) throws Exception {
Map<String, Integer> map1 = HashMapFactory.INSTANCE.newMap();
Map<String, Integer> map2 = DynamicMapFactory.make("java.util.TreeMap").newMap();
System.out.println(map1.getClass()); //class java.util.HashMap
System.out.println(map2.getClass()); //class java.util.TreeMap
}

Related

Gson polymorphic deserialization based on key

I can find plenty of examples of polymorphic deserialization based on a field within an object:
[
{
"type": "Engine",
"name": "Ford 6.7L",
"cylinders": 8
},
{
"type": "Tires",
"name": "Blizzak LM32",
"season": "winter"
}
]
But I can't seem to easily put together something that'll use object keys to determine type:
{
"Engine": {
"name": "Ford 6.7L",
"cylinders": 8
},
"Tires": {
"name": "Blizzak LM32",
"season": "winter"
}
}
without first parsing the file into a JsonObject and then iterating through that object and re-converting each value back to a string and re-parsing into a class based on the key (and rolling my own method of tracking types per key).
Ideally, I'd like to do something along these lines:
#JsonKey("Engine")
class Engine implements Equipment {
String name;
Integer cylinders;
}
#JsonKey("Tires")
class Tires implements Equipment {
String name;
String season;
}
And be able to parse the file like this:
Map<String, Equipment> car = gson.fromJson(fileContents, new TypeToken<Map<String, Equipment>>(){}.getType();
This seems like a pretty obvious use case to me. What am I missing?
There is nothing good in using object names as key to deserialize polymorphic type. This is leading to having multiple object's with same name being part of parent object (your case). When you would try to deserialize parent JSON object (in future there might be parent object containing attribute's Engine and Tires) you could end up with multiple JSON object's representing this attribute with same name (repeating type name) leading to parser exception.
Deserialization based on type attribute inside JSON object is common and convenient way. You could implement code to work as you expect but it would be not error prone in all cases and therefore JSON parser implementation's expect, in this case, to deserialize polymorphic type by nested type attribute which is error prone and clean way to do so.
Edit:
What you are trying to achieve is also against separation of concern (JSON object key is key itself and also type key at same time) while type attribute separate's type responsibility to one of JSON object's attribute's. That is also following KISS principle (keep it stupid simple) and also many of developer's are used to type attribute's in case of polymorphic deserialization.
All you have to do is to implement a custom Map<String, ...> deserializer that will be triggered for maps defined using a special deserializer that's aware of the mapping rules.
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#interface JsonKey {
#Nonnull
String value();
}
final class JsonKeyMapTypeAdapterFactory<V>
implements TypeAdapterFactory {
private final Class<V> superClass;
private final Map<String, Class<? extends V>> subClasses;
private final Supplier<? extends Map<String, V>> createMap;
private JsonKeyMapTypeAdapterFactory(final Class<V> superClass, final Map<String, Class<? extends V>> subClasses,
final Supplier<? extends Map<String, V>> createMap) {
this.superClass = superClass;
this.subClasses = subClasses;
this.createMap = createMap;
}
static <V> Builder<V> build(final Class<V> superClass) {
return new Builder<>(superClass);
}
#Override
#Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !Map.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
final Type type = typeToken.getType();
if ( !(type instanceof ParameterizedType) ) {
return null;
}
final ParameterizedType parameterizedType = (ParameterizedType) type;
final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
final Type valueType = actualTypeArguments[1];
if ( !(valueType instanceof Class) ) {
return null;
}
final Class<?> valueClass = (Class<?>) valueType;
if ( !superClass.isAssignableFrom(valueClass) ) {
return null;
}
final Type keyType = actualTypeArguments[0];
if ( !(keyType instanceof Class) || keyType != String.class ) {
throw new IllegalArgumentException(typeToken + " must represent a string-keyed map");
}
final Function<? super String, ? extends TypeAdapter<? extends V>> resolveTypeAdapter = subClasses.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> gson.getDelegateAdapter(this, TypeToken.get(e.getValue()))))
::get;
#SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) JsonKeyMapTypeAdapter.create(resolveTypeAdapter, createMap);
return castTypeAdapter;
}
static final class Builder<V> {
private final Class<V> superClass;
private final ImmutableMap.Builder<String, Class<? extends V>> subClasses = new ImmutableMap.Builder<>();
private Supplier<? extends Map<String, V>> createMap = LinkedHashMap::new;
private Builder(final Class<V> superClass) {
this.superClass = superClass;
}
Builder<V> register(final Class<? extends V> subClass) {
#Nullable
final JsonKey jsonKey = subClass.getAnnotation(JsonKey.class);
if ( jsonKey == null ) {
throw new IllegalArgumentException(subClass + " must be annotated with " + JsonKey.class);
}
return register(jsonKey.value(), subClass);
}
Builder<V> register(final String key, final Class<? extends V> subClass) {
if ( !superClass.isAssignableFrom(subClass) ) {
throw new IllegalArgumentException(subClass + " must be a subclass of " + superClass);
}
subClasses.put(key, subClass);
return this;
}
Builder<V> createMapWith(final Supplier<? extends Map<String, V>> createMap) {
this.createMap = createMap;
return this;
}
TypeAdapterFactory create() {
return new JsonKeyMapTypeAdapterFactory<>(superClass, subClasses.build(), createMap);
}
}
private static final class JsonKeyMapTypeAdapter<V>
extends TypeAdapter<Map<String, V>> {
private final Function<? super String, ? extends TypeAdapter<? extends V>> resolveTypeAdapter;
private final Supplier<? extends Map<String, V>> createMap;
private JsonKeyMapTypeAdapter(final Function<? super String, ? extends TypeAdapter<? extends V>> resolveTypeAdapter,
final Supplier<? extends Map<String, V>> createMap) {
this.resolveTypeAdapter = resolveTypeAdapter;
this.createMap = createMap;
}
private static <V> TypeAdapter<Map<String, V>> create(final Function<? super String, ? extends TypeAdapter<? extends V>> resolveTypeAdapter,
final Supplier<? extends Map<String, V>> createMap) {
return new JsonKeyMapTypeAdapter<>(resolveTypeAdapter, createMap)
.nullSafe();
}
#Override
public void write(final JsonWriter out, final Map<String, V> value) {
throw new UnsupportedOperationException();
}
#Override
public Map<String, V> read(final JsonReader in)
throws IOException {
in.beginObject();
final Map<String, V> map = createMap.get();
while ( in.hasNext() ) {
final String key = in.nextName();
#Nullable
final TypeAdapter<? extends V> typeAdapter = resolveTypeAdapter.apply(key);
if ( typeAdapter == null ) {
throw new JsonParseException("Unknown key " + key + " at " + in.getPath());
}
final V value = typeAdapter.read(in);
#Nullable
final V replaced = map.put(key, value);
if ( replaced != null ) {
throw new JsonParseException(value + " duplicates " + replaced + " using " + key);
}
}
in.endObject();
return map;
}
}
}
private static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.registerTypeAdapterFactory(JsonKeyMapTypeAdapterFactory.build(Equipment.class)
.register(Engine.class)
.register(Tires.class)
.create()
)
.create();
The Gson object above will deserialize your JSON document to a map that is toString-ed like this (assuming Lombok is used for toString):
{Engine=Engine(name=Ford 6.7L, cylinders=8), Tires=Tires(name=Blizzak LM32, season=winter)}

How can I define a generic method for Map<String, List<Object>> and MultivaluedMap<String, Object>?

I'm writing a http client library work for various platforms.(Java SE, Java EE)
With my Java SE implementation, I do
public void doSome(..., Map<String, List<Object>> params, ...);
With my Java EE implementation, I do
public void doSome(..., MultivaluedMap<String, Object> params, ...);
If I want to defined a generic abstract class for those methods, how can I define a method for both Map<String, List<Object>> and MultivaluedMap<String, Object>? Is there any elegant way to do this without a simple T?
Note that Multivalued<K, V> extends Map<K, List<V>>.
I, currently, am only capable of doing like this.
public abstract class AbstractClient {
public void doSome(..., Map<String, List<Object>> params, ...);
}
public class JavaEEClient extends AbstractClient {
static <K, V> MultivaluedMap<K, V> multivalued(Map<K, List<V>> map) {
if (map == null) {
return null;
}
if (map instanceof MultivaluedMap) {
return (MultivaluedMap<K, V>) map;
}
final MultivaluedMap<K, V> multi = new MultivaluedHashMap<>(map.size());
multi.putAll(map);
return multi;
}
public void doSome(..., MultivaluedMap<String, Object> params, ...) {
}
#Override
public void doSome(..., Map<String, List<Object>> params, ...) {
doSome(..., multivalued(params), ...);
}
}
UPDATE
I really have to admit that I'm miss-led myself. I actually started with JAX-RS only methods and started adding methods for Java SE with URLConnection.
It was O.K. with direct MultivaluedMap because WebTargets and Invocations accepts MultivaluedMap.
When I started adding classes and methods for URLConnections, the problem arose.
I think I have to stick to Map<String, List<Object>> and use my mutivalued utility method.
If you say that MultivaluedMap<K, V> extends Map<K, List<V>> then you can just do:
public abstract class AbstractClass {
public void doSome(..., Map<String, List<Object>> params, ...)
...
}
It will accept both MultivaluedMap and Map.

How to make diamond operator Type arguments dynamic in java?

I have a following interface
public interface Splitter<T, V> {
V[] split(T arg);
}
Following is the factory method implementation which I am using to get Splitter Implementation.
Factory Method Implementation
public static <T, V> Splitter<T, V> getSplitter(Class<T> key1, Class<V> key2) {
if (key1 == Company.class && key2 == Department.class)
return (Splitter<T, V>) new CompanySplitterImpl();
// more cases
}
Following is my call at client side which compiles fine
Splitter<Company, Department> split = getSplitter(Company.class, Department.class);
I want to avoid tight coupling of client side code with the implementation. Is there a way to avoid hardcoded Type params i.e. avoiding use Company and Department (Splitter<Company, Department>) at callee side and use some variable instead? Is there a way out through which they can be loaded from some external property file?
FYI : I am not sure about its feasibility in Java?
One thing you could do is have you factory not know anything about concrete implementations and instead have themselves register with it (or contain a predefined list) and ask each implementation if it can handle the types or not. For example, given a predefined list like your example above:
public class SplitterFactory {
private Set<Splitter> splitters = new HashSet<>() {{
add(new CompanySplitterImpl());
}};
public static <T, V> Splitter<T, V> getSplitter(Class<T> key1, Class<V> key2)
{
for (Splitter splitter : splitters) {
if (splitter.canAccept(key1, key2)) {
return splitter;
}
// no matched splitter
}
}
Obviously this is a very naive solution and you could implement the lookup more efficiently. If you don't know your types at compile time, you could also have a registration mechanism with the factory to include new ones at runtime. Because the Splitter itself is now responsible for reporting what types it can handle it's fully extensible.
You could make a simple map class that you can list them up to:
public final class SplitterMap {
private final List<SplitterType<?, ?>> list = new ArrayList<>();
private class SplitterType<T, V> {
private final Class<T> key1;
private final Class<V> key2;
private final Class<? extends Splitter<T, V>> clazz;
private SplitterType(Class<?> key1, Class<?> key2, Class<? extends Splitter<T, V> clazz) {
this.key1 = key1;
this.key2 = key2;
this.clazz = clazz;
}
private boolean matches(Class<?> key1, Class<?> key2) {
return this.key1 == key1 && this.key2 == key2;
}
}
public <T, V> void put(Class<T> key1, Class<V> key2, Class<? extends Splitter<T, V> clazz) {
list.add(new SplitterType<T, V>(key1, key2, clazz));
}
public <T, V> Splitter<T, V> get(Class<T> key1, Class<V> key2) {
for (SplitterType<?, ?> type : list) {
if (type.matches(key1, key2)) {
try {
return ((SplitterType<T, V>) type).clazz.newInstance();
} catch (Exception e) {
}
}
}
return null; // not found
}
}
Then you could just do:
SplitterMap map = new SplitterMap();
map.put(Company.class, Department.class, CompanySplitterImpl.class);
Splitter<Company, Department> splitter = map.get(Company.class, Department.class);
Not a good way but one way would be:
String companyClass = "Company";
String departmentClass = "Department";
Splitter split = getSplitter(Class.forName(companyClass), Class.forName(departmentClass));//raw splitter
System.out.println(split.split(new Company()));//you could use reflection here to create instance from companyClass String.
Firstly I assume you want something like
Splitter<Company, Department> s = Splitters.getSplitter()
Which is not posible without reflection, because of
type erasure
return type overloading not available in java yet
Secondly you're abusing FactoryMethod pattern. Which should look more like this:
interface Splitter<T, V> {
V[] split(T arg);
}
interface SplitterFactory {
<T, V> Splitter<T, V> getSplitter();
}
class CompanySplitterFactory implements SplitterFactory {
#Override
public Splitter<Company, Department> getSplitter() {
return new CompanySplitterImpl();
}
}

Adding Generic member variable in Generic Class

Folks,
Is there any easy way to add generic class in non generic class.
Basically the cache manager will have map of Cache class which is implemented with proper generics.
But in below class we return (getCache method) Cache via get method it requires explicit cast at callers place how to avoid it.
e.g.
public class CacheManager {
private Map<String, Cache<?,?>> cacheMap = new HashMap<String, Cache<?,?>>();
public Cache<?,?> getCache(String cacheName) {
return cacheMap.get(cacheName);
}
public void addCache(String cacheName,Cache<?,?> cache) {
cacheMap.put(cacheName, cache);
}
}
Short answer: No (as far as I know).
The problem here is that what you are doing is not type-safe in Java at all. Have a look at this example:
import java.util.*;
class ClassCast {
public static void main(String[] args) {
HashMap<String, Pair<?, ?>> map = new HashMap<>();
map.put("test", new Pair<String, Integer>("Hello", 5));
Pair<Double, Double> pair = (Pair<Double, Double>) map.get("test");
}
}
class Pair<T,V> {
T a;
V b;
Pair(T a, V b) {
this.a = a;
this.b = b;
}
}
You would expect a ClassCastException here, but it compiles and runs perfectly fine. The reason for this is that the actual class of Pair<String, Integer> and Pair<Double, Double> is in fact just Pair (after type erasure).
To get type safety you have to implement the "Typesafe heterogeneous container pattern" (explained in detail in Effective Java by Josh Bloch). In short, you have to involve the type parameter in the key of your map. Depending on your needs, you might be able to use a class as key directly, otherwise you might have to make a key object.
Example implementation:
public class CacheManager {
private Map<MultiKey, Cache<?,?>> cacheMap = new HashMap<>();
#SuppressWarnings("unchecked")
public <T,V> Cache<T,V> get(String name, Class<T> t, Class<V> v) {
// Type-safe since types are encoded in key(i.e. map will not
// return something with the wrong type), and key is type-checked
// on insertion.
return (Cache<T,V>) cacheMap.get(new MultiKey(name, t, v));
}
public <T,V> void put(String name, Class<T> t, Class<V> v, Cache<T,V> cache) {
cacheMap.put(new MultiKey(name, t, v), cache);
}
class MultiKey {
Object[] keys;
Integer hash = null;
MultiKey(Object... keys) {
this.keys = keys;
}
#Override
public int hashCode() {
if (hash == null) hash = Arrays.hashCode(keys);
return hash;
}
#Override
public boolean equals(Object o) {
if (o == null || !(o instanceof MultiKey)) return false;
return Arrays.equals(keys, ((MultiKey) o).keys);
}
}
}
Example usage:
CacheManager mng = new CacheManager();
mng.addCache("SI", String.class, Integer.class, new Cache<String, Integer>());
Cache<String, Integer> cache = mng.getCache("SI", String.class, Integer.class);
System.out.println(cache);
It's not pretty, but it is actually type-safe. It can be improved depending on the actual situation though, so you should not use this code as is. For example, if you can get the types from the Cache object you don't need the Class arguments in addCache.

builder for HashMap

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}]}

Categories

Resources