Java开发笔记(五十六)利用枚举类型实现高级常量

前面介绍了联合利用final和static可实现常量的定义,该方式用于简单的常量倒还凑合,要是用于复杂的、安全性高的常量,那就力不从心了。例如以下几种情况,final结合static的方式便缺乏应对之策:
1、虽然常量的名称以大写字母拼写,但是对应的取值基本为1、2、3之类的整数,如果把1、2、3直接写在调用的代码里面,岂不是浑水摸鱼顶替了现有的常量蒙混过关?
2、代码可以从常量名推出对应的常量值,可是反过来并不能从常量值推出对应的常量名,开发者晓得不代表程序也晓得。
3、每个常量只有唯一的数值表达,无法表示更丰富的涵义。比如星期一这个常量,可能包括数字“1”、英文单词“Monday”、中文词语“星期一”这些信息组合,然而final联合static的方式只能表达其中一个信息。
听起来似乎言之有理,可是不用整型常量的话,还有什么常量类型能派上用场呢?其实Java语言在设计之初就考虑到了这种情况,在之前的学习当中,已经出现过类似的处理方案。早在介绍本地日期类型LocalDate的时候,提到获取当前月份的办法是调用日期实例的getMonthValue方法,为啥这里不是调用getMonth方法?原来getMonth方法返回的并非整型数值,而是一个Month类型的月份实例,它属于枚举类型。调用该实例的getValue方法,得到的才是月份数字;调用该实例的name方法,可得到大写英文月份的英文单词。先来看看以下的一段月份测试代码:

	// 演示Month类型的调用方式。注意,Month类型是Java自带的一种枚举类型
	private static void testMonth() {
		LocalDate date = LocalDate.now();
		Month month = date.getMonth();
		System.out.println("month number="+month.getValue()+", month name="+month.name());
	}

 

运行以上的测试代码,观察到如下的日志文本:

month number=12, month name=DECEMBER

 

根据结果发现getValue方法返回了12,且name方法返回了DECEMBER。从中可见Month类型既包含了月份的数字信息,也包括了月份的英文单词,这正是枚举类型相对于普通常量的优势。
所谓枚举,指的是某些同类型常量的有限集合。Java不但提供了Month这种枚举类型,而且允许程序员自己定义新的枚举类型,如同定义类那样。不同的是,类定义使用class来标识,而枚举类型使用enum来标识。最简单的枚举定义只需一个名称列表,就像以下代码这般:

//演示枚举类型的简单定义
public enum Season {
	// 几个枚举变量之间以逗号分隔
	SPRING, SUMMER, AUTUMN, WINTER
}

 

以上代码定义了一种季节枚举Season,它包含了春天SPRING、夏天SUMMER、秋天AUTUMN、冬天WINTER这四个枚举项。四个枚举项既是常量,又都属于Season类型,外部访问它们的格式为“Season.枚举项的名称”。下面是外部访问季节枚举项的代码例子:

	// 演示简单枚举类型的调用方式
	private static void testEnum() {
		Season spring = Season.SPRING; // 声明一个春天的季节实例
		Season summer = Season.SUMMER; // 声明一个夏天的季节实例
		Season autumn = Season.AUTUMN; // 声明一个秋天的季节实例
		Season winter = Season.WINTER; // 声明一个冬天的季节实例
		// 枚举类型提供的通用方法主要有两个,
		// 其中ordinal方法可获得该枚举的序号,toString可获得该枚举的字段名称
		System.out.println("spring number="+spring.ordinal()+", name="+spring.toString());
		System.out.println("summer number="+summer.ordinal()+", name="+summer.toString());
		System.out.println("autumn number="+autumn.ordinal()+", name="+autumn.toString());
		System.out.println("winter number="+winter.ordinal()+", name="+winter.toString());
	}

 

运行上面的测试代码,输出下列的日志信息:

spring number=0, name=SPRING
summer number=1, name=SUMMER
autumn number=2, name=AUTUMN
winter number=3, name=WINTER

 

结合代码和日志结果,可知枚举项的ordinal方法返回了该枚举所处的序号,toString方法返回了该枚举的常量名称。由于ordinal方法和toString方法是枚举类型enum自带的保留方法,因此无需开发者显式定义即可拿来调用。然而这两个方法毕竟是系统提供的,无法满足丰富多变的个性要求,譬如下列两点需求,简单的枚举类型就无法实现:
1、枚举项的默认序号从0开始计数,但现实生活中很多组合是从1开始计数的。例如一月份对应的数字是1,星期一对应的数字也是1,诸如此类。
2、枚举项的默认名称取的是枚举定义里的列表项名称,但往往更需要中文名称。例如界面上希望展示“春天”而非“SPRING”,希望展示“夏天”而非“SUMMER”,等等。
既然枚举的默认序号与默认名称时常不符合实际情况,这势必要求开发者额外定义新的序号和新的名称。假如说给某个类定义新的属性,那真是易如反掌,可现在待处理的是枚举类型而不是类耶。其实枚举类型enum本来就源自类class,故而完全可以把枚举当作类一样来定义,也就是说,枚举允许定义自己的成员属性、自己的成员方法,乃至自己的构造方法。于是重新定义一个季节枚举,在新的枚举定义中添加序号与名称这两个属性,及其对应的get方法,并补充包含初始化赋值的构造方法。特别注意要在枚举项列表中把每个枚举项都换成携带构造方法的枚举声明,表示该枚举项是由指定构造方法生成的,重写后的季节枚举定义代码示例如下:

//演示枚举类型的扩展定义
public enum SeasonCn {
	// 在定义枚举变量的同时,调用该枚举变量的构造方法
	SPRING(1,"春天"), SUMMER(2,"夏天"), AUTUMN(3,"秋天"), WINTER(4,"冬天");

	private int value; // 定义季节的阿拉伯数字
	private String name; // 定义季节的中文名称字段

	// 在构造方法中传入该季节的阿拉伯数字和中文名称
	private SeasonCn(int value, String name) {
		this.value = value;
		this.name = name;
	}

	// 获取季节的阿拉伯数字
	public int getValue() {
		return this.value;
	}

	// 获取季节的中文名称
	public String getName() {
		return this.name;
	}
}

根据新的枚举定义代码,枚举项的序号数值与中文名称如愿换了过来。接着轮到外部调用新的枚举类型,大致流程保持不变,只需将原来的ordinal方法替换为getValue方法,将原来的toString方法替换为getName方法。修改之后的调用代码如下所示:

	// 演示扩展枚举类型的调用方式
	private static void testEnumCn() {
		SeasonCn spring = SeasonCn.SPRING; // 声明一个春天的季节实例
		SeasonCn summer = SeasonCn.SUMMER; // 声明一个夏天的季节实例
		SeasonCn autumn = SeasonCn.AUTUMN; // 声明一个秋天的季节实例
		SeasonCn winter = SeasonCn.WINTER; // 声明一个冬天的季节实例
		// 通过扩展而来的getName方法,可获得该枚举预先设定的中文名称
		System.out.println("spring number="+spring.getValue()+", name="+spring.getName());
		System.out.println("summer number="+summer.getValue()+", name="+summer.getName());
		System.out.println("autumn number="+autumn.getValue()+", name="+autumn.getName());
		System.out.println("winter number="+winter.getValue()+", name="+winter.getName());
	}

 

运行上述的调用代码,得到以下的日志结果:

spring number=1, name=春天
summer number=2, name=夏天
autumn number=3, name=秋天
winter number=4, name=冬天

 

由此可见,经过重新编写的SeasonCn枚举,顺利实现了个性化定制序号和名称的目标。



更多Java技术文章参见《Java开发笔记(序)章节目录

posted @ 2019-02-03 09:42  pinlantu  阅读(558)  评论(0编辑  收藏  举报