一、使用场景:
什么情况下使用枚举类?
有的时候一个类的对象的个数是固定的,这种情况下我们就应该用枚举类来表示这个类。
比如表示星期,就可以将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
}
}