在java中创建可扩展Enum行为的最佳方法

时间:2011-10-02 04:47:45

标签: java class inheritance inner-classes extends

我想创建一个类似于可扩展Enum的东西(理解在Java 6中不可能扩展Enums)。

这是我想做的事情:
我有许多“模型”类,每个类都有一组与之关联的字段。这些字段用于索引包含数据表示的地图。

我需要能够从Class OR实例obj访问Fields,如下所示:

MyModel.Fields.SOME_FIELD #=> has string value of "diff-from-field-name"

myModel.Fields.SOME_FIELD #=> has string value of "diff-from-field-name"

我还需要能够获得Model

的所有字段列表
MyModel.Fields.getKeys() #=> List<String> of all the string values ("diff-from-field name")

为每个Model定义“Fields”类时,我希望能够将定义保留在与Model相同的文件中。

public class MyModel {
    public static final Fields extends BaseFields { 
        public static final String SOME_FIELD = "diff-from-field-name";
        public static final String FOO = "bar";
    }

    public Fields Fields = new Fields();

    // Implement MyModel logic
}

我还想让OtherModel扩展MyModel并且可以继承MyModel.Fields中的Fields,然后在它上面添加自己的Fields ...

public class OtherModel extends MyModel {
   public static final class Fields extends MyModel.Fields { 
        public static final String CAT = "feline";
        ....

哪会允许

OtherModel.Fields.CAT #=> feline
OtherModel.Fields.SOME_FIELD #=> diff-from-field-name
OtherModel.Fields.FOO #=> bar
OtherModel.Fields.getKeys() #=> 3 ["feline", "diff-from-field-name", "bar"]

我试图让模型中“Fields”的定义尽可能简洁明了,因为各种开发人员都会构建这些“模型”对象。

由于

5 个答案:

答案 0 :(得分:2)

  

我需要能够从Class OR实例obj访问Fields,如下所示:

  MyModel.Fields.SOME_FIELD #=> has string value of "diff-from-field-name"

除非您使用真实enumSOME_FIELD是真实字段,否则在Java中无法实现。在任何一种情况下,“枚举”都不是可扩展的。

您在Java 6中可以做的最好的事情是将枚举建模为从String名称到int值的映射。这是可扩展的,但是从名称到值的映射会导致运行时成本......以及您的代码使用不是枚举成员的名称的可能性。


Java中enum类型不可扩展的原因是扩展的enum会破坏原始enum的隐式不变量,因此(因此)无法替代。

答案 1 :(得分:0)

我刚刚尝试了一些代码,试图按照你刚刚描述的那样做,而且真的很麻烦。

如果你在模型类的某个地方有一个Fields静态内部类:

public class Model {

  public static class Fields {

    public static final String CAT = "cat";
    protected static final List<String> KEYS = new ArrayList<String>();

    static {
      KEYS.add(CAT);
    }

    protected Fields() {}

    public static List<String> getKeys() {
      return Collections.unmodifiableList(KEYS);
    }
  }
}

你像这样扩展这个类:

public class ExtendedModel extends Model {

  public static class ExtendedFields extend Model.Fields {

    public static final String DOG = "dog";

    static {
      KEYS.add(DOG);
    }

    protected ExtendedFields() {}
  }
}
那么它的错误。如果你打电话给Model.Fields.getKeys(),你会得到你期望的结果:[cat],但是如果你打电话给ExtendedModel.ExtendedFields.getKeys(),你会得到同样的结果:[cat],没有dog 。原因是:getKeys()Model.Fields的静态成员,调用ExtendedModel.ExtendedFields.getKeys() 错误,因为您确实在那里调用了Model.Fields.getKeys()

因此,您要么使用实例方法操作,要么在所有Fields子类中创建静态getKeys()方法,这是错误的,我甚至无法描述。


也许您可以创建一个Field接口,客户可以实现该接口并插入您的模型。

public interface Field {
  String value();
}

public class Model {

  public static Field CAT = new Field() { 
    @Override public String value() {
      return "cat";
    }
  };

  protected final List<Field> fields = new ArrayList();

  public Model() {
    fields.add(CAT);
  }

  public List<Field> fields() {
    return Collections.unmodifiableList(fields);
  }      
}

public class ExtendedModel extends Model {

  public static Field DOG= new Field() { 
    @Override public String value() {
      return "dog";
    }
  };

