Java Hashmap store only value of a particular type in key
I am looking at creating a Hashmap class which allows me to store keys and values. However, the value can only be stored if it matches a specific type, and the type is dependent on the runtime value of the key. For example, if the key is EMAIL(String.class)
, then the stored value should be of type String
.
I have following custom ENUM:
public enum TestEnum {
TDD,
BDD,
SHIFT_RIGHT,
SHIFT_LEFT;
}
I have created following class :
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
public class test {
private static final Map<ValidKeys, Object> sessionData = new HashMap<>();
public enum ValidKeys {
EMAIL(String.class),
PASSWORD(String.class),
FIRST_NAME(String.class),
LAST_NAME(String.class),
CONDITION(TestEnum.class);
private Class type;
private boolean isList;
private Pattern pattern;
ValidKeys(Class<?> type, boolean isList) {
this.type = type;
this.isList = isList;
}
ValidKeys(Class<?> type) {
this.type = type;
}
}
public <T> void setData(ValidKeys key, T value) {
sessionData.put(key,value);
}
public Object getData(ValidKeys key) {
return key.type.cast(sessionData.get(key));
}
public static void main(String args) {
test t = new test();
t.setData(ValidKeys.CONDITION,TestEnum.TDD);
System.out.println(t.getData(ValidKeys.CONDITION));
}
}
I would like to use methods such as setData
and getData
and store values into sessionData
. Also, I want to ensure if the value is a list of objects then thats stored properly as well.
I am also struggling to avoid toString basically I need a generic getData which can work without type casting.
java reflection types
|
show 12 more comments
I am looking at creating a Hashmap class which allows me to store keys and values. However, the value can only be stored if it matches a specific type, and the type is dependent on the runtime value of the key. For example, if the key is EMAIL(String.class)
, then the stored value should be of type String
.
I have following custom ENUM:
public enum TestEnum {
TDD,
BDD,
SHIFT_RIGHT,
SHIFT_LEFT;
}
I have created following class :
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
public class test {
private static final Map<ValidKeys, Object> sessionData = new HashMap<>();
public enum ValidKeys {
EMAIL(String.class),
PASSWORD(String.class),
FIRST_NAME(String.class),
LAST_NAME(String.class),
CONDITION(TestEnum.class);
private Class type;
private boolean isList;
private Pattern pattern;
ValidKeys(Class<?> type, boolean isList) {
this.type = type;
this.isList = isList;
}
ValidKeys(Class<?> type) {
this.type = type;
}
}
public <T> void setData(ValidKeys key, T value) {
sessionData.put(key,value);
}
public Object getData(ValidKeys key) {
return key.type.cast(sessionData.get(key));
}
public static void main(String args) {
test t = new test();
t.setData(ValidKeys.CONDITION,TestEnum.TDD);
System.out.println(t.getData(ValidKeys.CONDITION));
}
}
I would like to use methods such as setData
and getData
and store values into sessionData
. Also, I want to ensure if the value is a list of objects then thats stored properly as well.
I am also struggling to avoid toString basically I need a generic getData which can work without type casting.
java reflection types
1
And your question is?
– GBlodgett
Dec 19 '18 at 23:34
1
So, what prevents you from declaring your hash map asHashMap<Foo, Bar>
?
– Andrey Tyukin
Dec 19 '18 at 23:41
1
Ever occurred to you to read the Javadoc, or to learn about generics?
– Cardinal System
Dec 19 '18 at 23:43
1
@AndreyTyukin, it looks like he wants to have a map, that can hold mixed key/value pair of any type, but the type check will be performed later while putting something to this map. It's likeHashMap<Object, Object>
with custom "put".
– itachi
Dec 19 '18 at 23:45
1
@JavaMan Ah, ok, I see now. You are trying to save runtime type-tags in the keys of the hash map, and those type-tags have to be able to differentiate between various types and lists of other types. Similar to what Guice'sTypeLiteral
s are doing.
– Andrey Tyukin
Dec 20 '18 at 1:02
|
show 12 more comments
I am looking at creating a Hashmap class which allows me to store keys and values. However, the value can only be stored if it matches a specific type, and the type is dependent on the runtime value of the key. For example, if the key is EMAIL(String.class)
, then the stored value should be of type String
.
I have following custom ENUM:
public enum TestEnum {
TDD,
BDD,
SHIFT_RIGHT,
SHIFT_LEFT;
}
I have created following class :
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
public class test {
private static final Map<ValidKeys, Object> sessionData = new HashMap<>();
public enum ValidKeys {
EMAIL(String.class),
PASSWORD(String.class),
FIRST_NAME(String.class),
LAST_NAME(String.class),
CONDITION(TestEnum.class);
private Class type;
private boolean isList;
private Pattern pattern;
ValidKeys(Class<?> type, boolean isList) {
this.type = type;
this.isList = isList;
}
ValidKeys(Class<?> type) {
this.type = type;
}
}
public <T> void setData(ValidKeys key, T value) {
sessionData.put(key,value);
}
public Object getData(ValidKeys key) {
return key.type.cast(sessionData.get(key));
}
public static void main(String args) {
test t = new test();
t.setData(ValidKeys.CONDITION,TestEnum.TDD);
System.out.println(t.getData(ValidKeys.CONDITION));
}
}
I would like to use methods such as setData
and getData
and store values into sessionData
. Also, I want to ensure if the value is a list of objects then thats stored properly as well.
I am also struggling to avoid toString basically I need a generic getData which can work without type casting.
java reflection types
I am looking at creating a Hashmap class which allows me to store keys and values. However, the value can only be stored if it matches a specific type, and the type is dependent on the runtime value of the key. For example, if the key is EMAIL(String.class)
, then the stored value should be of type String
.
I have following custom ENUM:
public enum TestEnum {
TDD,
BDD,
SHIFT_RIGHT,
SHIFT_LEFT;
}
I have created following class :
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
public class test {
private static final Map<ValidKeys, Object> sessionData = new HashMap<>();
public enum ValidKeys {
EMAIL(String.class),
PASSWORD(String.class),
FIRST_NAME(String.class),
LAST_NAME(String.class),
CONDITION(TestEnum.class);
private Class type;
private boolean isList;
private Pattern pattern;
ValidKeys(Class<?> type, boolean isList) {
this.type = type;
this.isList = isList;
}
ValidKeys(Class<?> type) {
this.type = type;
}
}
public <T> void setData(ValidKeys key, T value) {
sessionData.put(key,value);
}
public Object getData(ValidKeys key) {
return key.type.cast(sessionData.get(key));
}
public static void main(String args) {
test t = new test();
t.setData(ValidKeys.CONDITION,TestEnum.TDD);
System.out.println(t.getData(ValidKeys.CONDITION));
}
}
I would like to use methods such as setData
and getData
and store values into sessionData
. Also, I want to ensure if the value is a list of objects then thats stored properly as well.
I am also struggling to avoid toString basically I need a generic getData which can work without type casting.
java reflection types
java reflection types
edited Dec 20 '18 at 2:42
JavaMan
asked Dec 19 '18 at 23:32
JavaManJavaMan
568
568
1
And your question is?
– GBlodgett
Dec 19 '18 at 23:34
1
So, what prevents you from declaring your hash map asHashMap<Foo, Bar>
?
– Andrey Tyukin
Dec 19 '18 at 23:41
1
Ever occurred to you to read the Javadoc, or to learn about generics?
– Cardinal System
Dec 19 '18 at 23:43
1
@AndreyTyukin, it looks like he wants to have a map, that can hold mixed key/value pair of any type, but the type check will be performed later while putting something to this map. It's likeHashMap<Object, Object>
with custom "put".
– itachi
Dec 19 '18 at 23:45
1
@JavaMan Ah, ok, I see now. You are trying to save runtime type-tags in the keys of the hash map, and those type-tags have to be able to differentiate between various types and lists of other types. Similar to what Guice'sTypeLiteral
s are doing.
– Andrey Tyukin
Dec 20 '18 at 1:02
|
show 12 more comments
1
And your question is?
– GBlodgett
Dec 19 '18 at 23:34
1
So, what prevents you from declaring your hash map asHashMap<Foo, Bar>
?
– Andrey Tyukin
Dec 19 '18 at 23:41
1
Ever occurred to you to read the Javadoc, or to learn about generics?
– Cardinal System
Dec 19 '18 at 23:43
1
@AndreyTyukin, it looks like he wants to have a map, that can hold mixed key/value pair of any type, but the type check will be performed later while putting something to this map. It's likeHashMap<Object, Object>
with custom "put".
– itachi
Dec 19 '18 at 23:45
1
@JavaMan Ah, ok, I see now. You are trying to save runtime type-tags in the keys of the hash map, and those type-tags have to be able to differentiate between various types and lists of other types. Similar to what Guice'sTypeLiteral
s are doing.
– Andrey Tyukin
Dec 20 '18 at 1:02
1
1
And your question is?
– GBlodgett
Dec 19 '18 at 23:34
And your question is?
– GBlodgett
Dec 19 '18 at 23:34
1
1
So, what prevents you from declaring your hash map as
HashMap<Foo, Bar>
?– Andrey Tyukin
Dec 19 '18 at 23:41
So, what prevents you from declaring your hash map as
HashMap<Foo, Bar>
?– Andrey Tyukin
Dec 19 '18 at 23:41
1
1
Ever occurred to you to read the Javadoc, or to learn about generics?
– Cardinal System
Dec 19 '18 at 23:43
Ever occurred to you to read the Javadoc, or to learn about generics?
– Cardinal System
Dec 19 '18 at 23:43
1
1
@AndreyTyukin, it looks like he wants to have a map, that can hold mixed key/value pair of any type, but the type check will be performed later while putting something to this map. It's like
HashMap<Object, Object>
with custom "put".– itachi
Dec 19 '18 at 23:45
@AndreyTyukin, it looks like he wants to have a map, that can hold mixed key/value pair of any type, but the type check will be performed later while putting something to this map. It's like
HashMap<Object, Object>
with custom "put".– itachi
Dec 19 '18 at 23:45
1
1
@JavaMan Ah, ok, I see now. You are trying to save runtime type-tags in the keys of the hash map, and those type-tags have to be able to differentiate between various types and lists of other types. Similar to what Guice's
TypeLiteral
s are doing.– Andrey Tyukin
Dec 20 '18 at 1:02
@JavaMan Ah, ok, I see now. You are trying to save runtime type-tags in the keys of the hash map, and those type-tags have to be able to differentiate between various types and lists of other types. Similar to what Guice's
TypeLiteral
s are doing.– Andrey Tyukin
Dec 20 '18 at 1:02
|
show 12 more comments
1 Answer
1
active
oldest
votes
There is a particular pattern I've seen used for this sort of thing, which is a variant of Bloch's Typesafe Heterogenous Container pattern. I don't know if it has a name on its own or not, but for lack of a better name I'll call it Typesafe Enumerated Lookup Keys.
Basically, a problem that I've seen arise in various contexts is where you want a dynamic set of key/value pairs, where a particular subset of keys are "well-known" with predefined semantics. Additionally, each key is associated with a particular type.
The "obvious" solution is to use an enum. For example, you could do:
public enum LookupKey { FOO, BAR }
public final class Repository {
private final Map<LookupKey, Object> data = new HashMap<>();
public void put(LookupKey key, Object value) {
data.put(key, value);
}
public Object get(LookupKey key) {
return data.get(key);
}
}
This works just fine, but the obvious drawback is that now you need to cast everywhere. For example, suppose you know that LookupKey.FOO
always has a String
value, and LookupKey.BAR
always has an Integer
value. How do you enforce that? With this implementation, you can't.
Also: with this implementation, the set of keys is fixed by the enum. You can't add new ones at runtime. For some applications that's an advantage, but in other cases you really do want to allow new keys in certain cases.
The solution to both these problems is basically the same one: make LookupKey
a first-class entity, not just an enum. For example:
/**
* A key that knows its own name and type.
*/
public final class LookupKey<T> {
// These are the "enumerated" keys:
public static final LookupKey<String> FOO = new LookupKey<>("FOO", String.class);
public static final LookupKey<Integer> BAR = new LookupKey<>("BAR", Integer.class);
private final String name;
private final Class<T> type;
public LookupKey(String name, Class<T> type) {
this.name = name;
this.type = type;
}
/**
* Returns the name of this key.
*/
public String name() {
return name;
}
@Override
public String toString() {
return name;
}
/**
* Cast an arbitrary object to the type of this key.
*
* @param object an arbitrary object, retrieved from a Map for example.
* @throws ClassCastException if the argument is the wrong type.
*/
public T cast(Object object) {
return type.cast(object);
}
// not shown: equals() and hashCode() implementations
}
This gets us most of the way there already. You can refer to LookupKey.FOO
and LookupKey.BAR
and they behave like you would expect, but they also know the corresponding looked-up type. And you can also define your own keys by creating new instances of LookupKey
.
If we want to implement some nice enum-like abilities like the static values()
method, we just need to add a registry. As a bonus, we don't even need equals()
and hashCode()
if we add a registry, since we can just compare lookup keys by identity now.
Here's what the class ends up looking like:
/**
* A key that knows its own name and type.
*/
public final class LookupKey<T> {
// This is the registry of all known keys.
// (It needs to be declared first because the create() function needs it.)
private static final Map<String, LookupKey<?>> knownKeys = new HashMap<>();
// These are the "enumerated" keys:
public static final LookupKey<String> FOO = create("FOO", String.class);
public static final LookupKey<Integer> BAR = create("BAR", Integer.class);
/**
* Create and register a new key. If a key with the same name and type
* already exists, it is returned instead (Flywheel Pattern).
*
* @param name A name to uniquely identify this key.
* @param type The type of data associated with this key.
* @throws IllegalStateException if a key with the same name but a different
* type was already registered.
*/
public static <T> LookupKey<T> create(String name, Class<T> type) {
synchronized (knownKeys) {
LookupKey<?> existing = knownKeys.get(name);
if (existing != null) {
if (existing.type != type) {
throw new IllegalStateException(
"Incompatible definition of " + name);
}
@SuppressWarnings("unchecked") // our invariant ensures this is safe
LookupKey<T> uncheckedCast = (LookupKey<T>) existing;
return uncheckedCast;
}
LookupKey<T> key = new LookupKey<>(name, type);
knownKeys.put(name, key);
return key;
}
}
/**
* Returns a list of all the currently known lookup keys.
*/
public static List<LookupKey<?>> values() {
synchronized (knownKeys) {
return Collections.unmodifiableList(
new ArrayList<>(knownKeys.values()));
}
}
private final String name;
private final Class<T> type;
// Private constructor. Only the create method should call this.
private LookupKey(String name, Class<T> type) {
this.name = name;
this.type = type;
}
/**
* Returns the name of this key.
*/
public String name() {
return name;
}
@Override
public String toString() {
return name;
}
/**
* Cast an arbitrary object to the type of this key.
*
* @param object an arbitrary object, retrieved from a Map for example.
* @throws ClassCastException if the argument is the wrong type.
*/
public T cast(Object object) {
return type.cast(object);
}
}
Now LookupKey.values()
works more or less like an enum would. You can also add your own keys, and values()
will return them afterward:
LookupKey<Double> myKey = LookupKey.create("CUSTOM_DATA", Double.class);
Once you have this LookupKey
class, you can now implement a typesafe repository that uses these keys for lookup:
/**
* A repository of data that can be looked up using a {@link LookupKey}.
*/
public final class Repository {
private final Map<LookupKey<?>, Object> data = new HashMap<>();
/**
* Set a value in the repository.
*
* @param <T> The type of data that is being stored.
* @param key The key that identifies the value.
* @param value The corresponding value.
*/
public <T> void put(LookupKey<T> key, T value) {
data.put(key, value);
}
/**
* Gets a value from this repository.
*
* @param <T> The type of the value identified by the key.
* @param key The key that identifies the desired value.
*/
public <T> T get(LookupKey<T> key) {
return key.cast(data.get(key));
}
}
Thanks @Daniel Pryden, looks really cool and seems to have nailed the issue around Object / explicit casting. Awesome, Cheers !!
– JavaMan
Dec 21 '18 at 1:45
A quick one, I have no idea why I see a NPE for KnownKeys in create method. I have resolved it by initialising it again in the create method, but am not sure if that's correct. Pointers?
– JavaMan
Dec 31 '18 at 11:26
My bad, I had it laid out differently before and I reorganized the code when I posted it here. TheknownKeys
map needs to be declared before the "enumerated" values, since static fields are initialized in lexical order. I'll update the answer to show this. (As you point out, the other way to solve this is to initialize it lazily in thecreate
method, but then it can't befinal
.)
– Daniel Pryden
Dec 31 '18 at 13:52
Thanks a lot Daniel Pryden for responding to my comment. I should have picked it up yesterday whilst debugging but just didn't stuck me. Awesome, cheers !!!
– JavaMan
Dec 31 '18 at 14:23
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53860603%2fjava-hashmap-store-only-value-of-a-particular-type-in-key%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
There is a particular pattern I've seen used for this sort of thing, which is a variant of Bloch's Typesafe Heterogenous Container pattern. I don't know if it has a name on its own or not, but for lack of a better name I'll call it Typesafe Enumerated Lookup Keys.
Basically, a problem that I've seen arise in various contexts is where you want a dynamic set of key/value pairs, where a particular subset of keys are "well-known" with predefined semantics. Additionally, each key is associated with a particular type.
The "obvious" solution is to use an enum. For example, you could do:
public enum LookupKey { FOO, BAR }
public final class Repository {
private final Map<LookupKey, Object> data = new HashMap<>();
public void put(LookupKey key, Object value) {
data.put(key, value);
}
public Object get(LookupKey key) {
return data.get(key);
}
}
This works just fine, but the obvious drawback is that now you need to cast everywhere. For example, suppose you know that LookupKey.FOO
always has a String
value, and LookupKey.BAR
always has an Integer
value. How do you enforce that? With this implementation, you can't.
Also: with this implementation, the set of keys is fixed by the enum. You can't add new ones at runtime. For some applications that's an advantage, but in other cases you really do want to allow new keys in certain cases.
The solution to both these problems is basically the same one: make LookupKey
a first-class entity, not just an enum. For example:
/**
* A key that knows its own name and type.
*/
public final class LookupKey<T> {
// These are the "enumerated" keys:
public static final LookupKey<String> FOO = new LookupKey<>("FOO", String.class);
public static final LookupKey<Integer> BAR = new LookupKey<>("BAR", Integer.class);
private final String name;
private final Class<T> type;
public LookupKey(String name, Class<T> type) {
this.name = name;
this.type = type;
}
/**
* Returns the name of this key.
*/
public String name() {
return name;
}
@Override
public String toString() {
return name;
}
/**
* Cast an arbitrary object to the type of this key.
*
* @param object an arbitrary object, retrieved from a Map for example.
* @throws ClassCastException if the argument is the wrong type.
*/
public T cast(Object object) {
return type.cast(object);
}
// not shown: equals() and hashCode() implementations
}
This gets us most of the way there already. You can refer to LookupKey.FOO
and LookupKey.BAR
and they behave like you would expect, but they also know the corresponding looked-up type. And you can also define your own keys by creating new instances of LookupKey
.
If we want to implement some nice enum-like abilities like the static values()
method, we just need to add a registry. As a bonus, we don't even need equals()
and hashCode()
if we add a registry, since we can just compare lookup keys by identity now.
Here's what the class ends up looking like:
/**
* A key that knows its own name and type.
*/
public final class LookupKey<T> {
// This is the registry of all known keys.
// (It needs to be declared first because the create() function needs it.)
private static final Map<String, LookupKey<?>> knownKeys = new HashMap<>();
// These are the "enumerated" keys:
public static final LookupKey<String> FOO = create("FOO", String.class);
public static final LookupKey<Integer> BAR = create("BAR", Integer.class);
/**
* Create and register a new key. If a key with the same name and type
* already exists, it is returned instead (Flywheel Pattern).
*
* @param name A name to uniquely identify this key.
* @param type The type of data associated with this key.
* @throws IllegalStateException if a key with the same name but a different
* type was already registered.
*/
public static <T> LookupKey<T> create(String name, Class<T> type) {
synchronized (knownKeys) {
LookupKey<?> existing = knownKeys.get(name);
if (existing != null) {
if (existing.type != type) {
throw new IllegalStateException(
"Incompatible definition of " + name);
}
@SuppressWarnings("unchecked") // our invariant ensures this is safe
LookupKey<T> uncheckedCast = (LookupKey<T>) existing;
return uncheckedCast;
}
LookupKey<T> key = new LookupKey<>(name, type);
knownKeys.put(name, key);
return key;
}
}
/**
* Returns a list of all the currently known lookup keys.
*/
public static List<LookupKey<?>> values() {
synchronized (knownKeys) {
return Collections.unmodifiableList(
new ArrayList<>(knownKeys.values()));
}
}
private final String name;
private final Class<T> type;
// Private constructor. Only the create method should call this.
private LookupKey(String name, Class<T> type) {
this.name = name;
this.type = type;
}
/**
* Returns the name of this key.
*/
public String name() {
return name;
}
@Override
public String toString() {
return name;
}
/**
* Cast an arbitrary object to the type of this key.
*
* @param object an arbitrary object, retrieved from a Map for example.
* @throws ClassCastException if the argument is the wrong type.
*/
public T cast(Object object) {
return type.cast(object);
}
}
Now LookupKey.values()
works more or less like an enum would. You can also add your own keys, and values()
will return them afterward:
LookupKey<Double> myKey = LookupKey.create("CUSTOM_DATA", Double.class);
Once you have this LookupKey
class, you can now implement a typesafe repository that uses these keys for lookup:
/**
* A repository of data that can be looked up using a {@link LookupKey}.
*/
public final class Repository {
private final Map<LookupKey<?>, Object> data = new HashMap<>();
/**
* Set a value in the repository.
*
* @param <T> The type of data that is being stored.
* @param key The key that identifies the value.
* @param value The corresponding value.
*/
public <T> void put(LookupKey<T> key, T value) {
data.put(key, value);
}
/**
* Gets a value from this repository.
*
* @param <T> The type of the value identified by the key.
* @param key The key that identifies the desired value.
*/
public <T> T get(LookupKey<T> key) {
return key.cast(data.get(key));
}
}
Thanks @Daniel Pryden, looks really cool and seems to have nailed the issue around Object / explicit casting. Awesome, Cheers !!
– JavaMan
Dec 21 '18 at 1:45
A quick one, I have no idea why I see a NPE for KnownKeys in create method. I have resolved it by initialising it again in the create method, but am not sure if that's correct. Pointers?
– JavaMan
Dec 31 '18 at 11:26
My bad, I had it laid out differently before and I reorganized the code when I posted it here. TheknownKeys
map needs to be declared before the "enumerated" values, since static fields are initialized in lexical order. I'll update the answer to show this. (As you point out, the other way to solve this is to initialize it lazily in thecreate
method, but then it can't befinal
.)
– Daniel Pryden
Dec 31 '18 at 13:52
Thanks a lot Daniel Pryden for responding to my comment. I should have picked it up yesterday whilst debugging but just didn't stuck me. Awesome, cheers !!!
– JavaMan
Dec 31 '18 at 14:23
add a comment |
There is a particular pattern I've seen used for this sort of thing, which is a variant of Bloch's Typesafe Heterogenous Container pattern. I don't know if it has a name on its own or not, but for lack of a better name I'll call it Typesafe Enumerated Lookup Keys.
Basically, a problem that I've seen arise in various contexts is where you want a dynamic set of key/value pairs, where a particular subset of keys are "well-known" with predefined semantics. Additionally, each key is associated with a particular type.
The "obvious" solution is to use an enum. For example, you could do:
public enum LookupKey { FOO, BAR }
public final class Repository {
private final Map<LookupKey, Object> data = new HashMap<>();
public void put(LookupKey key, Object value) {
data.put(key, value);
}
public Object get(LookupKey key) {
return data.get(key);
}
}
This works just fine, but the obvious drawback is that now you need to cast everywhere. For example, suppose you know that LookupKey.FOO
always has a String
value, and LookupKey.BAR
always has an Integer
value. How do you enforce that? With this implementation, you can't.
Also: with this implementation, the set of keys is fixed by the enum. You can't add new ones at runtime. For some applications that's an advantage, but in other cases you really do want to allow new keys in certain cases.
The solution to both these problems is basically the same one: make LookupKey
a first-class entity, not just an enum. For example:
/**
* A key that knows its own name and type.
*/
public final class LookupKey<T> {
// These are the "enumerated" keys:
public static final LookupKey<String> FOO = new LookupKey<>("FOO", String.class);
public static final LookupKey<Integer> BAR = new LookupKey<>("BAR", Integer.class);
private final String name;
private final Class<T> type;
public LookupKey(String name, Class<T> type) {
this.name = name;
this.type = type;
}
/**
* Returns the name of this key.
*/
public String name() {
return name;
}
@Override
public String toString() {
return name;
}
/**
* Cast an arbitrary object to the type of this key.
*
* @param object an arbitrary object, retrieved from a Map for example.
* @throws ClassCastException if the argument is the wrong type.
*/
public T cast(Object object) {
return type.cast(object);
}
// not shown: equals() and hashCode() implementations
}
This gets us most of the way there already. You can refer to LookupKey.FOO
and LookupKey.BAR
and they behave like you would expect, but they also know the corresponding looked-up type. And you can also define your own keys by creating new instances of LookupKey
.
If we want to implement some nice enum-like abilities like the static values()
method, we just need to add a registry. As a bonus, we don't even need equals()
and hashCode()
if we add a registry, since we can just compare lookup keys by identity now.
Here's what the class ends up looking like:
/**
* A key that knows its own name and type.
*/
public final class LookupKey<T> {
// This is the registry of all known keys.
// (It needs to be declared first because the create() function needs it.)
private static final Map<String, LookupKey<?>> knownKeys = new HashMap<>();
// These are the "enumerated" keys:
public static final LookupKey<String> FOO = create("FOO", String.class);
public static final LookupKey<Integer> BAR = create("BAR", Integer.class);
/**
* Create and register a new key. If a key with the same name and type
* already exists, it is returned instead (Flywheel Pattern).
*
* @param name A name to uniquely identify this key.
* @param type The type of data associated with this key.
* @throws IllegalStateException if a key with the same name but a different
* type was already registered.
*/
public static <T> LookupKey<T> create(String name, Class<T> type) {
synchronized (knownKeys) {
LookupKey<?> existing = knownKeys.get(name);
if (existing != null) {
if (existing.type != type) {
throw new IllegalStateException(
"Incompatible definition of " + name);
}
@SuppressWarnings("unchecked") // our invariant ensures this is safe
LookupKey<T> uncheckedCast = (LookupKey<T>) existing;
return uncheckedCast;
}
LookupKey<T> key = new LookupKey<>(name, type);
knownKeys.put(name, key);
return key;
}
}
/**
* Returns a list of all the currently known lookup keys.
*/
public static List<LookupKey<?>> values() {
synchronized (knownKeys) {
return Collections.unmodifiableList(
new ArrayList<>(knownKeys.values()));
}
}
private final String name;
private final Class<T> type;
// Private constructor. Only the create method should call this.
private LookupKey(String name, Class<T> type) {
this.name = name;
this.type = type;
}
/**
* Returns the name of this key.
*/
public String name() {
return name;
}
@Override
public String toString() {
return name;
}
/**
* Cast an arbitrary object to the type of this key.
*
* @param object an arbitrary object, retrieved from a Map for example.
* @throws ClassCastException if the argument is the wrong type.
*/
public T cast(Object object) {
return type.cast(object);
}
}
Now LookupKey.values()
works more or less like an enum would. You can also add your own keys, and values()
will return them afterward:
LookupKey<Double> myKey = LookupKey.create("CUSTOM_DATA", Double.class);
Once you have this LookupKey
class, you can now implement a typesafe repository that uses these keys for lookup:
/**
* A repository of data that can be looked up using a {@link LookupKey}.
*/
public final class Repository {
private final Map<LookupKey<?>, Object> data = new HashMap<>();
/**
* Set a value in the repository.
*
* @param <T> The type of data that is being stored.
* @param key The key that identifies the value.
* @param value The corresponding value.
*/
public <T> void put(LookupKey<T> key, T value) {
data.put(key, value);
}
/**
* Gets a value from this repository.
*
* @param <T> The type of the value identified by the key.
* @param key The key that identifies the desired value.
*/
public <T> T get(LookupKey<T> key) {
return key.cast(data.get(key));
}
}
Thanks @Daniel Pryden, looks really cool and seems to have nailed the issue around Object / explicit casting. Awesome, Cheers !!
– JavaMan
Dec 21 '18 at 1:45
A quick one, I have no idea why I see a NPE for KnownKeys in create method. I have resolved it by initialising it again in the create method, but am not sure if that's correct. Pointers?
– JavaMan
Dec 31 '18 at 11:26
My bad, I had it laid out differently before and I reorganized the code when I posted it here. TheknownKeys
map needs to be declared before the "enumerated" values, since static fields are initialized in lexical order. I'll update the answer to show this. (As you point out, the other way to solve this is to initialize it lazily in thecreate
method, but then it can't befinal
.)
– Daniel Pryden
Dec 31 '18 at 13:52
Thanks a lot Daniel Pryden for responding to my comment. I should have picked it up yesterday whilst debugging but just didn't stuck me. Awesome, cheers !!!
– JavaMan
Dec 31 '18 at 14:23
add a comment |
There is a particular pattern I've seen used for this sort of thing, which is a variant of Bloch's Typesafe Heterogenous Container pattern. I don't know if it has a name on its own or not, but for lack of a better name I'll call it Typesafe Enumerated Lookup Keys.
Basically, a problem that I've seen arise in various contexts is where you want a dynamic set of key/value pairs, where a particular subset of keys are "well-known" with predefined semantics. Additionally, each key is associated with a particular type.
The "obvious" solution is to use an enum. For example, you could do:
public enum LookupKey { FOO, BAR }
public final class Repository {
private final Map<LookupKey, Object> data = new HashMap<>();
public void put(LookupKey key, Object value) {
data.put(key, value);
}
public Object get(LookupKey key) {
return data.get(key);
}
}
This works just fine, but the obvious drawback is that now you need to cast everywhere. For example, suppose you know that LookupKey.FOO
always has a String
value, and LookupKey.BAR
always has an Integer
value. How do you enforce that? With this implementation, you can't.
Also: with this implementation, the set of keys is fixed by the enum. You can't add new ones at runtime. For some applications that's an advantage, but in other cases you really do want to allow new keys in certain cases.
The solution to both these problems is basically the same one: make LookupKey
a first-class entity, not just an enum. For example:
/**
* A key that knows its own name and type.
*/
public final class LookupKey<T> {
// These are the "enumerated" keys:
public static final LookupKey<String> FOO = new LookupKey<>("FOO", String.class);
public static final LookupKey<Integer> BAR = new LookupKey<>("BAR", Integer.class);
private final String name;
private final Class<T> type;
public LookupKey(String name, Class<T> type) {
this.name = name;
this.type = type;
}
/**
* Returns the name of this key.
*/
public String name() {
return name;
}
@Override
public String toString() {
return name;
}
/**
* Cast an arbitrary object to the type of this key.
*
* @param object an arbitrary object, retrieved from a Map for example.
* @throws ClassCastException if the argument is the wrong type.
*/
public T cast(Object object) {
return type.cast(object);
}
// not shown: equals() and hashCode() implementations
}
This gets us most of the way there already. You can refer to LookupKey.FOO
and LookupKey.BAR
and they behave like you would expect, but they also know the corresponding looked-up type. And you can also define your own keys by creating new instances of LookupKey
.
If we want to implement some nice enum-like abilities like the static values()
method, we just need to add a registry. As a bonus, we don't even need equals()
and hashCode()
if we add a registry, since we can just compare lookup keys by identity now.
Here's what the class ends up looking like:
/**
* A key that knows its own name and type.
*/
public final class LookupKey<T> {
// This is the registry of all known keys.
// (It needs to be declared first because the create() function needs it.)
private static final Map<String, LookupKey<?>> knownKeys = new HashMap<>();
// These are the "enumerated" keys:
public static final LookupKey<String> FOO = create("FOO", String.class);
public static final LookupKey<Integer> BAR = create("BAR", Integer.class);
/**
* Create and register a new key. If a key with the same name and type
* already exists, it is returned instead (Flywheel Pattern).
*
* @param name A name to uniquely identify this key.
* @param type The type of data associated with this key.
* @throws IllegalStateException if a key with the same name but a different
* type was already registered.
*/
public static <T> LookupKey<T> create(String name, Class<T> type) {
synchronized (knownKeys) {
LookupKey<?> existing = knownKeys.get(name);
if (existing != null) {
if (existing.type != type) {
throw new IllegalStateException(
"Incompatible definition of " + name);
}
@SuppressWarnings("unchecked") // our invariant ensures this is safe
LookupKey<T> uncheckedCast = (LookupKey<T>) existing;
return uncheckedCast;
}
LookupKey<T> key = new LookupKey<>(name, type);
knownKeys.put(name, key);
return key;
}
}
/**
* Returns a list of all the currently known lookup keys.
*/
public static List<LookupKey<?>> values() {
synchronized (knownKeys) {
return Collections.unmodifiableList(
new ArrayList<>(knownKeys.values()));
}
}
private final String name;
private final Class<T> type;
// Private constructor. Only the create method should call this.
private LookupKey(String name, Class<T> type) {
this.name = name;
this.type = type;
}
/**
* Returns the name of this key.
*/
public String name() {
return name;
}
@Override
public String toString() {
return name;
}
/**
* Cast an arbitrary object to the type of this key.
*
* @param object an arbitrary object, retrieved from a Map for example.
* @throws ClassCastException if the argument is the wrong type.
*/
public T cast(Object object) {
return type.cast(object);
}
}
Now LookupKey.values()
works more or less like an enum would. You can also add your own keys, and values()
will return them afterward:
LookupKey<Double> myKey = LookupKey.create("CUSTOM_DATA", Double.class);
Once you have this LookupKey
class, you can now implement a typesafe repository that uses these keys for lookup:
/**
* A repository of data that can be looked up using a {@link LookupKey}.
*/
public final class Repository {
private final Map<LookupKey<?>, Object> data = new HashMap<>();
/**
* Set a value in the repository.
*
* @param <T> The type of data that is being stored.
* @param key The key that identifies the value.
* @param value The corresponding value.
*/
public <T> void put(LookupKey<T> key, T value) {
data.put(key, value);
}
/**
* Gets a value from this repository.
*
* @param <T> The type of the value identified by the key.
* @param key The key that identifies the desired value.
*/
public <T> T get(LookupKey<T> key) {
return key.cast(data.get(key));
}
}
There is a particular pattern I've seen used for this sort of thing, which is a variant of Bloch's Typesafe Heterogenous Container pattern. I don't know if it has a name on its own or not, but for lack of a better name I'll call it Typesafe Enumerated Lookup Keys.
Basically, a problem that I've seen arise in various contexts is where you want a dynamic set of key/value pairs, where a particular subset of keys are "well-known" with predefined semantics. Additionally, each key is associated with a particular type.
The "obvious" solution is to use an enum. For example, you could do:
public enum LookupKey { FOO, BAR }
public final class Repository {
private final Map<LookupKey, Object> data = new HashMap<>();
public void put(LookupKey key, Object value) {
data.put(key, value);
}
public Object get(LookupKey key) {
return data.get(key);
}
}
This works just fine, but the obvious drawback is that now you need to cast everywhere. For example, suppose you know that LookupKey.FOO
always has a String
value, and LookupKey.BAR
always has an Integer
value. How do you enforce that? With this implementation, you can't.
Also: with this implementation, the set of keys is fixed by the enum. You can't add new ones at runtime. For some applications that's an advantage, but in other cases you really do want to allow new keys in certain cases.
The solution to both these problems is basically the same one: make LookupKey
a first-class entity, not just an enum. For example:
/**
* A key that knows its own name and type.
*/
public final class LookupKey<T> {
// These are the "enumerated" keys:
public static final LookupKey<String> FOO = new LookupKey<>("FOO", String.class);
public static final LookupKey<Integer> BAR = new LookupKey<>("BAR", Integer.class);
private final String name;
private final Class<T> type;
public LookupKey(String name, Class<T> type) {
this.name = name;
this.type = type;
}
/**
* Returns the name of this key.
*/
public String name() {
return name;
}
@Override
public String toString() {
return name;
}
/**
* Cast an arbitrary object to the type of this key.
*
* @param object an arbitrary object, retrieved from a Map for example.
* @throws ClassCastException if the argument is the wrong type.
*/
public T cast(Object object) {
return type.cast(object);
}
// not shown: equals() and hashCode() implementations
}
This gets us most of the way there already. You can refer to LookupKey.FOO
and LookupKey.BAR
and they behave like you would expect, but they also know the corresponding looked-up type. And you can also define your own keys by creating new instances of LookupKey
.
If we want to implement some nice enum-like abilities like the static values()
method, we just need to add a registry. As a bonus, we don't even need equals()
and hashCode()
if we add a registry, since we can just compare lookup keys by identity now.
Here's what the class ends up looking like:
/**
* A key that knows its own name and type.
*/
public final class LookupKey<T> {
// This is the registry of all known keys.
// (It needs to be declared first because the create() function needs it.)
private static final Map<String, LookupKey<?>> knownKeys = new HashMap<>();
// These are the "enumerated" keys:
public static final LookupKey<String> FOO = create("FOO", String.class);
public static final LookupKey<Integer> BAR = create("BAR", Integer.class);
/**
* Create and register a new key. If a key with the same name and type
* already exists, it is returned instead (Flywheel Pattern).
*
* @param name A name to uniquely identify this key.
* @param type The type of data associated with this key.
* @throws IllegalStateException if a key with the same name but a different
* type was already registered.
*/
public static <T> LookupKey<T> create(String name, Class<T> type) {
synchronized (knownKeys) {
LookupKey<?> existing = knownKeys.get(name);
if (existing != null) {
if (existing.type != type) {
throw new IllegalStateException(
"Incompatible definition of " + name);
}
@SuppressWarnings("unchecked") // our invariant ensures this is safe
LookupKey<T> uncheckedCast = (LookupKey<T>) existing;
return uncheckedCast;
}
LookupKey<T> key = new LookupKey<>(name, type);
knownKeys.put(name, key);
return key;
}
}
/**
* Returns a list of all the currently known lookup keys.
*/
public static List<LookupKey<?>> values() {
synchronized (knownKeys) {
return Collections.unmodifiableList(
new ArrayList<>(knownKeys.values()));
}
}
private final String name;
private final Class<T> type;
// Private constructor. Only the create method should call this.
private LookupKey(String name, Class<T> type) {
this.name = name;
this.type = type;
}
/**
* Returns the name of this key.
*/
public String name() {
return name;
}
@Override
public String toString() {
return name;
}
/**
* Cast an arbitrary object to the type of this key.
*
* @param object an arbitrary object, retrieved from a Map for example.
* @throws ClassCastException if the argument is the wrong type.
*/
public T cast(Object object) {
return type.cast(object);
}
}
Now LookupKey.values()
works more or less like an enum would. You can also add your own keys, and values()
will return them afterward:
LookupKey<Double> myKey = LookupKey.create("CUSTOM_DATA", Double.class);
Once you have this LookupKey
class, you can now implement a typesafe repository that uses these keys for lookup:
/**
* A repository of data that can be looked up using a {@link LookupKey}.
*/
public final class Repository {
private final Map<LookupKey<?>, Object> data = new HashMap<>();
/**
* Set a value in the repository.
*
* @param <T> The type of data that is being stored.
* @param key The key that identifies the value.
* @param value The corresponding value.
*/
public <T> void put(LookupKey<T> key, T value) {
data.put(key, value);
}
/**
* Gets a value from this repository.
*
* @param <T> The type of the value identified by the key.
* @param key The key that identifies the desired value.
*/
public <T> T get(LookupKey<T> key) {
return key.cast(data.get(key));
}
}
edited Dec 31 '18 at 14:34
answered Dec 20 '18 at 18:31
Daniel PrydenDaniel Pryden
46.1k875117
46.1k875117
Thanks @Daniel Pryden, looks really cool and seems to have nailed the issue around Object / explicit casting. Awesome, Cheers !!
– JavaMan
Dec 21 '18 at 1:45
A quick one, I have no idea why I see a NPE for KnownKeys in create method. I have resolved it by initialising it again in the create method, but am not sure if that's correct. Pointers?
– JavaMan
Dec 31 '18 at 11:26
My bad, I had it laid out differently before and I reorganized the code when I posted it here. TheknownKeys
map needs to be declared before the "enumerated" values, since static fields are initialized in lexical order. I'll update the answer to show this. (As you point out, the other way to solve this is to initialize it lazily in thecreate
method, but then it can't befinal
.)
– Daniel Pryden
Dec 31 '18 at 13:52
Thanks a lot Daniel Pryden for responding to my comment. I should have picked it up yesterday whilst debugging but just didn't stuck me. Awesome, cheers !!!
– JavaMan
Dec 31 '18 at 14:23
add a comment |
Thanks @Daniel Pryden, looks really cool and seems to have nailed the issue around Object / explicit casting. Awesome, Cheers !!
– JavaMan
Dec 21 '18 at 1:45
A quick one, I have no idea why I see a NPE for KnownKeys in create method. I have resolved it by initialising it again in the create method, but am not sure if that's correct. Pointers?
– JavaMan
Dec 31 '18 at 11:26
My bad, I had it laid out differently before and I reorganized the code when I posted it here. TheknownKeys
map needs to be declared before the "enumerated" values, since static fields are initialized in lexical order. I'll update the answer to show this. (As you point out, the other way to solve this is to initialize it lazily in thecreate
method, but then it can't befinal
.)
– Daniel Pryden
Dec 31 '18 at 13:52
Thanks a lot Daniel Pryden for responding to my comment. I should have picked it up yesterday whilst debugging but just didn't stuck me. Awesome, cheers !!!
– JavaMan
Dec 31 '18 at 14:23
Thanks @Daniel Pryden, looks really cool and seems to have nailed the issue around Object / explicit casting. Awesome, Cheers !!
– JavaMan
Dec 21 '18 at 1:45
Thanks @Daniel Pryden, looks really cool and seems to have nailed the issue around Object / explicit casting. Awesome, Cheers !!
– JavaMan
Dec 21 '18 at 1:45
A quick one, I have no idea why I see a NPE for KnownKeys in create method. I have resolved it by initialising it again in the create method, but am not sure if that's correct. Pointers?
– JavaMan
Dec 31 '18 at 11:26
A quick one, I have no idea why I see a NPE for KnownKeys in create method. I have resolved it by initialising it again in the create method, but am not sure if that's correct. Pointers?
– JavaMan
Dec 31 '18 at 11:26
My bad, I had it laid out differently before and I reorganized the code when I posted it here. The
knownKeys
map needs to be declared before the "enumerated" values, since static fields are initialized in lexical order. I'll update the answer to show this. (As you point out, the other way to solve this is to initialize it lazily in the create
method, but then it can't be final
.)– Daniel Pryden
Dec 31 '18 at 13:52
My bad, I had it laid out differently before and I reorganized the code when I posted it here. The
knownKeys
map needs to be declared before the "enumerated" values, since static fields are initialized in lexical order. I'll update the answer to show this. (As you point out, the other way to solve this is to initialize it lazily in the create
method, but then it can't be final
.)– Daniel Pryden
Dec 31 '18 at 13:52
Thanks a lot Daniel Pryden for responding to my comment. I should have picked it up yesterday whilst debugging but just didn't stuck me. Awesome, cheers !!!
– JavaMan
Dec 31 '18 at 14:23
Thanks a lot Daniel Pryden for responding to my comment. I should have picked it up yesterday whilst debugging but just didn't stuck me. Awesome, cheers !!!
– JavaMan
Dec 31 '18 at 14:23
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53860603%2fjava-hashmap-store-only-value-of-a-particular-type-in-key%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
And your question is?
– GBlodgett
Dec 19 '18 at 23:34
1
So, what prevents you from declaring your hash map as
HashMap<Foo, Bar>
?– Andrey Tyukin
Dec 19 '18 at 23:41
1
Ever occurred to you to read the Javadoc, or to learn about generics?
– Cardinal System
Dec 19 '18 at 23:43
1
@AndreyTyukin, it looks like he wants to have a map, that can hold mixed key/value pair of any type, but the type check will be performed later while putting something to this map. It's like
HashMap<Object, Object>
with custom "put".– itachi
Dec 19 '18 at 23:45
1
@JavaMan Ah, ok, I see now. You are trying to save runtime type-tags in the keys of the hash map, and those type-tags have to be able to differentiate between various types and lists of other types. Similar to what Guice's
TypeLiteral
s are doing.– Andrey Tyukin
Dec 20 '18 at 1:02