enum,即枚举类型,在每种编程语言中都有类似的类型。

  因为用得少,语法规则很难记得住,我每次看到enum都会感到害怕。

  

  一般的enum语法是这样的:

  1. public class MyClass {
  2. private enum Fruit {APPLE, ORANGE, GRAPE, BANANA} //类型定义
  3.  
  4. private Fruit fruit = Fruit.APPLE; //类型使用
  5.  
  6. private void useEnum() {
  7. if (fruit == Fruit.ORANGE) {
  8. System.out.println("这是橘子");
  9. }
  10. }
  11. }

  在上面的“类型定义”部分,就发现定义一个enum类型的语句跟常规的Java语法有很大出入,一个类里面一般就是定义变量、函数、内部类这三样东西,但是这句话感觉什么都不是。

  对于enum,包括C++当中的类似的枚举类型,我以前都是采取死记硬背的方法,把它跟其他的语法区别对待来记忆。然而并没有卵用,就是因为枚举类型的“不寻常”,以及用得少,很快就会忘记具体的语法格式。

  前两天看《Effective Java》时,发现有一章专门讲enum的,而且书中强调用enum的好处,以及Java当中的enum相比其他语言的枚举类型的强大之处。我看得一知半解,但对enum这一特殊的东西开始重视起来。

  通过各方面的研究,我发现了enum不为人知的惊天内幕,以及各种暗箱操作。

  首先给出一个最重要的点:enum其实是一个class!

  有了这一逻辑,对于enum的基础语法就理解了一半。把上面的“enum”这个词换成“class”试试会怎样?比如枚举值两边用大括号是怎么回事,是不是清楚了很多?

  

  既然enum就是一个类,那我把 Fruit类(可以这么叫了吧?)的定义换个形式

  1. public class MyClass {
  2. private class Fruit { //把enum换成新的定义
  3. private Fruit() {} //不允许在外面new出Fruit
  4. public static final Fruit APPLE = new Fruit();
  5. public static final Fruit ORANGE = new Fruit();
  6. public static final Fruit GRAPE = new Fruit();
  7. public static final Fruit BANANA = new Fruit();
  8. }
  9.  
  10. private Fruit fruit = Fruit.APPLE; //类型使用
  11.  
  12. private void useEnum() {
  13. if (fruit == Fruit.ORANGE) {
  14. System.out.println("这是橘子");
  15. }
  16. }
  17. }

  看完这段转换之后的用常规class写的代码,是不是想惊呼一声:这特么不是效果完全一样的代码吗!

  是的,这就是编译器的暗箱操作,编译器一看到enum这个关键字,就会很自然地试图转换成这样。每一个枚举值,并不是C++里面那样的从0开始的int值,而是一个个类实例。而且因为加上了final关键字,知道为什么枚举值是全大写的了吧?又因为加了static关键字,为什么会写出“Fruit.APPLE”这样的静态调用形式了吧?

  这还没有完,以上只是enum定义时的原理,enum在具体使用时,如何遍历?如何输出每个枚举值代表的int值?如何输出每个枚举值的字面字符串值?

  事实上,上面给出的Fruit类很不完整,替代enum的类继承自java.lang.Enum,这是一个抽象类,编译器遇到enum时自动转化为Enum父类中对应的操作。

  

  1. //Enum类的定义
  2. public abstract class Enum<E extends Enum<E>> implements Serializable, Comparable<E> {...}

