1 枚举
枚举是指将变量的值一一列出来,变量的值只限于列举出来的值的范围内。举例:一周只有7天,一年只有12个月等。
回想单例设计模式:单例类是一个类只有一个实例
那么多例类就是一个类有多个实例,但不是无限个数的实例,而是有限个数的实例,这也叫枚举类。
1.1 特性
enum 与 class、interface 具有相同地位; 可以继承多个接口; 可以拥有构造器、成员方法、成员变量;
枚举类与普通类不同之处
默认继承 java.lang.Enum 类,所以不能继承其他父类;其中 java.lang.Enum 类实现了 java.lang.Serializable 和 java.lang.Comparable 接口;
使用 enum 定义,默认使用 final 修饰,因此不能派生子类;
构造器默认使用 private 修饰,且只能使用 private 修饰;
枚举类所有实例必须在第一行给出,默认添加 public static final 修饰,否则无法产生实例。
1.2 传统的自定义枚举类
1.2.1 第一种
public class Direction {
public static final Direction FRONT = new Direction();
public static final Direction BEHIND = new Direction();
public static final Direction LEFT = new Direction();
public static final Direction RIGHT = new Direction();
private Direction() {
}
}
1.2.2 第二种
public class Direction2 {
public static final Direction2 FRONT = new Direction2();
public static final Direction2 BEHIND = new Direction2("后");
public static final Direction2 LEFT = new Direction2("左");
public static final Direction2 RIGHT = new Direction2("右");
private Direction2() {
}
/**
* 加入一个成员变量
*/
private String name;
/**
* 加入有参构造器
*
* @param name
*/
private Direction2(String name) {
this.name = name;
}
/**
* 加入一个方法
*
* @return
*/
public String getName() {
return name;
}
}
1.2.3 第三种
public abstract class Direction3 {
public static final Direction3 FRONT = new Direction3("前") {
@Override
public void show() {
System.out.println("我是前面");
}
};
public static final Direction3 BEHIND = new Direction3("后") {
@Override
public void show() {
System.out.println("我是后面");
}
};
public static final Direction3 LEFT = new Direction3("左") {
@Override
public void show() {
System.out.println("我是左面");
}
};
public static final Direction3 RIGHT = new Direction3("右") {
@Override
public void show() {
System.out.println("我是右面");
}
};
/**
* 加入一个抽象方法.在定义枚举时,必须实现
*/
public abstract void show();
private String name;
private Direction3(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
1.2.4 测试
/**
* 枚举测试
*/
public class DirectionTest {
/**
* 测试自定义枚举1
*/
@Test
public void test1() {
Direction sbehind = Direction.BEHIND;
}
/**
* 测试自定义枚举2
*/
@Test
public void test2() {
//测试2
Direction2 behind = Direction2.BEHIND;
//后
System.out.println(behind.getName());
}
/**
* 测试自定义枚举3
*/
@Test
public void test3() {
//测试3
Direction3 d2 = Direction3.FRONT;
System.out.println(d2.getName());
d2.show();
}
}
1.3 JDK提供的枚举类
我们发现发现自己定义一个枚举类,比较麻烦,所以,JDK1.5开始就提供了枚举类供我们使用。
格式是:只有枚举项的枚举类
public enum 枚举类名 { 枚举项1,枚举项2,枚举项3…; }
1.3.1 第一种
public enum DirectionEnum1 {
/**
* 同第一种自定义枚举,不过构造器默认私有,成员默认public static final修饰,更加方便
*/
FRONT, BEHIND, LEFT, RIGHT
}
1.3.2 第二种
public enum DirectionEnum2 {
/**
* 同第二种自定义枚举,构造器默认私有,成员默认public static final修饰
*/
FRONT("前"), BEHIND("后"), LEFT("左"), RIGHT("右");
/**
* 可以有参数
*/
private String name;
/**
* 这里构造函数实际上默认是私有的
*
* @param name
*/
DirectionEnum2(String name) {
this.name = name;
}
/**
* 可以有方法
*
* @return
*/
public String getName() {
return name;
}
}
1.3.3 第三种
public enum DirectionEnum3 {
/**
* 同第三种自定义枚举,构造器默认私有,成员默认public static final修饰
* 抽象方法在枚举实例中必须实现
*/
FRONT("前") {
@Override
public void show() {
System.out.println("前面");
}
}, BEHIND("后") {
@Override
public void show() {
System.out.println("后面");
}
}, LEFT("左") {
@Override
public void show() {
System.out.println("左面");
}
}, RIGHT("右") {
@Override
public void show() {
System.out.println("右面");
}
};
private String name;
/**
* 这里构造函数实际上默认是私有的
*
* @param name
*/
DirectionEnum3(String name) {
this.name = name;
}
public String getName() {
return name;
}
/**
* 可以有抽象方法
*/
public abstract void show();
}
1.3.4 测试
/**
* 测试enum枚举1
*/
@Test
public void test4() {
//测试enum1
DirectionEnum1 front = DirectionEnum1.FRONT;
}
/**
* 测试enum枚举2
*/
@Test
public void test5() {
//测试enum2
DirectionEnum2 front = DirectionEnum2.FRONT;
System.out.println(front.getName());
}
/**
* 测试enum枚举3
*/
@Test
public void test6() {
//测试enum3
DirectionEnum3 front = DirectionEnum3.FRONT;
System.out.println(front.getName());
front.show();
}
1.4 注意事项
- 定义枚举类要用关键字enum
- 所有枚举类都是Enum的子类
- 枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其他的东西,这个分号就不能省略。建议不要省略
- 枚举类可以有带参构造器,但必须是private的,它默认的也是private的,同时带参枚举项的用法比较特殊:枚举项(实参);
- 枚举类也可以有抽象方法,但是枚举项必须重写该方法
- 枚举在switch语句中的使用(jdk1.5后),案例在下面。
- 枚举类型对象之间的值比较,是可以使用==,直接来比较值,是否相等的,不是必须使用equals方法的。因为枚举类Enum已经重写了equals方法
public final boolean equals(Object other) {
return this==other;
}
1.4.1 Switch中的枚举
Switch之前只能判断byte 、short、 char、 int;在jdk1.5后支持判断enum枚举;在jdk1.7后支持判断String。
Case后直接放枚举选项而不是通过类名.调用。
@Test
public void test7() {
DirectionEnum1 de = DirectionEnum1.BEHIND;
// de = DirectionEnum1.FRONT;
switch (de) {
case FRONT:
System.out.println("你选择了前");
break;
case BEHIND:
System.out.println("你选择了后");
break;
case LEFT:
System.out.println("你选择了左");
break;
case RIGHT:
System.out.println("你选择了右");
break;
default:
}
}
1.5 常用API方法
public abstract class Enum<E extends Enum> extends Object
implements Comparable, Serializable 位于java.lang.Enum包,JDK1.4.
是所有 Java 语言枚举类型的公共基本类。
1.5.1 public final int compareTo(E o)
比较此枚举与指定对象的顺序。在该对象小于、等于或大于指定对象时,分别返回负整数、零或正整数。 枚举常量只能与相同枚举类型的其他枚举常量进行比较。该方法实现的自然顺序就是声明常量的顺序。
案例:下列枚举声明顺序为: FRONT, BEHIND, LEFT, RIGHT
public class EnumApi {
@Test
public void test1() {
DirectionEnum1 front = DirectionEnum1.FRONT;
DirectionEnum1 behind = DirectionEnum1.BEHIND;
DirectionEnum1 left = DirectionEnum1.LEFT;
DirectionEnum1 right = DirectionEnum1.RIGHT;
System.out.println(front.compareTo(front)); //0
System.out.println(behind.compareTo(front)); //1
System.out.println(left.compareTo(front)); //2
System.out.println(right.compareTo(front)); //3
System.out.println(behind.compareTo(right)); //-2
}
}
1.5.2 public final String name()
返回此枚举常量的名称,在其枚举声明中对其进行声明。 与此方法相比,大多数程序员应该优先考虑使用 toString() 方法,因为 toString 方法返回更加用户友好的名称。该方法主要设计用于特殊情形,其正确性取决于获取正确的名称,其名称不会随版本的改变而改变。
System.out.println(front.name()); //FRONT
System.out.println(behind.name()); //BEHIND
System.out.println(left.name()); //LEFT
System.out.println(right.name()); //RIGHT
1.5.3 其他方法
public final int ordinal()
返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。 大多数程序员不会使用此方法。它被设计用于复杂的基于枚举的数据结构,比如 EnumSet 和 EnumMap。
以上枚举的常量序数为:0,1,2,3
public String toString()
返回枚举常量的名称,它包含在声明中。可以重写此方法。
public static <T extends Enum> T valueOf(Class enumType, String name)
返回带指定名称的指定枚举类型的枚举常量。名称必须与在此类型中声明枚举常量所用的标识符完全匹配。(不允许使用额外的空白字符。)
DirectionEnum2 behind1 =
DirectionEnum2.valueOf(DirectionEnum2.class, "BEHIND");
System.out.println(behind1.getName()); //后
static values()
此方法虽然在JDK文档中查找不到,但每个枚举类都具有该方法,它遍历枚举类的所有枚举值非常方便。这个方法是在编译其间虚拟机加的。
DirectionEnum2[] values = DirectionEnum2.values();
for (DirectionEnum2 value : values) {
System.out.print(value.getName()); //前 后 左 右
}
1.6 原理
枚举本质上是通过普通的类来实现的,只是编译器为我们进行了处理。每个枚举类型都继承自java.lang.Enum,并自动添加了values和valueOf方法。
而每个枚举常量是一个静态常量字段,使用内部类实现,该内部类继承了枚举类。所有枚举常量都通过静态代码块来进行初始化,即在类加载期间就初始化。
另外通过继承的Enum类中把clone、readObject、writeObject这三个方法定义为final的,同时实现是抛出相应的异常。这样保证了每个枚举类型及枚举常量都是不可变的。可以利用枚举的这两个特性来实现线程安全的单例。
反编译后的枚举类:
public final class DirectionEnum1 extends Enum
{
public static final DirectionEnum1 FRONT;
public static final DirectionEnum1 BEHIND;
public static final DirectionEnum1 LEFT;
public static final DirectionEnum1 RIGHT;
private static final DirectionEnum1 $VALUES[];
public static DirectionEnum1[] values()
{
return (DirectionEnum1[])$VALUES.clone();
}
public static DirectionEnum1 valueOf(String name)
{
return (DirectionEnum1)Enum.valueOf(com/ikang/enumm/DirectionEnum1, name);
}
private DirectionEnum1(String s, int i)
{
super(s, i);
}
static
{
FRONT = new DirectionEnum1("FRONT", 0);
BEHIND = new DirectionEnum1("BEHIND", 1);
LEFT = new DirectionEnum1("LEFT", 2);
RIGHT = new DirectionEnum1("RIGHT", 3);
$VALUES = (new DirectionEnum1[] {
FRONT, BEHIND, LEFT, RIGHT
});
}
}
继承的Enum类的方法
/**
* 防止反序列化,序列化时需要使用 ObjectOutputStream 的 writeObject() 将对象转换为字节流并输出。而 writeObject() 方法在传入的对象存在 writeObject() 的时候会去反射调用该对象的 writeObject() 来实现序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理类似。
*/
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
//防止克隆
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}