【问题标题】:Dictionary-like data structure. Is this a good practice?类似字典的数据结构。这是一个好习惯吗?
【发布时间】:2012-09-14 09:04:43
【问题描述】:

我需要一个数据结构来存储不同类型的对象。例如StringBoolean等类。
是否使用Map<String, Object> 使用密钥获得相应的对象,假设您知道如何将其转换为一个好的做法?
有没有更好的解决方案?

【问题讨论】:

  • 更好的做法是使用适当的对象类。然而,这并不总是可行的。
  • @PeterLawrey:我需要映射到不同类型的对象。或者切换到更合适的数据结构?虽然是哪个?
  • 如果没有更多细节就无法判断,但通常当人们使用Map<String, Object> 时,他们本可以使用自定义类,在这种情况下,该类要好得多。跨度>
  • 更少的演员,更好的节目。

标签: java generics data-structures map


【解决方案1】:

这是我不久前写的PropretyHolder 的完美用例。您可以在my blog 上详细了解它。我在开发它时考虑到了不变性,请随时根据您的需要进行调整。

一般来说,如果您想从 Java 中的类型安全中获益,您需要知道您的密钥。我的意思是——几乎不可能开发出密钥来自外部源的类型安全解决方案。


这是一个知道其值类型的特殊键(不完整请download the source获取完整版本):

public class PropertyKey<T> {
    private final Class<T> clazz;
    private final String name;

    public PropertyKey(Class<T> valueType, String name) {
        this.clazz = valueType;
        this.name = name;
    }

    public boolean checkType(Object value) {
        if (null == value) {
            return true;
        }
        return this.clazz.isAssignableFrom(value.getClass());
    }

    ... rest of the class

}

然后你开发一个利用它的数据结构:

public class PropertyHolder {

    private final ImmutableMap<PropertyKey<?>, ?> storage;

    /**
     * Returns value for the key of the type extending-the-one-declared-in-the {@link PropertyKey}.
     * 
     * @param key {@link PropertyKey} instance.
     * @return Value of the type declared in the key.
     */
    @SuppressWarnings("unchecked")
    public <T extends Serializable> T get(PropertyKey<T> key) {
        return (T) storage.get(key);
    }

    /**
     * Adds key/value pair to the state and returns new 
     * {@link PropertyHolder} with this state.
     * 
     * @param key {@link PropertyKey} instance.
     * @param value Value of type specified in {@link PropertyKey}.
     * @return New {@link PropertyHolder} with updated state.
     */
    public <T> PropertyHolder put(PropertyKey<T> key, T value) {
        Preconditions.checkNotNull(key, "PropertyKey cannot be null");
        Preconditions.checkNotNull(value, "Value for key %s is null", 
                key);
        Preconditions.checkArgument(key.checkType(value), 
                "Property \"%s\" was given " 
                + "value of a wrong type \"%s\"", key, value);
        // Creates ImmutableMap.Builder with new key/value pair.
        return new PropertyHolder(filterOutKey(key)
                .put(key, value).build());
    }

    /**
     * Returns {@link Builder} with all the elements from the state except for the given ket.
     * 
     * @param key The key to remove.
     * @return {@link Builder} for further processing.
     */
    private <T> Builder<PropertyKey<? extends Serializable>, Serializable> filterOutKey(PropertyKey<T> key) {
        Builder<PropertyKey<? extends Serializable>, Serializable> builder = ImmutableMap
                .<PropertyKey<? extends Serializable>, Serializable> builder();
        for (Entry<PropertyKey<? extends Serializable>, Serializable> entry : this.storage.entrySet()) {
            if (!entry.getKey().equals(key)) {
                builder.put(entry);
            }
        }
        return builder;
    }

    ... rest of the class

}

这里省略了很多不必要的细节,如果有不清楚的地方请告诉我。

【讨论】:

  • 请考虑,在此处发布相关代码以使您的答案自成一体。
  • @Ivan:这部分我没看懂:return new PropertyHolder(filterOutKey(key).put(key, value).build());
  • @munyengm 谢谢你的建议,我已经更新了我的答案。
  • @Ivan:你不应该同时覆盖equalshashCode 以使用PropertyHolder 作为地图中的键吗?
  • @Jim PropertyHolder 是一个不可变类,我在内部使用 Guava 不可变集合,filterOutKey 将生成器作为不可变映射的副本提供给删除了给定键的副本。然后我添加新的键/值对并构建新的地图。
【解决方案2】:

typesafe heterogeneous container 可用于此目的:

import java.util.HashMap;
import java.util.Map;

public class Container {

    private Map<Class<?>, Object> container = new HashMap<Class<?>, Object>();

    public <T> void putElement(Class<T> type, T instance) {
        if (type == null) {
            throw new NullPointerException("Type is null");
        }
        //container.put(type, instance); // 'v1'
        container.put(type, type.cast(instance)); // 'v2' runtime type safety!
    }

    public <T> T getElement(Class<T> type) {
        return type.cast(container.get(type));
    }

    public static void main(String[] args) {

        Container myCont = new Container();
        myCont.putElement(String.class, "aaa");
        myCont.putElement(Boolean.class, true);
        myCont.putElement(String[].class, new String[] {"one", "two"});

        System.out.println(myCont.getElement(String.class));
        System.out.println(myCont.getElement(String[].class)[1]);

    }

}

限制:这个容器的形式只能存储一个实例/对象类型。

putElement() 中,您可以通过使用动态转换实现运行时类型安全。但是,这会增加额外的开销。

例如:尝试将原始类对象传递给容器。注意异常发生的地方:

Class raw = Class.forName("MyClass");
myCont.putElement(raw, "aaa"); //ClassCastException if using 'v2'
System.out.println(myCont.getElement(raw)); //ClassCastException if using 'v1'

【讨论】:

  • 酷。它最常见的用例是什么?
  • 我不明白你为什么在putElement 中将instance 转换为type。它已经保证是T 类型(除非您希望使用反射来调用它)。另外,我是否认为每种类型只能保存一个实例对象?虽然这并不违背 OP 本身所描述的内容,但我认为他可能想要存储多个 String 例如。值得一提的是这个警告。
  • @Ivan:这种模式的想法是parameterize 是键而不是容器本身。如果您正在处理不同的对象,这会给您更大的灵活性。一个常见的用例可以是一个用于存储数据库表行的容器,该行可以具有不同的列类型,并且您希望以typesafe 的方式访问它们。
  • @LorandBendig:但是这样我只能添加一个String.Right 类型的成员吗?
  • @Jim 是的,请在我的回答中看到“限制”。可能您想存储更多相同类型的实例,但这在问题中没有明确说明
猜你喜欢
  • 2020-11-07
  • 1970-01-01
  • 2010-09-11
  • 1970-01-01
  • 2011-07-17
  • 1970-01-01
  • 1970-01-01
  • 2010-11-12
  • 1970-01-01
相关资源
最近更新 更多