  public ExtendedModel() {
    fields.add(DOG);
  } 
}

答案 2 :(得分:0)

我想知道你是否真的需要生成的字段枚举。如果要生成基于模型的字段的enum列表,为什么不生成列出所有字段及其类型的类?即,生成类并不比静态或动态生成的枚举更难,而且它更高效,更灵活,编译友好。

所以你可以从模型中生成像

这样的东西
class BaseClass { // with BaseField
    String field;
    int number;
}

class ExtendedClass extends BaseClass { // with OtherFields
    String otherField;
    long counter;
}

发明自己的类型系统真的有好处吗?

答案 3 :(得分:0)

诅咒 - 出于某种原因,我对我提出的解决方案的非常冗长的回应没有发布。

我只是粗略地概述一下,如果有人想要更多细节,我可以在有更多时间/耐心时重新发布。

我创建了一个java类(称为ActiveField),所有内部Fields都继承该类。 每个内部字段类都定义了一系列字段:

public static class Fields extends ActiveField {  
     public static final String KEY = "key_value";  
}

在ActiveRecord类中,我有一个非静态方法getKeys(),它使用反射来查看this上的所有字段,迭代,获取它们的值并将它们作为List返回。 / p>

它似乎工作得很好 - 如果您对更完整的代码示例感兴趣,请告诉我。

答案 4 :(得分:0)

我能够提出一个使用反射似乎有效的解决方案 - 我还没有完成全部测试,这更让我在看到我有什么可能的选择时傻瓜。

ActiveField:所有其他“Fields”类(将成为我的Model类中的内部类)的Java类将扩展。这有一个非静态方法“getKeys()”,它查看“this”的类,并从中提取所有Fields的列表。然后检查修改器,字段类型和套管等一些内容,以确保它只查看符合我惯例的字段:所有“字段键”必须是String类型的“public static final”,字段名称必须全部大写。

public class ActiveField {
private final String key;

protected ActiveField() {
    this.key = null;
}

public ActiveField(String key) {
    System.out.println(key);
    if (key == null) {
        this.key = "key:unknown";
    } else {
        this.key = key;
    }
}

public String toString() {
    return this.key;
}

@SuppressWarnings("unchecked")
public List<String> getKeys() {
    ArrayList<String> keys = new ArrayList<String>();
    ArrayList<String> names = new ArrayList<String>();

    Class cls;
    try {
        cls = Class.forName(this.getClass().getName());
    } catch (ClassNotFoundException e) {
        return keys;
    }

    Field fieldList[] = cls.getFields();

    for (Field fld : fieldList) {
        int mod = fld.getModifiers();

        // Only look at public static final fields
        if(!Modifier.isPublic(mod) || !Modifier.isStatic(mod) || !Modifier.isFinal(mod)) {
            continue;
        }

        // Only look at String fields
        if(!String.class.equals(fld.getType())) {
            continue;
        }

        // Only look at upper case fields
        if(!fld.getName().toUpperCase().equals(fld.getName())) {
            continue;
        }

        // Get the value of the field
        String value = null;
        try {
            value = StringUtils.stripToNull((String) fld.get(this));
        } catch (IllegalArgumentException e) {
            continue;
        } catch (IllegalAccessException e) {
            continue;
        }

        // Do not add duplicate or null keys, or previously added named fields
        if(value == null || names.contains(fld.getName()) || keys.contains(value)) {
            continue;
        }

        // Success! Add key to key list
        keys.add(value);
        // Add field named to process field names list
        names.add(fld.getName());

    }

    return keys;
}

public int size() {
    return getKeys().size();
} 
}

然后在我的“模型”类中(它们是Map周围的花哨包装器,可以使用Fields字段编制索引)

public class ActiveResource {     / **      *用于建模ActiveResource objs的基本字段 - 从中​​继承的所有类          * ActiveResource应该具有这些字段/值(除非被覆盖)      * /     public static class Fields extends ActiveField {         public static final String CREATED_AT =“node:created”;         public static final String LAST_MODIFIED_AT =“node:lastModified”;     }

public static final Fields Fields = new Fields();

    ... other model specific stuff ...

}

然后我可以创建一个扩展我的ActiveResource类

的类Foo
public class Foo extends ActiveResource {

public static class Fields extends ActiveResource.Fields {
    public static final String FILE_REFERENCE = "fileReference";
    public static final String TYPE = "type";
}

public static final Fields Fields = new Fields();

    ... other Foo specific stuff ...

现在,我可以执行以下操作:

ActiveResource ar = new ActiveResource().
Foo foo = new Foo();

ar.Fields.size() #=> 2
foo.Fields.size() #=> 4

ar.Fields.getKeys() #=> ["fileReference", "type", "node:created", "node:lastModified"]
foo.Fields.getKeys() #=> ["node:created", "node:lastModified"]


ar.Fields.CREATED_AT #=> "node:created"
foo.Fields.CREATED_AT #=> "node:created"
foo.Fields.TYPE #=> "type"
etc.

我还可以将“字段”作为模型对象的静态字段进行访问

Foo.Fields.size(); Foo.Fields.getKeys(); Foo.Fields.CREATED_AT; Foo.Fields.FILE_REFERENCE;

到目前为止,这看起来是一个非常好的解决方案,需要最少的指导来构建新的模型。