枚举

枚举

wiki

学习计划

  • 枚举的基本用法;
  • 枚举的实现原理;
  • 枚举的用处
    • 单例
    • 序列化

理解枚举

为什么使用枚举

在没有枚举之前,实现枚举的方式使用public static final定义常量

public class Season {
    public static final int SPRING = 1;
    public static final int SUMMER = 2;
    public static final int AUTUMN = 3;
    public static final int WINTER = 4;
}

这种方法在类型安全性和可读性方面比较差。

  • 类型安全性

    在下面的代码中,方法getChineseSeason中应该传入枚举中的值,但是传入其他的值也是可以的,不能进行类型检查,在后面的运行中可能会出现问题。

    private String getChineseSeason(int season){
        StringBuffer result = new StringBuffer();
        switch(season){
            case Season.SPRING :
                result.append("春天");
                break;
            case Season.SUMMER :
                result.append("夏天");
                break;
            case Season.AUTUMN :
                result.append("秋天");
                break;
            case Season.WINTER :
                result.append("冬天");
                break;
            default :
                result.append("地球没有的季节");
                break;
        }
        return result.toString();
    }
    
    public void doSomething(){
        System.out.println(this.getChineseSeason(Season.SPRING));//这是正常的场景
    
        System.out.println(this.getChineseSeason(5));//这个却是不正常的场景,这就导致了类型不安全问题
    }
    
  • 代码可读性:在传入枚举值时,无法知道枚举值表示的意义。

枚举的基本用法

用法一:常量

public enum Color {  
  RED, GREEN, BLANK, YELLOW  
} 

用法二:Switch

public class EnumTest {
    public static void main(String[] args)
    {
        Season curSeason = Season.SPRING;

        switch(curSeason){
            case SPRING:
                curSeason = Season.SUMMER;
                break;
            case SUMMER:
                curSeason = Season.AUTUMN;
                break;
            case AUTUMN:
                curSeason = Season.WINTER;
                break;
            case WINTER:
                curSeason = Season.SPRING;
                break;
        }
    }
}
Switch和enum的实现原理

EnumTes编译后生成两个文件,分别是EnumTest.classEnumTest$1.class,然后使用Jad对两个文件进行反编译。

首先查看EnumTest.class编译后的文件

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumTest.java


public class EnumTest
{

    public EnumTest()
    {
    }

    public static void main(String args[])
    {
        Season curSeason = Season.SPRING;
        static class _cls1
        {

            static final int $SwitchMap$Season[];

            static 
            {
                $SwitchMap$Season = new int[Season.values().length];
                try
                {
                    $SwitchMap$Season[Season.SPRING.ordinal()] = 1;
                }
                catch(NoSuchFieldError nosuchfielderror) { }
                try
                {
                    $SwitchMap$Season[Season.SUMMER.ordinal()] = 2;
                }
                catch(NoSuchFieldError nosuchfielderror1) { }
                try
                {
                    $SwitchMap$Season[Season.AUTUMN.ordinal()] = 3;
                }
                catch(NoSuchFieldError nosuchfielderror2) { }
                try
                {
                    $SwitchMap$Season[Season.WINTER.ordinal()] = 4;
                }
                catch(NoSuchFieldError nosuchfielderror3) { }
            }
        }

        switch(_cls1..SwitchMap.Season[curSeason.ordinal()])
        {
        case 1: // '\001'
            curSeason = Season.SUMMER;
            break;

        case 2: // '\002'
            curSeason = Season.AUTUMN;
            break;

        case 3: // '\003'
            curSeason = Season.WINTER;
            break;

        case 4: // '\004'
            curSeason = Season.SPRING;
            break;
        }
    }
}
  • Switch生成了一个内部类_cls1,里面有int数组$SwitchMap$Season
  • $SwitchMap$Season[Season.SPRING.ordinal()] = 1;,因为Season每一个枚举值都是一个Season对象,都有一个ordinal值(和枚举值的顺序相同,Season.SPRING.ordinal()=0,Season.SUMMER.ordinal()=1),将switch中出现的每一个枚举值都放入数组中;
  • 在switch进行判断时,取得是$SwitchMap$Season中的值。

用法三:向枚举中添加新方法

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

