原文作者:山高我为

原文地址:java enum的用法详解

目录

一、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

二、Enum类源码

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, 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<E> self = this;if (self.getClass() != other.getClass() && // optimizationself.getDeclaringClass() != other.getDeclaringClass())throw new ClassCastException();return self.ordinal - other.ordinal;}/*** 返回与此枚举常量的枚举类型对应的Class对象. * 当且仅当e1.getDeclaringClass()== e2.getDeclaringClass()时, 两个枚举常量e1和e2属于相同的枚举类型。*/@SuppressWarnings("unchecked")public final Class<E> getDeclaringClass() {Class<?> clazz = getClass();Class<?> zuper = clazz.getSuperclass();return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;}/*** 返回指定枚举类型的枚举常量指定的名称.  * 名称必须与声明此类型的枚举常量使用的标识符完全匹配(不允许使用无关的空格字符.)* 请注意,对于特定的枚举类型T,可以使用该枚举上隐式声明的valueOf(String)方法代替此方法从名称映射到相应的枚举常量。 * 枚举类型的所有常量都可以通过调用该类型的隐式方法 values()方法来获得。** @param <T> 返回常量的枚举类型* @param  枚举常量类型enumType* @param  要返回的枚举常量的名称name* @return 返回具有指定名称和指定枚举类型的枚举常量* @throws 如果指定的枚举类型没有具有指定名称的常量,或者指定的类对象不表示枚举类型,抛出IllegalArgumentException 异常* @throws 如果enumType或者code name为null,NullPointerException异常*/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);}/*** 枚举类不能有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枚举类型是“静态常量”,这里面包含了两层意思:

  • 枚举型中的实例隐式地用static final修饰过。
  • 枚举型作为某个类中的成员字段也隐式的用static final修饰过

还是你上面这个代码,反编译一下,你就能看到--编译器背着你偷偷做了哪些手脚

  • 首先,枚举型Light是个实实在在的类。集成自基类Enum<Light>。然后在你不知情的情况下,偷偷加了static final修饰词。然后3个枚举实例 GREEN,YELLOW,RED也确确实实是light的实例,然后前面加了static final。
  • 然后构造器也被偷偷的阉割成private。这种实例控制手段,是不是在单例模式里面见过,所以枚举也是实现单例器的一种方法。
  • 然后编译器还偷偷的告诉Light[]数组,一个values()方法,一个valueO()f方法,这个values在Enum文档里面找不到,如果在Enum里面定义一个相关方法,你还会看到一个匿名内部类

反编译的结果如下:

总之,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

四、Enum常见用法

用法一:常量

在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;}// 覆盖方法@Overridepublic 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;}// 接口方法@Overridepublic String getInfo() {return this.name;}// 接口方法@Overridepublic 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;}@Overridepublic 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<Light, String> currEnumMap = new EnumMap<Light, String>(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是一个抽象类,获取一个类型的枚举类型内容<BR/>* * 可以使用allOf方法*/private static void testEnumSet() {EnumSet<Light> 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

读后有收获可以支付宝请作者喝奶茶

java.lang包—枚举类Enum相关推荐

  1. 【JDK源码】java.lang包常用类详解

    接下来的几天开始JDK源码的学习和总结,之前看<java编程思想>的时候看到java的基础知识有很多,其中支撑着这些基础的基础中的基础当属JDK.JDK的基础代码里面又分了很多基础的模块, ...

  2. java.lang包—StringBuffer类和StringBuilder类

    目录 一.数据结构 二.线程安全性分析 三.源码 四.适用场景 一.数据结构 在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串.String 类是不可变类,即一旦一 ...

  3. java JPI中常使用的类介绍即java.lang包下的东西

    java.lang包是java语言的核心,它提供了java中的基础类.包括基本Object类.Class类.String类.基本类型的包装类.基本的数学类等等最基本的类. 下面分别介绍其中比较常用的类 ...

  4. 【Java 枚举 集合】枚举类Enum、映射EnumMap、集EnumSet

    枚举Enum.映射EnumMap.集EnumSet 一.枚举Enum 1.概述 2.介绍 ① valueOf ② values 3.分析 ※ 模仿一个枚举类 二.枚举映射 EnumMap 1.概述 2 ...

  5. java.lang包中的常用类

    java.lang包 java.lang.Boolean类 java.lang.Byte类 java.lang.Character java.lang.Character.Subset类 java.l ...

  6. Java中的枚举类是什么?enum关键字怎么使用?

    枚举类 文章目录 枚举类 枚举类的使用:入门 自定义枚举类 方法一:自定义枚举类 方式二: enum 关键字定义枚举类(主要用该方式) Enum类的主要方法 使用enum关键字定义的枚举类实现接口 主 ...

  7. java枚举类Enum入门理解

    目录 枚举的定义 JDK5.0之前只能自定义枚举类 自定义枚举类的理解: JDK5.0之后enum关键字定义枚举类 区别于自定义枚举类 enum的父类Enum的常用方法 toString方法和valu ...

  8. Throwable是java.lang包中一个专门用来处理异常的类

    答:Throwable是java.lang包中一个专门用来处理异常的类.它有两个子类,即Error 和Exception,它们分别用来处理两组异常. Error用来处理程序运行环境方面的异常,比如,虚 ...

  9. java.lang包—对象基类Object

    原文作者:Boblim 原文地址:Java:Object类详解 目录 一.上帝类 二.Object的类方法 三.常见面试题 Java的一些特性会让初学者感到困惑,但在有经验的开发者眼中,却是合情合理的 ...

最新文章

  1. python打地鼠游戏代码_妈妈和宝宝在家,自己做了个打地鼠游戏,网友:宝宝笑得好开心...
  2. hdu 5131 Song Jiang#39;s rank list 【2014ACM/ICPC亚洲区广州站-重现赛】
  3. 什么原因可能导致主备延迟?
  4. jax-rs/jersey_使用JAX-RS(Jersey)的HTTP状态错误消息响应中的自定义原因短语
  5. D3.js、echar.js 前端必备大数据技能
  6. 软件开发,维护与支持的困惑
  7. CentOS[linux]操作系统的安装手册
  8. flex制作一个用户登录框(含验证码)
  9. CSRF——攻击与防御
  10. Python-datetime模块
  11. git 还原到某个版本_Git常用命令
  12. matlab_取整函数
  13. 19年6月英语六级第一套听力单词
  14. 【笔记】分布式网络与分布式账本
  15. 关于广告投放系统:竞价策略(2018)
  16. word插入文件对象后,原文件中的阿拉伯数字尾注变成罗马数字尾注
  17. linux下gzip用法,Linux gzip 命令的使用
  18. 密码学的数学基础2-同余
  19. 饥荒控制台输入没用_饥荒控制台怎么用 控制台的使用方法以及代码说明解析...
  20. SATA 3.3协议 Error handing机制

热门文章

  1. 37-Invert Binary Tree
  2. [国嵌攻略][085][共享内存通讯]
  3. IOS Core Image之二
  4. dealloc 的水,很深?
  5. WIF - claims-based identity
  6. Android 可拖拽的GridView效果实现, 长按可拖拽和item实时交换
  7. windows下primer3-py安装
  8. Effective C#(二)
  9. 使用AFNetworking 报错提示
  10. VS2012 生成项目报 Lc.exe已退出,代码为-1 错误