在Java中字符串转枚举大致有两类方法:
一是使用JDK为枚举提供的valueOf
方法转换,一个也是JDK提供的通过获取所有枚举对象的数组values
方法,再遍历筛选得到目标枚举对象
先简单写一个枚举说明这两种方法
/**
* @author SeasonSoy
* @since 2019/9/20 11:53
*/
public enum Operator {
ADD,
SUBTRACT,
MULTIPLY,
DIVIDE
}
valueOf
使用方式
valueOf
方法的使用方式是一个静态方法获取,参数是String
类型,返回值是对应的类型
Operator operator = Operator.valueOf("ADD");
特点分析
但是如果需要使用这个方法,需要注意的是,这个方法会抛出两种异常,一个是NullPointerException
和IllegalArgumentException
因为枚举底层生成的字节码都是调用的Enum
类的valueOf
方法,其源码如下:
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
很明显的可以看到,实际获取的方法是通过对应枚举的字节码对象,调用enumConstantDirectory()
方法,这个方法返回的是一个Map<String, T>
枚举对象的Map,就通过get(name)获取对应的对象
这时的result
返回值有可能为null
,如果不为空则返回,如果为空则抛出异常
另外再仔细看一下enumConstantDirectory()
这个方法
Map<String, T> enumConstantDirectory() {
if (enumConstantDirectory == null) {
T[] universe = getEnumConstantsShared();
if (universe == null)
throw new IllegalArgumentException(
getName() + " is not an enum type");
Map<String, T> m = new HashMap<>(2 * universe.length);
for (T constant : universe)
m.put(((Enum<?>)constant).name(), constant);
enumConstantDirectory = m;
}
return enumConstantDirectory;
}
这个方法在Class
类中,可访问范围是default
;其中有一个比较关键的enumConstantDirectory
成员,用来存枚举对象
private volatile transient Map<String, T> enumConstantDirectory = null;
这个方法大致干的事情就是:如果一个类的字节码如果是一个枚举类,可以通过把常量数组在这里封装成一个Map并返回,如果不是第一次调用,就直接返回已经封装好的Map
在上一段源码中还有一个getEnumConstantsShared()
方法获取T[]
数组比较重要,等会儿会在第二种方式里再说明
使用要点
- 无需再添加多的取值方法,可直接使用
- 在上层代码中,必须要处理好可能会抛出的异常
- 看情况对字符串参数调用一次
toUpperCase
(这点非必须,也不会再提)
values
以values为标题名并不是说,一定需要使用values
方法,而是一种遍历过滤取值的思想
使用方式
其实本质上没什么差别,就是写法习惯上可能会有区别,而产生以下几种方式
方式一
可以在枚举内部直接提供一个方法,或者写在其他地方也行
public Operator fromText(String operator) {
Operator[] operators = Operator.values();
for (Operator member : operators) {
if (member.toString().equalsIgnoreCase(operator)) {
return member;
}
}
return null;
}
朴实无华的代码,没什么好说的
方式二
如果是JDK1.8以上版本,可以使用stream,而且可以加强这个获取过程
public Operator fromText(String operator) {
return Arrays.stream(Operator.values())
.filter(member -> member.toString().equalsIgnoreCase(operator))
.findFirst()
.orElse(null);
}
加强的时候,可以做更多的流操作,比如添加更多的filter等等
方式三
虽然标题说是方式三,其实再整得花里胡哨的,也跟上述方式没什么区别,只是用来学习
上文有提到过,可以通过字节码获取,方法如下
Operator[] enumConstants = Operator.class.getEnumConstants();
剩下的和上面处理一样
至于这个Class提供的getEnumConstants()
方法,可以看一看
public T[] getEnumConstants() {
T[] values = getEnumConstantsShared();
return (values != null) ? values.clone() : null;
}
这个public方法,直接提供了调用getEnumConstantsShared()
方法的方式;更值得学习的是返回的values数组,调用了clone
方法,我猜作者的意图是为了保护values的引用,因为只是一个浅拷贝,并不会影响其中的枚举对象
getEnumConstantsShared()
的源码如下
T[] getEnumConstantsShared() {
if (enumConstants == null) {
if (!isEnum()) return null;
try {
final Method values = getMethod("values");
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
values.setAccessible(true);
return null;
}
});
@SuppressWarnings("unchecked")
T[] temporaryConstants = (T[])values.invoke(null);
enumConstants = temporaryConstants;
}
// These can happen when users concoct enum-like classes
// that don't comply with the enum spec.
catch (InvocationTargetException | NoSuchMethodException |
IllegalAccessException ex) { return null; }
}
return enumConstants;
}
其中enumConstants
是一个数组成员,定义如下
private volatile transient T[] enumConstants = null;
可以看出底层代码通过反射其实最终还是执行的values
方法,而且也是为了防止在外部修改enumConstants
的指针,才使用的克隆方法
不过使用这种方式,可以写一个更泛化的方法,作为工具类使用。以下是示例
public static <T extends Enum> T parseEnum(String text, Class<T> clazz) {
T[] enumConstants = clazz.getEnumConstants();
if(Objects.isNull(enumConstants) || enumConstants.length == 0)
return null;
return Arrays.stream(enumConstants)
.filter(member -> member.toString().equalsIgnoreCase(text))
.findFirst()
.orElse(null);
}
另外
至于values()
具体是怎么工作的,可以看一看这一篇《java枚举类型的实现原理》,其中比较详细的说明了反编译以后的枚举字节码