我正在寻找创建一个Hashmap类,该类允许我存储键和值。但是,只有与特定类型匹配的值才能存储,并且该类型取决于键的运行时值。例如,如果键为EMAIL(String.class)
,则存储的值应为String
类型。
我有以下自定义枚举:
public enum TestEnum {
TDD,
BDD,
SHIFT_RIGHT,
SHIFT_LEFT;
}
我创建了以下课程:
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));
}
}
我想使用诸如setData
和getData
之类的方法,并将值存储到sessionData
中。另外,我想确保该值是否是对象列表,然后也可以正确存储。
我也在努力避免toString,我基本上需要一个通用的getData,它无需类型转换就可以工作。
答案 0 :(得分:1)
我见过一种用于这种事情的特殊模式,它是Bloch的Typesafe异构容器模式的一种变体。我不知道它是否有名称,但是由于缺少更好的名称,我将其称为Typesafe枚举查找键。
基本上,在各种情况下出现的一个问题是,您需要一组动态的键/值对,其中键的特定子集是具有预定义语义的“众所周知的”。此外,每个键都与特定类型相关联。
“显而易见的”解决方案是使用枚举。例如,您可以这样做:
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);
}
}
这很好用,但是明显的缺点是现在您需要在任何地方投射。例如,假设您知道LookupKey.FOO
总是具有String
的值,而LookupKey.BAR
总是具有Integer
的值。您如何执行?使用此实现,您将无法实现。
也:在此实现中,键集由枚举固定。您不能在运行时添加新的。对于某些应用程序来说这是一个优势,但是在其他情况下,您确实确实希望在某些情况下允许新密钥。
这两个问题的解决方案基本上是相同的:使LookupKey
成为一流的实体,而不仅仅是一个枚举。例如:
/**
* 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
}
这已经使我们获得了大部分机会。您可以引用LookupKey.FOO
和LookupKey.BAR
,它们的行为与您期望的一样,但它们也知道相应的查找类型。您还可以通过创建LookupKey
的新实例来定义自己的密钥。
如果我们想实现一些不错的类似于枚举的功能,例如静态values()
方法,我们只需要添加一个注册表即可。另外,如果添加注册表,我们甚至不需要equals()
和hashCode()
,因为现在我们可以按标识比较查找键了。
这是全班最终的样子:
/**
* 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);
}
}
现在LookupKey.values()
的工作方式与枚举差不多。您还可以添加自己的密钥,values()
随后将返回它们:
LookupKey<Double> myKey = LookupKey.create("CUSTOM_DATA", Double.class);
有了这个LookupKey
类之后,您现在就可以实现使用这些键进行查找的类型安全存储库:
/**
* 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));
}
}