It may be a bad practice, but I haven't been able to figure out any better solution for my problem. So I have this map
// Map<state, Map<transition, Map<property, value>>>
private Map<String, Map<String, Map<String, String>>> properties;
and I want to initialize it so I don't get NullPointerException with this
properties.get("a").get("b").get("c");
I tried this one but I didn't work (obviously)
properties = new HashMap<String, Map<String, Map<String,String>>>();
Other things I tried didn't compile.
Also if you have any ideas how to avoid this nested maps, I would appreciate it.
It seems to me that you need to create your own Key class:
public class Key {
private final String a;
private final String b;
private final String c;
public Key(String a, String b, String c) {
// initialize all fields here
}
// you need to implement equals and hashcode. Eclipse and IntelliJ can do that for you
}
If you implement your own key class, your map will look like this:
Map<Key, String> map = new HashMap<Key, String>();
And when looking for something in the map you can use:
map.get(new Key("a", "b", "c"));
The method above will not throw a NullPointerException.
Please remember that for this solution to work, you need to override equals and hashcode in the Key class. There is help here. If you don't override equals and hashcode, then a new key with the same elements won't match an existing key in the map.
There are other possible solutions but implementing your own key is a pretty clean one in my opinion. If you don't want to use the constructor you can initialize your key with a static method and use something like:
Key.build(a, b, c)
It is up to you.
You need to put maps in your maps in your map. Literally:
properties = new HashMap<String, Map<String, Map<String,String>>>();
properties.put("a", new HashMap<String, Map<String,String>>());
properites.get("a").put("b", new HashMap<String,String>());
If your target is lazy initialization without NPE you have to create your own map:
private static abstract class MyMap<K, V> extends HashMap<K, V> {
#Override
public V get(Object key) {
V val = super.get(key);
if (val == null && key instanceof K) {
put((K)key, val = create());
}
return val;
}
protected abstract V create();
}
public void initialize() {
properties = new MyMap<String, Map<String, Map<String, String>>>() {
#Override
protected Map<String, Map<String, String>> create() {
return new MyMap<String, Map<String, String>>() {
#Override
protected Map<String, String> create() {
return new HashMap<String, String>();
}
};
}
};
}
You could use a utility method:
public static <T> T get(Map<?, ?> properties, Object... keys) {
Map<?, ?> nestedMap = properties;
for (int i = 0; i < keys.length; i++) {
if (i == keys.length - 1) {
#SuppressWarnings("unchecked")
T value = (T) nestedMap.get(keys[i]);
return value;
} else {
nestedMap = (Map<?, ?>) nestedMap.get(keys[i]);
if(nestedMap == null) {
return null;
}
}
}
return null;
}
This can be invoked like this:
String result = get(properties, "a", "b", "c");
Note that care is required when using this as it is not type-safe.
The only way to do it with this structure is to pre-initialise the 1st and 2nd level maps with ALL possible keys. If this is not possible to do you can't achieve what you are asking with plain Maps.
As an alternative you can build a custom data structure that is more forgiving. For example a common trick is for a failed key lookup to return an "empty" structure rather than null, allowing nested access.
You can't initialize this in one go, since you normally don't know what keys you'll have in advance.
Thus you'd have to check whether the submap for a key is null and if so you might add an empty map for that. Preferably you'd only do that when adding entries to the map and upon retrieving entries you return null if one of the submaps in the path doesn't exist. You could wrap that in your own map implementation for ease of use.
As an alternative, apache commons collections' MultiKeyMap might provide what you want.
It's impossible to use properties.get("a").get("b").get("c"); and be sure to avoid null unless you make your own Map. In fact, you can't predict that your map will contains "b" key.
So try to make your own class to handle nested get.
I think a better solution is using an object as the only key to the map of values. The key will be composed of three fields, state, transition and property.
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public class Key {
private String state;
private String transition;
private String property;
public Key(String state, String transition, String property) {
this.state = state;
this.transition = transition;
this.property = property;
}
#Override
public boolean equals(Object other) {
return EqualsBuilder.reflectionEquals(this, other);
}
#Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}
When you check for a value, the map will return null for a key that is not associated with a value
Map<Key, String> values = new HashMap<Key, String>();
assert values.get(new Key("a", "b", "c")) == null;
values.put(new Key("a", "b", "c"), "value");
assert values.get(new Key("a", "b", "c")) != null;
assert values.get(new Key("a", "b", "c")).equals("value");
To efficiently and correctly use an object as a key in a Map you should override the methods equals() and hashCode(). I have built thos methods using the reflective functionalities of the Commons Lang library.
I think, following is the easier way:
public static final Map<Integer, Map<Integer, Map<Integer, Double>>> A_Map = new HashMap<Integer, Map<Integer, Map<Integer, Double>>>()
{
{
put(0, new HashMap<Integer, Map<Integer, Double>>()
{
{
put(0, new HashMap<Integer, Double>()
{
{
put(0, 1 / 60.0);
put(1, 1 / 3600.0);
}
});
put(1, new HashMap<Integer, Double>()
{
{
put(0, 1 / 160.0);
put(1, 1 / 13600.0);
}
});
}
});
put(1, new HashMap<Integer, Map<Integer, Double>>()
{
{
put(0, new HashMap<Integer, Double>()
{
{
put(0, 1 / 260.0);
put(1, 1 / 3600.0);
}
});
put(1, new HashMap<Integer, Double>()
{
{
put(0, 1 / 560.0);
put(1, 1 / 1300.0);
}
});
}
});
}
};
Using computeIfAbsent/putIfAbsent makes it simple:
private <T> void addValueToMap(String keyA, String keyB, String keyC, String value) {
map.computeIfAbsent(keyA, k -> new HashMap<>())
.computeIfAbsent(keyB, k -> new HashMap<>())
.putIfAbsent(keyC, value);
}
Related
I save my data in hashmap. I have two hashmap data, one from database and one from activity result. I want to do some equation with that data, but only if key in activity math with database key. I try to substract activity data with the database data, but the result is always 0.
Data from database and result activity is passed to this hashmap class:
public class PositionData implements Serializable {
private String name;
public HashMap<String, Integer> values;
public HashMap<String,String> routers;
public PositionData(String name) {
// TODO Auto-generated constructor stub
this.name=name;
values = new HashMap<String, Integer>();
routers = new HashMap<String, String>();
}
public void addValue(Router router,int strength){
values.put(router.getBSSID(), strength);
routers.put(router.getBSSID(),router.getSSID());
}
public String getName() {
return name;
}
public String toString() {
String result="";
result+=name+"\n";
for(Map.Entry<String, Integer> e: this.values.entrySet())
result+=routers.get(e.getKey())+" : "+e.getValue().toString()+"\n";
return result;
}
public HashMap<String, Integer> getValues() {
return values;
}
public HashMap<String, String> getRouters() {
return routers;
}
And this is how i do substraction in activity class:
PositionData positionData = (PositionData) intent
.getSerializableExtra("PositionData");
positionsData=db.getReadings(building);
HashMap<String, Integer> rssi = positionData.getValues();
HashMap<String, Integer> rssi1 = positionsData.get(0).getValues();
HashMap<String, String> dest = positionsData.get(0).getRouters();
int dista = 0;
if (positionData.equals(dest)){
dista = Integer.parseInt(String.valueOf(rssi))-Integer.parseInt(String.valueOf(rssi1));
}
Log.v("dis:", String.valueOf(dista));
I have data from class database and class result, both data is passed to HashMap activity to get positionData form. After i get the form, i calculate it in equation class. So, here i have 4 class.
I create generic example for you. I think that can help you understand how you can do equation on your hashmaps.
HashMap<String, Integer> activity= new HashMap<String, Integer>();
HashMap<String, Integer> database = new HashMap<String, Integer>();
Iterator iter = activity.keySet().iterator(); // getting iterator from keySet from one of hashmaps
// Iterate through one of hashmaps keys
while(iter.hasNext()){
String s = (String) iter.next();
if(database.containsKey(s)){ // finding key in second hashmap
// in here you can do your equation;
}
}
It appears that you are trying to compare the contents of a HashMap to object PositionData. You may have to override PositionData's .equals() method so it knows how to compare its contents to a HashMap. Maybe something like the following:
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof Map) {
Map m = (HashMap) o;
/* loop through both data structures and compare, return true if same */
}
/* compare two instances of PositionData */
return false;
Another option is to write a method to iterate through both and compare their contents, without the need of using/overriding .equals().
What is the best way to avoid multiple parallel if-else loop. I tried with switch statement as well, but again that doesn't look readable. I have hundreds of such statements:
public static Map getKqvSecureNodeResponse(Sample secureNodeData, Map<String, Object> map) {
if(map.containsKey(Constants.NAME_KQV)) {
map.put(Constants.NAME_KQV, secureNodeData.getNodename());
}
if(map.containsKey(Constants.SPOV)) {
map.put(Constants.SPOV, secureNodeData.getOverride());
}
if(map.containsKey(Constants.SPEP)) {
map.put(Constants.SPEP, secureNodeData.getEnabledProtocol());
}
if(map.containsKey(Constants.SPTO)) {
map.put(Constants.SPTO, secureNodeData.getAuthTimeout());
}
if(map.containsKey(Constants.TLCN)) {
map.put(Constants.TLCN, secureNodeData.getCommonName());
}
if(map.containsKey(Constants.SEDT)) {
map.put(Constants.SEDT, secureNodeData.getEncryptData());
}
if(map.containsKey(Constants.TLCF)) {
map.put(Constants.TLCF, secureNodeData.getKeyCertLabel());
}
if(map.containsKey(Constants.TLCL)) {
map.put(Constants.TLCL, secureNodeData.getCipherSuites());
}
return map;
}
Please note that I have to invoke different getter of secureNodeData for every check.
For each Constants value (e.g. Constants.NAME_KQV), you can provide a Function<Sample, Object> (e.g. sample -> sample.getNodename()).
If you organised it in a structure like Map or enum (here, I used a enum), you could end up with a simple loop:
public static Map<String, Object> getKqvSecureNodeResponse(Sample secureNodeData, Map<String, Object> map) {
for (Constant constant : Constant.values()) {
final String name = constant.getName();
if (map.containsKey(name)) {
map.put(name, constant.getFunction().apply(secureNodeData));
}
}
return map;
}
The enum was defined as:
enum Constant {
NAME_KQV(Constants.NAME_KQV, Sample::getNodename);
// other definitions
final String name;
final Function<Sample, Object> function;
Constant(String name, Function<Sample, Object> function) {
this.name = name;
this.function = function;
}
public String getName() {
return name;
}
public Function<Sample, Object> getFunction() {
return function;
}
}
It seems like this method does a lot. (1) It's unclear why it overrides existing values. (2) The method name is obscure. (3) You are using a raw Map, replace it with Map<String, Object> at least, and figure out how to substitute the Object part. (4)
I feel rethinking the design would help much more than the above approach and these small corrections.
You can try to take advantage of method references:
public static Map getKqvSecureNodeResponse(Sample node, Map<String, Object> map) {
applyParam(Constants.NAME_KQV, map, node::getNodename);
applyParam(Constants.SPOV, map, node::getOverride);
// ...
}
public static void applyParam(String key, Map<String, Object> data, Supplier<Object> getter) {
if (data.containsKey(key)) {
data.put(key, getter.get());
}
}
Alternatively you can use Function references that are instance independent:
private static final Map<String, Function<Sample, Object>> MAPPING;
static {
MAPPING = new LinkedHashMap<>();
MAPPING.put(Constants.NAME_KQV, Sample::getNodename);
MAPPING.put(Constants.SPOV, Sample::getOverride);
}
public static Map getKqvSecureNodeResponse(Sample node, Map<String, Object> map) {
for (String key : MAPPING.keySet()) {
if (map.containsKey(key)) {
map.put(key, MAPPING.get(key).apply(node));
}
}
}
There are many ways how you can approach your specific use case, but method references in general makes developer's life much much easier.
Is there a version of BeanUtils.describe(customer) that recursively calls the describe() method on the complex attributes of 'customer'.
class Customer {
String id;
Address address;
}
Here, I would like the describe method to retrieve the contents of the address attribute as well.
Currently, all I have can see the name of the class as follows:
{id=123, address=com.test.entities.Address#2a340e}
Funny, I would like the describe method to retrieve the contents of nested attributes as well, I don't understand why it doesn't. I went ahead and rolled my own, though. Here it is, you can just call:
Map<String,String> beanMap = BeanUtils.recursiveDescribe(customer);
A couple of caveats.
I'm wasn't sure how commons BeanUtils formatted attributes in collections, so i went with "attribute[index]".
I'm wasn't sure how it formatted attributes in maps, so i went with "attribute[key]".
For name collisions the precedence is this: First properties are loaded from the fields of super classes, then the class, then from the getter methods.
I haven't analyzed the performance of this method. If you have objects with large collections of objects that also contain collections, you might have some issues.
This is alpha code, not garunteed to be bug free.
I am assuming that you have the latest version of commons beanutils
Also, fyi, this is roughly taken from a project I've been working on called, affectionately, java in jails so you could just download it and then run:
Map<String, String[]> beanMap = new SimpleMapper().toMap(customer);
Though, you'll notice that it returns a String[], instead of a String, which may not work for your needs. Anyway, the below code should work, so have at it!
public class BeanUtils {
public static Map<String, String> recursiveDescribe(Object object) {
Set cache = new HashSet();
return recursiveDescribe(object, null, cache);
}
private static Map<String, String> recursiveDescribe(Object object, String prefix, Set cache) {
if (object == null || cache.contains(object)) return Collections.EMPTY_MAP;
cache.add(object);
prefix = (prefix != null) ? prefix + "." : "";
Map<String, String> beanMap = new TreeMap<String, String>();
Map<String, Object> properties = getProperties(object);
for (String property : properties.keySet()) {
Object value = properties.get(property);
try {
if (value == null) {
//ignore nulls
} else if (Collection.class.isAssignableFrom(value.getClass())) {
beanMap.putAll(convertAll((Collection) value, prefix + property, cache));
} else if (value.getClass().isArray()) {
beanMap.putAll(convertAll(Arrays.asList((Object[]) value), prefix + property, cache));
} else if (Map.class.isAssignableFrom(value.getClass())) {
beanMap.putAll(convertMap((Map) value, prefix + property, cache));
} else {
beanMap.putAll(convertObject(value, prefix + property, cache));
}
} catch (Exception e) {
e.printStackTrace();
}
}
return beanMap;
}
private static Map<String, Object> getProperties(Object object) {
Map<String, Object> propertyMap = getFields(object);
//getters take precedence in case of any name collisions
propertyMap.putAll(getGetterMethods(object));
return propertyMap;
}
private static Map<String, Object> getGetterMethods(Object object) {
Map<String, Object> result = new HashMap<String, Object>();
BeanInfo info;
try {
info = Introspector.getBeanInfo(object.getClass());
for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
Method reader = pd.getReadMethod();
if (reader != null) {
String name = pd.getName();
if (!"class".equals(name)) {
try {
Object value = reader.invoke(object);
result.put(name, value);
} catch (Exception e) {
//you can choose to do something here
}
}
}
}
} catch (IntrospectionException e) {
//you can choose to do something here
} finally {
return result;
}
}
private static Map<String, Object> getFields(Object object) {
return getFields(object, object.getClass());
}
private static Map<String, Object> getFields(Object object, Class<?> classType) {
Map<String, Object> result = new HashMap<String, Object>();
Class superClass = classType.getSuperclass();
if (superClass != null) result.putAll(getFields(object, superClass));
//get public fields only
Field[] fields = classType.getFields();
for (Field field : fields) {
try {
result.put(field.getName(), field.get(object));
} catch (IllegalAccessException e) {
//you can choose to do something here
}
}
return result;
}
private static Map<String, String> convertAll(Collection<Object> values, String key, Set cache) {
Map<String, String> valuesMap = new HashMap<String, String>();
Object[] valArray = values.toArray();
for (int i = 0; i < valArray.length; i++) {
Object value = valArray[i];
if (value != null) valuesMap.putAll(convertObject(value, key + "[" + i + "]", cache));
}
return valuesMap;
}
private static Map<String, String> convertMap(Map<Object, Object> values, String key, Set cache) {
Map<String, String> valuesMap = new HashMap<String, String>();
for (Object thisKey : values.keySet()) {
Object value = values.get(thisKey);
if (value != null) valuesMap.putAll(convertObject(value, key + "[" + thisKey + "]", cache));
}
return valuesMap;
}
private static ConvertUtilsBean converter = BeanUtilsBean.getInstance().getConvertUtils();
private static Map<String, String> convertObject(Object value, String key, Set cache) {
//if this type has a registered converted, then get the string and return
if (converter.lookup(value.getClass()) != null) {
String stringValue = converter.convert(value);
Map<String, String> valueMap = new HashMap<String, String>();
valueMap.put(key, stringValue);
return valueMap;
} else {
//otherwise, treat it as a nested bean that needs to be described itself
return recursiveDescribe(value, key, cache);
}
}
}
The challenge (or show stopper) is problem that we have to deal with an object graph instead of a simple tree. A graph may contain cycles and that requires to develop some custom rules or requirements for the stop criteria inside the recursive algorithm.
Have a look at a dead simple bean (a tree structure, getters are assumed but not shown):
public class Node {
private Node parent;
private Node left;
private Node right;
}
and initialize it like this:
root
/ \
A B
Now call a describe on root. A non-recursive call would result in
{parent=null, left=A, right=B}
A recursive call instead would do a
1: describe(root) =>
2: {parent=describe(null), left=describe(A), right=describe(B)} =>
3: {parent=null,
{A.parent=describe(root), A.left=describe(null), A.right= describe(null)}
{B.parent=describe(root), B.left=describe(null), B.right= describe(null)}}
and run into a StackOverflowError because describe is called with objects root, A and B over and over again.
One solution for a custom implementation could be to remember all objects that have been described so far (record those instances in a set, stop if set.contains(bean) return true) and store some kind of link in your result object.
You can simple use from the same commom-beanutils:
Map<String, Object> result = PropertyUtils.describe(obj);
Return the entire set of properties for which the specified bean provides a read method.
In short, if you want to write a map of e.g. constants in Java, which in e.g. Python and Javascript you would write as a literal,
T<String,String> CONSTANTS =
{
"CONSTANT_NAME_0": CONSTANT_VALUE_0 ,
"CONSTANT_NAME_1": CONSTANT_VALUE_1 ,
"CONSTANT_NAME_2": CONSTANT_VALUE_2 ,
//...
} ;
is there a Class or any preset Object that you can use for writing a data structure like that?
I like to do it this way:
Map map = new HashMap() {{
put("foo", "bar");
put(123, 456);
}};
The double {{ }} are an instance initialization block. They are a bit unusual but they are useful. No need for libraries or helpers.
No, Java doesn't have a map literal. The closest you'll come to this is using Google Collections' ImmutableMap:
Map<K,V> CONSTANTS = ImmutableMap.of(
NAME_1, VALUE_1,
NAME_2, VALUE_2
//etc.
);
Constants? I'd use an enum.
public enum Constants {
NAME_1("Value1"),
NAME_2("Value2"),
NAME_3("Value3");
private String value;
Constants(String value) {
this.value = value;
}
public String value() {
return value;
}
}
Value for e.g. NAME_2 can be obtained as follows:
String name2value = Constants.NAME_2.value();
Only give the enum a bit more sensible name, e.g. Settings, Defaults, etc, whatever those name/value pairs actually represent.
Sorry, I'm a tinkerer :-) Here's a somewhat cleaner way.
public class MapTest {
private static Map<String, String> map;
static {
Map<String, String> tmpMap = new HashMap<String, String>();
tmpMap.put("A", "Apple");
tmpMap.put("B", "Banana");
// etc
map = Collections.unmodifiableMap(tmpMap);
}
public Map<String, String> getMap() {
return map;
}
}
You can write yourself a quick helper function:
#SuppressWarnings("unchecked")
public static <K,V> Map<K,V> ImmutableMap(Object... keyValPair){
Map<K,V> map = new HashMap<K,V>();
if(keyValPair.length % 2 != 0){
throw new IllegalArgumentException("Keys and values must be pairs.");
}
for(int i = 0; i < keyValPair.length; i += 2){
map.put((K) keyValPair[i], (V) keyValPair[i+1]);
}
return Collections.unmodifiableMap(map);
}
Note the code above isn't going to stop you from overwriting constants of the same name, using CONST_1 multiple places in your list will result in the final CONST_1's value appearing.
Usage is something like:
Map<String,Double> constants = ImmutableMap(
"CONST_0", 1.0,
"CONST_1", 2.0
);
Here's another way, best suited for maps that won't be changing:
public class Whatever {
private static Map<String,String> map = new HashMap<String,String>();
static {
map.put("A", "Apple");
map.put("B", "Banana");
// etc
}
}
Java7 suppose to implement following syntax:
Map<String, String> = {
"key1": "value",
"key2": "value",
"key3": "value",
"key4": "value"
};
However now you're forced to use solutions proposed by Jorn or Tony Ennis.
Ok, with Jorn's improvement I can't seem to change the map at all, internally or externally. Perhaps not quite as readable, but if you need the map to be unmodifiable I think this is better.
public class MapTest {
private static Map<String, String> map = initMap();
private static Map<String, String> initMap() {
Map<String, String> map = new HashMap<String, String>();
map.put("A", "Apple");
map.put("B", "Banana");
// etc
return Collections.unmodifiableMap(map);
}
public Map<String, String> getMap() {
return map;
}
public static void main(String[] args) {
MapTest m = new MapTest();
System.out.println(m.getMap().get("A"));
m.getMap().put("this", "that");
}
}
I like to do the declaration and initialization on the same line. I've used this handy little utility for so long it basically is my "map literal" and until they're done "right" in the languange, I'm gonna continue on using it like that :)
Happy to share it here.
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* A handy utility for creating and initializing Maps in a single statement.
* #author Jonathan Cobb. This source code is in the Public Domain.
*/
public class MapBuilder {
/**
* Most common create/init case. Usage:
*
* Map<String, Boolean> myPremadeMap = MapBuilder.build(new Object[][]{
* { "a", true }, { "b", false }, { "c", true }, { "d", true },
* { "e", "yes, still dangerous but at least it's not an anonymous class" }
* });
*
* If your keys and values are of the same type, it will even be typesafe:
* Map<String, String> someProperties = MapBuilder.build(new String[][]{
* {"propA", "valueA" }, { "propB", "valueB" }
* });
*
* #param values [x][2] array. items at [x][0] are keys and [x][1] are values.
* #return a LinkedHashMap (to preserve order of declaration) with the "values" mappings
*/
public static <K,V> Map<K,V> build(Object[][] values) {
return build(new LinkedHashMap<K,V>(), values);
}
/**
* Usage:
* Map<K,V> myMap = MapBuilder.build(new MyMapClass(options),
* new Object[][]{ {k,v}, {k,v}, ... });
* #param map add key/value pairs to this map
* #return the map passed in, now containing new "values" mappings
*/
public static <K,V> Map<K,V> build(Map<K,V> map, Object[][] values) {
for (Object[] value : values) {
map.put((K) value[0], (V) value[1]);
}
return map;
}
/** Same as above, for single-value maps */
public static <K,V> Map<K,V> build(Map<K,V> map, K key, V value) {
return build(map, new Object[][]{{key,value}});
}
/**
* Usage:
* Map<K,V> myMap = MapBuilder.build(MyMapClass.class, new Object[][]{ {k,v}, {k,v}, ... });
* #param mapClass a Class that implements Map
* #return the map passed in, now containing new "values" mappings
*/
public static <K,V> Map<K,V> build(Class<? extends Map<K,V>> mapClass, Object[][] values) {
final Map<K,V> map;
try { map = mapClass.newInstance(); } catch (Exception e) {
throw new IllegalStateException("Couldn't create new instance of class: "+mapClass.getName(), e);
}
return build(map, values);
}
/** Usage: Map<K,V> myMap = MapBuilder.build(key, value); */
public static <K,V> Map build(K key, V value) {
Map<K,V> map = new HashMap<>();
map.put(key, value);
return map;
}
}
Since Java 9, you can create unmodifiable maps out of the box -
Map<String, String> emptymap = Map.of();
Map<String, String> map = Map.of("key1", "val1");
there are variants supporting upto 10 keypairs.
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Map.html#of()
This is also supported https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Map.html#ofEntries(java.util.Map.Entry...)
import static java.util.Map.entry;
Map<Integer,String> map = Map.ofEntries(
entry(1, "a"),
entry(2, "b"),
entry(3, "c"),
...
entry(26, "z"));
I have a nested map:
Map<Integer, Map<Integer, Double>> areaPrices = new HashMap<Integer, Map<Integer, Double>>();
and this map is populated using the code:
while(oResult.next())
{
Integer areaCode = new Integer(oResult.getString("AREA_CODE"));
Map<Integer, Double> zonePrices = areaPrices.get(areaCode);
if(zonePrices==null)
{
zonePrices = new HashMap<Integer, Double>();
areaPrices.put(areaCode, zonePrices);
}
Integer zoneCode = new Integer(oResult.getString("ZONE_CODE"));
Double value = new Double(oResult.getString("ZONE_VALUE"));
zonePrices.put(zoneCode, value);
myBean.setZoneValues(areaPrices);
}
I want to use the value of this Map in another method of the same class. For that I have a bean.
How do I populate it on the bean, so that I can get the ZONE_VALUE in this other method
In my bean I added one new field as:
private Map<Integer, Map<Integer, Double>> zoneValues;
with getter and setter as:
public Map<Integer, Map<Integer, Double>> getZoneValues() {
return zoneValues;
}
public void setZoneValues(Map<Integer, Map<Integer, Double>> areaPrices) {
this.zoneValues = areaPrices;
}
What I am looking for to do in the other method is something like this:
Double value = myBean.get(areaCode).get(zoneCode);
How do I make it happen :(
I would like to suggest a different, hopefully more readable solution:
public class PriceMap {
private Map<Integer, Map<Integer, Double>> priceMap =
new HashMap<Integer, Map<Integer, Double>>();
// You'd use this method in your init
public Double setPrice(Integer areaCode, Integer zoneCode, Double price) {
if (!priceMap.containsKey(zoneCode)) {
priceMap.put(zoneCode, new HashMap<Integer, Double>());
}
Map<Integer, Double> areaMap = priceMap.get(zoneCode);
areaMap.put(areaCode, price);
}
public void getPrice(Integer areaCode, Integer zoneCode) {
if (!priceMap.containsKey(zoneCode)) {
// Eek! Exception or return null?
}
Map<Integer, Double> areaMap = priceMap.get(zoneCode);
return areaMap.get(areaCode);
}
}
I think this is a better, more readable abstraction which, very importantly, makes it easier for you or someone else to read after a few months.
EDIT Added get get
If you're stuck with a get(areaCode).get(zoneCode) (order reversed), but myBean is entirely yours, you could do something like:
public class MyBean {
// I suppose you have this already
private final Map<Integer, Map<Integer, Double>> priceMap =
new HashMap<Integer, Map<Integer, Double>>();
private class LooksLikeAMap implements Map<Integer, Double> {
private Integer areaCode = areaCode;
public LooksLikeAMap(Integer areaCode) {
this.areaCode = areaCode;
}
public Double get(Object zoneCode) {
if (!priceMap.containsKey(zoneCode)) {
// Eek! Exception or return null?
}
Map<Integer, Double> areaMap = priceMap.get(zoneCode);
return areaMap.get(areaCode);
}
// Implement other methods similarly
}
public Map<Integer, Double> get(Integer areaCode) {
return new LooksLikeAMap(areaCode);
}
}
OK, programming in a HTML textarea is not my strong suit, but the idea is clear.
Make some Map like structure backed by the complete data set, and initialize that
Map structure with the required AreaCode.
If the idea is not clear, post a comment fast as it's late here:)
EDIT
I am an idiot. I thought the data was zone first, then area while the get should be area first, then zone. In this case the Map already has the right structure, first area then zone, so this is not necessary. The get-get is by default if you make
public MyBean {
public Map<Integer, Double> get(Integer areaCode) {
return data.get(areaCode);
}
}
To start with, all you need is
myBean.getZoneValues(areaCode).get(zoneCode);
the while loop has an annoyance, you need to call myBean.setZoneValues(areaPrices);
out side the while loop
You can't directly control the second get() call because you have a nested Map, you'll need to return the appropriate nested Map to be able to do what you want. A getter like this should do it:
public Map<Integer, Double> get(Integer areaCode) {
return zoneValues.get(areaCode);
}
So when the client code calls get(areaCode) a map will be returned that they can then call get(zoneCode) on.
I'd suggest that you refactor to eliminate the nested Maps though, because you can't stop client code from changing the returned Map, the code is tough to read and you'll have problems if you want to add any more functionality - imagine that you want to provide a String description of an area code in future.
Something like a Map<Integer, AreaCode> where AreaCode is an object that contains what you currently have as a nested Map might be a good place to start.