原文作者:山高我为
原文地址:java enum的用法详解
目录
一、enum关键字
二、Enum类源码
三、疑问
四、Enum常见用法
enum关键字是在Java1.5也就是Java SE5之后引入的一个新特性:它通过关键字enum来定义一个枚举类,这个被定义的枚举类继承Enum类,这个枚举类算是一种特殊类,它同样能像其他普通类一样拥有构造器、方法,也能够实现接口,但是它不能再继承其他别的类,因为它的直接父类是Enum类,并且因为它默认的修饰符有final的存在,因此它无法直接派生出其他子类,除非将其使用abstract修饰。
按照《Java编程思想》中的原话来说:关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件来使用。
在枚举类出现之前Java是将常量放在接口或是放在普通类当中,然后使用public、static、final去修饰定义的常量,如下两个例子:
public interface Constants2 {
public static final int CONSTANT_1 = 1;
public static final int CONSTANT_2 = 2;
public static final int CONSTANT_3 = 3;
}
public class Constants {
public static final int CONSTANT_1 = 1;
public static final int CONSTANT_2 = 2;
public static final int CONSTANT_3 = 3;
}
在枚举类型出现之后,就可以使用枚举类型来定义常量,这些枚举类型成员_1、_2、_3都默认被public、static、final修饰,语法如下:
public enum Constants {
CONSTANT_1,
CONSTANT_2,
CONSTANT_3
}
但是Java枚举类型输出其常量的时候不像C /C++的枚举那样是数字,输出的是其常量名,如果需要输出其类型成员声明时数字次序的话,需要调用ordinal()方法:
public enum Singleton2 {
SHERLOCK,
WASTON;
}
class Main{
public static void main(String[] args) {
System.out.println(Singleton2.SHERLOCK);
System.out.println(Singleton2.WASTON);
System.out.println(Singleton2.SHERLOCK.ordinal());
System.out.println(Singleton2.WASTON.ordinal());
}
}
//输出结果:
//SHERLOCK
//WASTON
//0
//1
public abstract class Enum> implements Comparable, Serializable {
/**
* 枚举常量的名称
* 使用toString方法访问此字段。
*/
private final String name;
/**
* 返回此枚举常量的名称,与其枚举声明中声明的完全相同.
* 大多数程序员应优先使用toString方法,因为toString方法可能会返回一个更加友好的名称。
* 此方法主要用于特殊情况,其中正确性取决于获取确切名称,不会因发行版本而异。
*/
public final String name() {
return name;
}
/**
* 枚举常量的序数(它指的是在枚举声明中的位置,其中初始常量的序数为零)。
* 大多数程序员都不会使用这个字段。 它设计用于复杂的基于枚举型的数据结构,例如EnumSet,EnumMap。
*/
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;
}
/**
* 如果指定的对象等于此枚举常量,则返回true。
* 【注意】此处比较形式是通过“==”进行,也即是枚举类之间可以通过 == 进行比较
*/
public final boolean equals(Object other) {
return this==other;
}
/**
* 返回此枚举常量的哈希码
*/
public final int hashCode() {
return super.hashCode();
}
/**
* 抛出CloneNotSupportedException异常.
* 这能保证了枚举常量类永远不会被克隆,从而保证其为”单例”状态。
*/
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;
}
/**
* 返回与此枚举常量的枚举类型对应的Class对象.
* 当且仅当e1.getDeclaringClass()== e2.getDeclaringClass()时, 两个枚举常量e1和e2属于相同的枚举类型。
*/
@SuppressWarnings("unchecked")
public final Class getDeclaringClass() {
Class> clazz = getClass();
Class> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class)clazz : (Class)zuper;
}
/**
* 返回指定枚举类型的枚举常量指定的名称.
* 名称必须与声明此类型的枚举常量使用的标识符完全匹配(不允许使用无关的空格字符.)
* 请注意,对于特定的枚举类型T,可以使用该枚举上隐式声明的valueOf(String)方法代替此方法从名称映射到相应的枚举常量。
* 枚举类型的所有常量都可以通过调用该类型的隐式方法 values()方法来获得。
*
* @param 返回常量的枚举类型
* @param 枚举常量类型enumType
* @param 要返回的枚举常量的名称name
* @return 返回具有指定名称和指定枚举类型的枚举常量
* @throws 如果指定的枚举类型没有具有指定名称的常量,或者指定的类对象不表示枚举类型,抛出IllegalArgumentException 异常
* @throws 如果enumType或者code name为null,NullPointerException异常
*/
public static > T valueOf(Class 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);
}
/**
* 枚举类不能有finalize方法
*/
protected final void finalize() { }
/**
* 无法反序列化枚举
*/
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");
}
}
1、为什么说enum本质是一个继承了Enum类的class?
Java语法就是这么规定的,还能为啥
2、枚举声明为什么是enum不是class,这样做的意图是什么?
3、枚举允许继承类吗?可以被别人结成么?
枚举不允许继承类。Jvm在生成枚举时已经继承了Enum类,由于Java语言是单继承,不支持再继承额外的类(唯一的继承名额被Jvm用了)。也不可以继承枚举。因为Jvm在生成枚举类时,将它声明为final。
4、枚举可以用等号比较吗?
枚举可以用等号比较。Jvm会为每个枚举实例对应生成一个类对象,这个类对象是用public static final修饰的,在static代码块中初始化,是一个单例。
5、为什么使用枚举代替常量类?
在我们平常的开发中,为表示同种类型的不同种类,经常的做法是声明一组具名的int常量来表示,每个类型成员一个常量,如:
public static final int DAY_MONDAY = 1;
public static final int DAY_TUESDAY = 2;
public static final int DAY_WEDNESDAY = 3;
public static final int DAY_THURSDAY = 4;
public static final int DAY_FRIDAY = 5;
public static final int DAY_SATURDAY = 6;
public static final int DAY_SUNDAY = 7;
public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;
这种方法称做 int枚举模式,这种方式在安全性和使用方便性方面没有任何帮助。
a、将day传到想要orange的方法中,编译器不会警告,执行也不会出现错误;
b、用==操作符将day与orange比较,编译器不会警告,执行也不会出现错误;
c、int枚举是编译时常量,被编译到客户端中,如果枚举常量关联的int发生变化,客户端必须重新编译,如果没有重新编译,程序仍可以运行,但行为就确定了,如DAY_MONDAY关联的常量不再是1,而是0。
d、将int枚举常量翻译成可打印的字符串很麻烦
e、如果想要遍历一个组中的所有int 枚举常量,甚至获得int枚举组的大小,这种实现没有啥方便可靠的方法。
因此,推荐使用枚举类型来代替这种int枚举常量:
public enum DAY {DAY_MONDAY, DAY_TUESDAY, DAY_WEDNESDAY,
DAY_THURSDAY, DAY_FRIDAY, DAY_SATURDAY, DAY_SUNDAY}
public enum ORANGE {ORANGE_NAVEL, ORANGE_TEMPLE, ORANGE_BLOOD}
这种枚举类型,提供了编译时的类型安全检查,如果声明了一个参数的类型为DAY,就可以保证,被传到该参数上的任何非null的对象引用一定属于其他有效值中的一个,试图传递类型错误的值时,会导致编译时错误,就像试图将某种枚举类型的表达式赋给另一种枚举类型的变量,或者试图利用==操作符比较不同枚举类型的值一样。同时包含同名常量的多个枚举类型可以共存,因为每个类型有自己的命名空间,增加或重新排列枚举类型的常量,无需重新编译客户端的代码。如果想获取类型对应的字符串,直接通过toString方法即可。
枚举类型除了完善了int枚举模式的不足之处外,枚举类型还允许添加任意的方法和域,并实现任意的接口。这个有什么用途呢?
a、能够将数据与它的常量关联起来,例如能够返回水果颜色或者水果图片的方法,对于我们的ORANGE类型来说可能就很有好处;
b、你可使用适当的方法来增强枚举类型,枚举类型可以先作为枚举常量的一个简单集合,随着时间的推移在演变成为全功能的抽象。
另外,当你想要每增加一种枚举常量时,需要强制选择一种对应的策略,可以使用枚举提供的策略枚举(strategy enum) 的方式。
4、究竟是枚举的性能好,还是常量类好?
5、为什么枚举要实现Comparable接口?
6、为什么枚举要实现Serializable接口?
7、为什么枚举支持泛型?
8、枚举的底层数据结构是数组还是链表?
9、为什么枚举类型实例化就能访?比如如下代码为什么不报错
public class Traffic{
public enum Light{GREEN,YELLOW,RED}
}
Traffic.Light state = Traffic.Light.GREEN;
Java枚举类型都是静态常量,隐式的用static final修饰过。确切的说,Java枚举类型是“静态常量”,这里面包含了两层意思:
还是你上面这个代码,反编译一下,你就能看到--编译器背着你偷偷做了哪些手脚
反编译的结果如下:
总之,Java的Enum枚举类型就是一个大大的“语法糖”。明明是一个完整的类,但只向用户暴露几个常态变量,隐藏掉大部分实现细节。
上述文字引用自知乎问答:Java 枚举型为什么是静态的,以及是怎么实现的?胖君的回答
10、是不是所有的枚举都默认是静态的?
通过可问题5,可知所有的枚举都默认是静态的
11、枚举有哪些应用场景?
12、枚举是如何实现单例的?
public enum Singleton2 {
SHERLOCK
}
class Main{
public static void main(String[] args) {
Singleton2 sherlock = Singleton2.SHERLOCK;
Singleton2 sherlock1 = Singleton2.SHERLOCK;
System.out.println(sherlock == Singleton2.SHERLOCK);
System.out.println(sherlock == sherlock1);
System.out.println(Singleton2.SHERLOCK.getDeclaringClass());
}
}
输出结果:
true
true
class com.sherlock.singleton.Singleton2
用法一:常量
在JDK1.5 之前,我们定义常量都是: public static fianl.... 。现在好了,有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法。
public enum Color {
RED, GREEN, BLANK, YELLOW
}
用法二:switch
JDK1.6之前的switch语句只支持int,char,enum类型,使用枚举,能让我们的代码可读性更强。
enum Signal {
GREEN, YELLOW, RED
}
public class TrafficLight {
Signal color = Signal.RED;
public void change() {
switch (color) {
case RED:
color = Signal.GREEN;
break;
case YELLOW:
color = Signal.RED;
break;
case GREEN:
color = Signal.YELLOW;
break;
}
}
}
用法三:向枚举中添加新方法
如果打算自定义自己的方法,那么必须在enum实例序列的最后添加一个分号。而且 Java 要求必须先定义 enum 实例。
public enum Color {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
// 普通方法
public static String getName(int index) {
for (Color c : Color.values()) {
if (c.getIndex() == index) {
return c.name;
}
}
return null;
}
// get set 方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
用法四:覆盖枚举的方法
下面给出一个toString()方法覆盖的例子。
public class Test {
public enum Color {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
// 覆盖方法
@Override
public String toString() {
return this.index + "_" + this.name;
}
}
public static void main(String[] args) {
System.out.println(Color.RED.toString());
}
}
用法五:实现接口
所有的枚举都继承自java.lang.Enum类。由于Java 不支持多继承,所以枚举对象不能再继承其他类。
public interface Behaviour {
void print();
String getInfo();
}
public enum Color implements Behaviour {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
// 接口方法
@Override
public String getInfo() {
return this.name;
}
// 接口方法
@Override
public void print() {
System.out.println(this.index + ":" + this.name);
}
}
用法六:使用接口组织枚举
public interface Food {
enum Coffee implements Food {
BLACK_COFFEE, DECAF_COFFEE, LATTE, CAPPUCCINO
}
enum Dessert implements Food {
FRUIT, CAKE, GELATO
}
}
用法七:关于枚举集合的使用
java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复;EnumMap中的 key是enum类型,而value则可以是任意类型。关于这个两个集合的使用就不在这里赘述,可以参考JDK文档。 完整示例代码
枚举类型的完整演示代码如下:
public class LightTest {
// 1.定义枚举类型
public enum Light {
// 利用构造函数传参
RED(1), GREEN(3), YELLOW(2);
// 定义私有变量
private int nCode;
// 构造函数,枚举类型只能为私有
private Light(int _nCode) {
this.nCode = _nCode;
}
@Override
public String toString() {
return String.valueOf(this.nCode);
}
}
public static void main(String[] args) {
// 1.遍历枚举类型
System.out.println("演示枚举类型的遍历 ......");
testTraversalEnum();
// 2.演示EnumMap对象的使用
System.out.println("演示EnmuMap对象的使用和遍历.....");
testEnumMap();
// 3.演示EnmuSet的使用
System.out.println("演示EnmuSet对象的使用和遍历.....");
testEnumSet();
}
/**
*
* 演示枚举类型的遍历
*/
private static void testTraversalEnum() {
Light[] allLight = Light.values();
for (Light aLight : allLight) {
System.out.println("当前灯name:" + aLight.name());
System.out.println("当前灯ordinal:" + aLight.ordinal());
System.out.println("当前灯:" + aLight);
}
}
/**
*
* 演示EnumMap的使用,EnumMap跟HashMap的使用差不多,只不过key要是枚举类型
*/
private static void testEnumMap() {
// 1.演示定义EnumMap对象,EnumMap对象的构造函数需要参数传入,默认是key的类的类型
EnumMap currEnumMap = new EnumMap(Light.class);
currEnumMap.put(Light.RED, "红灯");
currEnumMap.put(Light.GREEN, "绿灯");
currEnumMap.put(Light.YELLOW, "黄灯");
// 2.遍历对象
for (Light aLight : Light.values()) {
System.out.println("[key=" + aLight.name() + ",value="+ currEnumMap.get(aLight) + "]");
}
}
/**
*
* 演示EnumSet如何使用,EnumSet是一个抽象类,获取一个类型的枚举类型内容
*
* 可以使用allOf方法
*/
private static void testEnumSet() {
EnumSet currEnumSet = EnumSet.allOf(Light.class);
for (Light aLightSetElement : currEnumSet) {
System.out.println("当前EnumSet中数据为:" + aLightSetElement);
}
}
}
执行结果如下:
演示枚举类型的遍历 ...... 当前灯name:RED 当前灯ordinal:0 当前灯:1 当前灯name:GREEN 当前灯ordinal:1 当前灯:3 当前灯name:YELLOW 当前灯ordinal:2 当前灯:2 演示EnmuMap对象的使用和遍历..... [key=RED,value=红灯] [key=GREEN,value=绿灯] [key=YELLOW,value=黄灯] 演示EnmuSet对象的使用和遍历..... 当前EnumSet中数据为:1 当前EnumSet中数据为:3 当前EnumSet中数据为:2