一、使用场景:

什么情况下使用枚举类?
有的时候一个类的对象的个数是固定的,这种情况下我们就应该用枚举类来表示这个类。
比如表示星期,就可以将Week定义为一个枚举类, 同时为Week枚举类创建七个对象。
再比如表示季节,就可以将Season定义为一个枚举类,同时为Season枚举类创建四个对象。
再比如表示性别,可以将Gender定义为一个枚举类,同时为Gender枚举类创建两个对象。
再比如表示web项目给页码返回数据的状态,可以将State定义为一个枚举类,同时为State枚举类创建三个对象(200表示正常返回, 500表示异常返回, 404表示请求的资源不存在)。

二、用普通类实现枚举类相似的效果

为什么不用普通类来替代枚举类的效果?

比如表示季节,就可以将Season定义为一个枚举类,同时为Season枚举类创建四个对象。

package aah_enum;

public class Season {

    //1.创建4个Season类对象(只能在Season类中创建对象, 因为: Season类的构造方法是私有的)
    public static final Season SPRING = new Season("春天");
    public static final Season SUMMER = new Season("夏天");
    public static final Season FALL = new Season("秋天");
    public static final Season WINTER = new Season("冬天");
    //2.将成员变量和构造方法修饰为私有的  (构造方法用于: 给成员变量赋值)
    //构造方法修饰为私有的是为了在Season类中创建固定数量的对象(其他类中不能创建Season对象)
    private String seasonName;
    private Season(String sN){
        this.seasonName = sN;
    }
    //3.getSeasonName方法: 用于返回成员变量seasonName的值
    public String getSeasonName() {
        return seasonName;
    }

}



分析: 用枚举类的优点如下
枚举类更加直观,类型安全。使用普通类常量会有以下几个缺陷:
  1. 类型不安全。若一个方法中要求传入季节这个参数,用常量的话,形参就是String类型,假如构造方法不是私有的,则开发者传入任意类型的String类型值就行,但是如果是枚举类型的话,就只能使用枚举类Season中的四个对象。
  2. java提供了枚举这种语法适用于这种场景, 开发者看到枚举类型时,一下就知道此类只有固定数量的对象。


三、枚举类介绍:

3.1.枚举类的特点:

1.enum和class及interface的地位一样(是相似的)  (枚举enum更像普通类class)。
枚举enum更像普通类class:  构造方法私有、创建对象格式为:  对象名(构造方法参数值)
2.枚举类默认继承java.lang.Enum类(java.lang.Enum类继承java.lang.Object类)
3.枚举类的对象必须写在第一行而且默认用public static final修饰,不需使用new 关键字,不需显式调用构造器。
4.枚举类用enum关键字定义(而非class关键字)、枚举类不可以被继承
5.枚举类的构造方法只能是私有的(如果不写private,java会自动给构造添加private关键字)
6.枚举类中也可以定义成员变量和方法,可以是静态的也可以是非静态的。
枚举类的具体代码,如下:

3.2.枚举类案例:

package aah_enum;
//枚举类通常用于规定: 一个类只能有个别对象(不能有其他对象)   
//(也就是枚举类只能在枚举类中创建对象, 不能在此类之外创建对象)
public enum Season {
    //1.创建四个枚举类对象
    //这种写法比较特殊(通过 "对象名(构造方法参数值)"  形式调用第12行的构造方法 
    //创建对象  )
    // 比如 WINTER("冬天")  就是通过 "对象名(构造方法参数值)"  
    //形式调用第12行的构造方法 创建一个枚举对象WINTER(WINTER可以理解为对象的名称)
    //java编译器在编译时会自动将WINTER("冬天") 编译为 
    //public static final Season WINTER = new Season("冬天");
    WINTER("冬天"), SPRING("春天"), SUMMER("夏天"), FALL("秋天");
    //创建这四个对象的代码只能写在枚举类的最前边
    
    //2.枚举类的成员变量
    private String seasonName;
    //2.枚举类的构造方法: 枚举类的构造方法只能由private修饰
    //(如果不写private,java会自动给此构造方法添加private)
    private Season(String sn){
        this.seasonName=sn;
    }
    //3.get方法用于返回枚举类的成员变量seasonName的值
    // 在其他类中用 Season.WINTER.getSeasonName() 
    //调用getSeasonName获取seasonName成员变量的值
    public String getSeasonName(){
        return seasonName;
    }
}

