文章目录
枚举
- 枚举是JDK5 引入的一个特性,他是一种特殊的数据类型,它特殊在即是一种类型,又有一些特殊之处,这些特殊造就了枚举的特性,比如简单,安全等。
一、枚举
- 在没有枚举之前,或者现在开发中我们经常用常量来定义一些固定的变量,比如性别,星期等,
public class WeekConstant {
public static final int MONDAY =1;
public static final int TUESDAY=2;
public static final int WEDNESDAY=3;
public static final int THURSDAY=4;
public static final int FRIDAY=5;
public static final int SATURDAY=6;
public static final int SUNDAY=7;
}
- 引入枚举后,是这样的:
public enum WeekEnum {
MON, TUE, WEN,THR,FRI, SAT,SYN
}
- 使用枚举定义更加简洁,发生错误的时候编译器还会给出错误提示,可以按照如下方式引用,非常简洁:
WeekEnum.MON
二、本质
2.1 反编译
- 下面我们通过javac WeekEnum.java 编译前面的 WeekEnum 文件看枚举类的特殊之处,中文是添加的注释:
//反编译 WeekEnum.MON
//final类,不能被继承,是 Enum 抽象类的子类
public final class WeekEnum extends Enum {
//定义的枚举实例
public static final WeekEnum MON;
public static final WeekEnum TUE;
public static final WeekEnum WEN;
public static final WeekEnum THR;
public static final WeekEnum FRI;
public static final WeekEnum SAT;
public static final WeekEnum SYN;
//$VALUES保存了全部的枚举值
private static final WeekEnum $VALUES[];
//编译器添加的静态 values() 方法
public static WeekEnum[] values()
{
return (WeekEnum[])$VALUES.clone();
}
//编译器添加的静态的 valueOf() 方法,间接调用了 Enum 类的 valueOf 方法
public static WeekEnum valueOf(String name) {
return (WeekEnum)Enum.valueOf(com/intellif/vo/WeekEnum, name);
}
//构造私有,不能实例化
private WeekEnum(String s, int i)
{
super(s, i);
}
//静态代码块实例化自定义的枚举实例和一个额外的$VALUES,$VALUES是包括全部枚举实例的数组
static {
MON = new WeekEnum("MON", 0);
TUE = new WeekEnum("TUE", 1);
WEN = new WeekEnum("WEN", 2);
THR = new WeekEnum("THR", 3);
FRI = new WeekEnum("FRI", 4);
SAT = new WeekEnum("SAT", 5);
SYN = new WeekEnum("SYN", 6);
$VALUES = (new WeekEnum[] {
MON, TUE, WEN, THR, FRI, SAT, SYN
});
}
}
2.2 特点
我们来看看反编译之后的特点:
- 编译后的枚举类是 Enum 的子类,且无法被继承, 无法实例化
- 自定义的枚举实例是 final 修饰的,因此自定义的枚举实例是定义的枚举类对象,且是常量,通过静态代码块初始化
- 会新增两个重要的方法 values() 和 valueOf(String name),(前者返回全部的枚举对象,后者根据值返回指定的枚举对象)
2.3 values()方法
- valuse() 方法是静态方法,因此通过类调用和实例调用效果一致,他都会返回定义好的全部枚举实例,如下面代码所示,
public static void main(String[] args) {
WeekEnum[] values = WeekEnum.MON.values();
for (WeekEnum w : values) {
System.out.print(w + " ");
}
System.out.println();
WeekEnum[] values1 = WeekEnum.values();
for (WeekEnum w : values1) {
System.out.print(w+ " ");
}
}
输出:
MON TUE WEN THR FRI SAT SYN
MON TUE WEN THR FRI SAT SYN
三、Enum
- 由前面可知,Enum 是所有枚举类的抽象父类,因此我们看看 Enum 的特点,也帮助我们理解方法 values() 和 valueOf(String name),下面是主要的源码:
3.1 属性
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
//枚举类名称,比如前面的 MON
private final String name;
//枚举的顺序,默认第一个是0,后面一次递增,比如前面的MON到SUN就是0-6
private final int ordinal;
}
3.2 构造方法
- 构造方法,查看前面反编译,其实就是调用这个方法,初始化name和oridinal,这个方法外部是无法调用的,子类也是在静态代码块的时候调用,创建预先定义好的枚举常量;
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
3.3 其他方法
- valueOf : valueOf方法就是通过name去寻找一个枚举常量,找到就返回,找不到就抛出 IllegalArgumentException
//反编译的枚举实例调用的valueOf方法,在反编译后的代码在valueOf方法只有一个String
//类型的参数,另一个参数就是枚举的类型,然后参数会传给Enum的这个方法
//
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
//enumConstantDirectory 会返回一个Map,里面包含全部的枚举常量,然后通过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);
}
下面是使用示例:
WeekEnum m = WeekEnum.valueOf("MON1"); //返回WeekEnum.MON
WeekEnum m = WeekEnum.valueOf("MON1"); //抛出异常
- compareTo : 在类型一致的情况下,枚举之间可以比较 ordinal 的值,自身大则返回正数
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;
}
- 其他方法
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
//Enum将clone方法保护起来,不能在外部调用,只能子类调用,因此不能调用枚举类实例的clone方法
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
//返回类型
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
//阻止默认的序列化
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");
}
}
四、应用
4.1 自定义构造方法
- 枚举类也是一个类,因此也具备类的特性,可以定义方法和属性,下面我们给枚举类定义一个属性和构造方法,代码如下:
public enum WeekEnum {
MON("星期一"),
TUE("星期二"),
WEN("星期三"),
THR("星期四"),
FRI("星期五"),
SAT("星期六"),
SYN("星期日");
String desc;
WeekEnum(String desc) {
this.desc = desc;
}
}
- 测试:
WeekEnum[] weekEnum = WeekEnum.values();
for (WeekEnum w: weekEnum) {
System.out.print(w + w.desc+ " " );
}
- 输出:
MON星期一 TUE星期二 WEN星期三 THR星期四 FRI星期五 SAT星期六 SYN星期日
- 注意:即使我们定义了构造方法,但是却无法调用它,我们看编译后的就知道,就像最前面提到的,构造方法只能在编译器静态代码块中调用用于初始化属性,不能有用户显式调用;
4.2 抽象方法
- 可以给枚举类内部定义一个抽象方法,这样每个枚举实现实现该方法,可以有不同的行为,不过需要记住的是每个枚举实例都是一个实例,都是一个枚举类型的常量实例,不是一个类型,这种用法貌似不多见。
public enum GenderEnum {
MAN("男") {
@Override
protected String echo() {
return "男人好难!";
}
},
WOMAN("女") {
@Override
protected String echo() {
return "女人好难!";
}
};
String desc;
GenderEnum(String desc) {
this.desc = desc;
}
protected abstract String echo();
}
测试:
GenderEnum[] values = GenderEnum.values();
for (GenderEnum ge: values) {
System.out.println(ge.echo());
}
输出:
男人好难!
女人好难!
4.3 实现接口
- 枚举类本身继承了Enum,因此不能再继承其他类,但是可以实现接口,从代码上看,和前面的抽象类很相似;
public enum GenderEnum implements Hello {
MAN("男") {
@Override
public void say() {
System.out.println("男人好难!");
}
},
WOMAN("女") {
@Override
public void say() {
System.out.println("女人好难!");
}
};
String desc;
GenderEnum(String desc) {
this.desc = desc;
}
}
接口:
public interface Hello {
void say();
}
测试:
GenderEnum[] values = GenderEnum.values();
for (GenderEnum ge: values) {
ge.say();
}
输出:
男人好难!
女人好难!
4.4 枚举和单例
- 枚举类型的单例模式,枚举类型的单例模式在反射和序列化时也能保证安全
public enum SingletonEnum {
INSTANCE;
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}