Java Enum枚举

java枚举类 (enum),之前在我的印象里只是一个提供定义一些具体常量的一个类,在工作以后用它比较少,也就没在意过,直到我在《开发者头条》中看到一片关于java单例模式最佳实践的文章,在文章的最后写到最好的单例模式写法其实是用枚举实现单例模式,让我有点云里雾里,所以就研究了下enum。

1. 枚举的认知

java一个最基础的枚举类建立如下:

public enum EnumTest {
    ONE,TWO,THREE;
}

这样就建立了一个简单的枚举,我们可以将它当作一个常量集来使用

        EnumTest test = EnumTest.ONE;
        switch(test){
        case ONE:
            System.out.println(EnumTest.ONE);
            System.out.println(test);
            break;
        case TWO:
            System.out.println(EnumTest.TWO);
            System.out.println(test);
            break;
        }

其输出结果:

ONE
ONE

由这段代码可见,枚举中ONE、TWO等其实都是一个EnumTest类。
enum的语法结构虽然和class不一样,但编译后产生的依然是class文件,反编译该class文件可以看到,枚举实际上是一个继承了java.lang.Enum 类的一个特殊类而已,查看反编译的内容(我用的jad):

public final class EnumTest extends Enum {

    private EnumTest(String s, int i)
    {
        super(s, i);
    }

    public static EnumTest[] values()
    {
        EnumTest aenumtest[];
        int i;
        EnumTest aenumtest1[];
        System.arraycopy(aenumtest = ENUM$VALUES, 0, aenumtest1 = new EnumTest[i = aenumtest.length], 0, i);
        return aenumtest1;
    }

    public static EnumTest valueOf(String s)
    {
        return (EnumTest)Enum.valueOf(Test/EnumTest, s);
    }

    public static final EnumTest ONE;
    public static final EnumTest TWO;
    public static final EnumTest THREE;
    private static final EnumTest ENUM$VALUES[];

    static 
    {
        ONE = new EnumTest("ONE", 0);
        TWO = new EnumTest("TWO", 1);
        THREE = new EnumTest("THREE", 2);
        ENUM$VALUES = (new EnumTest[] {
            ONE, TWO, THREE
        });
    }
}

当我们使用enum关键字时,编译器就会自动的帮助我们生成以上代码。我们可以清楚的看到,public final class EnumTest extends Enum,EnumTest继承自Enum类且被定义为final类型,在EnumTest中定义的各个常量都被声明为public static final EnumTest 类型,而实现也都放在了static代码块里,有多少个常量,就是调用了多少次EnumTest(String s, int i) 构造函数。

因此我们可知,enum(枚举)本质上也是一种java特殊类,我们声明的各个常量都只是为声明的枚举类型的对象起一个名字,其余的实现由编译器帮我们自动进行实现。查看源码,可知ONE、TWO等字符串都将赋值给Enum类中final String类型的name属性,当调用toString()方法时,数据其name值。

2. 枚举的使用

从上面得知enum本质就是一个java类,自然也可以给Enum定义属性和方法:

public enum EnumTest {
    MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6) {
        @Override
        public boolean isRest() {
            return true;
        }
    },
    SUN(0) {
        @Override
        public boolean isRest() {
            return true;
        }
    };

    private int value;

    private EnumTest(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    public boolean isRest() {
        return false;
    }
}

这段代码给EnumTest 枚举类定义了一个value属性和3个方法,重写了Enum的构造函数,因此可以接受WED(3)枚举定义时传入的参数。且SAT(6)重写了Enum类中的isRest方法。
我们查看生成的class文件:
这里写图片描述
实际上生成了3个class文件,因此我们得知SAT(6) 重写类中方法时,实际上是在EnumTest中生成了一个内部类,这个内部类继承自EnumTest,重写isRest()方法。

测试代码:

public class Test1 {
    public static void main(String[] args) {
        System.out.println("EnumTest.FRI 的 value = " + EnumTest.FRI.getValue());
        System.out.println("EnumTest.MON 的isRest = " + EnumTest.MON.isRest());
        System.out.println("EnumTest.SAT 的isRest = " + EnumTest.SAT.isRest());
    }
}

输出结果为:

EnumTest.FRI 的 value = 5
EnumTest.MON 的isRest = false
EnumTest.SAT 的isRest = true

3. 枚举实现单例模式

上面已经说完了枚举的一些基本常识,下面来说一下枚举如何实现单例模式和使用枚举的优点
枚举单例模式:

public enum Singleton {
    INSTANCE;
    private Singleton(){
    }
}

单例模式约束一个类只能实例化一个对象。在Java中,为了强制只实例化一个对象,最好的方法是使用一个枚举量。这个优秀的思想直接源于Joshua Bloch的《Effective Java》(《Java高效编程指南》)
其优点:
1. 线程安全;
2. 写法简单;
3. 保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量);
4. 自由序列化;

枚举的线程安全

由上面对枚举介绍时,已经说明了,枚举实例的声明和实现都是static类型的,因为static类型的属性会在类被加载之后被初始化,
并且只会执行一次。所以创建enum类型是线程安全的。

我们知道,以前的所有的单例模式都有一个比较大的问题,就是一旦实现了Serializable接口之后,就不再是单例得了,因为,每次调用 readObject()方法返回的都是一个新创建出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。但是,为了保证枚举类型像Java规范中所说的那样,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定。原文如下:

Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant’s name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant’s enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream. The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored–all enum types have a fixedserialVersionUID of 0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.

大概意思就是说,在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。 我们看一下这个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 const " + enumType +"." + name);  
        }  

从代码中可以看到,代码会尝试从调用enumType这个Class对象的enumConstantDirectory()方法返回的map中获取名字为name的枚举对象,如果不存在就会抛出异常。再进一步跟到enumConstantDirectory()方法,就会发现到最后会以反射的方式调用enumType这个类型的values()静态方法,也就是上面我们看到的编译器为我们创建的那个方法,然后用返回结果填充enumType这个Class对象中的enumConstantDirectory属性。

所以,JVM对序列化有保证。

你可能感兴趣的:(java,enum)