深入理解Java中的枚举以及案例演示

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 注意事项

  1. 定义枚举类要用关键字enum
  2. 所有枚举类都是Enum的子类
  3. 枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其他的东西,这个分号就不能省略。建议不要省略
  4. 枚举类可以有带参构造器,但必须是private的,它默认的也是private的,同时带参枚举项的用法比较特殊:枚举项(实参);
  5. 枚举类也可以有抽象方法,但是枚举项必须重写该方法
  6. 枚举在switch语句中的使用(jdk1.5后),案例在下面。
  7. 枚举类型对象之间的值比较,是可以使用==,直接来比较值,是否相等的,不是必须使用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();
}

发布了58 篇原创文章 · 获赞 105 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43767015/article/details/105137780
今日推荐