您的位置:首页 > 职场人生

黑马程序员——其他6:枚举

2015-08-10 00:37 507 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

1 概述

枚举也是JDK1.5的新特性之一,由于这一知识点的重要性,因此将其单独拿出来进行介绍。枚举的作用就是当我们将一个类以枚举的形式定义时,可以在类的内部事先定义好该类的具体实例对象。那么使用者是无法自己创建枚举对象的,而是只能获取事先规定好的实例对象来使用,否则将无法通过编译。那么枚举相当于给类的使用者规定了一个使用范文,当超出这个范围的时候,将在编译阶段给出错误提示。
其实这样的应用实例有很多,比如定义一个表示星期的枚举,使用0-6这7个整型值来表示不同的星期,那么试图创建这一范围以外的的星期实例对象是错误的;再比如,定义表示红绿灯的枚举,规定这一枚举的只有红、黄、绿这三个实例对象,那么使用者将无法创建除此以外的其他红绿灯实例对象。那么从以上的例子可以看出,枚举同样可以用于描述某一类事物,但是在描述的同时,按照需求对这一类事物的具体对象作出了限定。
那么这样作出限定的好处是,枚举的使用者只能按照枚举定义者规定的方式使用它,任何超出这一限定范围外的操作都将视为错误,可以保证代码执行的安全性。

2 示例

为了帮助大家理解枚举的作用及其原理,我们先通过一个普通的类来模拟枚举的作用。
需求:定义一个表示星期的类WeekDay,规定该类的实例对象只能有7个,分别表示星期一(Monday)、星期二(Tuesday)、星期三(Wednesday)、星期四(Thursday)、星期五(Friday)、(Saturday)、星期日(Sunday),并通过0-6的整型值对这些对象进行区分。
思路1:
(1) 既然WeekDay类的使用者只能使用事先定义好的实例对象,因此有必要将WeekDay的构造方法设置为私有,以防止使用者自定义WeekDay实例对象;
(2) 既然要事先定义好WeekDay的实例对象,并提供给使用者,那么我们可以在WeekDay类内部定义7个常量,也即修饰符为“public static final”的WeekDay实例对象,这些常量的变量名就是一个星期中的七天。那么“创建”WeekDay类实例对象的唯一方式就是,通过类名调用其静态成员。
代码1:
class WeekDay {
//7个常量
public static final WeekDay MONDAY= new WeekDay();
public static final WeekDay TUESDAY = new WeekDay();
public static final WeekDay WEDNESDAY = new WeekDay();
public static final WeekDay THURSDAY = new WeekDay();
public static final WeekDay FRIDAY = new WeekDay();
public static final WeekDay SATURDAY = new WeekDay();
public static final WeekDay SUNDAY = new WeekDay();

//将构造方法私有化,以防外部类自定义WeekDay实例对象
private WeekDay(){}

public WeekDay nextDay(){
if(this == MONDAY)
return TUESDAY;
else if(this == TUESDAY)
return WEDNESDAY;
else if(this == WEDNESDAY)
return THURSDAY;
else if(this == THURSDAY)
return FRIDAY;
else if(this == FRIDAY)
return SATURDAY;
else if(this == SATURDAY)
return SUNDAY;
else
return MONDAY;
}
public String toString(){
if(this == MONDAY)
return "MONDAY";
else if(this == TUESDAY)
return "TUESDAY";
else if(this == WEDNESDAY)
return "WEDNESDAY";
else if(this == THURSDAY)
return "THURSDAY";
else if(this == FRIDAY)
return "FRIDAY";
else if(this == SATURDAY)
return "SATURDAY";
else
return "SUNDAY";
}
}
public class EnumTest {
public static void main(String[] args) {
WeekDay monday = WeekDay.MONDAY;
System.out.println("Todayis "+monday.toString());
System.out.println("Nextday is " + monday.nextDay());

WeekDay tuesday = WeekDay.TUESDAY;
System.out.println("Todayis "+tuesday.toString());
System.out.println("Nextday is " + tuesday.nextDay());

WeekDay wednesday = WeekDay.WEDNESDAY;
System.out.println("Todayis "+wednesday.toString());
System.out.println("Nextday is " + wednesday.nextDay());

WeekDay thursday = WeekDay.THURSDAY;
System.out.println("Todayis "+thursday.toString());
System.out.println("Nextday is " + thursday.nextDay());

WeekDay friday = WeekDay.FRIDAY;
System.out.println("Todayis "+friday.toString());
System.out.println("Nextday is " + friday.nextDay());

WeekDay saturday = WeekDay.SATURDAY;
System.out.println("Todayis "+saturday.toString());
System.out.println("Nextday is " + saturday.nextDay());

WeekDay sunday = WeekDay.SUNDAY;
System.out.println("Todayis "+sunday.toString());
System.out.println("Nextday is " + sunday.nextDay());

//自定义WeekDay实例对象时无法通过编译的
//WeekDaymyWeekDay = new WeekDay();
}
}
测试类EnumTest的执行结果为:
Today is MONDAY
Next day is TUESDAY
Today is TUESDAY
Next day is WEDNESDAY
Today is WEDNESDAY
Next day is THURSDAY
Today is THURSDAY
Next day is FRIDAY
Today is FRIDAY
Next day is SATURDAY
Today is SATURDAY
Next day is SUNDAY
Today is SUNDAY
Next day is MONDAY
代码说明:
(1) nextDay方法表示返回“明天”的星期。由于该方法必然是由一个WeekDay对象去调用,因此通过this关键将此对象与7个实例对象的进行比较,并返回下一个星期。
(2) 通过上述WeekDay的定义方式,事先规定了WeekDay实例对象的取值范围,如果想要获取其实例对象,只能通过调用其静态成员获取,而自定义实例对象将无法通过编译,就像测试类EnumTest的最后一行代码,如果去掉注释符将编译失败。

