深入分析Java的enum

简介: 一、定义(语法)enum全称为enumeration,中文意为枚举,枚举简单的说就是一种数据类型,只不过是这种数据类型只包含自定义的特定数据,它是一组有共同特性的数据的集合。

一、定义(语法)

enum全称为enumeration,中文意为枚举,枚举简单的说就是一种数据类型,只不过是这种数据类型只包含自定义的特定数据,它是一组有共同特性的数据的集合。

创建枚举类型是需要用到enum关键字,如:

public enum Color{  
    RED, GREEN, BLUE, BLACK, PINK, WHITE;  
}

enum的语法看似与class不同,但它实际上就是一个类,经过编译器编译之后得到一个.class文件。

创建的枚举类型默认是java.lang.enum<枚举类型名>(抽象类)的子类。

每个枚举项的类型都为public static final 。

因为Java的单继承机制,emum不能再用extends继承其他的类。

我们可以使用javap命令进行反编译验证。

img_f767f5d448f77ca36d0ebbf6f9be3e7c.png

既然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;  
        }  
    }

枚举实例的创建过程:枚举类型符合通用模式 Class Enum<E extends Enum<E>>,而 E 表示枚举类型的名称。枚举类型的每一个值都将映射到 protected Enum(String name, int ordinal) 构造函数中,在这里,每个值的名称都被转换成一个字符串,并且序数设置表示了此设置被创建的顺序。

public enum Color{  
    RED, GREEN, BLUE, BLACK, PINK, WHITE;  
}

相当于调用了六次Enum<Color>构造方法

Enum<Color>("RED", 0);

Enum<Color>("GREEN", 1);

Enum<Color>("BLUE", 2);

Enum<Color>("BLACK", 3);

Enum<Color>("PINK",4);

Enum<Color>("WHITE", 5);

枚举类型的常用方法:

int compareTo(E o) 比较此枚举与指定对象的顺序。

Class<E> getDeclaringClass() 返回与此枚举常量的枚举类型相对应的 Class 对象。

String name() 返回此枚举常量的名称,在其枚举声明中对其进行声明。

int ordinal() 返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零

String toString() 返回枚举常量的名称,它包含在声明中。

static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) 返回带指定名称的指定枚举类型的枚举常量。

二、常用用法
用法一:常量

在JDK1.5 之前,我们定义常量都是: public static fianl.... 。现在好了,有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法。

用法二:switch

JDK1.6之前的switch语句只支持int,char,enum类型,使用枚举,能让我们的代码可读性更强。

enum Color{  
    RED, GREEN, BLUE, BLACK, PINK, WHITE;  
}  
public class TestEnum {  
    public void changeColor(){  
        Color color = Color.RED;  
        System.out.println("原色:" + color);  
        switch(color){  
        case RED:  
            color = Color.GREEN;  
            System.out.println("变色:" + color);  
            break;  
        case GREEN:  
            color = Color.BLUE;  
            System.out.println("变色:" + color);  
            break;  
        case BLUE:  
            color = Color.BLACK;  
            System.out.println("变色:" + color);  
            break;  
        case BLACK:  
            color = Color.PINK;  
            System.out.println("变色:" + color);  
            break;  
        case PINK:  
            color = Color.WHITE;  
            System.out.println("变色:" + color);  
            break;  
        case WHITE:  
            color = Color.RED;  
            System.out.println("变色:" + color);  
            break;  
        }  
    }  
    public static void main(String[] args){  
        TestEnum testEnum = new TestEnum();  
        testEnum.changeColor();  
    }  
}

用法三:实现接口

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);  
        }  
    }

用法四:枚举集合的应用

java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复;EnumMap中的 key是enum类型,而value则可以是任意类型。关于这个两个集合的使用就不在这里赘述,可以参考JDK文档

public class Test {  
    public static void main(String[] args) {  
        // EnumSet的使用  
        EnumSet<EnumTest> weekSet = EnumSet.allOf(EnumTest.class);  
        for (EnumTest day : weekSet) {  
            System.out.println(day);  
        }  
   
        // EnumMap的使用  
        EnumMap<EnumTest, String> weekMap = new EnumMap(EnumTest.class);  
        weekMap.put(EnumTest.MON, "星期一");  
        weekMap.put(EnumTest.TUE, "星期二");  
        // ... ...  
        for (Iterator<Entry<EnumTest, String>> iter = weekMap.entrySet().iterator(); iter.hasNext();) {  
            Entry<EnumTest, String> entry = iter.next();  
            System.out.println(entry.getKey().name() + ":" + entry.getValue());  
        }  
    }  
}

