使用枚举之前必须了解一下,枚举的定义是什么。《Java编程思想》上说,枚举可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。这句话是个什么意思呢? 你比如说季节类,它只能有春夏秋冬四个对象;星期类,他只有周一到周天七个对象。诸如这种对象的个数有限的,我们都可以用枚举实现它。
首先看一个简单的枚举定义的例子:
enum Season{
SPRING,SUMMER,AUTUMN,WINTER
}
我们先不着急的使用这个enum,我们先看下字节码部分(这里只贴出来一部分)
看着图片我们可以发现以下信息:
1.Season被定义成为了一个类。 而且被声明为final,这说明他不可以被继承; 且继承了Enum类,说明他也不能在继承其他的类; 因此------>枚举不可以继承别的类,不可以被继承;
2.发现四个季节都被定义为static final类型,说明在链接阶段就已经初始化,且不可以在运行中改变他的值。
3.发现2个static函数,values()和valueOf(),干什么用的后面再说。
4.一个static代码块,里面调用了构造函数。就是invokespecail那一句。了解jvm的同学的可能会奇怪,一般情况下,构造函数是public的,应该用invokevirtual调用,而invokespecail一般调用非public方法的(比如private修饰的)<想了解的可以看下我写的静态分配和动态分派 的博客>;了解这点内容的就知道,enum类的构造方法是private的;所以不可以直接new;
基本上看了javap出来的字节码我们大致可以知道上述四点内容。下来,我们开始使用枚举。为了代码比较有权威性,我尽量使用《Thinking in Java》的代码。
enum Shrubber {
GRAND,CRAWLING,HANGING
}
public class EnumClass {
public static void main(String[] args) {
//获取enum的每个实例
for(Shrubber s:Shrubber.values()){
//打印每个具体值的位置
System.out.println(s.ordinal());
//打印具体的值。用name函数
System.out.println(s.name());
//打印所属枚举的类名
System.out.println(s.getDeclaringClass());
//判断 两个枚举常量是否相等
System.out.println(s.equals(Shrubber.GRAND));
System.out.println(s == Shrubber.GRAND);
}
//根据给定的名字返回对应enum的实例,如果枚举不存在,则抛出异常。
System.out.println(Enum.valueOf(Shrubber.class, "GRAND"));
}
}
输出:
0
GRAND
class BasicEnum.Shrubber
true
true
1
CRAWLING
class BasicEnum.Shrubber
false
false
2
HANGING
class BasicEnum.Shrubber
false
false
GRAND
上面列出了几种常用的方法,具体方法可以查JDK,这出不深入讲解。在上面我们发现,我们获取一个枚举的实例的时候,并没有通过new得到,因为构造方法是private的。 我们使用的是values()方法,通过他可以获取enum实例,这是一种方法。这也是字节码里面提到的一个static方法。
.2.<Thinking in Java>提到了静态导入,其作用是可以直接访问枚举类内的变量,但是至于实际中好用不好用很难讲。自己在使用时一般不加这个,因为代码看起来会很奇怪。
public enum Spiciness {
NOT,MILD,MEDIUM,HOT,FLAMING
}
//这里静态导入
import static StaticImport.Spiciness.*;
public class Burrito {
Spiciness degree;
public Burrito(Spiciness degree){
this.degree=degree;
}
public String toString(){
return "Burrito is"+degree;
}
public static void main(String[] args) {
// 这里访问不需要再写enum的类名
System.out.println(new Burrito(NOT));
}
}
上面的代码如果开始不知道枚举类存在的话,让你看起来会很奇怪,这个NOT从哪来的?因为一般都是用 public static final这样的语句来修饰,如果在java文件内没有找到就会让你很疑惑,除非注意到了静态导入。
3.我们通过字节码可以知道,enum实际上是一个final的类,既然是类,我们就可以在里面重写方法,甚至是main方法。但是要注意一点,在我们要先定义enum实例,在定义方法或者属性。
4.switch(exp),exp只能为一个整型表达式或者枚举常量。整型表达式是指int的基本类型或者可以被Integer包装的类型,例如byte,short,char(他们可以被转化为int)。所以在switch中使用枚举也是枚举的常用地方。很简单,举个例子就行了。没什么要注意的地方。
public enum Signal {
GREEN,YELLOW,RED;
}
public class TrafficLight {
//获取一个enum引用的方法
Signal color = Signal.RED;
public void change(){
switch(color){
case RED:
color = Signal.GREEN;
break;
case GREEN:
color = Signal.RED;
break;
case YELLOW:
color = Signal.YELLOW;
break;
}
}
public String toString(){
return color.name();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
TrafficLight t = new TrafficLight();
for(int i=0;i<7;i++){
System.out.println(t);
t.change();
}
}
}
这里我们直接通过访问枚举的一个实例来获取枚举的引用。
5.values()方法的特别地方。
我们前面说到过,enum类是继承Enum得到的。我们查看Enum的实现时,发现他并没有values()这个方法,而我们自己月没有实现,那么这个方法是怎么来的?Enum的实现
其实,values()这个static方法是由编译器添加的static方法,同样的还有ValueOf()方法。Enum里面实现的valuesOf有两个参数,jvm给添加的只有一个参数。在这里很容易出现下面一个错误:
enum Signal {
GREEN,YELLOW,RED;
}
public class demo111 {
public static void main(String[] args) {
Enum a = Signal.GREEN;
//values方法有jvm添加,向上转型后此方法丢失
// for(Signal s : a.values(){
//
// }
}
}
5. 枚举可以实现接口用来扩充自己。
6.EnumSet
《effective java》中讲到:尽量用EnumSet代替位域。
set是一种集合,只能向其中添加不重复的对象;对于enum,不能从一个给定enum中删除或者添加实例。 而EnumSset就是通过enum创建一种替代品,以替代传统的基于int的“位标志”,这些标记可以代表“开/关”信息表示添加、删除。例如
public enum AlarmPoints {
STAIR1,STAIR2,LOBBY,OFFICE1,OFFICE2,OFFICE3,
OFFICE4,BATHROOM,UTILITY,KICHEN
}
import static EnumSet.AlarmPoints.*;
import java.util.EnumSet;
public class EnumSets {
/**
* Enum的缺点:无法添加或者删除enum中的元素;通过EnumSet可以做到
*/
public static void main(String[] args) {
//noneof根据传入的的class新建一个新的enumset,此时他为空
EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class);
//想enumset中添加AlarmPoint中有的元素
points.add(BATHROOM);
//EnumSet.of根据传入的参数创建一个新的enum
points.addAll(EnumSet.of(STAIR2,LOBBY,KICHEN));
//不管放入的顺序怎么样,它内部都按照AlarmPoints的顺序打印
System.out.println(points);
}
}
EnumSet的基础是long,一个long有64位,而一个enum实例只需要一位表示开关,因此:在不超过一个long的表达能力下,EnumSet可以应用于最多64个元素的enum;如果超过64个,EnumSet会追加一个long.
7.EnumMap
《Effective Java》中说:用EnumMap代替序数索引。
EnumMap是一中特殊的map,他要求其中的key必须来自于一个enum。见下面例子。
public enum AlarmPoints {
STAIR1,STAIR2,LOBBY,OFFICE1,OFFICE2,OFFICE3,OFFICE4,BATHROOM,UTILITY,KICHEN
}
import static EnumMap.AlarmPoints.*;
import java.util.EnumMap;
interface Command{void action();}
/*
* EnumMap 用来代替序数索引0
*/
public class EnumMaps {
public static void main(String[] args) {
//EnumMap 创建需要一个enum作为key ; 再讲此enum的class作为形参传入
EnumMap<AlarmPoints,Command> em = new EnumMap<AlarmPoints,Command>(AlarmPoints.class);
//为enum的每一个常量添加事件
em.put(BATHROOM, new Command(){
public void action(){
System.out.println("BATHROOM BOOM");
}
});
em.put(OFFICE1, new Command(){
public void action(){
System.out.println("OFFICE1 fires");
}
});
//提取action的事件
em.get(BATHROOM).action();
}
}
8.Enum实现责任链
<详见我的设计模式中的责任链部分>
9.常量相关方法
enum是允许为每个enum的实例编写方法的。
import java.text.DateFormat;
import java.util.Date;
public enum ConstantSpecificMethod {
DATE_TIME{
public String getInfo(){
return DateFormat.getDateInstance().format(new Date());
}
}
,
CLASSPATH{
public String getInfo(){
return System.getenv("CLASSPATH");
}
}
,
VERSION{
public String getInfo(){
return System.getProperty("java.version");
}
String getS(){
return "hhhh";
}
};
/**
*这里的方法一定不能忘记。因为上面都是enum的实例,实例中的方法必须要在类里面先
*定义,实例里面才能 ;
*无论这里是抽象方法还是具体方法;抽象方法只是在后面实现了多态而已,具体方法说明
*在实例中被覆写了。 《Thinking in Java》里面在这里用的抽象方法;
*在这里:尤其注意enum里面定义的是enum的实例,可以理解为一个对象在new之后的实例。
*/
public String getInfo(){
return "this is class.GetInfo()";
}
public static void main(String[] args){
for(ConstantSpecificMethod s :ConstantSpecificMethod.values()){
System.out.println(s.getInfo());
}
}
}
10.多路分发