思路2:
以上思路的实现方式是在WeekDay内部创建7个本类实例对象,并将这些对象设置为静态常量。由于所有对象均定义在了一个类内部,因此nextDay和toString方法,需要定义长长的if/else语句。
另一种实现思路是,将WeekDay定义为抽象父类,并将nextDay和toString方法定义为抽象方法。当然7个实例对象依然定义在WeekDay内部作为静态常量存在,但是这些对象以匿名内部类的形式存在,并复写WeekDay的抽象nextDay和toString方法,由于每个匿名内部类就是WeekDay的一个的实例对象,因此不必通过if语句判断此对象是星期几,直接返回下一个星期和此对象的代表的字符串形式星期即可。
代码2:
abstract class WeekDay {
private WeekDay(){}

//将nextDay和toString方法定义为抽象方法,需要子类实现
public abstract WeekDay nextDay();
public abstract String toString();

//定义7个匿名内部类
//每个匿名内部类需要复写nextDay和toString方法
public static final WeekDay MONDAY = new WeekDay(){
@Override
public WeekDay nextDay(){
return TUESDAY;
}
@Override
public String toString(){
return "MONDAY";
}
};
public static final WeekDay TUESDAY = new WeekDay(){
@Override
public WeekDay nextDay(){
return WEDNESDAY;
}
@Override
public String toString(){
return "TUESDAY";
}
};
public static final WeekDay WEDNESDAY = new WeekDay(){
@Override
public WeekDay nextDay(){
return THURSDAY;
}
@Override
public String toString(){
return "WEDNESDAY";
}
};
public static final WeekDay THURSDAY = new WeekDay(){
@Override
public WeekDay nextDay(){
return FRIDAY;
}
@Override
public String toString(){
return "THURSDAY";
}
};
public static final WeekDay FRIDAY = new WeekDay(){
@Override
public WeekDay nextDay(){
return SATURDAY;
}
@Override
public String toString(){
return "FRIDAY";
}
};
public static final WeekDay SATURDAY = new WeekDay(){
@Override
public WeekDay nextDay(){
return SUNDAY;
}
@Override
public String toString(){
return "SATURDAY";
}
};
public static final WeekDay SUNDAY = new WeekDay(){
@Override
public WeekDay nextDay(){
return MONDAY;
}
@Override
public String toString(){
return "SUNDAY";
}
};
}
测试类EnumTest的执行结果为:
Today is MONDAY
Next day is TUESDAY
Today is TUESDAY
Next day is WEDNESDAY
Today is WEDNESDAY
Next day is THURSDAY
Today is THURSDAY
Next day is FRIDAY
Today is FRIDAY
Next day is SATURDAY
Today is SATURDAY
Next day is SUNDAY
Today is SUNDAY
Next day is MONDAY
与思路1的执行结果是相同的。那么思路2的特点在于,nextDay和toString方法由每个子类自己去实现,而不再由父类统一地定义,避免了冗长的if/else语句。这两个思路实际并没有本质区别,只是表现形式不同。

