枚举类型(Enumerated Type)
什么是枚举?
枚举是一个被命名的整型常数的集合。在多种编程语言中都有使用(C/C++/c#/java等)。
示例
public enum Size { S, M, L };
为什么使用枚举?
在JDK1.5 之前,定义常量都是: public static fianl …… 。
类常量
public class Size { public static final int S = 1; public static final int M = 2; public static final int L = 3; }
接口常量
public interface Size { public static final int S = 1; public static final int M = 2; public static final int L = 3; }
使用
System.out.println(Size.L);//3
但是这种方式存在一些缺陷,详见下面使用枚举与直接定义常量的比较。
所以在5.0 版本 SDK中,在语言层面上增加了枚举类型。
创建枚举类型要使用 enum 关键字,隐含了所创建的类型都是 java.lang.Enum 类的子类(java.lang.Enum 是一个抽象类)。
枚举可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法(如添加字段,方法,遍历访问)。
枚举可以用在变量的类型匹配的检测中,可以提前发现编程错误。
枚举定义了一个变量的可使用范围。能够使程序可读性提高。
在编译期间限定类型,不允许发生越界的情况。
和一般的类中使用接口一样,enum枚举中同样可以实现接口,并实现接口中的所有方法,这样做的好处在于可以更方便地对枚举中的值进行排序、比较等操作,封装性更好。
使用枚举与直接定义常量的比较
1、两种引用方式相同,都是类名.属性”。
2、常量是直接编译在代码中的,而枚举则是一种类,你可以通过反射根据值反查出它的枚举形式是什么。
3、枚举可只定义枚举项而不定义枚举值,而类常量(或接口常量)则必须定义值,否则编译失败;
4、常量不是数据类型,只是说明被定义数据是不可变的常量;枚举是一种数据类型。
5、枚举定义了一组类型相同的成员且成员的语义清晰;常量也可以是一个数组(比如int[]),虽然其中元素类型相同,但是没法清晰指出其中元素的意义。
6、类常量可以被继承,枚举类型是不能有继承的,也就是说一个枚举常量定义完毕后,除非修改重构,否则无法做扩展。
注意:
enum 类型不支持 public 和 protected 修饰符的构造方法,因此构造函数一定要是 private 或 默认的。所以枚举对象是无法在程序中通过直接调用其构造方法来初始化的。
所有的枚举都继承自java.lang.Enum类。由于Java 不支持多继承,所以枚举对象不能再继承其他类。enum枚举类型是一个不可以被继承的final类。
定义 enum 类型时候,如果是简单类型,那么最后一个枚举值后不用跟任何一个符号;但如果有定制方法,那么最后一个枚举值与后面代码要用分号';'隔开,不能用逗号或空格。
由于 enum 类型的值实际上是通过运行期构造出对象来表示的,所以在 cluster(集群) 环境下,每个虚拟机都会构造出一个同义的枚举对象。因而在做比较操作时候就需要注意,如果直接通过使用等号 ( ‘ == ’ ) 操作符,这些看似一样的枚举值一定不相等,因为这不是同一个对象实例。
方法
1、toString
返回枚举常量名。
System.out.println(Size.L.toString());//L
2、valueOf(Class, String)
Size s = (Size) Enum.valueOf(Size.class, "L"); System.out.println(s.toString());// L
3、values()
返回一个包含全部枚举值的数组。
Size[] arr = Size.values(); for (Size s : arr) { System.out.println(s);//依次输出S、M、L }
4、ordinal()
返回枚举常量的位置,位置从0开始计数。
System.out.println(Size.M.ordinal());//1
枚举集合(EnumSet)
JDK5.0 中也增加了两个工具类 EnumSet 和 EnumMap,都放在 java.util 包中。
EnumSet 是一个针对枚举类型的高性能的 Set 接口实现。EnumSet 中装入的所有枚举对象都必须是同一种类型。
EnumSet保证集合中的元素不重复;
枚举 set 在内部表示为位向量。此表示形式非常紧凑且高效。此类的空间和时间性能应该很好,足以用作传统上基于 int 的“位标志”的替换形式,具有高品质、类型安全的优势。
如果其参数也是一个枚举 set,则批量操作(如 containsAll 和 retainAll)也应运行得非常快。
EnumSet 支持在枚举类型的所有值的某个范围中进行迭代。
EnumMap中的 key是enum类型,而value则可以是任意类型。
注意
像大多数 collection 实现一样,EnumSet 是不同步的。
如果多个线程同时访问一个枚举 set,并且至少有一个线程修改该 set,则此枚举 set 在外部应该是同步的。这通常是通过对自然封装该枚举 set 的对象执行同步操作来完成的。
如果不存在这样的对象,则应该使用 Collections.synchronizedSet(java.util.Set)
方法来“包装”该 set。最好在创建时完成这一操作,以防止意外的非同步访问。
Set<MyEnum> s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));
方法
1、range(E from, E to)
创建一个最初包含由两个指定端点所定义范围内的所有元素的枚举 set。
for (Size s : EnumSet.range(Size.M, Size.L)) { System.out.println(s);// 依次打印M、L }
2、有多个of方法用于创建一个最初包含指定元素的枚举 set。
枚举集合(EnumMap)
EnumMap 是一个高性能的 Map 接口实现。
EnumMap中的 key是enum类型,而value则可以是任意类型。
枚举映射在内部表示为数组。此表示形式非常紧凑且高效。
EnumMap 将丰富的和安全的 Map 接口与数组快速访问结合到一起,如果你希望要将一个枚举类型映射到一个值,你应该使用 EnumMap。
枚举映射根据其键的自然顺序 来维护(该顺序是声明枚举常量的顺序)。
注意
像大多数 collection 一样,EnumMap 是不同步的。
如果多个线程同时访问一个枚举映射,并且至少有一个线程修改该映射,则此枚举映射在外部应该是同步的。这一般通过对自然封装该枚举映射的某个对象进行同步来完成。
如果不存在这样的对象,则应该使用 Collections.synchronizedMap(java.util.Map<k, v="">)
方法来“包装”该枚举。最好在创建时完成这一操作,以防止意外的非同步访问。
Map<EnumKey, V> m = Collections.synchronizedMap(new EnumMap<EnumKey, V>(...));
应用
switch (Size.M) { case S: System.out.println("小"); break; case M: System.out.println("中 "); break; default: System.out.println("无"); }
自定义
public enum Size { // Java要求必须先定义 enum 实例。必须在enum实例序列的最后添加一个分号。 S(1, "small", "小"), M(2, "middle", "中"), L(3, "large", "大"); // 定义私有变量 private int code; private String en; private String cn; // 构造函数,只能为私有或默认 private Size(int code, String en, String cn) { this.code = code; this.en = en; this.cn = cn; } // 覆盖toString方法 public String toString() { return this.name() + "(编号:" + this.code + ",英文:" + this.en + ",中文:" + this.cn + ")"; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getEn() { return en; } public void setEn(String en) { this.en = en; } public String getCn() { return cn; } public void setCn(String cn) { this.cn = cn; } }
调用
Size[] arr = Size.values(); for (Size s : arr) { System.out.println(s); }
打印
S(编号:1,英文:small,中文:小) M(编号:2,英文:middle,中文:中) L(编号:3,英文:large,中文:大)
enum与class的关系?
代码:
public enum Size { S, M, L };
反编译:
反编译后可以发现:
1、生成了一个Size类,该类继承了java.lang.Enum。
2、Size类被final修饰,可知枚举不能被继承。
3、属性是public static final,可知枚举项是常量,在定义枚举项时尽量用大写。
可以把枚举看成是一个不能被继承的类。