前言
java枚舉是在開發過程中用的最多的類,這裏對java之前的枚舉常量類和枚舉做了一個分析,並且對枚舉相關知識拾遺。
枚舉類
在出現枚舉之前,通常是一個final類去表示"可枚舉"這個概念,比如下面這個列舉數字的枚舉類
/**
* 模擬枚舉類 (枚舉類:在enum出現之前的表達 可枚舉的含義的類)
* 通常 private 構造函數
* final class
* private static final 本類型 成員
*
*/
final class EnumClass{
public static final EnumClass ONE = new EnumClass(1);
public static final EnumClass TWO = new EnumClass(2);
public static final EnumClass THREE = new EnumClass(3);
public static final EnumClass FOUR = new EnumClass(4);
@Getter
@Setter
private int value;
private EnumClass(int value) {
this.value = value;
}
public void print() {
System.out.println(this.toString());
}
}
可以看到枚舉類的特點:
- 成員用常量來表示,並且類型爲當前類型(當前類型)
- 常被設置爲final類
- 非public構造器(自己內部來創建實例)
這樣有些缺點,比如:
- 枚舉的輸出打印的時候要怎麼做?每個成員是第幾個定義的?要想達到這些操作就必須要寫一些方法,而每個枚舉類去這樣寫這樣的方法是比較蛋疼的,因爲他不具有枚舉的values方法。
枚舉
這裏寫出對應的java枚舉
enum CountingEnum {
ONE(1),
TWO(2),
THREE(3),
FOUR(4)
;
@Getter
private int value;
/** private */ CountingEnum (int value) {
this.value = value;
}
}
這裏如果想要輸出對應的名字和順序,那麼就十分方便了。
// 輸出 枚舉 中的名字、位置、輸出所有枚舉
Arrays.stream(CountingEnum.values()).forEach(e -> {
System.out.println("輸出枚舉中的順序: " + e.ordinal() + "名字:" + e.name() + "value:" + e.getValue());
});
可以看到輸出:
這是因爲java所有的枚舉都是繼承Enum抽象類的,而valueOf()方法、ordinal()方法、name()方法都是定義在其中的,可以看下Eunm抽象類。
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;
}
public final boolean equals(Object other) {
return this==other;
}
public final int hashCode() {
return super.hashCode();
}
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
/**
* enum classes cannot have finalize methods.
*/
protected final void finalize() { }
/**
* prevent default deserialization
*/
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
}
仔細看也許你會有兩個疑問:
- 沒看到顯示的定義CountingEnum時繼承Enum類的?
- values方法也沒有看到在父類中定義?
對這兩個疑問我們可以去看這個類對應的字節碼:
可以看到:
- enum其實也是final class, 雖然沒有顯示繼承,但是其實是繼承了
Enum<T>
類的,所以可以訪問到對應的name,ordinal字段,這個設計讓我們輸出枚舉一些信息的時候很便捷。也提供了valueOf方法,也可以在動態判斷枚舉的時候使用。
在來看下邊的字節碼:
可以看到是有values方法,其實這個是jvm通過字節碼提升的方式去爲枚舉做的優化。所以使用枚舉可以快速遍歷並且一些輸出之類的操作。
可以總結下枚舉的特點:
- 枚舉其實就是final class,並且繼承java.lang.Enum抽象類。
- 枚舉可以實現接口。
- 枚舉不能顯示的繼承和被繼承
- 留個坑:既然是final class,那麼枚舉裏可以定義抽象方法嗎?
枚舉中抽象方法的設計
我們在看基礎語法的時候,總是說final 和 abstract是互斥的,所以想當然的認爲枚舉中不能定義抽象方法,但結論其實是可以的。
我們先看一個枚舉來實現加減操作的例子:
enum Opration {
PLUS,
DIVIDE
;
public double apply(double x, double y) {
switch (this) {
case PLUS:
return x + y;
case DIVIDE:
return x - y;
}
throw new AssertionError("unknown");
}
}
這個實現其實是通過在枚舉中加入了非枚舉含義的方法和域來實現的操作的一個類型枚舉。但是有個問題,當拓展新的操作符時,需要破壞switch中的邏輯,這個不太符合開閉原則,這時候就可以通過把apply作爲抽象方法,使得拓展時只需要實現符合自己的抽象邏輯。
/**
* 通過抽象方法, 來實現加入新的操作的時候 能符合開閉原則,只關心自己操作符抽象的實現
*/
enum OperationOptimize {
PLUS("+"){
@Override
public int apply(int x, int y) {
return x + y;
}
},
DIVIDE("-") {
@Override
public int apply(int x, int y) {
return x - y;
}
}
;
@Getter
private String str;
private OperationOptimize(String str) {
this.str = str;
}
// 抽象方法
public abstract int apply(int x, int y);
}
所以枚舉是可以定義抽象方法的。
jdk中其實也有對應的例子,可以看下TimeUnit這個時間單位枚舉,枚舉類型都是通過實現抽象方法(其實是返回異常的普通方法,思想是一樣的)來實現不同時間單位的轉化。
彩蛋
如何給上邊的枚舉類實現一個values方法?
因爲需要遍歷所有的字段,所以很自然的想到了反射去實現。這裏需要注意,因爲枚舉類定義的枚舉都是public static final,而作爲val變量是int的一個修飾符,需要將除了枚舉外的val變量排除~
示例代碼:
final class EnumClass {
public static final EnumClass ONE = new EnumClass(1);
public static final EnumClass TWO = new EnumClass(2);
public static final EnumClass THREE = new EnumClass(3);
public static final EnumClass FOUR = new EnumClass(4);
@Getter
@Setter
private int value;
private EnumClass(int value) {
this.value = value;
}
@Override
public String toString() {
return "EnumClass{" +
"value=" + value +
'}';
}
public void print() {
System.out.println(this.toString());
}
/**
* 爲枚舉類實現一個values方法
*/
public static EnumClass[] values() {
// 獲取枚舉類中所有字段
return Stream.of(EnumClass.class.getDeclaredFields())
// 過濾出 public static final的
.filter(field -> {
// 修飾符
int modifiers = field.getModifiers();
return Modifier.isPublic(modifiers)
&& Modifier.isStatic(modifiers)
&& Modifier.isFinal(modifiers);
})
// 取出對應的字段值
.map(field -> {
try {
return field.get(null);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}).toArray(EnumClass[]::new);
}
}