3 枚举的应用

3.1 枚举的简单应用

在岁以上演示示例中,我们通过一个普通了模拟了枚举的特点。那么实际上枚举就是在具备上述WeekDay类的特点的同时,简化了一些代码的书写,提高了代码的阅读性。我们还是以星期为例对枚举的应用进行演示。
代码3:
//定义一个枚举
enum WeekDay {
//规定枚举可创建的实例对象范围
MONDAY,TUESDAY,WEDNESDAY,THRUSDAY,FRIDAY,SATURDAY,SUNDAY
}
public class EnumTest2 {
publi cstatic void main(String[] args) {
WeekDay monday = WeekDay.MONDAY;
System.out.println(monday);

//枚举的非静态方法
System.out.println("-----method-----");
System.out.println("name= " + monday.name());//名称
System.out.println("ordinal= " + monday.ordinal());//顺序
System.out.println("Class= " + monday.getClass());//字节码对象

//枚举中的静态方法
System.out.println("-----staticmethod-----");
System.out.println(WeekDay.valueOf("SUNDAY"));
System.out.println(Arrays.toString(WeekDay.values()));
}
}
以上代码的执行结果为:
MONDAY
-----method-----
name = MONDAY
ordinal = 0
Class = class WeekDay
-----static method-----
SUNDAY
[MONDAY, TUESDAY, WEDNESDAY, THRUSDAY,FRIDAY, SATURDAY, SUNDAY]
代码说明:
(1) 定义枚举的关键词是“enum”,可以认为是enumeration的缩写。枚举的定义格式与普通基本一致,也是在enum关键词后面加上枚举的名称,后接一对大括号,用于定义枚举的内容。
(2) 枚举的构造方法是默认私有的,因此外部类不能自己创建枚举类实例对象。
(3) 代码3中演示的枚举是最为简单的枚举定义方式,WeekDay内部只定义了7个用大写字母标识的常量。实际上这些常量就是WeekDay枚举的实例对象,与代码2中定义在WeekDay类成员位置上的7个常量具有相同的含义,只不过不再需要显示的标识修饰符“public static final”了。此外,如上所示,每个常量之间使用逗号进行分隔。
(4) 虽然我们并没有在WeekDay枚举内显示的重写toString方法,但是从测试类EnumTest2的执行结果来看,枚举中为每个常量(WeekDay枚举的实例对象)均复写toString方法,返回值就是常量的变量名。
(5) 除了toString方法以外,枚举中还默认地定义了一些其他方法,这些方法实际都是从枚举的根父类Enum中继承而来,可以查阅java.lang包中Eum类的API文档,代码3中仅演示了其中一部分。
其中ordinal方法返回的是在枚举中此常量(枚举实例对象)在枚举中的定义顺序的整型值,从0开始。比如MONDAY由于排在第一位因此它的ordinal返回值为0。
从getClass的返回值可以看出,枚举其实也是一种类,它的特殊之处仅仅是对此类的使用作出了一些限制,并省略了一些代码声明语句的显示表示。
valueOf是静态方法,它的作用就是将传递的字符串转换为对应的枚举常量。该方法的实际应用就是将用户输入转换为Java对象。比如,在网页或者应用程序中,用户键入或者通过下拉菜单选择的方式,输入了一个日期,此时的日期还是字符串形式,为了参与Java程序的执行,就可以通过valueOf方法将其转换为枚举的常量。
values方法是将枚举中的所有常量转换为数组。当我们需要遍历枚举中的所有常量时,可以通过这个方法,将所有常量转换为一个数组,对其进行遍历草偶作。

