疑问,常量为什么要用Enum 定义,而不用final 来定义一个成员变量或者在Interface 中定义常量
先来了解什么是Enum 类:Enum类是java.lang包中一个类,他是Java语言中所有枚举类型的公共基类。
一、定义:
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable
-
抽象类
首先,抽象类不能被实例化,所以我们在java程序中不能使用new关键字来声明一个Enum,如果想要定义可以使用这样的语法:
enum enumName{ value1, value2; method1(){}; method2(){}; }
其次,看到抽象类,第一印象是肯定可以继承,但是很抱歉,Enum 类是无法被继承的,为什么一个抽象类无法被继承?enum 定义的枚举类是怎么来的?难道不是对Enum 的一种继承吗?带着这些疑问我们来反编译以下代码。
enum Color {RED, BLUE, GREEN}
编译器将会把他们编译成如下代码
public final class Color extends Enum<Color> { public static final Color[] values() { return (Color[]) $VALUES.clone(); } public static Color valueOf(String name) { ... } private Color(String s, int i) { super(s, i); } public static final Color RED; public static final Color BLUE; public static final Color GREEN; private static final Color $VALUES[];//返回该枚举的所有值。 编译器插入的方法,在任何地方都是找不到的 static { RED = new Color("RED", 0); BLUE = new Color("BLUE", 1); GREEN = new Color("GREEN", 2); $VALUES = (new Color[] { RED, BLUE, GREEN }); } }
从反编译之后的代码中,我们发现,编译器不让我们继承Enum,但是当我们使用enum关键字定义一个枚举的时候,他会帮我们在编译后默认继承java.lang.Enum类,而不像其他的类一样默认继承Object类。且采用enum声明后,该类会被编译器加上final声明,故该类是无法继承的。 PS:由于JVM类初始化是线程安全的,所以可以采用枚举类实现一个线程安全的单例模式。
-
实现Comparable 和 Serializable 接口
Enum实现了Serializable接口,可以序列化。 Enum实现了Comparable接口,可以进行比较,默认情况下,只有同类型的enum才进行比较(原因见后文),要实现不同类型的enum之间的比较,只能复写compareTo方法。
-
泛型 <E extends Enum<E>>
首先我们先来“翻译”一下这个Enum<E extends Enum>到底什么意思,然后再来解释为什么Java要这么用。 我们先看一个比较常见的泛型:List。这个泛型的意思是,List中存的都是String类型,告诉编译器要接受String类型,并且从List中取出内容的时候也自动帮我们转成String类型。 所以Enum<E extends Enum>可以暂时理解为Enum里面的内容都是E extends Enum类型。 这里的E我们就理解为枚举,extends表示上界,比如: List<? extends Object>,List中的内容可以是Object或者扩展自Object的类。这就是extends的含义。 所以,E extends Enum表示为一个继承了Enum类型的枚举类型。 那么,Enum<E extends Enum>就不难理解了,就是一个Enum只接受一个Enum或者他的子类作为参数。相当于把一个子类或者自己当成参数,传入到自身,引起一些特别的语法效果。
二、属性
在Enum中,有两个成员变量,一个是名字(name),一个是序号(ordinal)。 序号是一个枚举常量,表示在枚举中的位置,从0开始,依次递增。
private final String name; public final String name() { return name; } private final int ordinal; public final int ordinal() { return ordinal; }
三、方法
-
构造方法
前面我们说过,Enum是一个抽象类,不能被实例化,但是他也有构造函数,从前面我们反编译出来的代码中,我们也发现了Enum的构造函数,在Enum中只有一个保护类型的构造函数:
protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; }
文章开头反编译的代码中private Color(String s, int i) { super(s, i); }中的super(s, i);就是调用Enum中的这个保护类型的构造函数来初始化name和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 self = this; if (self.getClass() != other.getClass() && // optimization self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException(); return self.ordinal - other.ordinal; } public final Class<E> getDeclaringClass() { Class clazz = getClass(); Class zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? clazz : zuper; }
拓展:values()
从上面的反编译代码中我们还可以看到一个$VALUES[] 的东西,那么这个东西是什么呢?没错,他就是我们在定义Enum 类是所使用的 values() 方法。习惯性的我们想Ctrl + 左键 进入该方法,但是很抱歉,我们不能对这个方法有任何想法。那么这个方法到底是什么,为什么这么神秘呢?经过查阅资料得知,该方法是编译器在编译Enum类的时候自动插入一些方法,其中就有该方法。
实例:
public enum CommandToken implements BaseEnum { TECH(5, "/152", "/tech"), ZHINENG(6, "/152/153", "/tech/zhineng"), JIANKANG(7, "/154", "/jiankang"), YINSHI(8, "/154/155", "/jiankang/yinshi"), MONEY(9, "/156", "/money"), YINHANG(10, "/156/157", "/money/yinhang"); private int value; // 命令值 private String url; // 匹配的url private String catagory; // 替换的目录 private CommandToken(int value, String url, String catagory) { this.value = value; this.url = url; this.catagory = catagory; } public int getValue() { return value; } public String getUrl() { return url; } public String getCatagory() { return catagory; } public static CommandToken parse(String value) { CommandToken[] vs = values();//values() 其实就是 CommandToken.values(); 一个编译器自动产生的静态方法 for (CommandToken s : vs) { if (value.equals(s.url)) { return s; } } return null; } @Override public String toString() { return catagory; } @Override public int toInt() { // TODO Auto-generated method stub return 0; } } public String getColumnPath(String url){ String columnPathTem = null; CommandToken token = CommandToken.parse(url); if(token != null){ columnPathTem = token.toString(); }else{ columnPathTem = null; } return columnPathTem; }
实例2:
现在,假设要为该枚举实现一个根据整数值生成枚举值的方法,可以这样做:
public enum TypeEnum implements BaseEnum { // VIDEO(1, "视频") --> VIDEO = new TypeEnum(1,"视频") VIDEO(1, "视频"), AUDIO(2, "音频"), TEXT(3, "文本"), IMAGE(4, "图像"); // 成员变量 private int value; private String name; // 构造方法,自定义,在enum实例序列的最后添加一个分号,而且 Java 要求必须先定义 enum 实例(例如VIDEO) TypeEnum(int value, String name) { this.value = value; this.name = name; } // 提供访问的get set 方法 public int getValue() { return value; } public String getName() { return name; } public void setValue(int value) { this.value = value; } public void setName(String name) { this.name = name; } // 普通方法 public static TypeEnum getByValue(int value) { for (TypeEnum typeEnum : TypeEnum.values()) { if (typeEnum.value == value) { return typeEnum; } } throw new IllegalArgumentException("No element matches " + value); } // 覆盖Object类的 toString 方法 public String toString() { return this.value + "_" + this.name; } // 实现接口方法,因为Java 不支持多继承,因此只能通过实现接口 @Override public int toInt() { // TODO Auto-generated method stub return 0; } } ------------------------------------------------------------------------------------- public interface BaseEnum { /** * 返回该对象的字符串表示 * @return 字符串 */ public String toString(); /** * 返回该对象的整型表示 * @return 整型 */ public int toInt() ; }
getByValue(int)即为整数值转枚举值的方法。调用values()方法获取到该枚举下的所有值,然后遍历该枚举下面的每个值和给定的整数是否匹配,若匹配直接返回,若无匹配值则抛出IllegalArgumentException异常,表示参数不合法,兼有有效性验证的作用。
综上,我们可以看到,在JDK5中新引入的枚举完美地解决了之前通过常量来表示离散量所带来的问题,大大加强了程序的可读性、易用性和可维护性,并且在此基础之上又进行了扩展,使之可以像类一样去使用,更是为Java对离散量的表示上升了一个台阶。因此,如果在Java中需要表示诸如颜色、方式、类别、状态等等数目有限、形式离散、表达又极为明确的量,应当尽量舍弃常量表示的做法,而将枚举作为首要的选择。
弄清楚什么是Enum 之后,回到我们的问题,为什么用Enum 来定义常量而不用普通的静态类final static
-
保证类型安全。比如类似状态之类的常量,订单状态,警报状态等,如果传入final static 的String 类型,在接收参数数,可能是状态之外的值,这样会产生异常
-
更规范。有自己的name,ordinal
-
增加程序的可读性和可维护性