是否可以使用仅允许基于键的值类型的映射?

时间:2011-11-15 00:21:33

标签: java

我知道这听起来有点疯狂,但现在确实如此。我有一个代表一组不同属性的枚举类型。每个都可能只是一个字符串,但强制执行某种类型的安全性会很好。因此,基本上检查与每个枚举值关联的类型,如果不匹配则抛出异常。我想这可以通过实例来完成,但我很好奇是否有另一种方法可以在没有instanceof的情况下执行此操作。我知道这可能不可能,但我很好奇。

编辑,我创建了一个新示例,我认为这说明了我要求的更好:

public class CmisProperties {

    public enum CmisPropEnum{
        Name (PropertyIds.NAME, new String() ),
        CreatedBy (PropertyIds.CREATED_BY, new String()  ),
        CreationDate (PropertyIds.CREATION_DATE, new Date() ),
        LastModifiedBy (PropertyIds.LAST_MODIFIED_BY, new String() ),
        LastModificationDate (PropertyIds.LAST_MODIFICATION_DATE, new Date() ),
        ChangeToken (PropertyIds.CHANGE_TOKEN, new String()  );

            private String propId;

        CmisPropEnum ( String propId , Object templateObject ){
            this.propId = propId;
        }

        public <T> String getPropId(){
            return propId;
        }
    }

        private Map<CmisPropEnum, Object> propertyMap = new HashMap<CmisPropEnum, Object>();

    public Object getProperty(CmisPropEnum propEnum){
         return propertyMap.get(propEnum.getPropId());
    }

    public void setProperty( CmisPropEnum propEnum, Object value){
             propertyMap.put(propEnum, value);
    }
}

稍后我想要发生这种情况:

CmisProperties props = new CmisProperties();
/* This causes a compile time exception */
props.setProperty(CmisPropEnum.CreationDate, "foobar" );
/* This I want to be ok, because the type matches that in the enum */
props.setProperty(CmisPropEnum.CreationDate, new Date() );

3 个答案:

答案 0 :(得分:1)

查看Josh Bloch的 Effective Java ,第29项,其中他描述了一个他称之为收藏夹的“类型安全异构容器”。 API是

public class Favorites {
   public <T> void putFavorite(Class<T> type, T instance);
   public <T> T getFavorite(Class<T> type);
}

我认为它符合您的需求(可能更改名称???)。你可以打电话给

favorite.putFavorite(Name.getClass(), "Fred");
favorite.putFavorite(ADate.getClass(), new Date(1234));

以后

Date date = favorite.getFavorite(ADate.getClass());

答案 1 :(得分:1)

正如已经提到的那样,无可争议的,你需要基于类型(即泛型)的类具有可变性。这是您示例的相应通用版本:

public class Properties {

  public static class Property<E> {
    private Property(String name) { this.name = name; }
    private final String name;
    public String getName() { return name; }
  }
  public static final Property<String> NAME = new Property<String>("name");
  // ... other properties

  private Map<Property<?>, Object> propertyMap = 
    new HashMap<Property<?>, Object>();

  @SuppressWarnings("unchecked") 
  public <E> E getProperty(Property<E> property){
    return (E) propertyMap.get(property);
  }
  public <E> void setProperty(Property<E> property, E value){
    propertyMap.put(property, value);
  } 

}

用法是类型安全的,并在编译时检查:

Properties p = new Properties();
p.setProperty(Properties.NAME, "a string"); // only strings allowed for NAME
String s = p.getProperty(Properties.NAME); // can only get strings for NAME

答案 2 :(得分:0)

枚举不能是通用的,所以我们需要一个普通的类

public class Prop<T>
{
    // some predefined props
    static public final Prop<String> NAME = new Prop<>("Name", String.class);
    ...

    public Prop(String name, Class<T> type) // it's ok, anyone can create new kind of Prop
    {...}

    Class<T> getClassT() {...}

}

然后set / get属性方法可以有更强的静态类型检查:

private Map< Prop,Object > propMap = new HashMap<>();

public <T> void setProperty(Prop<T> key, T value){
    propMap.put(key, value);
}

@SuppressWarnings("unchecked")
public <T> T getProperty(Prop<T> key)
{
    return (T)propMap.get(key);
}

这样就不会编译

setProperty(Prop.NAME, new Integer(1)); // fail

int x = getProperty(Prop.NAME); //fail

请注意,propMap中的每个条目都有一个Prop<X>的密钥,X的值为X,而X可能与条目不同条目。我们无法在Java中对Map表达这种约束;但约束确实是由app逻辑强制执行的(即setProperty()只插入这样的条目)

getProperty中,我们必须取消未经检查的警告。这是合理的,因为我们知道密钥的值必须是T类型,这是由于前面提到的约束。避免明确禁止警告的一个技巧是Class.cast()

public <T> T getProperty(Prop<T> key)
{
    return key.getClassT().cast( propMap.get(key) );
}

但这只是一个技巧,因为基本上我们将@SupressWarnings移到了Class.cast()。这是性能和语义清晰度方面的更糟糕版本。