3.2 枚举的复杂应用

以上内容演示了对枚举最为简单的应用——定义若干常量,获取这些常量,并调用它们的默认方法。那么既然枚举也是一种特殊的类,那么能不能为它定义构造方法和自定义方法,来实现更为复杂的需求呢?答案是可以的。下面我们就来演示如何为枚举显示地定义构造方法(包括带参数的构造方法),以及其他方法。

(1) 定义构造方法

代码4:
import java.util.Arrays;

enum WeekDay {
//常量元素列表后若有定义其他成员,常量元素列表必须以分好结束
MONDAY,TUESDAY,WEDNESDAY,THRUSDAY,FRIDAY,SATURDAY,SUNDAY;

//枚举的所有成员必须位于常量元素之后
//枚举的构造方法必须是私有的
private WeekDay(){
System.out.println("Constructorwithout parameter.");
}
private WeekDay(int day){
System.out.println("Constructorwith parameter. Today is " + day);
}
}
public class EnumTest3 {
public static void main(String[] args) {
System.out.println(Arrays.toString(WeekDay.values()));
}
}
执行结果为:
Constructor without parameter.
Constructor without parameter.
Constructor without parameter.
Constructor without parameter.
Constructor without parameter.
Constructor without parameter.
Constructor without parameter.
[MONDAY, TUESDAY, WEDNESDAY, THRUSDAY,FRIDAY, SATURDAY, SUNDAY]
代码说明:
(1) 代码4中分别为WeekDay定义了无参和含参构造方法。需要三点注意:1) 枚举除常量元素以外的所有成员必须定义在常量元素之后;2) 若常量元素之后还定义有其他成员,那么常量元素列表的定义必须以分好结束,否则可以不写分好;3) 如前所述,构造方法必须是私有的。
(2) 正如我们前文所述,枚举中的每个常量,实际就是枚举的一个实例对象,那么在初始化这些实例对象的时候,也是需要通过调用构造方法来进行。如果简单的定义常量元素时,就像代码3中那样,实际就是在默认的调用无参构造方法,这可以从测试类的执行结果中看出——当调用WeekDay的静态方法values时,首次加载了WeekDay类,同时就会对WeekDay类的所有静态成员进行初始化。
如果想要通过含参构造方法创建并初始化化枚举的常量元素,那么只需在每个常量后面添加一对括号,并传递一个参数即可,如下代码所示,
代码5:
enum WeekDay{
//在每个常量元素后面添加一对括号,并传递一个参数,显示调用含参构造方法
MONDAY(0),TUESDAY(1),WEDNESDAY(2),THRUSDAY(3),FRIDAY(4),SATURDAY(5),SUNDAY(6);

private WeekDay(){
System.out.println("Constructorwithout parameter.");
}
private WeekDay(int day){
System.out.println("Constructorwith parameter. Today is " + day);
}
}
再次执行测试类的执行结果为:
Constructor with parameter. Today is 0
Constructor with parameter. Today is 1
Constructor with parameter. Today is 2
Constructor with parameter. Today is 3
Constructor with parameter. Today is 4
Constructor with parameter. Today is 5
Constructor with parameter. Today is 6
[MONDAY, TUESDAY, WEDNESDAY, THRUSDAY,FRIDAY, SATURDAY, SUNDAY]
结果表明WeekDay的含参构造方法被调用了。从形式上看,与一般类通过含参构造方法创建对象是一样的。当然,如果仅仅是显示地添加一对括号,而不传递任何参数,则是调用无参构造方法。

