都有常量了?为啥还用枚举?
比如你定义一个方法用来接收四季的数值,然后根据不同的数值执行不同的逻辑,如果只是用常量,此时你会发现你没办法限制调用者传什么值进来,他可以按照常量的范围传递1,2,3,4也可以传递5,6,7,8,最主要的你没有办法让调用者直观感受应该传什么值。毕竟你的参数只是int类型,代表只要传递int数值就是合法的。当你你可以加注释或者对数字做一些逻辑,但这不是一个好的设计。
为了更好的限制调用者,我们可以将之前定义的常量改为自定义的季节类型同时将类设置为final以防被继承,构造方法设置为private防止外部实例化,这样保证季节类型的对象只会有这几个常量,方法参数就可以改为季节类型,这样调用者就能传递你定义好的常量,他最多只能额外传递一个null值,要是传别的类型编译器会直接报错。通过这种设计就能在没有任何额外说明和额外逻辑的情况下,限制了调用者传递的内容,调用者也能直观感受到这个参数的含义。
这种设计就是枚举的原理,我们可以通过枚举来满足之前的操作
enum关键字声明枚举
枚举反编译也和class没什么区别
枚举类
枚举类与普通类的区别:
1.枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承java.lang.Enum类,而不是默认继承Object类,因此枚举类不能显示继承其他父类,其中 java.lang.Enum类实现了java.lang.Serializable和java.lang.Comparable两个接口。
2.使用enum定义、非抽象的枚举类默认会使用final修饰,因此枚举类不能派生子类。
3.枚举类的构造器只能使用private访问控制符,如果省略了构造器的访问控制符,则默认使用private修饰;如果强制指定访问控制符,则只能指定private修饰符。
4.枚举类的所有实例必须在枚举类的第一行显示列出,否则这个枚举类永远不能产生实例。列出这些实例时,系统会自动添加public static final修饰,无需我们显式添加。
枚举类提供了一个values()方法,该方法可以很方便的遍历所以枚举值。
一、常量
枚举类型可以取代以往常量的定义方式,即将常量封装在类或接口中。此外,枚举类型还提供了安全检查功能。枚举类型本质上还是以类的形式存在。
package meiju;
public enum SeasonEnum {
//在第一行列出4个枚举实例
SPRING,SUMMER,FALL,WINTER;
}
package meiju;
public class EnumTest {
public void judge(SeasonEnum s){
//switch语句里的表达式可以是枚举值
switch (s){
case SPRING:
System.out.println("春天");
break;
case SUMMER:
System.out.println("夏天");
break;
case FALL:
System.out.println("秋天");
break;
case WINTER:
System.out.println("冬天");
break;
}
}
public static void main(String[] args) {
//枚举类默认有一个values()方法,返回该枚举类的所有实例
for (SeasonEnum s:SeasonEnum.values()){
System.out.println(s);
}
//使用枚举实例时,可通过EnumClass.variable形式来访问
new EnumTest().judge(SeasonEnum.SPRING);
}
}
定义枚举类时,需要显示列出所有的枚举值,如上面的SPRING,SUMMER,FALL,WINTER;所示,所有的枚举之间以英文逗号隔开,这些枚举值代表了该枚举类的所有可能的实例。
如果需要使用该枚举类的某个实例,则可以使用EnumClass.variable的形式,如SeasonEnum.SPRING。
switch的控制表达式可以是任何枚举类型。不仅如此,当switch控制表达式使用枚举类型时,后面case表达式的值直接使用枚举值的名字,无需添加枚举类作为限定。
二、枚举类的成员变量、方法和构造器
枚举类型较传统定义常量的方式,除了具有参数类型检测(switch)的优势之外,还具有其他方面的优势。
可以将一个枚举类型看作是一个类,它继承于java.lang.Enum类,当定义一个枚举类型时,每一个枚举类型成员都可以看作是枚举类型的一个实例,这些枚举类型成员默认都被final、public、static所修饰,所以当使用枚举类型成员时直接使用枚举类型名称调用枚举类型成员即可。
package meiju;
public enum Gender {
MALE,FEMALE;
//定义一个public修饰的实例变量
public String name;
}
package meiju;
public class GenderTest {
public static void main(String[] args) {
//通过Enum的valueOf()方法来获取指定枚举类的枚举值
Gender g = Enum.valueOf(Gender.class, "FEMALE");//此时的g也就是枚举值,相当于new产生的对象
//直接为枚举值的name实例变量赋值
g.name="女";
//直接访问枚举值的name实例变量
System.out.println(g+"代表"+g.name);
}
}
上面程序在使用Gender枚举类时与使用一个普通类没有什么太大的区别,差别只是产生Gender对象的方式不同,枚举类的实例只能是枚举值(如上面程序的MALE,FEMALE),而不是随意的像普通类一样通过new来创建枚举类对象。
package meiju;
public enum Gender {
MALE,FEMALE;
//定义一个public修饰的实例变量
private String name;
public void setName(String name){
switch (this){
case MALE:
if(name.equals("男")){
this.name=name;
}else{
System.out.println("参数错误");
return;
}
break;
case FEMALE:
if(name.equals("女")){
this.name=name;
}else {
System.out.println("参数错误");
return;
}
break;
}
}
public String getName() {
return this.name;
}
}
package meiju;
public class GenderTest {
public static void main(String[] args) {
Gender g = Gender.valueOf("FEMALE");
g.setName("女");
System.out.println(g+"代表"+g.getName());
//此时设置name值时将会提示参数错误
g.setName("男");
System.out.println(g+"代表"+g.getName());
}
}
g.setName(“男”); 上面程序试图将一个FEMALE枚举值的name变量设置为"男",系统将会提示参数错误并输出出来。这种做法不好,枚举类通常应该设计成不可变类,也就是说,他的成员变量值不应该允许改变,这样更安全,代码更整洁。因此建议将枚举类的成员变量都使用private final修饰。如果将所有的成员变量使用了final修饰,那么必须在构造器里为这些成员变量指定初始值(或者在定义成员变量时指定默认值,或者在初始化块中指定初始值,但这两种情况并不常见)因此应该为枚举类显示定义带参数的构造器。
一旦为枚举类显示定义了带参数的构造器,列出枚举值时就必须对应地传入参数。
package meiju;
public enum Gender {
//此处的枚举值必须调用对应的构造器来创建
MALE("男"),FEMALE("女");
//定义一个public修饰的实例变量
private String name;
private Gender(String name){
this.name=name;
}
public String getName() {
return this.name;
}
}
package meiju;
public class GenderTest {
public static void main(String[] args) {
Gender g = Gender.valueOf("FEMALE");
Gender M = Gender.valueOf("MALE");
System.out.println(g+"代表"+g.getName());
System.out.println(M+"代表"+M.getName());
}
}
可以看出在枚举类中列出枚举值时,实际上就是调用构造器创建枚举类对象,这里无须使用new关键字,也无须显示调用构造器。前面列出枚举值时无须传入参数,甚至无须使用括号,因为前面的枚举类包含无参构造器。
MALE(“男”),FEMALE(“女”);实际上等同于public static final Gender MALE=new Gender(“男”); public static final Gender FEMALE=new Gender(“女”);
三、实现接口的枚举类
枚举类也可以实现一个或多个接口。与普通类实现一个或多个接口完全一样,枚举类实现一个或多个接口时,也需要实现该接口所包含的方法。
package meiju;
public interface d {
public int getValue();
public String getDescription();
}
package meiju;
public enum AnyEnum implements d{
RED{
@Override
public int getValue() {
return 1;
}
@Override
public String getDescription() {
return "我是红色";
}
},BLUE {
@Override
public int getValue() {
return 2;
}
@Override
public String getDescription() {
return "我是蓝色";
}
},GREEN{
@Override
public int getValue() {
return 3;
}
@Override
public String getDescription() {
return "我是绿色";
}
};
public static void main(String[] args) {
//通过枚举类名.values()可以遍历所以的枚举值
for(AnyEnum c:AnyEnum.values()){
System.out.println("枚举成员:"+c+"值"+c.getValue()+" 描述:"+c.getDescription());
}
}
}
当创建RED,BLUE,GREEN三个枚举值时,后面又紧跟着一对花括号,这对花括号包含了两个getValue和getDescription方法定义。花括号实际上是一个类体部分,在这种情况下,当创建RED,BLUE,GREEN枚举值时,并不是创建AnyEnum枚举类的实例,而是相当于创建AnyEnum的匿名子类的实例。这个部分代码和匿名内部类的语法大致相似。
四、包含抽象方法的枚举类
package meiju;
public enum Operation {
PLUS {
@Override
public double eval(double x, double y) {
return x + y;
}
}, MINUS {
@Override
public double eval(double x, double y) {
return x - y;
}
};
//为枚举类定义一个抽象方法
//这个抽象方法由不同的枚举值提供不同的实现
public abstract double eval(double x, double y);
public static void main(String[] args) {
System.out.println(Operation.PLUS.eval(3, 4));
System.out.println(Operation.MINUS.eval(5, 4));
}
}
枚举类里定义抽象方法时不能使用abstract关键字将枚举类定义成抽象类(因为系统自动会为它添加abstract关键字),但因为枚举类需要显示创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。
五、操作枚举类型成员的方法
方法名称 | 具体含义 | 使用方法 |
---|---|---|
values() | 该方法可以将枚举类型成员以数组的形式返回 | 枚举类型名称.values() |
valueOf() | 该方法可以实现将普通字符串转换为枚举实例 | 枚举类型名称.valueOf(“ABC”) |
compareTo() | 该方法用于比较两个枚举对象在定义时的顺序 | 枚举对象.compareTo() |
ordinal() | 该方法用于得到枚举成员的位置索引 | 枚举对象.ordinal() |
package meiju;
public class showEnum {
enum ColorEnum{
RED,BLUE,GREEN
};
//循环由values()方法返回的数组
public static void main(String[] args) {
System.out.println("方式一:");
for (int i=0;i<ColorEnum.values().length;i++){
//将枚举成员变量打印
System.out.println("枚举类型成员变量: "+ColorEnum.values()[i]);
}
System.out.println("方式二");
for (ColorEnum c:ColorEnum.values()){
//将枚举成员变量打印
System.out.println("枚举类型成员变量: "+c);
}
}
}
2)valueOf()与compareTo()方法
枚举类型中静态方法valueOf()可以实现将普通字符串转换为枚举实例(字符串要与枚举值一致这样才能获取到对应的枚举实例),而compareTo()方法用于比较两个枚举对象在定义时的顺序。
示例:枚举中valueOf()与compareTo()方法的使用。
package meiju;
public class EnumMethodTest {
enum ColorEnum{
RED,BLUE,GREEN
};
//定义比较枚举类型方法,参数类型为枚举类型
public static void compare(ColorEnum c){
//根据values()方法返回的数组做循环操作
for(int i=0;i<ColorEnum.values().length;i++){
//将比较结果返回
System.out.println(c+"与"+ColorEnum.values()[i]+"的比较结果为:"+c.compareTo(ColorEnum.values()[i]));
}
}
public static void main(String[] args) {
//使用valueof()将字符串转换为枚举实例
ColorEnum c = ColorEnum.valueOf("BLUE");
compare(c);
}
}
说明:调用compareTo()方法返回的结果,正值代表方法中的参数在调用该方法的枚举对象位置之前;0代表两个相互比较的枚举成员的位置相同;负值代表方法中参数在调用该方法的枚举对象位置之后。
(3)ordinal()方法
该方法用于得到枚举成员的位置索引。
示例:枚举中ordinal()方法的使用。
package meiju;
//枚举中ordinal()方法的使用
public class EnumOrdinalTest {
public enum ColorEnum{
RED,BLUE,GREEN
};
public static void main(String[] args) {
for(int i=0;i<ColorEnum.values().length;i++){
//在循环中获取枚举类型成员的索引位置
System.out.println(ColorEnum.values()[i]+"在枚举类型中位置索引值"+ColorEnum.values()[i].ordinal());
}
}
}