在Java中,我们可以通过static final来定义常量。例如定义周一到周日:
public class weekday { public static final int SUN = 0; public static final int MON = 1; public static final int TUE = 2; public static final int WED =3; public static final int THU = 4; public static final int FRI = 5; public static final int SAT = 6; }
使用常量的时候,可以这么引用:
if (day == Weekday.SAT || day == Weekday.SUN) {}
当然也可以将常量定义为字符串类型。
无论是int常量还是String常量,使用这些常量来表示一组枚举的时候,
有一个严重的问题就是,编译器无法检查每个值得合理性。比如:
if (weekday == 6 || weekday == 7) { if (tasks == weekday.MON) { // TODO } }
上面代码存在两个问题:
(1)weekday定义得常量范围是0-6,并不包含7,编译器无法检测不在枚举中得int值
(2)定义的常量仍可以于其他变量比较,但其用途并非是枚举星期值
为了让编译器能自动检查某个值在枚举的集合内,并且不同用途的枚举需要不同的类型来标记:
enum Weekday { SUN,MON,TUE,WED,THU,FRI,SAT; } public class catchExample2 { public static void main(String[] args) throws Exception { Weekday day = Weekday.SUN; if (day == Weekday.SAT || day == Weekday.SUN) { System.out.println("At home"); } else { System.out.println("At office"); } } }
枚举类是通过关键字enum实现的,我们只需要一次列出枚举的常量名。
和int定义的常量相比,使用enum定义枚举有如下好处:
(1)enum常量本身带有类型信息,即Weekday.SUN类型是Weekday,编译器会自动检查类型错误。
(2)不可能引用到非枚举的值,因为无法通过编译。
(3)不同类型的枚举不能相互比较或者赋值,因为类型不符。
这使得编译器可以在编译期自动检查所有可能潜在的错误。
使用enum定义的枚举是一种引用类型。
前面我们讲到,引用类型比较,要使用equals()方法,如果使用==,它比较两个引用类型的变量是否是同一个对象。
因此,引用类型比较,要始终使用equals()方法,但enum类型可以例外。
因为enum类型的每个常量在JVM中只有一个唯一实例,所以可以直接使用==比较。
enum定义的枚举类,与class有什么区别了?
enum是就是一个class,只是被单独定义了。主要区别有以下几点:
(1)定义的enum类型总是继承自java.lang.Enum,且无法被继承。
(2)只能定义出enum的实例,无法通过new操作符创建enum对象。
(3)定义的每个实例都是引用类型的唯一实例
(4)可以将enum类型用于switch语句。
例如,我们定义的color类:
public enum Color { RED, GREEN, BLUE; }
编译器编译的class大概就是这样:
public final class Color extends Enum { // 继承自Enum,标记为final class // 每个实例均为全局唯一: public static final Color RED = new Color(); public static final Color GREEN = new Color(); public static final Color BLUE = new Color(); // private构造方法,确保外部无法调用new操作符: private Color() {} }
所以,编译后的enum类和普通class并没有任何区别。
但是我们自己无法按定义普通class那样来定义enum关键字,这是Java语法规定。
因为enum是一个class,每个枚举的值都是class实例,因此,这些实例有一些方法:
name():返回常量名
String s = Weekday.SUN.name();
ordianl():返回定义的常量的顺序,从0开始技术。
int n = Weekday.MON.ordinal();
改变枚举常量定义的顺序就会导致ordinal()返回值。
上面创建的枚举依旧存在一个问题,如果我们不小心修改枚举的顺序,
那么其所代表的ordinal的返回值也会被修改。
因为我们可以稍微优化一下,给每个枚举常量添加字段:
package com.imooc.iexecption; enum Weekday { SUN(0), MON(1),TUE(2),WED(3),THU(4),FRI(5),SAT(6); public final int dayValue; private Weekday(int dayValue) { this.dayValue = dayValue; } } public class catchExample2 { public static void main(String[] args) throws Exception { Weekday day = Weekday.SUN; if (day.dayValue == 6 || day.dayValue == 0) { System.out.println("At home"); } else { System.out.println("At office"); } } }
这样,我们就不用过多的关注顺序的问题。
默认情况下,对枚举常量调用toString()会返回和name()一样的字符串,
但是,toString()可以被覆写,而name则不行,我们可以给weekday添加toString()方法。
package com.imooc.iexecption; enum Weekday { SUN(0,"星期日"), MON(1,"星期一"), TUE(2,"星期二"), WED(3,"星期三"), THU(4,"星期四"), FRI(5,"星期五"), SAT(6,"星期六"); public final int dayValue; private final String chinese; private Weekday(int dayValue, String chinese) { this.dayValue = dayValue; this.chinese = chinese; } @Override //不允许被覆写,也可以直接调用name() public String toString() { return this.chinese; } } public class catchExample2 { public static void main(String[] args) throws Exception { Weekday day = Weekday.SUN; if (day.dayValue == 6 || day.dayValue == 0) { System.out.println("Today is " + day + " At home"); } else { System.out.println("Today is" + day + "At office"); } } }
覆写toString()的目的是在输出时更有可读性。
枚举可以应用在switch语句中。因为枚举天生举有类型信息和有限个枚举常量,
所以比int、String类型更适合用在switch语句中。
package com.imooc.iexecption; enum Weekday { SUN, MON, TUE, WED, THU, FRI, SAT; } public class catchExample2 { public static void main(String[] args) throws Exception { Weekday day = Weekday.SUN; switch (day) { case SUN: case MON: case THU: System.out.println("Today is " + day + " At home"); break; case WED: case TUE: System.out.println("Today is " + day + " At office"); break; case FRI: case SAT: default: throw new RuntimeException("cannot process" + day); } } }
加上default语句,可以在漏写某个枚举常量时自动报错。