枚举中的可配置值

我经常在我的代码中使用这个设计来维护可配置的值。 考虑以下代码:

public enum Options { REGEX_STRING("Some Regex"), REGEX_PATTERN(Pattern.compile(REGEX_STRING.getString()), false), THREAD_COUNT(2), OPTIONS_PATH("options.config", false), DEBUG(true), ALWAYS_SAVE_OPTIONS(true), THREAD_WAIT_MILLIS(1000); Object value; boolean saveValue = true; private Options(Object value) { this.value = value; } private Options(Object value, boolean saveValue) { this.value = value; this.saveValue = saveValue; } public void setValue(Object value) { this.value = value; } public Object getValue() { return value; } public String getString() { return value.toString(); } public boolean getBoolean() { Boolean booleanValue = (value instanceof Boolean) ? (Boolean) value : null; if (value == null) { try { booleanValue = Boolean.valueOf(value.toString()); } catch (Throwable t) { } } // We want a NullPointerException here return booleanValue.booleanValue(); } public int getInteger() { Integer integerValue = (value instanceof Number) ? ((Number) value).intValue() : null; if (integerValue == null) { try { integerValue = Integer.valueOf(value.toString()); } catch (Throwable t) { } } return integerValue.intValue(); } public float getFloat() { Float floatValue = (value instanceof Number) ? ((Number) value).floatValue() : null; if (floatValue == null) { try { floatValue = Float.valueOf(value.toString()); } catch (Throwable t) { } } return floatValue.floatValue(); } public static void saveToFile(String path) throws IOException { FileWriter fw = new FileWriter(path); Properties properties = new Properties(); for (Options option : Options.values()) { if (option.saveValue) { properties.setProperty(option.name(), option.getString()); } } if (DEBUG.getBoolean()) { properties.list(System.out); } properties.store(fw, null); } public static void loadFromFile(String path) throws IOException { FileReader fr = new FileReader(path); Properties properties = new Properties(); properties.load(fr); if (DEBUG.getBoolean()) { properties.list(System.out); } Object value = null; for (Options option : Options.values()) { if (option.saveValue) { Class clazz = option.value.getClass(); try { if (String.class.equals(clazz)) { value = properties.getProperty(option.name()); } else { value = clazz.getConstructor(String.class).newInstance(properties.getProperty(option.name())); } } catch (NoSuchMethodException ex) { Debug.log(ex); } catch (InstantiationException ex) { Debug.log(ex); } catch (IllegalAccessException ex) { Debug.log(ex); } catch (IllegalArgumentException ex) { Debug.log(ex); } catch (InvocationTargetException ex) { Debug.log(ex); } if (value != null) { option.setValue(value); } } } } } 

这样,我可以轻松地保存和检索文件中的值。 问题是我不想在任何地方重复这个代码。 就像我们所知,枚举不能延长; 所以无论我在哪里使用它,我都必须把所有这些方法放在那里。 我只想声明值,如果它们应该被保留。 每次都没有方法定义; 有任何想法吗?

使用enum来保存这样的可配置值看起来像是一个完全错误的设计。 枚举是单例,因此有效地在任何给定时间只能激活一个配置。

EnumMap听起来更像你需要的东西。 它位于enum的外部,因此您可以根据需要实例化任意数量的配置。