比如上面的Fruit类其实是这样的

  1. private class Fruit extends java.lang.Enum {
  2. ....
  3. }

  来看一下Enum中主要的成员。

  1. //这是Enum类仅有的两个成员变量,比如{APPLE,ORANGE,GRAPE},3个枚举值其实都是Enum类的子类的实例,name分别是"APPLE","ORANGE","GRAPE", ordinal分别是0,1,2
  2.  
  3. private final String name; //枚举值的字面字符串表示,比如"APPLE","ORANGE",
  4.  
  5. private final int ordinal; //枚举值所代表的int值,跟C++中的枚举类型类似,可以理解为索引,按照定义时顺序,第一个为0,第二个为1,以此类推

  可以通过类似于get方法得到这两个属性值

  1. //得到字面值
  2. public final String name() {
  3. return name;
  4. }
  5.  
  6. //得到索引值
  7. public final int ordinal() {
  8. return ordinal;
  9. }

  另外在输出枚举值时,输出的是枚举值的字面字符串值,因为调用的是toString()方法

  1. @Override
  2. public String toString() {
  3. return name;
  4. }

  Enum类实现了Comparable接口,因而可以进行枚举值间的比较,事实上就是索引值比较

  1. //返回两个枚举值的索引值之差
  2. public final int compareTo(E o) {
  3. return ordinal - ((Enum<?>) o).ordinal;
  4. }

  enum还有两个操作应该是编译器调用Enum类的其他方法实现的。

  第一个是enum的values()方法,返回所有的类实例组成的数组,比如  enum Fruit {APPLE,ORANGE,GRAPE},就返回 new Fruit[]{Fruit.APPLE, Fruit.ORANGE, Fruit.GRAPE},这可以用于遍历操作。

  1. for (Fruit f Fruit.values()) {
  2. ....
  3. }

  第二个是用switch做选择的时候,case后面只要写出枚举值即可,不必写类名

  1. Fruit f = Fruit.APPLE;
  2. switch(f) {
  3. case (APPLE) { //此处不必也不能写成case (Fruit.APPLE),编译器自动判断这是Fruit类的实例
  4. ...
  5. break;
  6. }
  7. case (ORANGE) {
  8. ...
  9. break;
  10. }
  11. default {
  12. ...
  13. }
  14. }

  关于这是如何实现的,就是编译器自己的暗箱操作了,此处也不太清楚。

  另外,更高级一点的,就是把enum完全看成是一个class,因而可以重写自己的方法,添加自己的成员变量。例如用枚举值实现四则运算:

  1. public enum Operation {
  2. //每个枚举值,即Operation实例,其实自己在声明时定义了一个匿名的类,继承了Operation类,匿名子类里面重写了父类Operation的apply方法
  3. PLUS {double apply(double x,double y) {return x+y;}},
  4. MINUS {double apply(double x,double y) {return x-y;}},
  5. TIMES {double apply(double x,double y) {return x*y;}},
  6. DIVIDE {double apply(double x,double y) {return x/y;}};
  7.  
  8. abstract double apply(double x, double y); //Operation类中定义的抽象方法
  9. }

  上面的代码翻译过来其实是

  1. public class Operation extends Enum{
  2.  
  3. public static final Operation PLUS = new Operation(){ //匿名内部类
  4. @Override
  5. double apply(double x,double y) {return x+y;}
  6. };
  7. ......
  8.  
  9. abstract double apply(double x, double y); //Operation类中定义的抽象方法
  10. }

  当然也可以自己实现enum,添加成员变量,重载构造函数

  1. public enum Operation {
  2.  
  3. PLUS("+"), //等于:public static final Operation PLUS = new Operation("+");
  4. MINUS("-"),
  5. TIMES("*"),
  6. DIVIDE("/");
  7.  
  8. private final String symbol; //自己加的成员变量
  9.  
  10. Operation(String symbol) {this.symbol = symbol;} //构造函数,在列枚举值的时候可以加括号给出参数,注意访问权限最好别写成public
  11.  
  12. @Override
  13. public String toString() {return symbol;} //重写Enum类的toString()方法
  14. }

  最后,有一个小小的细节要注意,枚举值之间用“,”隔开。当enum定义中只有枚举值,没有其他东西时,枚举值最后可以不加“;”。但还有其他定义,比如还有一个成员方法,必须先写出枚举值,再加其他定义,且枚举值最后加“;”

  1. private enum Haha {
  2.  
  3. private void f() {
  4. System.out.println("!!!!!!!!!");
  5. };
  6. FACE,SUGAR,APPLE; //枚举值必须先写
  7. }

枚举值的一大用处是用来替换零散的常量定义(public static final XXX XX = XX;),而且这些常量仅做标识符用,对于常量值是多少并不关心。使用了enum定义之后,可以进行类型检查,当常量很多时还可以归类,而且可以输出标识符的字面值。

