枚举(Enum)
JDK中Enum的API:
概述:
Java在1.5中添加了java.lang.Enum
抽象类,它是所有枚举类型基类。JDK1.6后switch语句支持枚举类型。提供了一些基础属性和基础方法。同时,对把枚举用作Set
和Map
也提供了支持,即java.util.EnumSet
和java.util.EnumMap
。
使用枚举的好处:可以将常量按照相同类型组织起来,便于使用与管理。
枚举的典型应用场景:错误码、基础类型、基础数据、状态机等。
使用实例
public enum AddressEnum {
PROVINCE(1, "省份"),
CITY(2, "城市"),
COUNTY(3, "区县"),
TOWN(4, "镇");
private Integer code;
private String desc;
AddressEnum(Integer status, String desc) {
this.code = status;
this.desc = desc;
}
public Integer getCode() {
return code;
}
public String getDesc() {
return desc;
}
/**
* 通过编码获取枚举类型
* @param code
* @return
*/
public static AddressEnum getAddressEnumByCode(Integer code) {
if (code == null) return null;
for (AddressEnum address : AddressEnum.values()) {
if (address.getCode().intValue() == code.intValue()) {
return address;
}
}
return null;
}
/**
* 通过描述获取枚举类型
* @param desc
* @return
*/
public static AddressEnum getAddressEnumByDesc(String desc) {
if (StrUtil.isBlankIfStr(desc)) return null;
for (AddressEnum address : AddressEnum.values()) {
if (StrUtil.equals(address.getDesc(), desc)) {
return address;
}
}
return null;
}
}
//编译之后的class文件通过javap反编译之后得到如下代码:
public final class com.hmio.study.enumt.AddressEnum extends java.lang.Enum<com.hmio.study.enumt.AddressEnum> {
// 枚举产生之前 使用 public static final ... 常量 enum只是做了层封装
public static final com.hmio.study.enumt.AddressEnum PROVINCE;
public static final com.hmio.study.enumt.AddressEnum CITY;
public static final com.hmio.study.enumt.AddressEnum COUNTY;
public static final com.hmio.study.enumt.AddressEnum TOWN;
public static com.hmio.study.enumt.AddressEnum[] values();
public static com.hmio.study.enumt.AddressEnum valueOf(java.lang.String);
public java.lang.Integer getCode();
public java.lang.String getDesc();
public static com.hmio.study.enumt.AddressEnum getAddressEnumByCode(java.lang.Integer);
public static com.hmio.study.enumt.AddressEnum getAddressEnumByDesc(java.lang.String);
static {};
}
通过上面实例可以看出:
enum
是一种final
修饰的类,并且继承了java.lang.Enum
类,有自己的方法。
新建Enum类编译之后最终转为的格式为:
public final class XXXEnum extends java.lang.Enum<XXXEnum>
常用方法说明:
方法名称 | 描述 |
---|---|
values() | 以数组形式返回枚举类型的所有成员,顺序为声明的顺序。 |
valueOf(java.lang.String) | 将普通字符串转换为枚举实例,是枚举类重新的类方法 |
compareTo() | 比较两个枚举成员在定义时的顺序,因为java.lang.Enum 实现了Comparable接口 |
ordinal() | 获取枚举成员的索引位置,java.lang.Enum 内方法 |
枚举与switch
JDK1.6后switch语句支持枚举类型。switch case 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。switch 语句中的变量类型可以是: byte、short、int 、char或者枚举类。从 Java SE 7 开始,switch 支持字符串 String 类型了,同时 case 标签必须为字符串常量或字面量。
enum Color
{
RED, GREEN, BLUE;
}
public class MyClass {
public static void main(String[] args) {
Color myVar = Color.BLUE;
switch(myVar) {
case RED:
System.out.println("红色");
break;
case GREEN:
System.out.println("绿色");
break;
case BLUE:
System.out.println("蓝色");
break;
}
}
}
枚举与单例模式
单例模式可以说是最常使用的设计模式了,它的作用是确保某个类只有一个实例,自行实例化并向整个系统提供这个实例。在实际应用中,线程池、缓存、日志对象、对话框对象常被设计成单例,总之,选择单例模式就是为了避免不一致状态。
使用枚举实现单例模式
public enum DataSourceEnum {
DATASOURCE;
private DBConnection connection = null;
private DataSourceEnum() {
connection = new DBConnection();
}
public DBConnection getConnection() {
return connection;
}
}
EnumMap 与 EnumSet
为了更好地支持枚举类型,java.util 中添加了两个新类:EnumMap 和 EnumSet。使用它们可以更高效地操作枚举类型。
EnumMap 类
EnumMap 是专门为枚举类型量身定做的 Map 实现。虽然使用其他的 Map(如HashMap)实现也能完成枚举类型实例到值的映射,但是使用 EnumMap 会更加高效。
HashMap 只能接收同一枚举类型的实例作为键值,并且由于枚举类型实例的数量相对固定并且有限,所以 EnumMap 使用数组来存放与枚举类型对应的值,使得 EnumMap 的效率非常高。
EnumMap 的使用
// 定义数据库类型枚举
public enum DataBaseType {
MYSQL,ORACLE,DB2,SQLSERVER
}
// 某类中定义的获取数据库URL的方法以及EnumMap的声明
private EnumMap<DataBaseType,String> urls = new EnumMap<DataBaseType,String>(DataBaseType.class);
public DataBaseInfo() {
urls.put(DataBaseType.DB2,"jdbc:db2://localhost:5000/sample");
urls.put(DataBaseType.MYSQL,"jdbc:mysql://localhost/mydb");
urls.put(DataBaseType.ORACLE,"jdbc:oracle:thin:@localhost:1521:sample");
urls.put(DataBaseType.SQLSERVER,"jdbc:microsoft:sqlserver://sql:1433;Database=mydb");
}
//根据不同的数据库类型,返回对应的URL
// @param type DataBaseType 枚举类新实例
// @return
public String getURL(DataBaseType type) {
return this.urls.get(type);
}
JDK对EnumMap
的描述:
一个专门Map实现与枚举类型键一起使用。 枚举映射中的所有密钥必须来自创建映射时明确或隐式指定的单个枚举类型。 枚举地图在内部表示为数组。 这种表示非常紧凑和高效。
枚举映射以其键的自然顺序 (枚举枚举常数被声明的顺序)维护。 这反映在由所述集合的视图(返回的迭代keySet() , entrySet()和values() )。由集合视图返回的迭代器弱一致 :它们不会抛出
ConcurrentModificationException
,并且它们可能显示或可能不显示在迭代进行时发生的映射的任何修改的影响。不允许使用空键。 尝试插入空键将丢失
NullPointerException
。 然而,尝试测试是否存在空键或删除一个将会正常工作。 允许空值。像大多数集合实现一样,
EnumMap
不同步。 如果多个线程同时访问枚举映射,并且至少有一个线程修改映射,则应该在外部进行同步。 这通常通过在自然地封装枚举映射的某些对象上进行同步来实现。 如果没有此类对象存在,则应使用Collections.synchronizedMap(java.util.Map<K, V>)
方法“包装”。 这最好在创建时完成,以防止意外的不同步访问:Map<EnumKey, V> m = Collections.synchronizedMap(new EnumMap<EnumKey, V>(...));
实现注意事项:所有基本操作都在不间断的时间内执行。 他们可能(虽然不能保证)比他们的HashMap同行更快。
EnumSet 类
public abstract class EnumSet<E extends Enum<E>>
extends AbstractSet<E>
implements Cloneable, Serializable
EnumSet 是枚举类型的高性能 Set 实现,它要求放入它的枚举常量必须属于同一枚举类型。
JDK对EnumSet
的描述:
枚举集中的所有元素都必须来自创建集合时明确或隐式指定的单个枚举类型。
枚举集在内部表示为位向量。这种表示非常紧凑和高效。
由iterator返回的迭代器以自然顺序 (枚举枚举常量的声明顺序)遍历元素。 返回的迭代器是弱一致的 :它永远不会抛出
ConcurrentModificationException
,它可能显示或可能不显示在迭代进行中发生的集合的任何修改的影响。不允许使用零元素。 尝试插入一个空元素将抛出
NullPointerException
。 然而,尝试测试null元素的存在或删除一个将会正常工作。像大多数集合实现一样, EnumSet不同步。 如果多个线程同时访问枚举集,并且至少有一个线程修改该集合,则它应该在外部同步。 这通常通过在自然地封装枚举集的一些对象上进行同步来实现。 如果没有这样的对象存在,那么该组件应该使用
Collections.synchronizedSet(java.util.Set)
方法“包装”。 这最好在创建时完成,以防止意外的不同步访问:Set<MyEnum> s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));
实现注意事项:所有基本操作都在不间断的时间内执行。 他们很可能(虽然不能保证)比他们的
HashSet
同行快得多。 如果它们的参数也是一个枚举集,那么即使批量操作也会在一段时间内执行。
EnumSet使用
public static void main(String[] args) {
EnumSet<Color> enumSet = EnumSet.noneOf(Color.class);//EnumSet.noneOf()方法创建一个空的set
System.out.println(enumSet);
enumSet.add(Color.BLUE);
enumSet.add(Color.PURPLE);
System.out.println(enumSet);
EnumSet<Color> allOfEnumSet = EnumSet.allOf(Color.class);//EnumSet.allOf()方法创建一个满的set
System.out.println(allOfEnumSet);
EnumSet<Color> enumSetRange = EnumSet.range(Color.YELLOW,Color.BLUE);//EnumSet.range创建指定范围set
System.out.println(enumSetRange);
EnumSet<Color> enumSetComplement = EnumSet.complementOf(enumSetRange);//EnumSet.complementOf补集创建set
System.out.println(enumSetComplement);
EnumSet<Color> enumSetRange2 = EnumSet.range(Color.YELLOW,Color.BLUE);
System.out.println(enumSetRange2);
EnumSet<Color> enumSet1 = EnumSet.copyOf(enumSetRange2);//EnumSet.copyOf复制创建集合
System.out.println(enumSet1);
// EnumSet.copyOf 过滤集合相同的元素
List<Color> colors = new ArrayList<Color>();
colors.add(Color.PURPLE);
colors.add(Color.BLUE);
colors.add(Color.BLUE);
System.out.println(colors);
EnumSet<Color> enumSetCopyOf = EnumSet.copyOf(colors);//EnumSet.copyOf复制创建集合
System.out.println(enumSet);
}
枚举的好处以及与常量类的区别
-
枚举型可以直接与数据库打交道,我通常使用varchar类型存储,对应的是枚举的常量名。(数据库中好像也有枚举类型,不过也没用过)
-
switch语句支持枚举型,当switch使用int、String类型时,由于值的不稳定性往往会有越界的现象,对于这个的处理往往只能通过if条件筛选以及default模块来处理。而使用枚举型后,在编译期间限定类型,不允许发生越界的情况
-
当你使用常量类时,往往得通过equals去判断两者是否相等,使用枚举的话由于常量值地址唯一,可以用==直接对比,性能会有提高
-
常量类编译时,是直接把常量的值编译到类的二进制代码里,常量的值在升级中变化后,需要重新编译引用常量的类,因为里面存的是旧值。枚举类编译时,没有把常量值编译到代码里,即使常量的值发生变化,也不会影响引用常量的类。
-
枚举类编译后默认为final class,不允许继承可防止被子类修改。常量类可被继承修改、增加字段等,容易导致父类的不兼容。
总结:常量的定义在开发中是必不可少的,虽然无论是通过常量类定义常量还是枚举定义常量都可以满足常量定义的需求。建议使用枚举类型。
网友评论