Java枚举的反向查找


java的枚举常常被用来替代常量值,每个枚举值代表一个特定的常数。
在反序列化时有常常需要用到常数到枚举的转换,这就涉及到枚举的反向查找。

1、从枚举名反向查找枚举
这种方法是最先使用也最为简便的
可以用到枚举的静态方法valueOf(String name)
valueOf方法为内置方法,使用简便,但在查找不到枚举时会抛出异常。
熟悉异常的同学可能知道异常抛出时,需要收集虚拟机的调用堆栈上下文信息,对性能影响较大。
使用时,常常需要使用这么一个反序列化查找方法
E find(String name, E defaultValue),在查找不到时能够返回一个默认值而不是抛出异常

2、从枚举值所包含的描述值反向查找枚举
例如这种枚举定义

public enum SomeEnum{
    A("ADes", 1),
    B("BDes", 2),
    unknown("UNKNWONDes", 3);

    private string des;
    private int order;

    private SomeEnum(string des, int order){
        this.des = des;
        this.order = order; 
    }
}

此时可以在枚举类中增加一个HashMap,并在类加载时初始化好。

public enum SomeEnum{
    A("ADes"),
    B("BDes"),
    unknown("UNKNWONDes");

    private string des;

    private static final map lookup = new hashmap();

    static {
        for(SomeEnum e : EnumSet.allOf(SomeEnum.class)){
            lookup.put(e.des, e);
        }
    }

    private SomeEnum(string des){
        this.des = des;
    }

    public static SomeEnum find(string des, SomeEnum defaultValue){
        SomeEnum value = lookup.get(des);
        if(value == null){
            return defaultValue;
        }
        return value;
    }
}
                        

3、进一步,如果枚举中有多个描述值,并且描述值类型不一样
这时初始化代码和查找代码需要写多遍。

public enum SomeEnum{
	A("ADes", 1),
	B("BDes", 2),
	unknown("UNKNWONDes", 3);

	private string des;
	private int order;

	private static final map lookup = new hashmap();

	private static final map lookup_int = new hashmap();

	static {
		for(SomeEnum e : EnumSet.allOf(SomeEnum.class)){
			lookup.put(e.des, e); 
			lookup_int.put(e.order, e);
		}
	}

	private SomeEnum(string des, int order){
		this.des = des;
		this.order = order;
	}

	public static SomeEnum find(string des, SomeEnum defaultValue){
		SomeEnum value = lookup.get(des);
		if(value == null){
			return defaultValue;
		}
		return value;
	}

	public static SomeEnum find(int order, SomeEnum defaultValue){
		SomeEnum value = lookup_int.get(order);
		if(value == null){
			return defaultValue;
		}
		return value;
	}
}

不少代码有重复,
枚举类需要自己实现反向查找映射关系在HashMap中的初始化。
根据DRY原则,重复代码地方一般都有一定的坏味道。

 

4、改进
枚举的反向查找其实只需关注两件事情,
a 提供枚举值描述到枚举值的映射
b 能从枚举值描述查找到枚举值,查找不到能提供默认值

据此,可以提炼出枚举反向查找的帮助类:

public class EnumFindHelper<T extends Enum<T>, K> {
	protected Map<K, T> map = new HashMap<K, T>();

	public EnumFindHelper(Class<T> clazz, EnumKeyGetter<T, K> keyGetter) {
		try {
			for (T enumValue : EnumSet.allOf(clazz)) {
				map.put(keyGetter.getKey(enumValue), enumValue);
			}
		} catch (Exception e) {
			//eror handler
		}
	}

	public T find(K key, T defautValue) {
		T value = map.get(key);
		if (value == null) {
			value = defautValue;
		}
		return value;
	}
}


这里需要一个接口定义:

public static interface EnumKeyGetter<T extends Enum<T>, K> {

		K getKey(T enumValue);

}


查找帮助类EnumFindHelper构造时需要EnumKeyGetter来提供枚举值到枚举类型的的对应关系。
定义枚举类时实现EnumKeyGetter接口,并传入EnumFindHelper。

修改后的枚举定义可能为:

public enum SomeEnum{
	A("ADes", 1),
	B("BDes", 2),
	unknown("UNKNWONDes", 3);

	private String des;
	private int order;

	private SomeEnum(string des, int order){
		this.des = des;
		this.order = order;
	}

	static final EnumFindHelper<SomeEnum, String> desptHelper = new EnumFindHelper<SomeEnum, String>(
		SomeEnum.class, new DesptGetter());
		
	static final EnumFindHelper<SomeEnum, Integer> orderHelper = new EnumFindHelper<SomeEnum, Integer>(
		SomeEnum.class, new OrderKeyGetter());

	static class DesptGetter implements EnumKeyGetter<SomeEnum, String> {
		@Override
		public String getKey(SomeEnum enumValue) {
			return enumValue.des;
		}
	}
	
	static class OrderKeyGetter implements EnumKeyGetter<SomeEnum, Integer> {
		@Override
		public String Integer(SomeEnum enumValue) {
			return enumValue.order;
		}
	}
	
	public static SomeEnum find(string despt, SomeEnum defaultValue){
		return desptHelper.find(des, defaultValue);
	}

	public static SomeEnum find(int order, SomeEnum defaultValue){
		return orderHelper.find(des, defaultValue);
	}
}

 

EnumFindHelper内部类的定义有些繁琐,不过java8之后用lambda实现后会简便一些。

 

5 总结

每个枚举类的反向查找功能委托一个帮助类来实现,从枚举类的构造上来看,比较类似GOF的桥接模式。

而从委托类的实现查找功能来看,需要一个EnumKeyGetter接口,每种不同的接口实现不同的查找方式,有根据描述字符查找,有根据枚举顺序值查找。

这又有些类似GOF的策略模式。

 

当然不需要去纠结于哪种设计模式,代码的设计就是要求代码不冗余,功能的重用,依赖的解耦。

上面的设计中:

1)利用EnumFindHelper做到枚举查找的算法重用,利用一个map提供枚举描述到枚举值的查找。

2)EnumFindHelper初始化时需要构造好查找用的map,所以会依赖于具体的枚举定义。

这里抽象出EnumKeyGetter,EnumFindHelper由依赖于具体的枚举类改为依赖于EnumKeyGetter接口,在构造时注入了EnumKeyGetter的实现。

这么看起来是不是有些DPI(依赖注入)的感觉?!

 

(转载请注明出处)

 

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM