有的人说,不推荐使用枚举。有的人说,枚举很好用。究竟怎么使用,如何使用,仁者见仁智者见智。总之,先学会再说~
为什么要引入枚举类
一个小案例
你写了一个小程序,不过好久不用了,突然有一天,你想使用一下它。程序要想正确运行,需要将今天星期几存到数据库里。这个时候,你开始犯难了。
当初的你还很年轻,不懂程序界的险恶,设计这个程序的时候,傻不拉几把这个字段设计为int类型的,用0代表周日,1代表周一。。。6代表周六,添加的时候就setWeekday(0)。但是这么长时间没用了,你忘记自己是从周一开始计算还是周日开始计算了,换句话说,你想不起来0代表的是周一还是周日了!
于是你各种翻代码,看数据库的字段,数据库保存的信息,终于搞懂了,你很开心,用了一次之后,觉得这个程序没意思,又不用了。
很久之后,你心血来潮,又想用一次它,很不幸,你又忘记到底0代表周一还是周日了,一番查找之后。你决定重构代码,因为你受不了了!!
静态变量来帮忙
经过一番思考,你决定使用七个静态变量来代表星期几,以后只要引用和静态变量就可以了,而不用自己输入012….你这么写:
public class Weekday {
public final static int SUN = 0;
public final static int MON = 1;
public final static int TUE = 2;
public final static int WED = 3;
public final static int THU = 4;
public final static int FRI = 5;
public final static int SAT = 6;
}
机智如你,这个时候,只要Weekday.SUN
就可以了,不用操心到底应该填写0还是填写1。
但是这个时候的你,也不是当初初出茅庐的小伙子了,很明显,这样写已经不能满足你了。你还想让这个类做更多的事,比如,你想知道下一天是星期几,还想把今天是星期几打印出来。一番深思熟虑后,你改成了这样:
public class Weekday {
private Weekday(){}
public final static Weekday SUN = new Weekday();
public final static Weekday MON = new Weekday();
public final static Weekday TUE = new Weekday();
public final static Weekday WED = new Weekday();
public final static Weekday THU = new Weekday();
public final static Weekday FRI = new Weekday();
public final static Weekday SAT = new Weekday();
public static Weekday getNextDay(Weekday nowDay){
if(nowDay == SUN) {
return MON;
}else if(nowDay == MON) {
return TUE;
}else if(nowDay == TUE) {
return WED;
}else if(nowDay == WED) {
return THU;
}else if(nowDay == THU) {
return FRI;
}else if(nowDay == FRI) {
return SAT;
}else {
return SUN;
}
}
public static void printNowDay(Weekday nowDay){
if(nowDay == SUN)
System.out.println("sunday");
else if(nowDay == MON)
System.out.println("monday");
else if(nowDay == TUE)
System.out.println("tuesday");
else if(nowDay == WED)
System.out.println("wednesday");
else if(nowDay == THU)
System.out.println("thursday");
else if(nowDay == FRI)
System.out.println("friday");
else
System.out.println("saturday");
}
}
class Test1{
public static void main(String[] args) {
Weekday nowday = Weekday.SUN;
Weekday.printNowDay(nowday);
Weekday nextDay = Weekday.getNextDay(nowday);
System.out.print("nextday ====> ");
Weekday.printNowDay(nextDay);
}
}
哟,不错。考虑的很详细。并且私有构造方法后,外界就不能创建该类的对象了,这样就避免了星期八星期九的出现,所有Weekday的对象都在该类内部创建。
不对,好像缺了点什么,我要的是int!我的int呢?!。所以,你还需要一个这样的方法:
public static int toInt(Weekday nowDay){
if(nowDay == SUN)
return 0;
else if(nowDay == MON)
return 1;
else if(nowDay == TUE)
return 2;
else if(nowDay == WED)
return 3;
else if(nowDay == THU)
return 4;
else if(nowDay == FRI)
return 5;
else
return 6;
}
当你需要一个整形数据的时候,只需要Weekday.toInt(Weekday.SUN);
,看起来你好像完成了你的任务。
但是,你有没有发现,这样写,好麻烦啊。如果想要扩展一下功能,大量的ifelse会让人眼花缭乱。
有没有更好的方式呢?你大概已经知道了,没错,我们需要枚举类!
我们先来看看枚举类是什么。
一个简单的枚举类
话不多说,先来代码:
public enum Weekday {
SUN,MON,TUS,WED,THU,FRI,SAT
}
代码这么少?
没错,这就是枚举类,我们来看看怎么使用它:
class Test2{
public static void main(String[] args) {
Weekday sun = Weekday.SUN;
System.out.println(sun); // 输出 SUN
}
}
看起来和上面的静态变量使用方式差不多,而且默认的toString方法返回的就是对应的名字。
我们上面的那段代码重写toString也是不可以打印出当前是星期几的,因为toString方法没有参数。所以我们自己写了一个printNowDay方法。
当然,这么简单的枚举类是不可能实现我们的要求的,所以,我们还要接着写:
public enum Weekday {
SUN(0),MON(1),TUS(2),WED(3),THU(4),FRI(5),SAT(6);
private int value;
private Weekday(int value){
this.value = value;
}
public static Weekday getNextDay(Weekday nowDay){
int nextDayValue = nowDay.value;
if (++nextDayValue == 7){
nextDayValue =0;
}
return getWeekdayByValue(nextDayValue);
}
public static Weekday getWeekdayByValue(int value) {
for (Weekday c : Weekday.values()) {
if (c.value == value) {
return c;
}
}
return null;
}
}
class Test2{
public static void main(String[] args) {
System.out.println("nowday ====> " + Weekday.SAT);
System.out.println("nowday int ====> " + Weekday.SAT.ordinal());
System.out.println("nextday ====> " + Weekday.getNextDay(Weekday.SAT)); // 输出 SUN
//输出:
//nowday ====> SAT
//nowday int ====> 6
//nextday ====> SUN
}
}
这样就完成了我们的目标,和之前的代码比起来,有没有觉得突然高大上了许多?没有那么多烦人的ifelse,世界都清净了。
好了,现在你大概知道为什么要引入枚举类了吧?就是因为在没有枚举类的时候,我们要定义一个有限的序列,比如星期几,男人女人,春夏秋冬,一般会通过上面那种静态变量的形式,但是使用那样的形式如果需要一些其他的功能,需要些很多奇奇怪怪的代码。所以,枚举类的出现,就是为了简化这种操作。
可以将枚举类理解为是java的一种语法糖。
枚举类的用法
最简单的使用
最简单的枚举类就像我们上面第一个定义的枚举类一样:
public enum Weekday {
SUN,MON,TUS,WED,THU,FRI,SAT
}
如何使用它呢?
先来看看它有哪些方法:
![Weekday方法](https://i-blog.csdnimg.cn/blog_migrate/4e16d2ec111fb99b3765b3f24c7e0967.png)
这是Weekday可以调用的方法和参数。发现它有两个方法:value()和valueOf()。还有我们刚刚定义的七个变量。
![枚举变量的方法](https://i-blog.csdnimg.cn/blog_migrate/1d5797e7e1ab6c0f41ccfdc1863d2ac4.png)
这些事枚举变量的方法。我们接下来会演示几个比较重要的:
public enum Weekday {
SUN,MON,TUS,WED,THU,FRI,SAT
}
class Test3{
public static void main(String[] args) {
System.out.println(Weekday.valueOf("mon".toUpperCase()));
//MON
for (Weekday w : Weekday.values()){
System.out.println(w + ".ordinal() ====>" +w.ordinal());
}
//SUN.ordinal() ====>0
//MON.ordinal() ====>1
//TUS.ordinal() ====>2
//WED.ordinal() ====>3
//THU.ordinal() ====>4
//FRI.ordinal() ====>5
//SAT.ordinal() ====>6
System.out.println("Weekday.MON.compareTo(Weekday.FRI) ===> " + Weekday.MON.compareTo(Weekday.FRI));
System.out.println("Weekday.MON.compareTo(Weekday.MON) ===> " + Weekday.MON.compareTo(Weekday.MON));
System.out.println("Weekday.MON.compareTo(Weekday.SUM) ===> " + Weekday.MON.compareTo(Weekday.SUN));
//Weekday.MON.compareTo(Weekday.FRI) ===> -4
//Weekday.MON.compareTo(Weekday.MON) ===> 0
//Weekday.MON.compareTo(Weekday.SUM) ===> 1
System.out.println("Weekday.MON.name() ====> " + Weekday.MON.name());
//Weekday.MON.name() ====> MON
}
}
这段代码,我们演示了几个常用的方法和功能:
Weekday.valueOf() 方法:
它的作用是传来一个字符串,然后将它转变为对应的枚举变量。前提是你传的字符串和定义枚举变量的字符串一抹一样,区分大小写。如果你传了一个不存在的字符串,那么会抛出异常。
Weekday.values()方法。
这个方法会返回包括所有枚举变量的数组。在该例中,返回的就是包含了七个星期的Weekday[]。可以方便的用来做循环。
枚举变量的toString()方法。
该方法直接返回枚举定义枚举变量的字符串,比如MON就返回【”MON”】。
枚举变量的.ordinal()方法。
默认请款下,枚举类会给所有的枚举变量一个默认的次序,该次序从0开始,类似于数组的下标。而.ordinal()方法就是获取这个次序(或者说下标)
枚举变量的compareTo()方法。
该方法用来比较两个枚举变量的”大小”,实际上比较的是两个枚举变量的次序,返回两个次序相减后的结果,如果为负数,就证明变量1”小于”变量2 (变量1.compareTo(变量2),返回【变量1.ordinal() - 变量2.ordinal()】)
这是compareTo的源码,会先判断是不是同一个枚举类的变量,然后再返回差值。
枚举类的name()方法。
它和toString()方法的返回值一样,事实上,这两个方法本来就是一样的:
这两个方法的默认实现是一样的,唯一的区别是,你可以重写toString方法。name变量就是枚举变量的字符串形式。
还有一些其他的方法我就暂时不介绍了,感兴趣的话可以自己去看看文档或者源码,都挺简单的。
要点:
- 使用的是enum关键字而不是class。
- 多个枚举变量直接用逗号隔开。
- 枚举变量最好大写,多个单词之间使用”_”隔开(比如:INT_SUM)。
- 定义完所有的变量后,以分号结束,如果只有枚举变量,而没有自定义变量,分号可以省略(例如上面的代码就忽略了分号)。
- 在其他类中使用enum变量的时候,只需要【类名.变量名】就可以了,和使用静态变量一样。
但是这种简单的使用显然不能体现出枚举的强大,我们来学习一下复杂的使用:
枚举的高级使用方法
就像我们前面的案例一样,你需要让每一个星期几对应到一个整数,比如星期天对应0。上面讲到了,枚举类在定义的时候会自动为每个变量添加一个顺序,从0开始。
假如你希望0代表星期天,1代表周一。。。并且你在定义枚举类的时候,顺序也是这个顺序,那你可以不用定义新的变量,就像这样:
public enum Weekday {
SUN,MON,TUS,WED,THU,FRI,SAT
}
使用接口组织枚举
public interface Food {
enum Coffee implements Food {
BLACK_COFFEE, DECAF_COFFEE, LATTE, CAPPUCCINO
}
enum Dessert implements Food {
FRUIT, CAKE, GELATO
}
}
使用枚举创建单例模式
使用枚举创建的单例模式:
public enum EasySingleton{
INSTANCE;
}
代码就这么简单,你可以使用EasySingleton.INSTANCE调用它,比起你在单例中调用getInstance()方法容易多了。
我们来看看正常情况下是怎样创建单例模式的:
用双检索实现单例:
下面的代码是用双检索实现单例模式的例子,在这里getInstance()方法检查了两次来判断INSTANCE是否为null,这就是为什么叫双检索的原因,记住双检索在java5之前是有问题的,但是java5在内存模型中有了volatile变量之后就没问题了。
public class DoubleCheckedLockingSingleton{
private volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}
}
你可以访问DoubleCheckedLockingSingleTon.getInstance()来获得实例对象。
用静态工厂方法实现单例:
public class Singleton{ private static final Singleton INSTANCE = new Singleton(); private Singleton(){} public static Singleton getSingleton(){ return INSTANCE; } }