枚举探索

枚举是一种很常用的java功能,他可以将一组具名的值的有限集合创建伟一种新的类型,而且这些值可是作为常规的程序组件使用。我们在平常开发中只是简单的使用枚举,例如通过枚举来定义一组常量。下面我们通过进一步的探索来更深入的了解枚举这个工具。

枚举的特性

作为程序员,一言不合就上代码:

//:Fruit.java
enum Fruit {
    APPLE, PEAR, ORANGE;
    public String toString() {
        return "# " + ordinal() + " " + name();
    }
}
//:FruitTest.java
public class FruitTest {
    public static void main(String[] args) {
        for(Fruit fruit : Fruit.values()) {
            System.out.println("ordinal: " + fruit.ordinal());
            System.out.println(fruit.compareTo(Fruit.PEAR) + " ,");
            System.out.println(fruit.equals(Fruit.PEAR) + " ,");
            System.out.println(fruit.toString());
            System.out.println("-------------------");
        }
    }
}

运行结果如下!
枚举探索_第1张图片
执行结果

我们可以看出来枚举有以下特性:

  • 我们可以像使用类一样使用枚举,覆盖toString()方法;
  • 如果需要在枚举里写方法,需要在最后一个定义的枚举值后面加分号;
  • values()静态方法返回enum实例的数组;
  • 每个枚举值都有一个编号,从0开始,通过ordinal()方法返回;
  • 枚举实现了Comparable接口,还有equals方法;
  • name()方法返回实例声明时的名字。

还有一些看不出来的特性:

  • 枚举不能被继承;
  • 定义枚举时可以实现接口,但是不能继承类。
探寻背后的原因

将Fruit.class文件执行以下javap反编译一下:


枚举探索_第2张图片
反编译Fruit

看看反编译出来的代码:

  • 枚举本质上就是一个类,因为是用class声明的;
  • 枚举继承了java.lang.Enum类,所以不能再继承其他的类了,因为java是单继承的,但是还可以实现接口的;
  • 枚举是被final修饰的类,所以枚举不能被继承;

既然枚举是继承了java.lang.Enum类,那我们来看一下Enum类的结构:

public abstract class Enum>
        implements Comparable, Serializable {

枚举探索_第3张图片
Enum结构

这也正好证实了枚举实现了Comparable接口的说法。
并且Enum类定义了ordinal() 方法,所以我们可以直接调用。
不过这里细心的人可能发现了一个奇怪的点: 上面定义的Fruit枚举反编译出来有一个values()方法,但是这个方法我们既没有在Fruit里面定义,Enum类中也没有定义,那么这个方法是那里来的呢?
答案是values()方法是由编译器添加的,在创建Fruit的过程中,编译器还添加了valueOf()方法,该方法不同于Enum中的valueOf()方法,Enum中的valueOf方法需要两个参数,而编译器新增的valueOf方法则只需要一个参数。

枚举的用途

枚举最常用的就是被定义成一组值当作常量使用,而且枚举也是支持switch语句的,例如:

enum Color{
    RED,GREEN,BLUE;
}
public class Hello {
    public static void main(String[] args){
        Color color=Color.RED;
        int counter=10;
        while (counter-->0){
            switch (color){
                case RED:
                    System.out.println("Red");
                    color=Color.BLUE;
                    break;
                case BLUE:
                    System.out.println("Blue");
                    color=Color.GREEN;
                    break;
                case GREEN:
                    System.out.println("Green");
                    color=Color.RED;
                    break;
            }
        }
    }
}

但是更多程序员喜欢用static final去定义一组常量使用,例如:

class IntEnum {
    public static final int APPLE = 0;
    public static final int PEAR = 1;
    public static final int ORANGE = 2;
}

但是通过枚举的方式与通过static final的方式相比有以下优点:

  • 由static final实现的枚举很难保证安全性,即当调用不在枚举范围内的数值时,需要额外的维护,而枚举方式则不存在此问题;
  • 枚举方式更利于log的查看。

但是枚举方式相比与static final方式相比也是有缺点的:那就是枚举会占用更大的内存。也正因为如此,谷歌推荐在安卓开发中尽量使用static final的方式代替枚举。

其实除此之外枚举还有个更巧妙的用处:单例模式。
用枚举实现单例模式的代码简单到你不敢相信自己的眼睛:

enum Sington {
    INSTANCE
}

对,就是这么简单,你就实现了一个线程安全,保证单例,自由序列化的单例模式。下面就来说说为什么。
1,通过前面的探索我们直到枚举就是通过class实现的,所以我们是可以自由扩展成员变量和方法的;
2,enum有且仅有私有构造方法(枚举构造方法的private可以省略),这恰好契合单例模式的要求;
3,通过前面反编译枚举的class文件我们知道:枚举量的实现其实是public static final T 类型的未初始化变量。如果枚举量有伴随参数并且手动添加了构造器,那么将会解析成一个静态的代码块在类加载时对变量进行初始化。这就类似于饿汉式的单例模式,从而式线程安全的;
4,每一个枚举类型和枚举变量在JVM中都是唯一的,即Java在序列化和反序列化枚举时做了特殊的规定,枚举的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被编译器禁用的,所以枚举天然可以防止被反序列化攻击。

到此为止就算是完成了一次浅尝则止的探索,其实枚举还有其他一些用途比如:责任链模式,多路分发等,与enum相关的还有EnumSet,EnumMap等这里就不展开了,如果有兴趣可以自行阅读Thinking in Java的枚举类型相关的章节。

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