Java暗箱操作之enum的更多相关文章

  1. 关于Java中枚举Enum的深入剖析

    在编程语言中我们,都会接触到枚举类型,通常我们进行有穷的列举来实现一些限定.Java也不例外.Java中的枚举类型为Enum,本文将对枚举进行一些比较深入的剖析. 什么是Enum Enum是自Java ...

  2. 深入掌握Java中的enum

    对于要在程序中要表示有限种类的某事物,一般我们可以采用两种方式,一是使用:public static final String 常量:二是使用enum来表示.一般而言前者简单,但是不能够很好的提供更多 ...

  3. 【转】java枚举类型enum的使用

    原文网址:http://blog.csdn.net/wgw335363240/article/details/6359614 java 枚举类型enum 的使用 最近跟同事讨论问题的时候,突然同事提到 ...

  4. 转载 java枚举类型enum的使用 (原文地址:http://blog.csdn.net/wgw335363240/article/details/6359614)

    java枚举类型enum的使用 最近跟同事讨论问题的时候,突然同事提到我们为什么java中定义的常量值不采用enmu枚举类型,而采用public final static 类型来定义呢?以前我们都是采 ...

  5. C++和Java中枚举enum的用法

    在C++和java中都有枚举enum这个关键字,但是它们之间又不太一样.对于C++来说,枚举是一系列命名了的整型常量,而且从枚举值转化为对应的整型值是在内部进行的.而对于Java来说,枚举更像一个类的 ...

  6. 深入理解Java枚举类型(enum)

    https://blog.csdn.net/javazejian/article/details/71333103 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(en ...

  7. 聊一聊Java的枚举enum

    一. 什么是枚举 枚举是一种数据类型,具有集合的一些特点,可以存放多个元素,但存储对象有限且固定,枚举也有比较常见的使用场景,如我们需要表达性别(男.女),颜色(红.黄.蓝),星期(星期一.星期二.. ...

  8. Java 枚举(enum)详解

    概念: Java1.5发行版本中增加了新的引用类型--枚举类型(enum type).枚举类型是指由一组固定的常量组成合法值的类型.在Java虚拟机中,枚举类在进行编译时会转变成普通的Java类. 创 ...

  9. Java 枚举(enum)的学习

    Java 枚举(enum)的学习 本文转自:https://blog.csdn.net/javazejian/article/details/71333103 枚举的定义 在定义枚举类型时我们使用的关 ...

随机推荐

  1. EntityFramework 7 Linq Contains In 奇怪问题

    这篇博文纪录一下:当使用 EF7,Linq 实现类似 where filename in('','','') SQL 代码,使用 Contains 出现报错问题. project.json 配置文件( ...

  2. 订制DOM选择器

    本来是打算参考zepto.js,然后将里面想要的部分抽出来做函数,随调随用. 但后面发现这种写法重复代码太多,代码不整洁,于是就打算模仿下zepto的写法,挑出些比较实用的方法,造一下轮子. 起名叫“ ...

  3. 软件工程-构建之法 理解C#一小段程序

    一.前言 老师给出的要求: 阅读下面程序,请回答如下问题: 问题1:这个程序要找的是符合什么条件的数? 问题2:这样的数存在么?符合这一条件的最小的数是什么? 问题3:在电脑上运行这一程序,你估计多长 ...

  4. iOS_UIImage_毛玻璃效果

    效果图: 核心方法: // 出入UIImage 和 blur模糊成度 (0-1) - (UIImage *)blurryImage:(UIImage *)image withBlurLevel:(CG ...

  5. [AngularJS] AngularJS系列(7) 进阶篇之promise

    目录 使用promise 补充说明 $q.all $q.when 在上节中,我们在http中使用了then 和 在ngResource中返回了一个'延迟对象'. 本节介绍一下angular中的prom ...

  6. 六、CsrfViewMiddleware

    CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF. 你这可 ...

  7. js+html5双人五子棋(源码下载)

    代码如下: <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" c ...

  8. ym—— Android网络框架Volley(体验篇)

    VolleyGoogle I/O 2013推出的网络通信库,在volley推出之前我们一般会选择比较成熟的第三方网络通信库,如: android-async-http retrofit okhttp ...

  9. Asp.net 面向接口可扩展框架之数据处理模块及EntityFramework扩展和Dapper扩展(含干货)

    接口数据处理模块是什么意思呢?实际上很简单,就是使用面向接口的思想和方式来做数据处理. 还提到EntityFramework和Dapper,EntityFramework和Dapper是.net环境下 ...

  10. 【Java每日一题】20161222

    package Dec2016; import java.util.Random; public class Ques1222 { public static void main(String[] a ...