(2) 自定义其他方法

既然枚举是一种特殊的类,那么我们也可以在其内部自定义方法,以满足各种复杂的需求。由上面的内容可知,枚举本身是枚举常量元素的父类,那么如果想自定义方法的话,就首先需要在父类中定义,也就是在枚举本身中定义,然后在常量元素中继承并复写。
这里我们设计一个表示交通灯的枚举类,代码如下。
代码6:
public enum TrafficLamp {
/*
定义红灯、绿灯、黄灯三个常量元素
每个元素在初始化时调用含参构造方法, 因此传递表示等待时间的整型常量
每个元素中继承TrafficLamp枚举的nextLamp方法并复写
*/
RED(35){
public TrafficLamp nextLamp(){
return GRREN;
}
},GRREN(45){
public TrafficLamp nextLamp(){
return YELLOW;
}
},YELLOW(3){
public TrafficLamp nextLamp(){
return RED;
}
};

private int time;

private TrafficLamp(){}
private TrafficLamp(int time){
this.time= time;
}

public String toString(){
return this.name()+"="+time+"s";
}
//在枚举中定义了一个抽象方法 ,供常量元素(子类实例对象)去复写
public abstract TrafficLamp nextLamp();
}
//测试类
import java.util.Arrays;

public class EnumTest4 {
public static void main(String[] args) {
for(TrafficLamp trafficLamp : TrafficLamp.values()){
//同时打印此时和下一个交通灯的颜色及时间
System.out.println(trafficLamp+",next:"+trafficLamp.nextLamp());
}
}
}
执行结果为:
RED=35s, next:GRREN=45s
GRREN=45s, next:YELLOW=3s
YELLOW=3s, next:RED=35s
代码说明:
(1) nextLamp方法的作用是返回下一个颜色的等。比如现在是绿灯,那么下一个颜色等就是黄灯,再下一个就是红灯,因此nextLamp方法的返回值类型依然是TrafficLamp类型的。枚举中nextLamp方法被定义为了抽象方法,主要是为了供子类元素常量复写,当然也可以为其定义具体的方法体。
(2) 正像代码2中为抽象类WeekDay定义匿名内部类常量一样,枚举的每个常量元素就是枚举的子类实例对象,既然是子类就需要复写父类的抽象方法。复写抽象方法的方式同样是在初始化常量元素时,后接一对大括号,并在其中复写父类(枚举类)抽象方法即可,这与匿名内部类抽象父类方法的方式是一样的。那么最终就写成了代码6中的形式。大家要注意,每个常量元素大括号之间还是要用逗号进行分隔,并在最后一个元素的闭括号后接一个分号,表示常量元素定义完毕。
(3) 在Classpath路径下,可以看到除了“TrafficLamp.class”文件以外,还有三个表示TrafficLamp匿名内部类的“.class”文件,如下图所示。



这也就更为直接的证明了枚举的常量元素就是枚举的子类实例对象。

小知识点:
当枚举中只有一个常量元素时,就可以作为单例设计模式的一种实现方式。这里我们首先复写一下饿汉式单例设计模式的三个要素:
(1) 私有化构造方法,防止调用者自己创建实例对象;
(2) 在单例类内部床架一个私有静态本类对象,供外部类调用;
(3) 定义一个公有静态方法,返回上述本类对象。
那么通过以上三个要素,就保证了除了单例类内部提供的唯一实例对象以外,外部调用者无法自己创建单例类实例对象。
当枚举中仅定义有一个常量元素时,正好符合前两条:外部调用者无法自己创建枚举的实例,只能获取规定好的枚举常量元素。唯一的区别在于获取这一唯一实例对象的方式:单例设计模式中,通过一个方法返回唯一对象;枚举则直接访问此常量元素即可返回对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: