Java基礎:Enum 枚舉

Java 基礎:Enum 枚舉

簡介

本篇將要介紹 Java 中的枚舉類型(Enum),枚舉類型的作用在於限定實例(或是說對象(object)的個數),有點像是常數集合的替代,但是它比一般的常數更強大也更安全,接下來將會介紹枚舉和一般常數的區別以及用法。

參考

Java 列舉(Enum)範例https://matthung0807.blogspot.com/2017/10/java-enum.html
Java 枚举源码分析https://blog.jrwang.me/2016/java-enum/

正文

常數定義

首先我們先來看到最基本的常數定義。當我們想要定義一組常數或是一個固定值的時候,為了避免魔術值(magic number) 的出現,我們很理所當然地會想到下列用法:

public class Task {
  // Task State
  public static final int START = 1;
  public static final int ONGOING = 2;
  public static final int END = 3;
  public static final int ERROR = -1;
  // Task other logic
}

但是這時候可能會出現問題,第一個是你可能沒辦法限定狀態的個數,因為類中可能混雜了其他常量或是業務功能,我們希望將限定狀態(枚舉狀態)的值歸納到同一個類中,如下

public class Task {
  // Task other logic
  class TaskState {
    public static final int START = 1;
    public static final int ONGOING = 2;
    public static final int END = 3;
    public static final int ERROR = -1;
  }
}

第二個問題是沒辦法進行有效的類型檢查,同時也不能保證枚舉值的可靠性,因為普通的常數定義可能出現如下情況

public static final int START = 1;
public final int ONGOING = 2;
public static int END = 3;
public int ERROR = -1;
public static final String ALIVE = "keep alive";

接下來我們將使用 Enum 枚舉來歸納我們的限定類型

Enum 枚舉

Definition 定義

首先我們先來看一下枚舉類的定義的核心部分:

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
  private final String name;
  public final String name() {
    return name;
  }

  private final int ordinal;
  public final int ordinal() {
    return ordinal;
  }

  protected Enum(String name, int ordinal) {
    this.name = name;
    this.ordinal = ordinal;
  }

  public String toString() {
    return name;
  }
}

一個枚舉類最重要的兩個屬性 name 表示字面量(也就是對象名)、ordinal 表示順序(表示定義的索引);同時我們看到 toString 方法可以看到當你直接打印一個枚舉對象時,將會直接輸出他的 name 本質上就是一個 String

name 方法

成員變量 name 保存了枚舉對象的字面量,也就是他的名字,而我們可以使用與他同名的方法來訪問

Color.BLUE.name() // BLUE
ordinal 方法

ordinal 則紀錄著枚舉對象的序號,也就是定義的位置,它同樣也定義了一個同名方法用於訪問

Color.BLUE.ordinal // 2

1. Basic Usage 基本用法

接下來我們看第一個例子,也是最簡單的僅僅定義限定值,沒有任何其他操作

public enum Color {
  RED, GREEN, BLUE
}

我們先嘗試反編譯一下這段代碼

public final class Color extends java.lang.Enum<Color> {
  public static final Color RED;
  public static final Color GREEN;
  public static final Color BLUE;
  public static Color[] values();
  public static Color valueOf(java.lang.String);
  static {};
}

我們可以看到所謂的枚舉(Enum)本質上就是創建多個類作為 public static final 的靜態變量,並且附加了兩個新方法:values()valueOf

values 方法

使用 Color.values() 方法可以返回所有枚舉對象

Color[] colors = Color.values();
for(Color color : colors) {
  System.out.println(color);
}

// output:
// RED
// GREEN
// BLUE
valueOf 方法

原本我們可以透過字面量 Color.xxx 來拿到對應的實例,我們還可以透過使用 valueOf 傳入字面量的字符串來獲取對象,這樣的靈活性和可編程性更高

Color blue = Color.valueOf("BLUE")

2. Properties 成員變量

上面構造了最基礎的枚舉對象,僅僅是作為一個類別的符號,接下來我們想要擴展一下每一個枚舉對象

public enum CustomerException {
  ControllerException(0, "unknown controller exception"),
  ServiceException(1, "unknown service exception"),
  DaoException(2, "unknown dao exception");

  private int code;
  private String message;

  CustomerException(int code, String message) {
    this.code = code;
    this.message = message;
  }

  public int getCode() {
    return code;
  }

  public String getMessage() {
    return message;
  }

  @Override
  public String toString() {
    return this.code + "\n" + this.message;
  }
}
  • 測試
CustomerException controllerException = CustomerException.ControllerException;
System.out.println(controllerException);
CustomerException serviceException = CustomerException.ServiceException;
System.out.println(serviceException.getCode());

// output:
// 0
// unknown controller exception
// 1

我們發現其實枚舉對象就是普通的對象實例,而 ControllerException(0, "unknown controller exception") 這種語法就好像是調用它的構造函數一樣 ,並且成員變量聲明為 private 使用 getter/setter 來調用。

3. Implement 實現接口

第三個例子我們將要使用枚舉類來實現接口

  • IAnimal.java
public interface IAnimal {
  void spark();
}
  • Animal.java
public enum Animal implements IAnimal {
  DOG("dog") {
    public void spark() {
      System.out.println("wang wang");
    }
  },
  CAT("cat") {
    public void spark() {
      System.out.println("miao miao");
    }
  };

  private String type;

  Animal(String type) {
    this.type = type;
  }
}
  • Test.java
Animal dog = Animal.DOG;
dog.spark();
Animal cat = Animal.CAT;
cat.spark();

// output:
// wang wang
// miao miao

如此一來枚舉類幾乎和一般的 class 沒什麼兩樣,也能夠實現接口(interface),語法形式

CAT("cat") {
  public void spark() {
    System.out.println("miao miao");
  }
};

是不是跟匿名內部類的使用方式很像呢。

4. Singleton 單例模式

最後一個例子我們講解透過枚舉(Enum)來實現單例模式

  • Singleton.java
public enum Singleton {
  INSTANCE;

  private int id;

  Singleton() {
    this.id = 0;
  }

  public void doSomthing() {
    System.out.println("do something with same instance: id = " + id);
  }
}
  • Test.java
Singleton s1 = Singleton.INSTANCE;
s1.doSomthing();
Singleton s2 = Singleton.valueOf("INSTANCE");
s2.doSomthing();
System.out.println("s1 == s2 ? " + (s1 == s2));

// output:
// do something with same instance: id = 0
// do something with same instance: id = 0
// s1 == s2 ? true

我們將實例定義為枚舉類的唯一枚舉對象,就能夠實現單例模式啦!同時他也是線程(thread)安全的喔!供大家參考

結語

本篇介紹了枚舉類的基本形式,nameordinalvaluesvalueOf 四個方法,還有使用枚舉(Enum)實現接口(Interface),最後我們展示了一下最基礎的單例模式用法。從多線程的角度來看枚舉就是單例模式最好的實現,可以避免複雜的鎖的邏輯。下篇我們將會介紹使用枚舉配合 @ExceptionHandler 來實現 SpringMVC 中的全局異常處理,就可以不用在業務邏輯中間插一堆 try ... catch 塊了,不僅能夠將異常處理獨立出來,還能透簡化代碼提升可讀性,非常實用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值