三、综合实例

最简单的使用

最简单的枚举类

public enum Weekday {
    SUN,MON,TUS,WED,THU,FRI,SAT
}

如何使用它呢?
先来看看它有哪些方法:


img_b2c0c7f7b5e1b9fffad3e7e90ff7403a.png

这是Weekday可以调用的方法和参数。发现它有两个方法:values()和valueOf()。还有我们刚刚定义的七个变量


img_095ba5e9b8fe46d05aae69bff93952a7.png

这些事枚举变量的方法。我们接下来会演示几个比较重要的
img_bb79b612affb88a4b21f384828684303.png

这段代码,我们演示了几个常用的方法和功能:

  1. Weekday.valueOf() 方法:

    它的作用是传来一个字符串,然后将它转变为对应的枚举变量。前提是你传的字符串和定义枚举变量的字符串一样,区分大小写。如果你传了一个不存在的字符串,那么会抛出异常。

  2. Weekday.values()方法。

    这个方法会返回包括所有枚举变量的数组。在该例中,返回的就是包含了七个星期的Weekday[]。可以方便的用来做循环。

  3. 枚举变量的toString()方法。

    该方法直接返回枚举定义枚举变量的字符串,比如MON就返回【”MON”】。

  4. 枚举变量的.ordinal()方法。

    默认情况下,枚举类会给所有的枚举变量一个默认的次序,该次序从0开始,类似于数组的下标。而.ordinal()方法就是获取这个次序(或者说下标)

  5. 枚举变量的compareTo()方法。

    该方法用来比较两个枚举变量的”大小”,实际上比较的是两个枚举变量的次序,返回两个次序相减后的结果,如果为负数,就证明变量1”小于”变量2 (变量1.compareTo(变量2),返回【变量1.ordinal() - 变量2.ordinal()】)


    img_532c7e633098b46c07995580fda33bb6.png

    这是compareTo的源码,会先判断是不是同一个枚举类的变量,然后再返回差值。

  6. 枚举类的name()方法。

    它和toString()方法的返回值一样,事实上,这两个方法本来就是一样的:


    img_fb3abbaa7492289b1c8758989c930e91.png

    img_b000be6b3ecaa5c1d72804bc293096c8.png

    这两个方法的默认实现是一样的,唯一的区别是,你可以重写toString方法。name变量就是枚举变量的字符串形式。

还有一些其他的方法我就暂时不介绍了,感兴趣的话可以自己去看看文档或者源码,都挺简单的。

要点:

  • 使用的是enum关键字而不是class。
  • 多个枚举变量直接用逗号隔开。
  • 枚举变量最好大写,多个单词之间使用”_”隔开(比如:INT_SUM)。
  • 定义完所有的变量后,以分号结束,如果只有枚举变量,而没有自定义变量,分号可以省略(例如上面的代码就忽略了分号)。
  • 在其他类中使用enum变量的时候,只需要【类名.变量名】就可以了,和使用静态变量一样。

但是这种简单的使用显然不能体现出枚举的强大,我们来学习一下复杂的使用:

枚举的高级使用方法

就像我们前面的案例一样,你需要让每一个星期几对应到一个整数,比如星期天对应0。上面讲到了,枚举类在定义的时候会自动为每个变量添加一个顺序,从0开始。

假如你希望0代表星期天,1代表周一。。。并且你在定义枚举类的时候,顺序也是这个顺序,那你可以不用定义新的变量,就像这样:

public enum Weekday {
    SUN,MON,TUS,WED,THU,FRI,SAT
}

这个时候,星期天对应的ordinal值就是0,周一对应的就是1,满足你的要求。但是,如果你这么写,那就有问题了:

public enum Weekday {
    MON,TUS,WED,THU,FRI,SAT,SUN
}

