Reputation: 365
OK here's the scenario. I have static pairs of keys that always go together. One key is the index (aka int
) and one key is the description (aka string
or enum
). I know all these keys in advance so the only thing that's really changing are the values, new keys are never added.
The values however can be any type: string, int, long. Some values are not singular and are made of more than one value. I do know however, in advance, to which type of value each key pair will point.
The values will most likely always be set using the index. However, I want to be able to access the values quickly (no loops please, and hopefully no casting either) by either the index(int) or the description (string/enum). Also, when accessing the values via the index I should also have access to the description
This might make things clearer:
1/Name ----> "danny" //1 and Name are known in advance and always go together. also, they always point to a string
2/Age ----> 24 //2 and Age are known in advance and always go together. also, they always point to an int
3/Time ----> 352343463463L //3 and Time are known in advance and always go together. also, they always point to a long
4/Occupation ---> [description] "magician"
---> [type] "entertainer"
---> [years] 3 //4 and Status are known in advance and always go together. also they will always point to 2 strings and and an int (or an object contraining 2 strings and an int...)
Functionality needed:
set(1, "Jasmine");
get(1); //returns "Jasmine"
get(Name); //return "Jasmine" (name can be either string or enum I suppose)
getDescription(1); // returns Name (again, name could be either string or enum). this function could possibly be merged with get(1) to have it return both description and value in the first place.
set(2, 32);
get(2); //returns 32
get(Age); //returns 32
Upvotes: 3
Views: 3673
Reputation: 12305
UPDATED Here's my definitive answer:
You have a static relationship between an index, a description and the type of value that is associated with it. So let's capture this in a class:
public class Key<VALUETYPE> {
private final Integer index;
private final String description;
private final Class<VALUETYPE> valueType;
public Key(final Integer index, final String description, final Class<VALUETYPE> valueType) {
super();
this.index = index;
this.description = description;
this.valueType = valueType;
}
public Integer getIndex() {
return index;
}
public String getDescription() {
return description;
}
public Class<VALUETYPE> getValueType() {
return valueType;
}
@Override
public int hashCode() {
return index.hashCode();
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (getClass() != obj.getClass()) { return false; }
Key<?> other = (Key<?>) obj;
return index.equals(other.index);
}
}
I've assumed the indexes to be unique identifiers of the key, so have based hashCode
and equals
on it.
Now you write an accessor class that covers your use cases:
public class MapAccessor {
private final Map<Integer, Key<?>> keyMap;
private final Map<Key<?>, Object> valueMap;
public MapAccessor(final Map<Integer, Key<?>> keysByIndex, final Map<Key<?>, Object> valueMap) {
this.keyMap = keysByIndex;
this.valueMap = valueMap;
}
public void put(final Integer index, final Object value) {
Key<?> key = keyMap.get(index);
if (key.getValueType().isInstance(value) || value == null) {
valueMap.put(key, value);
}
else {
throw new IllegalArgumentException("Wrong type of value for index " + index + ", expected: " + key.getValueType()
+ ", actual: " + value.getClass());
}
}
public <VALUETYPE> VALUETYPE get(final Key<VALUETYPE> key) {
return key.getValueType().cast(valueMap.get(key));
}
public Object get(final Integer index) {
Key<?> key = getKey(index);
return key == null ? null : get(key);
}
public Key<?> getKey(final Integer index) {
return keyMap.get(index);
}
public String getDescription(final Integer index) {
Key<?> key = getKey(index);
return key == null ? null : key.getDescription();
}
}
Alternatively, you could put this in a subclass of HashMap<Key<?>, Object>
instead of delegating to it.
Let's demonstrate how to use the above. Note that a cast is unavoidable for lookup by index.
public class ExampleUsage {
private static final Key<String> NAME = new Key<>(1, "Name", String.class);
private static final Key<Integer> AGE = new Key<>(2, "Age", Integer.class);
private static final Map<Integer, Key<?>> keysByIndex = buildKeysByIndex(NAME, AGE);
public static void main(final String... args) {
Map<Key<?>, Object> valueMap = new HashMap<>();
MapAccessor accessor = new MapAccessor(keysByIndex, valueMap);
accessor.put(1, "Jasmine");
String nameByIndex = (String) accessor.get(1); // returns "Jasmine", cast can't be avoided
String nameByKey = accessor.get(NAME); // returns "Jasmine", no cast necessary
Key<?> nameKeyByIndex = accessor.getKey(1); // returns NAME
String nameDescriptionByIndex = accessor.getDescription(1); // returns "Name"
accessor.put(2, 32);
Integer ageByIndex = (Integer) accessor.get(2); // returns 32, cast can't be avoided
Integer ageByKey = accessor.get(AGE); // returns 32, no cast necessary
Key<?> ageKeyByIndex = accessor.getKey(2); // returns AGE
String ageDescriptionByIndex = accessor.getDescription(2); // returns "Age"
}
private static Map<Integer, Key<?>> buildKeysByIndex(final Key<?>... keys) {
Map<Integer, Key<?>> keyMap = new HashMap<Integer, Key<?>>();
for (Key<?> key : keys) {
keyMap.put(key.getIndex(), key);
}
return Collections.unmodifiableMap(keyMap);
}
}
Upvotes: 0
Reputation: 5024
Create a simple class to hold the data and make things like Name
, Age
, etc. bean-like properties with a backing field:
private int age;
public getAge() { return age; }
public setAge(int value) { age = value; }
Make a custom annotation called @FieldInfo
which contains a field ordinal and a field description:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldInfo {
int ordinal();
String description();
}
Decorate the data class with your annotation:
@FieldInfo(ord=1, description="Age")
private int age;
public getAge() { return age; }
public setAge(int value) { age = value; }
Make a base class for your data class which contains universal get()
and set()
methods which can get/set values using the field ordinal and a getDescription() method which returns the field description given the ordinal. Use this.getClass().getDeclaredFields()
to get Fields[]
using reflection, and then use Field.getAnnotation(FieldInfo.class)
to get the annotation for each field.
Since data class properties don't change at runtime you can build two static lookups <int, Field>
and <int, String>
for each data class type in a static constructor to speed things up if you are frequently using ordinals to access fields. Using this approach you can further describe fields by extending the annotation and your data class is still a simple class with traditional getters/setters.
Upvotes: 0
Reputation: 328840
You can use my TypedMap for this. In a nutshell, it gives you a type-safe map which can store any kind of object as value under a key:
TypedMap map = new TypedMap();
String expected = "Hallo";
map.set( KEY1, expected );
String value = map.get( KEY1 ); // Look Ma, no cast!
assertEquals( expected, value );
List<String> list = new ArrayList<String> ();
map.set( KEY2, list );
List<String> valueList = map.get( KEY2 ); // Even with generics
assertEquals( list, valueList );
To give you fast access to the typed keys, I suggest to use an enum:
enum Key {
Name(1) { @Override public TypedMapKey<String> getKey() { return NAME_KEY; },
...;
private static Key[] byIndex = new Key[MAX_INDEX+1];
static {
for( Key key : values() ) { byIndex[key.index] = key; }
}
public static byIndex(int index) {
return byIndex[index]; // I suggest non-null checks here if you have gaps
}
private Key(int index) {
this.index = index;
}
public TypedMapKey<?> getKey() { throw new UnsupportedOperationException( "Please override"; ) }
}
Upvotes: 1
Reputation: 13535
create
class Entry {
int index;
String description;
Object value;
}
declare 2 HashMaps:
HashMap<Integer, Entry> idxValue=new HashMap<Integer, Entry>();
HashMap<String, Entry> descrValue=new HashMap<String, Entry>();
define methods to store and retrieve entries and values which work with both tables.
Upvotes: 2