通过反编译查看枚举的本质

  • 源代码

    public enum Season {
        SPRING, SUMMER, AUTUMN, WINTER;
    }
    
  • 使用Jad反编译

    // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
    // Jad home page: http://www.kpdus.com/jad.html
    // Decompiler options: packimports(3) 
    // Source File Name:   Season.java
    
    
    public final class Season extends Enum
    {
    
        public static Season[] values()
        {
            return (Season[])$VALUES.clone();
        }
    
        public static Season valueOf(String name)
        {
            return (Season)Enum.valueOf(Season, name);
        }
    
        private Season(String s, int i)
        {
            super(s, i);
        }
    
        public static final Season SPRING;
        public static final Season SUMMER;
        public static final Season AUTUMN;
        public static final Season WINTER;
        private static final Season $VALUES[];
    
        static 
        {
            SPRING = new Season("SPRING", 0);
            SUMMER = new Season("SUMMER", 1);
            AUTUMN = new Season("AUTUMN", 2);
            WINTER = new Season("WINTER", 3);
            $VALUES = (new Season[] {
                SPRING, SUMMER, AUTUMN, WINTER
            });
        }
    }
    

    对编译后的代码进行分析

    • 枚举类都是继承了Enum类;
    • 枚举类是一个final类,说明无法被继承;
    • 枚举中的每一个枚举值都是一个枚举类的对象;
    • 在创建枚举类的时候,使用的是静态变量和静态块。当一个Java类第一次被真正使用到的时候静态资源被初始化,Java类的加载和初始化过程都是线程安全的。所以,创建一个enum类型是线程安全的

枚举与序列化

序列化

在序列化中,在方法ObjectOutputStream.writeObject0中判断对象是否为Enum类型,然后调用ObjectOutputStream.writeEnum

private void writeEnum(Enum<?> en,
                           ObjectStreamClass desc,
                           boolean unshared)
        throws IOException
    {
        bout.writeByte(TC_ENUM);
        ObjectStreamClass sdesc = desc.getSuperDesc();
        writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false);
        handles.assign(unshared ? null : en);
        writeString(en.name(), false);
    }

可以看出,枚举类进行序列化时调用writeString()写入枚举值的name;

反序列化

在对Enum进行反序列化时,方法ObjectInputStream.readObject0在判断时enum后,调用ObjectInputStream.readEnum方法,通过调用Enum.valueOf进行反序列化。

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

valueOf方法调用Enum.enumConstantDirectory(),通过反射调用enum类的方法values,在枚举的本质一章中可以看到,values方法可以获取所有的枚举值。

例子

枚举类Season具有一个私有变量,如果在对Season.SPRING执行序列化后,然后将SPRING(1)改为SPRING(5),则Season.SPRING.getVal得到的是5。原因是序列化时只是写入枚举值的name,在反序列化时通过name获得对于的枚举实例,而这个实例是根据最新的类实例化得到的。

enum Season
{
    SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);

    private int val;

    private Season(int val){
        this.val = val;
    }

    public int getVal()
    {
        return this.val;
    }
}

枚举与单例

因为枚举可以保证线程安全和序列化安全性,并且实现比较简洁,适合用于单例模式

enum SingletonDemo{
    INSTANCE;
    public void otherMethods(){
        System.out.println("Something");
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,273评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,349评论 3 398
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,709评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,520评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,515评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,158评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,755评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,660评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,203评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,287评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,427评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,122评论 5 349
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,801评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,272评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,393评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,808评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,440评论 2 359

推荐阅读更多精彩内容

  • 假如有一笔业务需要审核,审核状态分:未审核,审核中,审核通过,审核不通过。我们在程序里是否可以直接这么写: if(...
    scandly阅读 9,266评论 0 0
  • 一 Java 枚举7常见种用法DK1.5引入了新的类型——枚举。在 Java 中它虽然算个“小”功能,却给我的开发...
    欢乐时光欢乐你我阅读 875评论 0 6
  • 8yue17 枚举 赋值给变量的有命名的整数常量的方法 定义:枚举类型为定义了一组可以赋值给变量的命名整数常 量...
    cGunsNRoses阅读 501评论 0 0
  • 系统里实现常量的方式有三种:1、接口常量2、类常量3、枚举 1、接口常量:jdk1.5之前,没有枚举类,有的用接口...
    静心安分读书阅读 1,994评论 0 0
  • 这是兰子陪伴你的第356天 培训的一天,学习的6小时,收获颇多。 培训后,我的三个感受: 第一个感受: 1、小游戏...
    白云兰子阅读 582评论 0 0