我吧SUN放到了最后,但是我还是希0代表SUN,1代表MON怎么办呢?默认的ordinal是指望不上了,因为它只会傻傻的给第一个变量0,给第二个1。。。

所以,我们需要自己定义变量!

看代码:

img_4ff93c7b7bbbee3dcf2ab1ae5baab784.png

我们对上面的代码做了一些改变:

首先,我们在每个枚举变量的后面加上了一个括号,里面是我们希望它代表的数字。

然后,我们定义了一个int变量,然后通过构造函数初始化这个变量。

你应该也清楚了,括号里的数字,其实就是我们定义的那个int变量。这句叫做自定义变量。

请注意:这里有三点需要注意:

  1. 一定要把枚举变量的定义放在第一行,并且以分号结尾。
  1. 构造函数必须私有化。事实上,private是多余的,你完全没有必要写,因为它默认并强制是private,如果你要写,也只能写private,写public是不能通过编译的。
  1. 自定义变量与默认的ordinal属性并不冲突,ordinal还是按照它的规则给每个枚举变量按顺序赋值。

好了,你很聪明,你已经掌握了上面的知识,你想,既然能自定义一个变量,能不能自定义两个呢?

当然可以:

img_dfd63c4dde8f4ea73d7af9c60641ac01.png

你可以定义任何你想要的变量。学完了这些,大概枚举类你也应该掌握了,但是,还有没有其他用法呢?

枚举类中的抽象类

如果我在枚举类中定义一个抽象方法会怎么样?

你要知道,枚举类不能继承其他类,也不能被其他类继承。至于为什么,我们后面会说到。

你应该知道,有抽象方法的类必然是抽象类,抽象类就需要子类继承它然后实现它的抽象方法,但是呢,枚举类不能被继承。。你是不是有点乱?

我们先来看代码:


img_66eafa0cf99d124d600c21979edaf9c7.png

你好像懂了点什么。但是你好像又不太懂。为什么一个变量的后边可以带一个代码块并且实现抽象方法呢?

别着急,带着这个疑问,我们来看一下枚举类的实现原理。

枚举类的实现原理

从最简单的看起:


img_dca879f03080cbb8fd2585f557ac274a.png

还是这段熟悉的代码,我们编译一下它,再反编译一下看看它到底是什么样子的:

img_c8c3111aa173bed071831deaa797c3b2.png

你是不是觉得很熟悉?反编译出来的代码和我们用静态变量自己写的类出奇的相似!

而且,你看到了熟悉的values()方法和valueOf()方法。

仔细看,这个类继承了java.lang.Enum类!所以说,枚举类不能再继承其他类了,因为默认已经继承了Enum类。

并且,这个类是final的!所以它不能被继承!

回到我们刚才的那个疑问:

img_fa0a9d0849fc7a26a9dcf5e526eb172e.png

为什么会有这么神奇的代码?现在你差不多懂了。因为RED本身就是一个TrafficLamp对象的引用。实际上,在初始化这个枚举类的时候,你可以理解为执行的是 TrafficLamp RED = new TrafficLamp(30) ,但是因为TrafficLamp里面有抽象方法,还记得匿名内部类么?

我们可以这样来创建一个TrafficLamp引用:

img_951bbdc7e18048428d096dd10ad58cf8.png

而在枚举类中,我们只需要像上面那样写【RED(30){}】就可以了,因为java会自动的去帮我们完成这一系列操作

枚举类的其他用法

img_d5fb2bee68259814d7bc4dca38a764f4.png
switch语句

虽然枚举类不能继承其他类,但是还是可以实现接口的


img_9d31b6bbabfd95d966cbe8a9c634bb02.png
接口定义

img_e25313357e685862a1eec52e42335440.png
实现接口

img_0549a7826ce6c4b77d69cddf3047bf02.png
使用接口组织枚举

使用枚举创建单例模式

使用枚举创建的单例模式:

public enum EasySingleton{
    INSTANCE;
}

代码就这么简单,你可以使用EasySingleton.INSTANCE调用它,比起你在单例中调用getInstance()方法容易多了。

我们来看看正常情况下是怎样创建单例模式的:

用双检索实现单例:

下面的代码是用双检索实现单例模式的例子,在这里getInstance()方法检查了两次来判断INSTANCE是否为null,这就是为什么叫双检索的原因,记住双检索在java5之前是有问题的,但是java5在内存模型中有了volatile变量之后就没问题了。

