Java Hashmap 仅在键中存储特定类型的值 [英] Java Hashmap store only value of a particular type in key

查看:57
本文介绍了Java Hashmap 仅在键中存储特定类型的值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在考虑创建一个 Hashmap 类,它允许我存储键和值.但是,该值只有在匹配特定类型时才能存储,并且该类型取决于键的运行时值.例如,如果键是 EMAIL(String.class),那么存储的值应该是 String 类型.

我有以下自定义枚举:

公共枚举 TestEnum {TDD,BDD,SHIFT_RIGHT,SHIFT_LEFT;}

我创建了以下类:

import java.util.HashMap;导入 java.util.Map;导入 java.util.regex.Pattern;公开课测试{private static final MapsessionData = new HashMap<>();公共枚举 ValidKeys {电子邮件(字符串.类),密码(字符串.类),FIRST_NAME(String.class),LAST_NAME(String.class),条件(TestEnum.class);私有类类型;私人布尔 isList;私有模式模式;ValidKeys(Class type, boolean isList) {this.type = 类型;this.isList = isList;}ValidKeys(Class type) {this.type = 类型;}}公共 <T>void setData(ValidKeys 键,T 值) {sessionData.put(key,value);}公共对象 getData(ValidKeys 键) {返回 key.type.cast(sessionData.get(key));}公共静态无效主(字符串 [] args){测试 t = 新测试();t.setData(ValidKeys.CONDITION,TestEnum.TDD);System.out.println(t.getData(ValidKeys.CONDITION));}}

我想使用诸如 setDatagetData 之类的方法并将值存储到 sessionData 中.此外,我想确保该值是否是一个对象列表,然后也正确存储.

我也在努力避免 toString 基本上我需要一个可以在没有类型转换的情况下工作的通用 getData.

解决方案

我见过一种特殊的模式用于这类事情,它是 Bloch 的 Typesafe Heterogenous Container 模式的变体.我不知道它是否有自己的名称,但由于没有更好的名称,我将其称为 Typesafe Enumerated Lookup Keys.

基本上,我在各种上下文中看到的一个问题是,您需要一组动态的键/值对,其中特定的键子集是具有预定义语义的众所周知".此外,每个键都与特定类型相关联.

显而易见"的解决方案是使用枚举.例如,您可以这样做:

public enum LookupKey { FOO, BAR }公共最终类存储库{私有最终 Map数据 = 新的 HashMap<>();public void put(LookupKey key, Object value) {data.put(key, value);}公共对象获取(LookupKey 键){返回 data.get(key);}}

这工作得很好,但明显的缺点是现在你需要在任何地方进行投射.例如,假设您知道 LookupKey.FOO 总是有一个 String 值,而 LookupKey.BAR 总是有一个 Integer值.你如何强制执行?使用此实现,您不能.

另外:在这个实现中,键集由枚举固定.您不能在运行时添加新的.对于某些应用程序来说这是一个优势,但在其他情况下,您确实希望在某些情况下允许使用新密钥.

这两个问题的解决方案基本相同:使 LookupKey 成为一流的实体,而不仅仅是枚举.例如:

/*** 知道自己名称和类型的密钥.*/公共最终类 LookupKey<T>{//这些是枚举"键:public static final LookupKeyFOO = new LookupKey<>("FOO", String.class);public static final LookupKeyBAR = new LookupKey<>("BAR", Integer.class);私有最终字符串名称;私人final Class<T>类型;public LookupKey(字符串名称,类类型){this.name = 名称;this.type = 类型;}/*** 返回此键的名称.*/公共字符串名称(){返回名称;}@覆盖公共字符串 toString() {返回名称;}/*** 将任意对象强制转换为该键的类型.** @param object 一个任意对象,例如从 Map 中检索.* @throws ClassCastException 如果参数类型错误.*/公共 T 演员表(对象对象){返回 type.cast(object);}//未显示:equals() 和 hashCode() 实现}

这让我们已经完成了大部分工作.您可以参考 LookupKey.FOOLookupKey.BAR 并且它们的行为与您预期的一样,但它们也知道相应的查找类型.您还可以通过创建 LookupKey 的新实例来定义自己的键.

如果我们想实现一些不错的类似枚举的能力,比如静态 values() 方法,我们只需要添加一个注册表.作为奖励,如果我们添加注册表,我们甚至不需要 equals()hashCode(),因为我们现在可以通过身份比较查找键.>

这堂课最后的样子:

/*** 知道自己名称和类型的密钥.*/公共最终类 LookupKey<T>{//这是所有已知键的注册表.//(需要先声明,因为create()函数需要它.)private static final Map>knownKeys = new HashMap<>();//这些是枚举"键:public static final LookupKeyFOO = create("FOO", String.class);public static final LookupKeyBAR = create("BAR", Integer.class);/*** 创建并注册一个新密钥.如果具有相同名称和类型的键* 已经存在,而是返回(飞轮模式).** @param name 唯一标识此键的名称.* @param type 与此键关联的数据类型.* @throws IllegalStateException 如果键名相同但不同* 类型已注册.*/公共静态<T>LookupKey创建(字符串名称,类 类型){同步(已知密钥){查找键现有 = knownKeys.get(name);如果(现有!= null){if (existing.type != type) {抛出新的非法状态异常("" + 名称的定义不兼容);}@SuppressWarnings("unchecked")//我们的不变量确保这是安全的LookupKeyuncheckedCast = (LookupKey) 存在;返回 uncheckedCast;}LookupKeykey = new LookupKey<>(名称,类型);knownKeys.put(name, key);返回键;}}/*** 返回所有当前已知的查找键的列表.*/公共静态列表>值(){同步(已知密钥){返回 Collections.unmodifiableList(新的 ArrayList(knownKeys.values()));}}私有最终字符串名称;私人final Class<T>类型;//私有构造函数.只有 create 方法应该调用它.私有 LookupKey(字符串名称,类 类型){this.name = 名称;this.type = 类型;}/*** 返回此键的名称.*/公共字符串名称(){返回名称;}@覆盖公共字符串 toString() {返回名称;}/*** 将任意对象强制转换为该键的类型.** @param object 一个任意对象,例如从 Map 中检索.* @throws ClassCastException 如果参数类型错误.*/公共 T 演员表(对象对象){返回 type.cast(object);}}

现在 LookupKey.values() 或多或少像枚举一样工作.您也可以添加自己的键,values() 之后会返回它们:

LookupKeymyKey = LookupKey.create("CUSTOM_DATA", Double.class);

一旦你有了这个 LookupKey 类,你现在可以实现一个使用这些键进行查找的类型安全存储库:

/*** 可以使用 {@link LookupKey} 查找的数据存储库.*/公共最终类存储库{private final Map, Object>数据 = 新的 HashMap<>();/*** 在存储库中设置一个值.** @param <T>正在存储的数据类型.* @param key 标识值的键.* @param value 对应的值.*/公共 <T>void put(LookupKey 键,T 值) {data.put(key, value);}/*** 从这个存储库中获取一个值.** @param <T>键标识的值的类型.* @param key 标识所需值的键.*/公共 <T>T get(LookupKey键){返回 key.cast(data.get(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.

解决方案

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

这篇关于Java Hashmap 仅在键中存储特定类型的值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