枚举是JDK1.5中新增加的一种数据类型,它最大的特点就是枚举数据类型的取值范围由程序员自己规定,本小节将会讲解枚举的用法以及实现枚举的原理。
8.7.1 枚举的概念及定义方式
如果希望在程序中表示三种颜色的交通信号灯,可以使用一个整型变量的三个值来表示。例如用1表示红灯、2表示黄灯、3表示绿灯。但可能有人会使用1-3之外的其他数字对整型变量赋值,这就会导致程序在得到变量的值之后出无法解释它到底是什么颜色的灯。如何保证每个人都使用规定好的3个值来表示信号灯呢?通过使用枚举类型就可以轻松的解决这个问题。枚举是JDK1.5中新增加的一种数据类型,枚举数据类型的取值范围由程序员自己规定。当规定好了枚举的取值范围后,任何人都不能用这个范围以外的值来给枚举变量赋值。
枚举用关键字enum来表示,它本质上也是一种类,但这种类的对象不是通过new关键字创建出来的,而是从程序员规定的众多枚举值中选择的。因此,每一个枚举值本质上都是一个枚举对象。程序员只需要从File菜单或右键菜单中选择“New”子菜单,然后在菜单项中选择“Enum”菜单项即可弹出创建接口的对话框,在对话框中填写枚举的名称并设置其访问度即可创建出一个枚举。当创建出枚举之后,程序员就可以根据需要为它定义枚举值。下面的【例08_19】展示了如何定义枚举值来表示交通灯的三种颜色,并且如何在switch结构中以枚举作为参数。
【例08_19 以枚举表示交通灯颜色】
TrafficLight.java
Exam08_19.java
【例08_19】中定义了一个枚举TrafficLight,在这个枚举中定义了三个值来代表交通信号灯的三种颜色。可以看到:枚举值的定义过程非常简单,只需要为每一个枚举值起一个名称就可以。按照行业惯例,枚举值以全大写字母的方式来命名。当枚举值被定义好之后,每个枚举对象都只能从枚举值列表范围内选取,否则会引起编译器报错。
在main()方法中创建了一个枚举对象t1,t1选自列表中的RED。程序中以t1作为switch结构的参数来判断t1表示哪一种颜色的交通信号灯。此处要提醒各位读者注意:switch结构的每一个case都直接列出了枚举值的名称,前面并没有加枚举类型的名称,例如第一个case后面写的是RED而不是TrafficLight.RED。
8.7.2 枚举的常用方法
前文讲过,枚举的本质是类。既然是类,当然就可以在其中定义属性和方法。但是需要注意:在枚举中定义的属性和方法都只能出现在枚举值列表的下面,如果没有这样做都会导致出现语法错误。实际上,每一个枚举本身都会自带一些方法。这些自带的方法来自于哪里呢?编译器在对枚举的源代码进行编译时,看到前面使用了enum关键字,就会让这个枚举去继承Enum这个类,因此程序中所定义的每一个枚举其实都是Enum的子类,而枚举自带的那些方法就来自于Enum类。下面来讲解一下枚举中自带的那些方法是如何使用的。
- name()方法:这个方法没有参数,它可以返回当前枚举对象的名称字符串。
- ordinal()方法:枚举的各个值会组成一个数组,ordinal()方法的作用就是获得当前枚举对象在数组中的下标。
- compareTo()方法:这是一个有参数的方法,它的参数是另一个同类型的枚举对象,运行结果是当前对象与参数对象在枚举值列表中的排位差。
- values()方法:这是一个静态方法,方法没有参数,方法的返回值是一个枚举数组,数组中存放着这种枚举类型所有的值。
- valueOf()方法:这也是一个静态方法,这个方法的作用是通过字符串形式的枚举值名称获得对应的枚举对象。
下面的【例08_20】展示了如何使用这些枚举自带的方法以及它们的运行效果。
【例08_20 枚举自带的方法】
Exam08_20.java
【例08_20】的运行结果如图8-29所示。
图8-29 【例08_20】运行结果
从图8-29可以看出,t1和t3在枚举值组成的数组中排位差是-2,这是因为t1对应的枚举值是RED,而t3对应的枚举值是GREEN,按照定义的先后顺序,RED在枚举值数组中的下标是0,而GREEN在枚举值数组中的下标是2,经下标值相减得到它们的排位差是-2。此外,在调用valueOf()方法时还需注意:为该方法传入的字符串参数必须是某一个枚举值的名称,如果传入“BLUE”这样的字符串将会出现异常。
8.7.3枚举的构造方法
枚举除了可以定义普通方法外,还可以定义构造方法,通过构造方法可以对枚举对象的属性值完成初始化操作。例如,每一种颜色的交通灯亮灯时间并不相同,因此可以为TrafficLight这个枚举定义一个int型的time属性来表示每种颜色交通灯亮灯的时间,并且通过构造方法初始化time属性的值。
枚举的构造方法与普通类的构造方法有所不同。首先,枚举的构造方法必须是私有的,如果程序员没有在构造方法前面添加private关键字,编译器会自动补充添加。其次,调用构造方法时并不是通过构造方法的名称来调用的。前文讲过,每一个枚举值本质上都是一个枚举对象。当程序员把枚举值列举出来的时候实际上就是创建了若干枚举对象。调用枚举构造方法创建对象的正确形式是:在枚举值的后面加上括号并传入参数。如果用无参数的构造方法创建枚举值,则可以不用在枚举值后面加括号,例如【例08_19】和【例08_20】中的TrafficLight枚举在列举每一个枚举值时,都没有在枚举值后面加括号。下面的【例08_21】展示了如何使用TrafficLight枚举的带参数构造方法创建枚举值。
【例08_21 用带参数构造方法创建枚举值】
TrafficLight.java
Exam08_21.java
【例08_21】对TrafficLight枚举进行了修改,为它增加了int型的time属性以及两个构造方法,当然,按照语法规定这些新增的属性和构造方法都要定义在所有枚举值的下面。各位读者请注意标记为①的那一行代码,可以看到:创建枚举值的时候在每个枚举值的后面都加上了小括号,并在小括号中传入了参数,这个操作其实就是通过带参数的构造方法来创建枚举对象。例如程序中的“RED(30)”就表示创建了一个叫做RED的枚举对象,在创建这个对象时调用的是带有int型参数的构造方法,并且为构造方法传递的参数值是30。由此可见,枚举的构造方法并不是直接通过构造方法的名称调用的,而是通过枚举值的名称来调用的,各位读者一定要牢记这个细节。
TrafficLight枚举有参数的构造方法在接收到实际参数后,会用这个参数来初始化time属性,而每一个枚举值都是一个枚举对象,因此枚举值可以直接调用到time属性。【例08_21】的运行结果如图8-30所示。
图8-30 【例08_21】运行结果
从运行结果可以看出:每个枚举值的time属性都按照传入构造方法的参数值完成了初始化。这说明创建枚举值的时候确实是通过带有参数的构造方法完成的。
8.7.4枚举的继承问题
前文曾经讲过,每一个枚举实际上都是Enum类的子类。但程序员不可以用自己定义的类来继承Enum类,这是因为定义一个Enum类的子类,就相当于自己编写了一个枚举。但枚举在用法和实现过程上都有很多特殊要求,而程序员的水平参差不齐,如果允许程序员自己编写枚举就可能导致被编写出的枚举存在各种漏洞。为了避免出现这种有漏洞的枚举,Java语言规定程序员只能通过enum关键字定义枚举,这样编译器就能以定义enum的特殊语法规则来约束程序员,从而不至于定义出有漏洞的枚举。
此外,通过enum关键字定义出来的枚举也不能被继承,因为如果一个枚举能够被继承,那么它的子枚举就可以扩展更多的枚举值,从而导致枚举值的范围可以被随意扩大,这显然违背了枚举这种数据类型的设计初衷。为了防止枚举被继承,编译器在编译枚举源代码时会在枚举前面添加一个final关键字。
除此文字版教程外,小伙伴们还可以点击这里观看我在本站的视频课程学习Java。