枚举的简单使用
在java中,我们可以使用enum关键字来定义枚举:
1 2 3
| public enum Color { RED, GREEN, BLUE; }
|
就像上面一样,我们定义了一个名为Color的枚举类,包含了RED、GREEN、BLUE三个常量。当我们使用枚举类的时候,直接通过枚举类名.枚举常量即可。就像如下的形式:
1 2 3 4 5 6 7 8 9 10
| void f(Color c) { switch (c) { case RED: System.out.println("It's RED"); break; case GREEN: System.out.println("It's GREEN"); break; default: System.out.println("It's BLUE"); } }
f(Color.RED);
|
枚举的本质
虽然Java提供枚举类的定义,但是实际上他并不是Java中一个新的对象类型,我们通过对Color枚举类进行反编译,得到如下的反编译结果:
1 2 3 4 5 6 7 8 9 10 11
| $ javap Color.class
Compiled from "Color.java" public final class EnumerationAndAnnotation.Color extends java.lang.Enum<EnumerationAndAnnotation.Color> { public static final EnumerationAndAnnotation.Color RED; public static final EnumerationAndAnnotation.Color GREEN; public static final EnumerationAndAnnotation.Color BLUE; public static EnumerationAndAnnotation.Color[] values(); public static EnumerationAndAnnotation.Color valueOf(java.lang.String); static {}; }
|
我们可以看到,枚举类实际上在编译的过程中,被编译器进行调整,它并不是一个新的类型,本质上依然是一个类(Color),这个类继承了java.lang.Enum,而对于每一个枚举常量,实际上是public static final修饰的枚举类的静态实例对象。
同时注意,编译器会为我们添加两个新的static方法:values() 和 valueOf(java.lang.String),其实分别作用是返回枚举类中定义的所有的枚举常量,以及根据枚举名来获取枚举常量(注意,这里就是定义枚举常量的枚举名)。
当然,由于每一个枚举常量实际上是实现了java.lang.Enum的枚举类的一个静态实例对象,而这个过程是编译器为我们进行的,所以,自然,我们可以在枚举类中定义任何的方法、变量,以及构造函数的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public enum Color { RED("RED"), GREEN("GREEN"), BLUE("BLUE"); private String colorName; Color(String colorName) { this.colorName = colorName; } public void printColorName() { System.out.println("Color name is " + colorName); } @Override public String toString() { return colorName; } }
|
自然,我们可以枚举常量当作一个枚举的实例化对象,调用枚举类中的方法:
1 2 3 4
| public static void main(String[] args) { System.out.println(Color.RED); Color.RED.printColorName(); }
|
为什么枚举构造器不能访问枚举的静态域
查看下面这段代码:
1 2 3 4 5 6 7
| public enum Color { RED, GREEN, BLUE; private static int value = 0; Color() { System.out.println(value); } }
|
我们可以看到这里编译不通过,提示我们枚举构造器不能够访问枚举的静态域(以及静态变量)。我们知道,一般的类中,静态域以及静态变量是优于实例对象的变量、方法的初始化的。这里简要复习一下类中变量的加载机制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class Init { public Init(String init) { System.out.println("Init()" + init); } } class Fa { static final Init initFa = new Init("Fa's static"); Init initFa2 = new Init("Fa's no static"); static { System.out.println("Fa's static"); } Fa() { System.out.println("Fa()"); } } class Su extends Fa { static { System.out.println("Su's static"); } static final Init initSu = new Init("Su's static"); Init initSu2 = new Init("Su's no static"); Su() { System.out.println("Su()"); } }
|
接下来,我们new出Su实例对象,并观察输出结果:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static void main(String[] args) { new Su(); }
|
我们可以看到,有static修饰的始终优于实例对象的相关的初始化的,在输出中 1 - 4 是static修饰部分,5 - 8是实例域部分。此外,在继承情形下,父类由于子,输出中 1- 2 是父类static域的初始化,3 - 4 是子类static域的初始化。在static域加载完成之后,才开始加载父类非static域,最后加载子类的非static域。注意,都为static修饰的情况下,加载顺序根绝定义时候的顺序而来,1、2与3、4就可以看出。
看到这里,也许会有点疑问,既然静态域加载优于实例域(包含构造函数),那为什么在枚举类中就不行呢?让我们回到前面对枚举类的反编译,其实答案就出来了。反编译的过程我们可以看到,我们的枚举常量实际上是我们枚举类的静态实例化对象,在编译器的修改下,我们运行加载枚举类的过程中,枚举常量是static修饰的,其他静态域也是static修饰的,枚举常量又排在其他静态域的前面,按照上面的额初始化顺序,首先就会调用构造器实例化枚举常量对象,此时,枚举类中的其他静态域都还没来得及初始化,自然在构造函数中不能访问静态域了。有人可能想说,那我静态域放到枚举常量前面,让他先加载怎么样?很遗憾,Java不允许这样做:
1 2 3 4
| public enum Color { private static int value = 0; RED, GREEN, BLUE; }
|
使用抽象函数为枚举统一方法
上面的内容探讨了枚举类的一些基础,这里提一些关于使用枚举的代码策略设计。
有的时候,我们想要给枚举常量定义某一些通用的方法,同时,针对不同的枚举,该通用方法呈现不同的具体内容。例如,我现在有一个如下的Color枚举类,当针对不同的Color常量的时候,能有一个方式给我返回该颜色的十六进制颜色码。也许你会如下来实现
1 2 3 4 5 6 7 8 9 10 11 12 13
| public enum Color { RED, GREEN, BLUE; public String getHexCode() { switch (this) { case RED: return "#FF0000"; case GREEN: return "#00FF00"; default: return "#0000FF"; } } }
|
以上的方式较为简洁与易读,但是存在问题:枚举常量越来越多,case会不断增长,如果编写代码的过程由于不注意,增加颜色常量的时候,忘记了增加对应的case,那么编译是不会有任何的问题的,但是却隐含的将增加的颜色常量也返回的是BLUE的十六进制颜色码。
鉴于上述的问题,我们需要某种方式来防止我们犯错,能够想到的,就是通过编译器来告诉我们。于是,我们在枚举类中定一个抽象方法getHexCode,于是乎,对于每一个枚举常量,编译器会提示我们实现具体实例的getHexCode:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public enum Color { RED { @Override public String getHexCode() { return "#FF0000"; } }, GREEN { @Override public String getHexCode() { return "#00FF00"; } }, BLUE { @Override public String getHexCode() { return "#0000FF"; } };
abstract public String getHexCode(); }
|
在这样的定义下,代码牺牲了一定的简洁性,但是易读性丝毫不输于最开始的方式。针对Color枚举类,我们定义了抽象方法,表明了对于Color中的每一个枚举常量,都应该有getHexCode方法,返回自己的十六进制颜色码。如果我们新添加了枚举常量,而没有实现该方法,编译器会报错警告我们。