Java Hashmap store only value of a particular type in key












1















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.










share|improve this question




















  • 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 TypeLiterals are doing.

    – Andrey Tyukin
    Dec 20 '18 at 1:02


















1















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.










share|improve this question




















  • 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 TypeLiterals are doing.

    – Andrey Tyukin
    Dec 20 '18 at 1:02
















1












1








1


2






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.










share|improve this question
















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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 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 TypeLiterals are doing.

    – Andrey Tyukin
    Dec 20 '18 at 1:02
















  • 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 TypeLiterals 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 TypeLiterals 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 TypeLiterals are doing.

– Andrey Tyukin
Dec 20 '18 at 1:02














1 Answer
1






active

oldest

votes


















1














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));
}
}





share|improve this answer


























  • 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. 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











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
});


}
});














draft saved

draft discarded


















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









1














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));
}
}





share|improve this answer


























  • 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. 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
















1














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));
}
}





share|improve this answer


























  • 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. 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














1












1








1







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));
}
}





share|improve this answer















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));
}
}






share|improve this answer














share|improve this answer



share|improve this answer








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. 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 @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. 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 @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




















draft saved

draft discarded




















































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.




draft saved


draft discarded














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





















































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







Popular posts from this blog

Monofisismo

Angular Downloading a file using contenturl with Basic Authentication

Olmecas