class Test{
    public static void main(String[] args) {
        Season 季节 = Season.SPRING;
        switch (季节){
            case SPRING: System.out.println("现在是春暖花开的季节"); break;
            case SUMMER: System.out.println("现在是烈日如火的季节"); break;
            case FALL: System.out.println("现在是大雁南飞的季节"); break;
            case WINTER: System.out.println("现在是白雪茫茫的季节"); break;
        }
    }
}

实际上在第一行写枚举类实例的时候,默认是调用了构造器的,所以此处需要传入参数,因为没有显式申明无参构造器,只能调用有参数的构造器。
  构造器需定义成私有的,这样就不能在别处申明此类的对象了。枚举类通常应该设计成不可变类,它的Field不应该被改变,这样会更安全,而且代码更加简洁。所以我们将Field用private final修饰。
  

 四、枚举类实现接口:

  枚举类可以实现一个或多个接口。与普通类一样,实现接口的时候需要实现接口中定义的所有方法,若没有完全实现,那这个枚举类就是抽象的,只是不需显式加上abstract修饰,系统化会默认加上。

案例:

public interface Behaviour {
    void print();
 
    String getInfo();
}
 
public enum Color implements Behaviour {
    RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
    // 成员变量
    private String name;
    private int index;
 
    // 构造方法
    private Color(String name, int index) {
        this.name = name;
        this.index = index;
    }
 
    // 接口方法
    @Override
    public String getInfo() {
        return this.name;
    }
 
    // 接口方法
    @Override
    public void print() {
        System.out.println(this.index + ":" + this.name);
    }
}
使用接口组织枚举
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()方法容易多了。

//扩展: 代码模式的写法如下:
//写法一: 双检索实现方式
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;
     }
}
//方式二: 静态工厂方法实现
public class Singleton{
    private static final Singleton INSTANCE = new Singleton();
 
    private Singleton(){}
 
    public static Singleton getSingleton(){
        return INSTANCE;
    }
}

 六、枚举类中的抽象类:

 

package aah_enum;
public enum Operation {
    PLUS(){
        public double eval(double x, double y) {
            return x + y;
        }
    },
    MINUS(){
        public double eval(double x, double y) {
            return x - y;
        }
    },
    TIMES(){
        public double eval(double x, double y) {
            return x * y;
        }
    },
    DIVIDE{
        public double eval(double x, double y) {
            return x / y;
        }
    };
    /**
     * 抽象方法,由不同的枚举值提供不同的实现。
     * @param x
     * @param y
     * @return
     */
    public abstract double eval(double x, double y);
    public static void main(String[] args) {
        System.out.println(Operation.PLUS.eval(10, 2));
        System.out.println(Operation.MINUS.eval(10, 2));
        System.out.println(Operation.TIMES.eval(10, 2));
        System.out.println(Operation.DIVIDE.eval(10, 2));
    }
}

注:Operation类实际上是抽象的,不可以创建枚举值,所以此处在申明枚举值的时候,都实现了抽象方法,这其实是匿名内部类的实现,花括号部分是一个类体。我们可以看下编译以后的文件。

比如,下边的代码

public enum Weekday {
    SUN,MON,TUS,WED,THU,FRI,SAT
}


反编译之后:

这个类继承了java.lang.Enum类!所以说,枚举类不能再继承其他类了,因为默认已经继承了Enum类。  并且,这个类是final的!所以它不能被继承!

解释上边的抽象类:
PLUS是Operation 枚举类的一个引用类型的变量且用final修饰了, 此变量指向一个Operation对象(在上边反编译的代码中看不到), 但是因为Operation枚举类中有抽象方法eval。  回忆之前讲过的匿名内部类写法,如下:

Operation  PLUS=new Operation(){
  public double eval(double x, double y) {
            return x + y;
  }
};


注: 实际是不能写上边的代码的,因为Operation是枚举类。
在枚举类Operation中可以将上边匿名类格式写成下边格式:
//相当于省略 new Operation 保留 PLUS (){方法}

PLUS(){
        public double eval(double x, double y) {
            return x + y;
        }
}

 

