- JavaSE-Adventure(VI) Enum 枚举
- 如何使用枚举
- 定义枚举
- 枚举为什么特殊(枚举类型的本质)
- Enum 类
- 枚举的特性, 优点
- 枚举可以添加成员变量, 方法, 构造器
- 枚举可以实现接口
- 枚举不可以继承
- EnumSet和EnumMap
- EnumSet
- EnumMap
- 枚举的应用
- switch 状态机
- 组织枚举
- 策略枚举
Java 5.0引入了枚举,枚举限制变量只能是预先设定好的值。
当类的对象时有限且固定的,如季节类,它只有春夏秋冬4个对象这种实例有限且固定的类,在 Java 中可以定义为枚举类。
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
枚举是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性。
如何使用枚举 定义枚举访问修辞符 enum 枚举名 {
枚举成员,
枚举成员,
...
};
public enum Color {
RED, GREEN, BLUE
}
如果枚举不添加任何方法,枚举值默认为从0开始的有序数值。以 Color 枚举类型举例,它的枚举常量依次为 RED:0,GREEN:1,BLUE:2。
枚举为什么特殊(枚举类型的本质)尽管 enum看起来像是一种新的数据类型,事实上,enum是一种受限制的类,并且具有自己的方法。
且创建enum时,编译器会为你生成一个相关的类,这个类继承自 java.lang.Enum。
public abstract class Enum> implements Comparable , Serializable { ... }
接下来更深入一点, 假如我们定义一个星期类:
//定义枚举类型
enum Day {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
通过编译Day.java 后生成的.class 文件并反编译后, 我们查看反编译后的代码:
final class Day extends Enum
{
//编译器为我们添加的静态的values()方法
public static Day[] values()
{
return (Day[])$VALUES.clone();
}
//编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum也类的valueOf方法
public static Day valueOf(String s)
{
return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s);
}
//私有构造函数
private Day(String s, int i)
{
super(s, i);
}
//前面定义的7种枚举实例
public static final Day MONDAY;
public static final Day TUESDAY;
public static final Day WEDNESDAY;
public static final Day THURSDAY;
public static final Day FRIDAY;
public static final Day SATURDAY;
public static final Day SUNDAY;
private static final Day $VALUES[];
static
{
//实例化枚举实例
MONDAY = new Day("MONDAY", 0);
TUESDAY = new Day("TUESDAY", 1);
WEDNESDAY = new Day("WEDNESDAY", 2);
THURSDAY = new Day("THURSDAY", 3);
FRIDAY = new Day("FRIDAY", 4);
SATURDAY = new Day("SATURDAY", 5);
SUNDAY = new Day("SUNDAY", 6);
$VALUES = (new Day[] {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
});
}
}
从反编译的代码可以看出:
- 编译器生成了一个Day类
- 该类由final修饰,将无法被继承
- 该类继承自java.lang.Enum
- 该类构造器是私有的, 无法通过new 关键字进行实例化
- 生成了7个Day 类型的实例对象, 分别对应枚举中定义的7个日期
- 编译器还生成了两个静态方法,分别是values()和 valueOf()
values() 方法返回一个包含枚举中所定义常量的数组。
valueOf(String) 方法根据传入字符串的值, 和枚举中所定义的常量名进行匹配, 并返回匹配到的枚举值。
Enum自带的静态方法valueOf()则需要传递两个方法,从前面反编译后的代码可以看出,编译器生成的valueOf方法最终还是调用了Enum类的valueOf方法
Enum 类public abstract class Enum枚举的特性, 优点> implements Constable, Comparable , 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(); } // 比较此枚举与指定对象的顺序 public final int compareTo(E o) { Enum> other = (Enum>)o; Enum self = this; if (self.getClass() != other.getClass() && // optimization self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException(); return self.ordinal - other.ordinal; } }
-
提供编译期的类型检查, 枚举类型预定义的类型, 避免了错误赋值导致的运行时报错
-
枚举类型可以直接通过 == 进行比较, 因为枚举值都是默认使用 static, final 进行修饰, 内存中仅存在一个实例
-
switch 状态机, 使代码可读性更高, 效率更高
-
枚举类型可以拥有构造器(可拥有多个), 成员变量, 方法, 实现接口
-
枚举类型的构造器永远是私有的, 无法通过new 关键字进行实例化, 只有在定义枚举类时就进行初始化
除了不能继承,基本上可以将 enum看做一个常规的类。
枚举可以添加成员变量, 方法, 构造器枚举值默认为从0开始的有序数值。那么问题来了:如何为枚举显示的赋值。
枚举可以添加普通方法、静态方法、抽象方法、构造方法
Java 虽然不能直接为实例赋值,但是它有更优秀的解决方案:为 enum 添加方法来间接实现显示赋值。
创建 enum时,可以为其添加多种方法,甚至可以为其添加构造方法。
注意一个细节:如果要为enum定义方法,那么必须在enum的最后一个实例尾部添加一个分号。此外,在enum中,必须先定义实例,不能将字段或方法定义在实例前面。否则,编译器会报错。
public enum Planet {
MERCURY (3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6),
JUPITER (1.9e+27, 7.1492e7),
SATURN (5.688e+26, 6.0268e7),
URANUS (8.686e+25, 2.5559e7),
NEPTUNE (1.024e+26, 2.4746e7);
// in kilograms
private final double mass;
// in meters
private final double radius;
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
private double mass() { return mass; }
private double radius() { return radius; }
// universal gravitational constant (m3 kg-1 s-2)
public static final double G = 6.67300E-11;
double surfaceGravity() {
return G * mass / (radius * radius);
}
double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();
}
public static void main(String[] args) {
if (args.length != 1) {
System.err.println("Usage: java Planet ");
System.exit(-1);
}
double earthWeight = Double.parseDouble(args[0]);
double mass = earthWeight/EARTH.surfaceGravity();
for (Planet p : Planet.values())
System.out.printf("Your weight on %s is %f%n",
p, p.surfaceWeight(mass));
}
}
枚举可以实现接口
enum可以像一般类一样实现接口。
public interface Introduce {
String getDescription();
}
public enum Planet implements Introduce {
MERCURY (3.303e+23, 2.4397e6) {
@Override
String getDescription() {
// ...
}
},
VENUS (4.869e+24, 6.0518e6) {
@Override
String getDescription() {
// ...
}
},
EARTH (5.976e+24, 6.37814e6) {
@Override
String getDescription() {
// ...
}
},
//...
;
// ...
String getDescription();
}
枚举不可以继承
enum 不可以继承另外一个类,当然,也不能继承另一个 enum 。
因为 enum实际上都继承自 java.lang.Enum类,而 Java 不支持多重继承,所以 enum不能再继承其他类,当然也不能继承另一个 enum。
EnumSet和EnumMap EnumSetEnumSet是枚举类型的高性能 Set实现。它要求放入它的枚举常量必须属于同一枚举类型,其内部使用long值作为比特向量,以最大化Set的性能
EnumSet存在两种实现类:
- RegularEnumSet 针对枚举数量小于等于64的EnumSet实现,内部使用long作为存储。
- JumboEnumSet 针对枚举数量大于64的EnumSet实现,内部使用long数组进行存储。
EnumSet作为工厂类,提供大量的静态方法,以方便的创建EnumSet:
- noneOf()
创建EnumSet,空Set,没有任何元素 - allOf()
创建EnumSet,满Set,所有元素都在其中 - copyOf()
从已有Set中创建EnumSet - of()
根据提供的枚举值创建EnumSet - range()
根据枚举定义的顺序,定义一个区间的EnumSet
EnumMap是专门为枚举类型量身定做的 Map实现。它只能接收同一枚举类型的实例作为键值,并且由于枚举类型实例的数量相对固定并且有限,所以 EnumMap 使用数组来存放与枚举类型对应的值。这使得 EnumMap 的效率非常高。
private transient Object[] vals;
public V get(Object key) {
return (isValidKey(key) ?
unmaskNull(vals[((Enum>)key).ordinal()]) : null);
}
public V put(K key, V value) {
typeCheck(key);
int index = key.ordinal();
Object oldValue = vals[index];
vals[index] = maskNull(value);
if (oldValue == null)
size++;
return unmaskNull(oldValue);
}
由上可见,EnumMap内部由数组实现(ordrial),以提高Map的操作速度。enum中的每个实例作为键,总是存在,但是如果没有为这个键调用put方法来存入相应值的话,其对应的值便是null。在使用上,EnumMap与key为枚举的Map并无差异。
枚举的应用 switch 状态机enum Signal {RED, YELLOW, GREEN}
public static String getTrafficInstruct(Signal signal) {
String instruct = "信号灯故障";
switch (signal) {
case RED:
instruct = "红灯停";
break;
case YELLOW:
instruct = "黄灯请注意";
break;
case GREEN:
instruct = "绿灯行";
break;
default:
break;
}
return instruct;
}
组织枚举
可以将类型相近的枚举通过接口或类组织起来, 但是一般用接口方式进行组织。
原因是:Java接口在编译时会自动为enum类型加上public static修饰符, Java类在编译时会自动为 enum类型加上static修饰符。
就是说,在类中组织 enum,如果你不给它修饰为 public,那么只能在本包中进行访问。
public interface Plant {
enum Vegetable implements INumberEnum {
POTATO(0, "土豆"),
TOMATO(0, "西红柿");
Vegetable(int number, String description) {
this.code = number;
this.description = description;
}
private int code;
private String description;
@Override
public int getCode() {
return 0;
}
@Override
public String getDescription() {
return null;
}
}
enum Fruit implements INumberEnum {
APPLE(0, "苹果"),
ORANGE(0, "桔子"),
BANANA(0, "香蕉");
Fruit(int number, String description) {
this.code = number;
this.description = description;
}
private int code;
private String description;
@Override
public int getCode() {
return 0;
}
@Override
public String getDescription() {
return null;
}
}
}
策略枚举
EffectiveJava中展示了一种策略枚举。这种枚举通过枚举嵌套枚举的方式,将枚举常量分类处理。
enum PayrollDay {
MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY), WEDNESDAY(
PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY), FRIDAY(PayType.WEEKDAY), SATURDAY(
PayType.WEEKEND), SUNDAY(PayType.WEEKEND);
private final PayType payType;
PayrollDay(PayType payType) {
this.payType = payType;
}
double pay(double hoursWorked, double payRate) {
return payType.pay(hoursWorked, payRate);
}
private enum PayType {
WEEKDAY {
double overtimePay(double hours, double payRate) {
return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT)
* payRate / 2;
}
},
WEEKEND {
double overtimePay(double hours, double payRate) {
return hours * payRate / 2;
}
};
private static final int HOURS_PER_SHIFT = 8;
abstract double overtimePay(double hrs, double payRate);
double pay(double hoursWorked, double payRate) {
double basePay = hoursWorked * payRate;
return basePay + overtimePay(hoursWorked, payRate);
}
}
}
System.out.println("时薪100的人在周五工作8小时的收入:" + PayrollDay.FRIDAY.pay(8.0, 100));
System.out.println("时薪100的人在周六工作8小时的收入:" + PayrollDay.SATURDAY.pay(8.0, 100));
// TODO 目前觉得对枚举了解比较浅层, 看到了很多更高级的用法, 日后更加的深入的了解后, 有机会回来补充
参考:
一文搞懂 Java 中的枚举,写得非常好!:https://www.cnblogs.com/javastack/p/12837890.html
Java枚举——高级用法与深入解读:https://blog.csdn.net/CSDN_SAVIOR/article/details/122499089
Java 枚举详解:http://events.jianshu.io/p/4ea3f8661f8a
Understanding Java enums:https://blog.knoldus.com/understanding-java-enums/
When to and When Not to Use Enums in Java:https://betterprogramming.pub/when-to-and-when-not-to-use-enums-in-java-8d6fb17ba978
Advanced Java Enum Features You Need to Know:https://alexpower.me/advanced-java-enum-features-you-need-to-know/
Oracle Java Documentation:https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html



