【问题标题】:Configurable Values in Enum枚举中的可配置值
【发布时间】:2011-02-23 12:46:24
【问题描述】:

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

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

这样,我可以轻松地从文件中保存和检索值。问题是我不想到处重复这段代码。正如我们所知,枚举不能扩展。所以无论我在哪里使用它,我都必须把所有这些方法放在那里。我只想声明这些值以及是否应该保留它们。每次都没有方法定义;有什么想法吗?

【问题讨论】:

  • 为什么不使用 use 属性?
  • 因为我不想 :) 使用 .properties 文件(使用 Properties 类检索),您必须在引号中指定属性的名称。在编译时未验证,而仅在运行时验证。我的意思是属性由我们指定的查找键(字符串)的映射支持。我希望这是一个标识符; java 标识符,如枚举成员。

标签: java configuration enums extend


【解决方案1】:

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

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

您可以找到示例程序here

【讨论】:

    【解决方案2】:

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

    package p;
    
    import java.util.*;
    import java.io.*;
    
    public class GenericAttributes<T extends Enum<T>> {
        public GenericAttributes(final Class<T> keyType) {
            map = new EnumMap<T, Object>(this.keyType = keyType);
        }
    
        public GenericAttributes(final Class<T> 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<T, Object> 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<T, Object> 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
            // i.e. static GenericAttributes<PA> fromProperties();
            // which would use a fromString()
            for (Map.Entry<Object, Object> 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<T> keyType;
        static final String defaultEquals = "=", defaultEndOfLine = "\n";
        private final EnumMap<T, Object> 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>(A1.class);
        }
        @Test public void testGenericAttributesKeyTypeProperties() {
            final Properties expected=gA1.toProperties();
            final GenericAttributes<A1> gA=new GenericAttributes<A1>(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<A2> gA2=new GenericAttributes<A2>(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<A1> ga=new GenericAttributes<A1>(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<A1> gA1=new GenericAttributes<A1>(A1.class);
        {
            gA1.put(A1.foo,"a foo value");
            gA1.put(A1.bar,"a bar value");
        }
        GenericAttributes<A1> emptyGA1=new GenericAttributes<A1>(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<GA> fromProperties(final Properties properties) {
            final GenericAttributes<GA> pas=new GenericAttributes<GA>(GA.class);
            for(Map.Entry<Object,Object> 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);
    }
    

    【讨论】:

    • 问题仍然存在,我必须指定字符串常量,而不是标识符,才能获取值。有什么不同?在编译时检查标识符,在运行时检查字符串常量。我认为解决它的一种方法是使用所有这些方法创建一个抽象类,然后从中继承 FileOptions、NetworkOptions 等类,我在其中定义静态最终选项,如 FileName 等
    【解决方案3】:

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

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

    import java.util.*;
    public class EnumMapExample {
        static enum Options {
            DEBUG, ALWAYS_SAVE, THREAD_COUNT;
        }
        public static void main(String[] args) {
            Map<Options,Object> normalConfig = new EnumMap<Options,Object>(Options.class);
            normalConfig.put(Options.DEBUG, false);
            normalConfig.put(Options.THREAD_COUNT, 3);
            System.out.println(normalConfig);
            // prints "{DEBUG=false, THREAD_COUNT=3}"
    
            Map<Options,Object> debugConfig = new EnumMap<Options,Object>(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

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

    【讨论】:

    • 您是否认为从 EnumMap 扩展并将我所有的方法,如 getInteger 和 saveToFile;并提供一个接受所需特定配置标识符(例如 Options.class)的枚举类实例的构造函数是个好主意吗?
    • @Omer:Effective Java 第 2 版,第 16 条:优先组合优于继承。我不认为扩展EnumMap 是个好主意;创建具有EnumMap 字段的东西会更好。搜索“组合与继承”等短语以进行全面讨论。
    • 我的想法不正确,我的想法并不能解决问题。但是,如果我们想要 SAME 配置键/标识的多个值,您的建议似乎很有用。我正在寻找的是多组配置键/标识符中的每组的单组值,每组配置键/标识符都在不同的枚举中定义。例如,枚举 NetworkOptions、枚举 ParserOptions、枚举 FileOptions ......但我不希望将所有这些 getter 和 saveToFile 方法分别添加到每个枚举中。我想以某种方式将它们放在一个地方,让每个 Option 枚举都使用它。
    猜你喜欢
    • 2020-02-27
    • 1970-01-01
    • 1970-01-01
    • 2019-07-08
    • 1970-01-01
    • 2011-11-14
    • 2021-10-21
    • 2013-01-08
    • 2021-04-20
    相关资源
    最近更新 更多