# 七、枚举类的常用操作:
## 7.1.枚举类可用内容:
用idea查看枚举类的可用内容:


我们接下来会演示几个比较重要的:

package aah_enum;
public enum Week {
    //创建一个对象,将对象的地址赋值给变量MONDAY
    //Week MONDAY=new Week(1);
    MONDAY(1),TUESDAY(2),
    WEDNESDAY(3),THURSDAY(4),FRIDAY(5),SATURDAY(6),SUNDAY(7);
    private int dayValue;
    Week(int v) {
        this.dayValue = v;
    }
    public int getDayValue() {
        return dayValue;
    }
}
class  TestWeek{
    public static void main(String[] args) {
        //Week.valueOf("MONDAY")将字符串转换为枚举类型对象MONDAY
        System.out.println(  Week.valueOf("monday".toUpperCase())  );
        System.out.println(
            Week.valueOf("tuesday".toUpperCase()).getDayValue()  );
        System.out.println(Week.values()[3]); 
        //Week.values() 获取枚举类里的所有对象
        System.out.println(Week.values()[3].ordinal());
        //Week.values().ordinal() 获取枚举类里的所有对象的序号
        System.out.println(Week.MONDAY.compareTo(Week.MONDAY));
        System.out.println(Week.MONDAY.name());
        //Week.MONDAY.name(): 获取枚举类的对象的名字
        System.out.println(Week.MON.compareTo(Week.MON));
        System.out.println(Week.MON.name());
        //Week.MONDAY.name(): 获取枚举类的对象的名字
        System.out.println(Week.THU); // 默认会调用toString()方法
        System.out.println(Week.THU.toString());
        System.out.println(Week.THU.name());
        //name方法和toString()方法效果一样,只不过name方法用final修饰了不能被重写
        System.out.println(Week.THU.ordinal());//获取Weekday.THU的序号4
        System.out.println(Week.THU.compareTo(Week.SAT));
        //THU的序号 减去  SAT的序号
    }
}

 

class  TestWeek{
    public static void main(String[] args) {
        //测试类第1步: 定义一个枚举类型的变量weekDay,赋值为对象Week.MONDAY
        Week weekDay=Week.MONDAY;
        //测试类第2步:
        // 用switch判断枚举类型变量weekDay的值,看等于枚举类Week中七个对象的哪一个:
        // MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY
        switch (weekDay){
            case MONDAY:  System.out.println("今天是周1"); break;
            case TUESDAY: System.out.println("今天是周2"); break;
            case WEDNESDAY: System.out.println("今天是周3"); break;
            case THURSDAY: System.out.println("今天是周4"); break;
            case FRIDAY: System.out.println("今天是周5"); break;
            case SATURDAY: System.out.println("今天是周6"); break;
            case SUNDAY: System.out.println("今天是周7"); break;
            default:System.out.println("今天不知道是周几");
        }
    }
}

7.2.方法列举:


1.Weekday.valueOf() 方法:
它的作用是传来一个字符串,然后将它转变为对应的枚举变量。前提是你传的字符串和定义枚举变量的字符串一抹一样,区分大小写。如果你传了一个不存在的字符串,那么会抛出异常。
2.Weekday.values()方法。
这个方法会返回包括所有枚举变量的数组。在该例中,返回的就是包含了七个星期的Weekday[]。可以方便的用来做循环。
3.枚举变量的toString()方法。
该方法直接返回枚举定义枚举变量的字符串,比如MON就返回【”MON”】。
4.枚举变量的.ordinal()方法。
默认请款下,枚举类会给所有的枚举变量一个默认的次序,该次序从0开始,类似于数组的下标。而.ordinal()方法就是获取这个次序(或者说下标)


5.枚举变量的compareTo()方法。
该方法用来比较两个枚举变量的”大小”,实际上比较的是两个枚举变量的次序,返回两个次序相减后的结果,如果为负数,就证明变量1”小于”变量2 (变量1.compareTo(变量2),返回【变量1.ordinal() - 变量2.ordinal()】)

6.name()方法:
它和toString()方法的返回值一样,事实上,这两个方法本来就是一样的: 

 

八、案例:

如果需要用一个类表示七个星期值,并且需要根据当前的值获取下一天的星期值,可以用如下的案例代码来实现。

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
    }
}