 import java.util.*; public class EnumMapExample { static enum Options { DEBUG, ALWAYS_SAVE, THREAD_COUNT; } public static void main(String[] args) { Map normalConfig = new EnumMap(Options.class); normalConfig.put(Options.DEBUG, false); normalConfig.put(Options.THREAD_COUNT, 3); System.out.println(normalConfig); // prints "{DEBUG=false, THREAD_COUNT=3}" Map debugConfig = new EnumMap(Options.class); debugConfig.put(Options.DEBUG, true); debugConfig.put(Options.THREAD_COUNT, 666); System.out.println(debugConfig); // prints "{DEBUG=true, THREAD_COUNT=666}" } } 

API链接

  • java.util.EnumMap

    用于enum类型键的专用Map实现。 枚举映射中的所有键必须来自创建映射时显式或隐式指定的单个枚举类型。 枚举映射在内部表示为数组。 这种表现非常紧凑和高效。

我尝试用枚举映射和属性文件做类似的事情(请参阅下面的代码)。 但我的枚举很简单,除了嵌入式案例外只有一个值。 我可能有一些更安全的东西。 我会环顾四周。

 package p; import java.util.*; import java.io.*; public class GenericAttributes> { public GenericAttributes(final Class keyType) { map = new EnumMap(this.keyType = keyType); } public GenericAttributes(final Class keyType, final Properties properties) { this(keyType); addStringProperties(properties); } public Object get(final T key) { // what does a null value mean? // depends on P's semantics return map.containsKey(key) ? map.get(key) : null; } public boolean contains(final T key) { return map.containsKey(key); } public void change(final T key, final Object value) { remove(key); put(key, value); } public Object put(final T key, final Object value) { if (map.containsKey(key)) throw new RuntimeException("map already contains: " + key); else return map.put(key, value); } public Object remove(final T key) { if (!map.containsKey(key)) throw new RuntimeException("map does not contain: " + key); return map.remove(key); } public String toString() { return toString(defaultEquals, defaultEndOfLine); } // maybe we don;t need this stuff // we have tests for it though // it might be useful public String toString(final String equals, final String endOfLine) { final StringBuffer sb = new StringBuffer(); for (Map.Entry entry : map.entrySet()) sb.append(entry.getKey()).append(equals).append(entry.getValue()).append(endOfLine); return sb.toString(); } public Properties toProperties() { final Properties p = new Properties(); for (Map.Entry entry : map.entrySet()) p.put(entry.getKey().toString(), entry.getValue().toString()); return p; } public void addStringProperties(final Properties properties) { // keep this for strings, but mostly do work in the enum class // ie static GenericAttributes fromProperties(); // which would use a fromString() for (Map.Entry entry : properties.entrySet()) { final String key = (String) entry.getKey(); final String value = (String) entry.getValue(); addProperty(key, value); } } public void addProperty(final String key, final Object value) { try { final T e = Enum.valueOf(keyType, key); map.put(e, value); } catch (IllegalArgumentException e) { System.err.println(key + " is not an enum from: " + keyType); } } public int size() { return map.size(); } public static Properties load(final InputStream inputStream,final Properties defaultProperties) { final Properties p=defaultProperties!=null?new Properties(defaultProperties):new Properties(); try { p.load(inputStream); } catch(IOException e) { throw new RuntimeException(e); } return p; } public static Properties load(final File file,final Properties defaultProperties) { Properties p=null; try { final InputStream is=new FileInputStream(file); p=load(is,defaultProperties); is.close(); } catch(IOException e) { throw new RuntimeException(e); } return p; } public static void store(final OutputStream outputStream, final Properties properties) { try { properties.store(outputStream, null); } catch (IOException e) { throw new RuntimeException(e); } } public static void store(final File file, final Properties properties) { try { final OutputStream os = new FileOutputStream(file); store(os, properties); os.close(); } catch (IOException e) { throw new RuntimeException(e); } } final Class keyType; static final String defaultEquals = "=", defaultEndOfLine = "\n"; private final EnumMap map; public static void main(String[] args) { } } package p; import static org.junit.Assert.*; import org.junit.*; import java.io.*; import java.util.*; enum A1 { foo,bar,baz; } enum A2 { x,y,z; } public class GenericAttributesTestCase { @Test public void testGenericAttributes() { new GenericAttributes(A1.class); } @Test public void testGenericAttributesKeyTypeProperties() { final Properties expected=gA1.toProperties(); final GenericAttributes gA=new GenericAttributes(A1.class,expected); final Properties actual=gA.toProperties(); assertEquals(expected,actual); } @Test public void testGet() { final A1 key=A1.foo; emptyGA1.put(key,null); final Object actual=emptyGA1.get(key); assertEquals(null,actual); } @Test public void testGetInteger() { // attributes.add(key,integer); // assertEquals(integer,attributes.get("key")); } @Test public void testContains() { for(A1 a:A1.values()) assertFalse(emptyGA1.contains(a)); } @Test public void testChange() { final A1 key=A1.foo; final Integer value=42; emptyGA1.put(key,value); final Integer expected=43; emptyGA1.change(key,expected); final Object actual=emptyGA1.get(key); assertEquals(expected,actual); } @Test public void testAdd() { final A1 key=A1.foo; final Integer expected=42; emptyGA1.put(key,expected); final Object actual=emptyGA1.get(key); assertEquals(expected,actual); } @Test public void testRemove() { final A1 key=A1.foo; final Integer value=42; emptyGA1.put(key,value); emptyGA1.remove(key); assertFalse(emptyGA1.contains(key)); } @Test public void testToString() { final String actual=gA1.toString(); final String expected="foo=a foo value\nbar=a bar value\n"; assertEquals(expected,actual); } @Test public void testToStringEqualsEndOfLine() { final String equals=","; final String endOFLine=";"; final String actual=gA1.toString(equals,endOFLine); final String expected="foo,a foo value;bar,a bar value;"; assertEquals(expected,actual); } @Test public void testEmbedded() { final String equals=","; final String endOfLine=";"; //System.out.println("toString(\""+equals+"\",\""+endOFLine+"\"):"); final String embedded=gA1.toString(equals,endOfLine); GenericAttributes gA2=new GenericAttributes(A2.class); gA2.put(A2.x,embedded); //System.out.println("embedded:\n"+gA2); // maybe do file={name=a.jpg;dx=1;zoom=.5}?? // no good, key must be used more than once // so file:a.jpg={} and hack // maybe file={name=...} will work // since we have to treat it specially anyway? // maybe this is better done in ss first // to see how it grows? } @Test public void testFromString() { // final Attributes a=Attributes.fromString(""); // final String expected=""; // assertEquals(expected,a.toString()); } @Test public void testToProperties() { final Properties expected=new Properties(); expected.setProperty("foo","a foo value"); expected.setProperty("bar","a bar value"); final Properties actual=gA1.toProperties(); assertEquals(expected,actual); } @Test public void testAddProperties() { final Properties p=gA1.toProperties(); final GenericAttributes ga=new GenericAttributes(A1.class); ga.addStringProperties(p); // assertEquals(ga1,ga); // fails since we need to define equals! // hack, go backwards final Properties p2=ga.toProperties(); assertEquals(p,p2); // hack until we define equals } @Test public void testStore() throws Exception { final Properties expected=gA1.toProperties(); final ByteArrayOutputStream baos=new ByteArrayOutputStream(); GenericAttributes.store(baos,expected); baos.close(); final byte[] bytes=baos.toByteArray(); final ByteArrayInputStream bais=new ByteArrayInputStream(bytes); final Properties actual=GenericAttributes.load(bais,null); bais.close(); assertEquals(expected,actual); } @Test public void testLoad() throws Exception { final Properties expected=gA1.toProperties(); final ByteArrayOutputStream baos=new ByteArrayOutputStream(); GenericAttributes.store(baos,expected); baos.close(); final ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray()); final Properties actual=GenericAttributes.load(bais,null); bais.close(); assertEquals(expected,actual); } @Test public void testMain() { // fail("Not yet implemented"); } GenericAttributes gA1=new GenericAttributes(A1.class); { gA1.put(A1.foo,"a foo value"); gA1.put(A1.bar,"a bar value"); } GenericAttributes emptyGA1=new GenericAttributes(A1.class); } 

回答你的评论:

似乎我通过使用枚举作为关键来获取值。 我可能很困惑。

枚举可以实现一个接口,每组枚举可以一个该基类的实例并委托对它的调用(参见http://java.sun.com/docs/books/effective/toc.html的第 34项)

我发现其他代码与我的通用属性(请参见下文),但我找不到任何测试,我不太确定我在做什么,除了可能添加一些更强大的打字。

我所有这一切的动机是为像picasa这样的照片浏览器存储一些属性,我想在一行属性文件中存储一堆图片的属性

 package p; import java.util.*; public enum GA { // like properties, seems like this wants to be constructed with a set of default values i(Integer.class) { Integer fromString(final String s) { return new Integer(s); } Integer fromNull() { return zero; // return empty string? } }, b(Boolean.class) { Boolean fromString(final String s) { return s.startsWith("t")?true:false; } Boolean fromNull() { return false; } }, d(Double.class) { Double fromString(final String s) { return new Double(s); } Double fromNull() { return new Double(zero); } }; GA() { this(String.class); } GA(final Class clazz) { this.clazz=clazz; } abstract Object fromString(String string); abstract Object fromNull(); static GenericAttributes fromProperties(final Properties properties) { final GenericAttributes pas=new GenericAttributes(GA.class); for(Map.Entry entry:properties.entrySet()) { final String key=(String)entry.getKey(); final GA pa=valueOf(key); if(pa!=null) { final String stringValue=(String)entry.getValue(); Object value=pa.fromString(stringValue); pas.addProperty(key,value); } else throw new RuntimeException(key+"is not a member of "+"GA"); } return pas; } // private final Object defaultValue; // lose type?; require cast? /* private */final Class clazz; static final Integer zero=new Integer(0); } 

如果您仍在寻找答案,可以尝试使用MIT许可证开源的属性库。 使用它,您不必指定字符串常量,所有内容都将由您定义的枚举确定。 而且,它还有其他一些function。 这个图书馆的亮点是:

  1. 所有属性键都在一个地方定义,即用户定义的enum
  2. 属性值可以包含变量(以$ sign开头,例如$PATH ),其中PATH是同一文件中的属性键
  3. 属性值可以作为指定的数据类型获取,因此无需将字符串值转换为所需的数据类型
  4. 可以获取属性值作为指定数据类型的列表
  5. 属性值可以是多行文本
  6. 可以使属性键成为必需或可选
  7. 如果值不可用,则可以指定属性键的默认值
  8. 是线程安全的

你可以在这里找到示例程序