public class DoubleCheckedLockingSingleton{
     private volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

你可以访问DoubleCheckedLockingSingleTon.getInstance()来获得实例对象。

用静态工厂方法实现单例:

public class Singleton{
    private static final Singleton INSTANCE = new Singleton();

    private Singleton(){}

    public static Singleton getSingleton(){
        return INSTANCE;
    }
}

你可以调用Singleton.getInstance()方法来获得实例对象。


上面的两种方式就是懒汉式和恶汉式单利的创建,但是无论哪一种,都不如枚举来的方便。而且传统的单例模式的另外一个问题是一旦你实现了serializable接口,他们就不再是单例的了。但是枚举类的父类【Enum类】实现了Serializable接口,也就是说,所有的枚举类都是可以实现序列化的,这也是一个优点。

总结

  • 可以创建一个enum类,把它看做一个普通的类。除了它不能继承其他类了。(java是单继承,它已经继承了Enum),可以添加其他方法,覆盖它本身的方法
  • switch()参数可以使用enum
  • values()方法是编译器插入到enum定义中的static方法,所以,当你将enum实例向上转型为父类Enum是,values()就不可访问了。解决办法:在Class中有一个getEnumConstants()方法,所以即便Enum接口中没有values()方法,我们仍然可以通过Class对象取得所有的enum实例
  • 无法从enum继承子类,如果需要扩展enum中的元素,在一个接口的内部,创建实现该接口的枚举,以此将元素进行分组。达到将枚举元素进行分组。
  • enum允许程序员为eunm实例编写方法。所以可以为每个enum实例赋予各自不同的行为。

【java.util.EnumSet和java.util.EnumMap】

目录
相关文章
|
1月前
|
算法 Java
java面向对象和面向过程分析
java面向对象和面向过程分析
38 0
|
1月前
|
存储 Java 编译器
java和c++的主要区别、各自的优缺点分析、java跨平台的原理的深度解析
java和c++的主要区别、各自的优缺点分析、java跨平台的原理的深度解析
88 0
|
13天前
|
Java 调度
Java中常见锁的分类及概念分析
Java中常见锁的分类及概念分析
15 0
|
13天前
|
Java
Java中ReentrantLock中tryLock()方法加锁分析
Java中ReentrantLock中tryLock()方法加锁分析
12 0
|
29天前
|
人工智能 监控 算法
java智慧城管源码 AI视频智能分析 可直接上项目
Java智慧城管源码实现AI视频智能分析,适用于直接部署项目。系统运用互联网、大数据、云计算和AI提升城市管理水平,采用“一级监督、二级指挥、四级联动”模式。功能涵盖AI智能检测(如占道广告、垃圾处理等)、执法办案、视频分析、统计分析及队伍管理等多个模块,利用深度学习优化城市管理自动化和智能化,提供决策支持。
213 4
java智慧城管源码 AI视频智能分析 可直接上项目
|
2月前
|
算法 前端开发 JavaScript
【JAVA】JDK11新特性个人分析
【JAVA】JDK11新特性个人分析
55 0
|
3月前
|
存储 缓存 Java
深入理解Java中的逃逸分析
深入理解Java中的逃逸分析
47 0
|
13天前
|
Java
Java中关于ConditionObject的signal()方法的分析
Java中关于ConditionObject的signal()方法的分析
21 4
|
13天前
|
Java
Java中关于ConditionObject的分析
Java中关于ConditionObject的分析
17 3
|
16天前
|
设计模式 缓存 安全
分析设计模式对Java应用性能的影响,并提供优化策略
【4月更文挑战第7天】本文分析了7种常见设计模式对Java应用性能的影响及优化策略:单例模式可采用双重检查锁定、枚举实现或对象池优化;工厂方法和抽象工厂模式可通过对象池和缓存减少对象创建开销;建造者模式应减少构建步骤,简化复杂对象;原型模式优化克隆方法或使用序列化提高复制效率;适配器模式尽量减少使用,或合并多个适配器;观察者模式限制观察者数量并使用异步通知。设计模式需根据应用场景谨慎选用,兼顾代码质量和性能。