java - Java Hashmap 仅在键中存储特定类型的值
问题描述
我正在考虑创建一个允许我存储键和值的 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
and之类的方法getData
并将值存储到sessionData
. 此外,我想确保该值是否是一个对象列表,那么也可以正确存储。
我也在努力避免 toString 基本上我需要一个通用的 getData ,它可以在没有类型转换的情况下工作。
解决方案
我见过一种用于这类事情的特殊模式,它是 Bloch 的 Typesafe Heterogenous Container 模式的变体。我不知道它是否有自己的名称,但由于没有更好的名称,我将其称为 Typesafe Enumerated Lookup Keys。
基本上,我在各种情况下看到的一个问题是,您需要一组动态的键/值对,其中键的特定子集是“众所周知的”,具有预定义的语义。此外,每个键都与特定类型相关联。
“显而易见”的解决方案是使用枚举。例如,您可以这样做:
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
andLookupKey.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));
}
}
推荐阅读
- python - 怎么做 ”#!” 可移植到在不同位置安装了 python 的不同系统?
- python - 为什么不打印
- python - 使用 Apache Spark 连接到 Presto 时,SQL Exception-Unsupported type JAVA_OBJECT
- django - 未找到“allproduct”的反向。“allproduct”不是有效的视图函数或模式名称
- c# - 比较数字范围内的小数
- c++ - 为 c++ 正确构建 dlib 后的未知错误,同时导入它
- asp.net-core - CORS 政策错误
- postgresql - 比较 2 列并在 PostgreSQL 中的新表中输出差异
- node.js - Implementing Spotify's authorization flow using NextJS's api routes throws cors error
- javascript - 带有反例的 JavaScript 闭包问题