What
枚举类型本质上也是类,但由于编译器自动做了很多事情,因此它的使用更为简洁、安全和方便。
特性
- 枚举类是一种特殊的类,它一样可以有自己的成员变量、方法,可以实现一个或者多个接口,也可以定义自己的构造器(默认都是private修饰,而且只能是private。因为枚举类的实例不能让外界来创建)。使用该枚举类中的任何一个枚举项都会导致所用的枚举项对象创建并初始化。
- 一个Java源文件中最多只能定义一个public访问权限的枚举类,且该Java源文件也必须和该枚举类的类名相同。
- 每个枚举都是通过 Class 在内部实现的,且默认所有的枚举值都是 public static final 的,但当一个枚举包含了抽象方法,它就是抽象枚举类,系统会默认使用abstract修饰,而不会再采用final修饰了。
- enum 定义的枚举类默认继承了 java.lang.Enum 类,它实现了 java.lang.Seriablizable 和 java.lang.Comparable 两个接口。因此,enum不能继承其他类了。
Enum类中提供的方法
- int compareTo(E o)
该方法用于与指定枚举对象比较顺序,同一个枚举实例只能与相同类型的枚举实例进行比较。如果该枚举对象位于指定枚举对象之后,则返回正整数;如果该枚举对象位于指定枚举对象之前,则返回负整数,否则返回零。
- String name()
返回此枚举实例的名称,这个名称就是定义枚举类时列出的所有枚举值之一。与此方法相比,大多数程序员应该优先考虑使用toString()方法,因为toString()方法返回更加用户友好的名称。
- String toString()
返回枚举常量的名称,与name方法相似,但toString()方法更常用。
- int ordinal()
返回枚举值在枚举类中的索引值(就是枚举值在枚举声明中的位置,第一个枚举值的索引值为零)。
- public static
> T valueOf(Class enumType,String name)
这是一个静态方法,用于返回指定枚举类中指定名称的枚举值。名称必须与在该枚举类中声明枚举值时所用的标识符完全匹配,不允许使用额外的空白字符。
枚举的两个特殊方法
每个枚举类都有两个不用声明就可以调用的static方法,而且这两个方法不是父类中的方法。这又是枚举类特殊的地方。
- static Direction[] values() :返回本类所有枚举常量;
- static Direction valueOf(String name):通过枚举常量的名字返回Direction常量;
- 注意,这个方法与Enum类中的valueOf()方法的参数个数不同。
How
基本语法
使用 enum 关键字定义,枚举项必须位于类的第一行,且多个枚举项之间使用逗号分隔,最后一个枚举项后需要给出分号; 但如果枚举类中只有枚举项(没有构造器、方法、实例变量),那么可以省略分号,但建议不要省略分号。
不能使用new来创建枚举类的对象,因为枚举类中的实例就是类中的枚举项,所以在类外只能使用类名.枚举项。
- 注意:在SpringMVC中,枚举可以通过枚举名或者index下标进行枚举映射。
示例:
public enum Season{
SPRING("春天"),SUMMER("夏天"),AUTUMN("秋天"),WINTER("冬天");
private String name;
Season(String name){
this.name = name;
}
public String getName(){
return name;
}
}
自定义实现枚举
- 构造方法私有化;
在本类创建几个固定的对象。
public class Season{
//季节名
private String name;
//构造方法私有化
private Season(String name){
this.size = name;
}
//定义枚举变量并持有对象
public static final Season SPRING = new Season("春天");
public static final Season SUMMER = new Season("夏天");
public static final Season AUTUMN = new Season("秋天");
public static final Season WINTER = new Season("冬天");
public String getName(){
return name;
}
}
内部类中使用枚举
public class Test {
enum Color {
RED, GREEN, BLUE;
}
// 执行输出结果
public static void main(String[] args) {
Color c1 = Color.RED;
System.out.println(c1);
}
}
======输出======
RED
枚举定义抽象方法
枚举类中的抽象方法实现,需要枚举类中的每个对象都通过匿名内部类的形式对其进行实现。
enum Color{
RED{
//枚举对象实现抽象方法
public String getColor(){
return "红色";
}
},
GREEN{
//枚举对象实现抽象方法
public String getColor(){
return "绿色";
}
},
BLUE{
//枚举对象实现抽象方法
public String getColor(){
return "蓝色";
}
};
//定义抽象方法
public abstract String getColor();
}
public class Test{
public static void main(String[] args) {
for (Color c:Color.values()){
System.out.print(c.getColor() + "、");
}
}
}
枚举实现接口
类级别的接口实现 ```java public enum Gender implements GenderDesc{ MALE,FEMALE;
@Override public void info(){
System.out.println("啦啦啦啦!");
} }
interface GenderDesc{ void info(); }
- **对象级别的接口实现**
当创建MALE、FEMALE枚举值时,并不是直接创建Gender枚举类的实例,而是相当于创建Gender的匿名子类的实例。
```java
public enum Gender implements GenderDesc{
MALE{
@Override
public void info(){
System.out.println("我是男士!");
}
},
FEMALE{
@Override
public void info(){
System.out.println("我是女士!");
}
};
}
interface GenderDesc{
void info();
}
Why
普通类 vs 枚举
- 枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承了java.lang.Enum类,而不是默认继承Object类,因此枚举类不能显式继承其他父类。其中java.lang.Enum类实现了java.lang.Serializable和java.lang.Comparable两个接口。
- 使用enum定义、非抽象的枚举类默认会使用final修饰。
- 枚举类的构造器只能使用private访问控制符,如果省略了构造器的访问控制符,则默认使用private修饰;如果强制指定访问控制符,则只能指定private修饰符。由于枚举类的所有构造器都是private的,而子类构造器总要调用父类构造器一次,因此枚举类不能派生子类。
枚举类的所有实例必须在枚举类的第一行显式列出,否则这个枚举类永远都不能产生实例。列出这些实例时,系统会自动添加public static final修饰,无须显式添加。
静态常量 vs 枚举
在早期代码中,通过静态常量来实现枚举,例如如下代码:
这种定义方法简单明了,但存在如下几个问题:类型不安全:因为上面的每个季节实际上是一个int整数,因此完全可以把一个季节当成一个int整数使用,例如进行加法运算SEASON_SPRING+SEASON_SUMMER,这样的代码完全正常。
- 没有命名空间:当需要使用季节时,必须在SPRING前使用SEASON_前缀,否则程序可能与其他类中的静态常量混淆。
- 打印输出的意义不明确:当输出某个季节时,例如输出SEASON_SPRING,实际上输出的是1,这个1很难猜测到它代表了春天。
但枚举又确实有存在的意义,因此早期也可采用通过定义类的方式来实现,可以采用如下设计方式:
- 通过private将构造器隐藏起来。
- 把这个类的所有可能实例都使用public static final修饰的类变量来保存。
- 如果有必要,可以提供一些静态方法,允许其他程序根据特定参数来获取与之匹配的实例。
- 使用枚举类可以使程序更加健壮,避免创建对象的随意性。
但通过定义类来实现枚举的代码量比较大,实现起来也比较麻烦,Java从JDK 1.5后就增加了对枚举类的支持。
枚举的实现原理
枚举类型实际上会被Java编译器转换为一个对应的类,这个类继承了Java API中的java.lang.Enum类。Enum类有name和ordinal两个实例变量,在构造方法中需要传递,name()、toString()、ordinal()、compareTo()、equals()方法都是由Enum类根据其实例变量name和ordinal实现的。values和valueOf方法是编译器给每个枚举类型自动添加的。
枚举类
public enum Size{
SMALL,MEDIUM,LARGE
}
普通类
public final class Size extends Enum<Size> {
public static final Size SMALL = new Size("SMALL",0);
public static final Size MEDIUM = new Size("MEDIUM",1);
public static final Size LARGE = new Size("LARGE",2);
private static Size[] VALUES = new Size[]{SMALL, MEDIUM, LARGE};
private Size(String name, int ordinal){
super(name, ordinal);
}
public static Size[] values(){
Size[] values = new Size[VALUES.length];
System.arraycopy(VALUES, 0, values, 0, VALUES.length);
return values;
}
public static Size valueOf(String name){
return Enum.valueOf(Size.class, name);}
}
- 说明
- Size类是final修饰,也就不能被继承;
- Size对象的构造函数是私有的也就不能外部创建;
- 三个枚举值是静态常量保证类不能被修改;
- values方法是编译器添加的,内部有一个values数组持有所有枚举值;
- valueOf方法调用的是父类的方法,额外传递了参数Size.class,表示类的类型信息,父类实际上是回过头来调用values方法,根据name对比得到对应的枚举值的;
- 一般枚举变量会被转换为对应的类变量,在switch语句中,枚举值会被转换为其对应的ordinal值。