枚举

枚举

wiki

  • Java的枚举类型用法介绍
  • 深入理解Java枚举类型(enum)
  • 为什么要用enum?

学习计划

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

理解枚举

为什么使用枚举

在没有枚举之前,实现枚举的方式使用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 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);
}

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

你可能感兴趣的:(枚举)