枚举

为什么需要枚举

类常量不好用么?为什么要使用枚举。拿我之前的代码来做例子:

酒店订单的状态公有7种:已创建、支付超时、已取消、待商家确认、待入住、商家取消订单、已消费,我给它们都用一个类常量表示

class OrderStatus
{
    public static final int  CRETEA            = 1;
    public static final int  TIMEOUT           = 10;
    public static final int  CANCELED          = 20;
    public static final int  WAIT_CONFIRM      = 30;
    public static final int  WAIT_COME         = 40;
    public static final int  BUSINESS_CANCELED = 50;
    public static final int  CONSUMED          = 60;
}

对于这种单值类型的静态常量定义,本身也没错,主要是在使用的地方没有一个明确性的约束而已,比如:

/*
 * 修改订单状态
 */
public bool setStatus (int orderId, int orderStatus)
{
    // ...
}

这里的orderStatus,我们初意是传入OrderStatus类常量,但是由于没有类型约束,传入任意一个int值都是可以的,编译器也不会发出任何警告。

现在,我们用枚举类来修改上述代码:

enum OrderStatus{
    CRETEA,TIMEOUT,CANCELED,WAIT_CONFIRM,WAIT_COME,BUSINESS_CANCELED,CONSUMED
}

给setStatus改成更严格的类型约束:

public boolean setStatus (int orderId, OrderStatus status)
{
    return true;
}

自定义枚举类

好了,我们已经知道了枚举是很有作用的。接下来开始学习如何使用枚举类了。

在jdk1.5之前,是没有enum关键字的,需要自定义枚举类。会写自定义枚举类,对照学习enum就非常轻松。

自定义枚举类过程:

  • 私有化构造器,类外不能创建其对象
  • 在类的内部创建枚举类的实例。声明为:public static final
  • 对象如果有实例属性,应该声明为private final,并在构造器中初始化

下面我们来创建两个枚举类,一个含属性,一个不含属性

第一个是关于星期的枚举类:

class Weekend
{
    public static final Weekend MON = new Weekend();
    public static final Weekend TUS = new Weekend();
    public static final Weekend WED = new Weekend();
    public static final Weekend THU = new Weekend();
    public static final Weekend FRI = new Weekend();
    public static final Weekend SAT = new Weekend();
    public static final Weekend SUN = new Weekend();

    private Weekend ()
    {

    }
}

第二个是关于订单状态的枚举类:

class OrderStatus
{
    private final int val;
    private final String desc;

    public static final OrderStatus CREATE = new OrderStatus(10, "订单已创建");
    public static final OrderStatus TIMEOUT = new OrderStatus(20, "支付超时");
    public static final OrderStatus PAYED = new OrderStatus(30, "已支付");
    public static final OrderStatus REFUND = new OrderStatus(40, "已退款");

    private OrderStatus (int value, String d)
    {
        val = value;
        desc = d;
    }

    public int getVal() {
        return val;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public String toString() {
        return "OrderStatus{" +
                "val=" + val +
                ", desc='" + desc + '\'' +
                '}';
    }
}

测试代码如下:

OrderStatus[] arrs = {OrderStatus.CREATE ,OrderStatus.TIMEOUT,OrderStatus.PAYED,OrderStatus.REFUND };

for (OrderStatus o : arrs) {
    System.out.println(o);
}

使用enum关键字定义枚举类

使用enum关键字后,就可以简化我们的操作,定义枚举类就变得非常方便。

enum Week { MON ,TUS,WED,THU,FRI,SAT,SUN}
enum OrderStatus
{
    CREATE(10, "订单已创建"),
    TIMEOUT(20, "支付超时"),
    PAYED(30, "已支付"),
    REFUND(40, "已退款");

    private final int val;
    private final String desc;

    private OrderStatus (int value, String d)
    {
        val = value;
        desc = d;
    }

    public int getVal() {
        return val;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public String toString() {
        return "OrderStatus{" +
                "val=" + val +
                ", desc='" + desc + '\'' +
                '}';
    }
}

// 测试代码
OrderStatus[] arrs = {OrderStatus.CREATE, OrderStatus.TIMEOUT, OrderStatus.PAYED, OrderStatus.REFUND};

for (OrderStatus o : arrs) {
    System.out.println(o.getDesc());
}

enum特点和常用方法

有以下几个特点:

  • 定义的enum类型总是继承自java.lang.Enum,且无法被继承;
  • 只能定义出enum的实例,而无法通过new操作符创建enum的实例;
  • 定义的每个实例都是引用类型的唯一实例;
  • 可以将enum类型用于switch语句。

编译后的enum类和普通class并没有任何区别。但是我们自己无法按定义普通class那样来定义enum,必须使用enum关键字,这是Java语法规定的。

因为enum是一个class,每个枚举的值都是class实例,因此,这些实例有一些方法:

values()

返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。

OrderStatus[] arrs = OrderStatus.values();

name()

name:返回当前枚举类对象常量的名称。默认情况下,toString方法和name方法返回值是一样的。但是,name方法不会被重写,toString方法可以。所以,想要获取枚举类对象常量的名称,必须使用name方法。

注意:判断枚举常量的名字,要始终使用name()方法,绝不能调用toString()!

OrderStatus[] arrs = OrderStatus.values();

for (OrderStatus o :arrs){
    System.out.println(o.name());
}

// 依次打印CREATE TIMEOUT PAYED REFUND

valueOf()

valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。

String createStr = "CREATE";
OrderStatus create = OrderStatus.valueOf(createStr);
System.out.println(create.getDesc());  // 订单已创建

enum的比较

使用enum定义的枚举类是一种引用类型。前面我们讲到,引用类型比较,要使用equals()方法,如果使用==比较,它比较的是两个引用类型的变量是否是同一个对象。因此,引用类型比较,要始终使用equals()方法,但enum类型可以例外。

这是因为enum类型的每个常量在JVM中只有一个唯一实例,所以可以直接用==比较:

if (day == Weekday.FRI) { // ok!
}
if (day.equals(Weekday.SUN)) { // ok, but more code!
}

switch中使用enum

枚举类可以应用在switch语句中。因为枚举类天生具有类型信息和有限个枚举常量,所以比intString类型更适合用在switch语句中:

public class Main {
    public static void main(String[] args) {
        Weekday day = Weekday.SUN;
        switch(day) {
        case MON:
        case TUE:
        case WED:
        case THU:
        case FRI:
            System.out.println("Today is " + day + ". Work at office!");
            break;
        case SAT:
        case SUN:
            System.out.println("Today is " + day + ". Work at home!");
            break;
        default:
            throw new RuntimeException("cannot process " + day);
        }
    }
}

enum Weekday {
    MON, TUE, WED, THU, FRI, SAT, SUN;
}

枚举与接口

和普通 Java 类一样,枚举类可以实现一个或多个接口。若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可。若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法

interface IPay
{
    void pay();
}

enum PayType implements IPay
{
    WXPAY("微信支付") {
        @Override
        public void pay() {
            System.out.println("微信支付成功");
        }
    },
    ALIPAY("支付宝支付") {
        @Override
        public void pay() {
            System.out.println("支付宝支付成功");
        }
    };

    private final String desc;

    private PayType (String desc)
    {
        this.desc = desc;
    }

    @Override
    public String toString() {
        return desc;
    }
}

测试代码如下:

public class T2 {
    public static void main(String[] args) {
        PayType[] paytypes = PayType.values();

        for (PayType t : paytypes) {
            System.out.print(t + ":");
            t.pay();
        }
    }
}

输入内容如下:

微信支付:微信支付成功
支付宝支付:支付宝支付成功