java.lang包—枚举类Enum

原文作者:山高我为

原文地址:java enum的用法详解

目录

一、enum关键字

二、Enum类源码

三、疑问

四、Enum常见用法


一、enum关键字

enum关键字是在Java1.5也就是Java SE5之后引入的一个新特性:它通过关键字enum来定义一个枚举类,这个被定义的枚举类继承Enum类,这个枚举类算是一种特殊类,它同样能像其他普通类一样拥有构造器、方法,也能够实现接口,但是它不能再继承其他别的类,因为它的直接父类是Enum类,并且因为它默认的修饰符有final的存在,因此它无法直接派生出其他子类,除非将其使用abstract修饰。

按照《Java编程思想》中的原话来说:关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件来使用。

在枚举类出现之前Java是将常量放在接口或是放在普通类当中,然后使用public、static、final去修饰定义的常量,如下两个例子:

public interface Constants2 {
    public static final int CONSTANT_1 = 1;
    public static final int CONSTANT_2 = 2;
    public static final int CONSTANT_3 = 3;
}


public class Constants {
    public static final int CONSTANT_1 = 1;
    public static final int CONSTANT_2 = 2;
    public static final int CONSTANT_3 = 3;
}

在枚举类型出现之后,就可以使用枚举类型来定义常量,这些枚举类型成员_1、_2、_3都默认被public、static、final修饰,语法如下:

public enum Constants {
    CONSTANT_1,
    CONSTANT_2,
    CONSTANT_3
}

 

但是Java枚举类型输出其常量的时候不像C /C++的枚举那样是数字,输出的是其常量名,如果需要输出其类型成员声明时数字次序的话,需要调用ordinal()方法:

public enum Singleton2 {
    SHERLOCK,
    WASTON;
}

class Main{
    public static void main(String[] args) {
        System.out.println(Singleton2.SHERLOCK);
        System.out.println(Singleton2.WASTON);
        System.out.println(Singleton2.SHERLOCK.ordinal());
        System.out.println(Singleton2.WASTON.ordinal());
    }
}

//输出结果:
//SHERLOCK
//WASTON
//0
//1

 

二、Enum类源码

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
    /**
     * 枚举常量的名称
     * 使用toString方法访问此字段。
     */
    private final String name;
    /**
     * 返回此枚举常量的名称,与其枚举声明中声明的完全相同.
     * 大多数程序员应优先使用toString方法,因为toString方法可能会返回一个更加友好的名称。
     * 此方法主要用于特殊情况,其中正确性取决于获取确切名称,不会因发行版本而异。
     */
    public final String name() {
        return name;
    }
    /**
     * 枚举常量的序数(它指的是在枚举声明中的位置,其中初始常量的序数为零)。
     * 大多数程序员都不会使用这个字段。 它设计用于复杂的基于枚举型的数据结构,例如EnumSet,EnumMap。
	 */
	private final int ordinal;
	 /**
     * 返回枚举常量的序数
     */
    public final int ordinal() {
        return ordinal;
    }
     /**
     * 唯一的构造函数。 程序员无法调用此构造函数。它由编译器发出的代码用于响应枚举类型声明
     */
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }
    /**
     * 返回枚举常量的名称。虽然没有覆盖的必要性,但该方法允许进行覆盖。
     * 当存在需要更“友好”的字符串形式时,枚举类型类应该重写此方法。
     */
    public String toString() {
        return name;
    }
    /**
     * 如果指定的对象等于此枚举常量,则返回true。
     * 【注意】此处比较形式是通过“==”进行,也即是枚举类之间可以通过 == 进行比较
     */
    public final boolean equals(Object other) {
        return this==other;
    }
    /**
     * 返回此枚举常量的哈希码
     */
    public final int hashCode() {
        return super.hashCode();
    }
    /**
     * 抛出CloneNotSupportedException异常. 
     * 这能保证了枚举常量类永远不会被克隆,从而保证其为”单例”状态。
     */
    protected final Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

/**
     * 将此枚举与指定的对象进行比较以进行排序. 
     * 返回一个负整数,零或正整数,因为此对象小于,等于或大于指定的对象,枚举常量只能与其他具有相同枚举类型的枚举常量相相比较.
     * 此方法实现的自然顺序是声明常量的顺序
     */
    public final int compareTo(E o) {
        Enum<?> other = (Enum<?>)o;
        Enum<E> self = this;
        if (self.getClass() != other.getClass() && // optimization
            self.getDeclaringClass() != other.getDeclaringClass())
            throw new ClassCastException();
        return self.ordinal - other.ordinal;
    }

    /**
     * 返回与此枚举常量的枚举类型对应的Class对象. 
     * 当且仅当e1.getDeclaringClass()== e2.getDeclaringClass()时, 两个枚举常量e1和e2属于相同的枚举类型。
     */
    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }

    /**
     * 返回指定枚举类型的枚举常量指定的名称.  
     * 名称必须与声明此类型的枚举常量使用的标识符完全匹配(不允许使用无关的空格字符.)
     * 请注意,对于特定的枚举类型T,可以使用该枚举上隐式声明的valueOf(String)方法代替此方法从名称映射到相应的枚举常量。 
     * 枚举类型的所有常量都可以通过调用该类型的隐式方法 values()方法来获得。
     *
     * @param <T> 返回常量的枚举类型
     * @param  枚举常量类型enumType
     * @param  要返回的枚举常量的名称name
     * @return 返回具有指定名称和指定枚举类型的枚举常量
     * @throws 如果指定的枚举类型没有具有指定名称的常量,或者指定的类对象不表示枚举类型,抛出IllegalArgumentException 异常
     * @throws 如果enumType或者code name为null,NullPointerException异常
     */
    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }

    /**
     * 枚举类不能有finalize方法
     */
    protected final void finalize() { }

    /**
     * 无法反序列化枚举
     */
    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");
    }    
}

三、疑问

1、为什么说enum本质是一个继承了Enum类的class?

Java语法就是这么规定的,还能为啥

2、枚举声明为什么是enum不是class,这样做的意图是什么?

3、枚举允许继承类吗?可以被别人结成么?

枚举不允许继承类。Jvm在生成枚举时已经继承了Enum类,由于Java语言是单继承,不支持再继承额外的类(唯一的继承名额被Jvm用了)。也不可以继承枚举。因为Jvm在生成枚举类时,将它声明为final。

4、枚举可以用等号比较吗?

枚举可以用等号比较。Jvm会为每个枚举实例对应生成一个类对象,这个类对象是用public static final修饰的,在static代码块中初始化,是一个单例。

5、为什么使用枚举代替常量类?

在我们平常的开发中,为表示同种类型的不同种类,经常的做法是声明一组具名的int常量来表示,每个类型成员一个常量,如:

public static final int DAY_MONDAY = 1;
public static final int DAY_TUESDAY = 2;
public static final int DAY_WEDNESDAY = 3;
public static final int DAY_THURSDAY = 4;
public static final int DAY_FRIDAY = 5;
public static final int DAY_SATURDAY = 6;
public static final int DAY_SUNDAY = 7;

public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;

这种方法称做 int枚举模式,这种方式在安全性和使用方便性方面没有任何帮助。

a、将day传到想要orange的方法中,编译器不会警告,执行也不会出现错误;
b、用==操作符将day与orange比较,编译器不会警告,执行也不会出现错误;
c、int枚举是编译时常量,被编译到客户端中,如果枚举常量关联的int发生变化,客户端必须重新编译,如果没有重新编译,程序仍可以运行,但行为就确定了,如DAY_MONDAY关联的常量不再是1,而是0。
d、将int枚举常量翻译成可打印的字符串很麻烦
e、如果想要遍历一个组中的所有int 枚举常量,甚至获得int枚举组的大小,这种实现没有啥方便可靠的方法。

因此,推荐使用枚举类型来代替这种int枚举常量:

public enum DAY {DAY_MONDAY, DAY_TUESDAY, DAY_WEDNESDAY,
 				DAY_THURSDAY, DAY_FRIDAY, DAY_SATURDAY, DAY_SUNDAY}

public enum ORANGE {ORANGE_NAVEL, ORANGE_TEMPLE, ORANGE_BLOOD}

这种枚举类型,提供了编译时的类型安全检查,如果声明了一个参数的类型为DAY,就可以保证,被传到该参数上的任何非null的对象引用一定属于其他有效值中的一个,试图传递类型错误的值时,会导致编译时错误,就像试图将某种枚举类型的表达式赋给另一种枚举类型的变量,或者试图利用==操作符比较不同枚举类型的值一样。同时包含同名常量的多个枚举类型可以共存,因为每个类型有自己的命名空间,增加或重新排列枚举类型的常量,无需重新编译客户端的代码。如果想获取类型对应的字符串,直接通过toString方法即可。

枚举类型除了完善了int枚举模式的不足之处外,枚举类型还允许添加任意的方法和域,并实现任意的接口。这个有什么用途呢?

a、能够将数据与它的常量关联起来,例如能够返回水果颜色或者水果图片的方法,对于我们的ORANGE类型来说可能就很有好处;
b、你可使用适当的方法来增强枚举类型,枚举类型可以先作为枚举常量的一个简单集合,随着时间的推移在演变成为全功能的抽象。

另外,当你想要每增加一种枚举常量时,需要强制选择一种对应的策略,可以使用枚举提供的策略枚举(strategy enum) 的方式

4、究竟是枚举的性能好,还是常量类好?

5、为什么枚举要实现Comparable接口?

6、为什么枚举要实现Serializable接口?

7、为什么枚举支持泛型?

8、枚举的底层数据结构是数组还是链表?

9、为什么枚举类型实例化就能访?比如如下代码为什么不报错

public class Traffic{
    public enum Light{GREEN,YELLOW,RED}
}
Traffic.Light state = Traffic.Light.GREEN;

Java枚举类型都是静态常量,隐式的用static final修饰过。确切的说,Java枚举类型是“静态常量”,这里面包含了两层意思:

  • 枚举型中的实例隐式地用static final修饰过。
  • 枚举型作为某个类中的成员字段也隐式的用static final修饰过

还是你上面这个代码,反编译一下,你就能看到--编译器背着你偷偷做了哪些手脚

  • 首先,枚举型Light是个实实在在的类。集成自基类Enum<Light>。然后在你不知情的情况下,偷偷加了static final修饰词。然后3个枚举实例 GREEN,YELLOW,RED也确确实实是light的实例,然后前面加了static final。
  • 然后构造器也被偷偷的阉割成private。这种实例控制手段,是不是在单例模式里面见过,所以枚举也是实现单例器的一种方法。
  • 然后编译器还偷偷的告诉Light[]数组,一个values()方法,一个valueO()f方法,这个values在Enum文档里面找不到,如果在Enum里面定义一个相关方法,你还会看到一个匿名内部类

反编译的结果如下:

总之,Java的Enum枚举类型就是一个大大的“语法糖”。明明是一个完整的类,但只向用户暴露几个常态变量,隐藏掉大部分实现细节。

上述文字引用自知乎问答:Java 枚举型为什么是静态的,以及是怎么实现的?胖君的回答

10、是不是所有的枚举都默认是静态的?

通过可问题5,可知所有的枚举都默认是静态的

 

11、枚举有哪些应用场景?

12、枚举是如何实现单例的?

public enum Singleton2 {
    SHERLOCK
}

class Main{
    public static void main(String[] args) {
        Singleton2 sherlock = Singleton2.SHERLOCK;
        Singleton2 sherlock1 = Singleton2.SHERLOCK;
        System.out.println(sherlock == Singleton2.SHERLOCK);
        System.out.println(sherlock == sherlock1);
        System.out.println(Singleton2.SHERLOCK.getDeclaringClass());
    }
}

输出结果:
true
true
class com.sherlock.singleton.Singleton2

四、Enum常见用法

用法一:常量

在JDK1.5 之前,我们定义常量都是: public static fianl.... 。现在好了,有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法。

public enum Color {  
    RED, GREEN, BLANK, YELLOW  
}

用法二:switch

JDK1.6之前的switch语句只支持int,char,enum类型,使用枚举,能让我们的代码可读性更强。

enum Signal {
        GREEN, YELLOW, RED
    }

public class TrafficLight {
    Signal color = Signal.RED;

    public void change() {
        switch (color) {
            case RED:
                color = Signal.GREEN;
                break;
            case YELLOW:
                color = Signal.RED;
                break;
            case GREEN:
                color = Signal.YELLOW;
                break;
        }
    }
}

 

用法三:向枚举中添加新方法

如果打算自定义自己的方法,那么必须在enum实例序列的最后添加一个分号。而且 Java 要求必须先定义 enum 实例。

 

public enum Color {
    RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
    // 成员变量
    private String name;
    private int index;

    // 构造方法
    private Color(String name, int index) {
        this.name = name;
        this.index = index;
    }

    // 普通方法
    public static String getName(int index) {
        for (Color c : Color.values()) {
            if (c.getIndex() == index) {
                return c.name;
            }
        }
        return null;
    }

    // get set 方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }
}

 

用法四:覆盖枚举的方法

下面给出一个toString()方法覆盖的例子。

public class Test {
    public enum Color {
        RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
        // 成员变量
        private String name;
        private int index;

        // 构造方法
        private Color(String name, int index) {
            this.name = name;
            this.index = index;
        }

        // 覆盖方法
        @Override
        public String toString() {
            return this.index + "_" + this.name;
        }
    }

    public static void main(String[] args) {
        System.out.println(Color.RED.toString());
    }
}

用法五:实现接口

所有的枚举都继承自java.lang.Enum类。由于Java 不支持多继承,所以枚举对象不能再继承其他类。

 

public interface Behaviour {
    void print();
    String getInfo();
}

public enum Color implements Behaviour {
    RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
    // 成员变量
    private String name;
    private int index;

    // 构造方法
    private Color(String name, int index) {
        this.name = name;
        this.index = index;
    }

    // 接口方法
    @Override
    public String getInfo() {
        return this.name;
    }
    // 接口方法
    @Override
    public void print() {
        System.out.println(this.index + ":" + this.name);
    }
}

 

用法六:使用接口组织枚举 

 

public interface Food {
    enum Coffee implements Food {
        BLACK_COFFEE, DECAF_COFFEE, LATTE, CAPPUCCINO
    }

    enum Dessert implements Food {
        FRUIT, CAKE, GELATO
    }
}

 

用法七:关于枚举集合的使用

java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复;EnumMap中的 key是enum类型,而value则可以是任意类型。关于这个两个集合的使用就不在这里赘述,可以参考JDK文档。 完整示例代码

枚举类型的完整演示代码如下:

 

public class LightTest {

    // 1.定义枚举类型
    public enum Light {
        // 利用构造函数传参
        RED(1), GREEN(3), YELLOW(2);
        // 定义私有变量
        private int nCode;
        // 构造函数,枚举类型只能为私有
        private Light(int _nCode) {
            this.nCode = _nCode;
        }

        @Override
        public String toString() {
            return String.valueOf(this.nCode);
        }
    }


    public static void main(String[] args) {
        // 1.遍历枚举类型
        System.out.println("演示枚举类型的遍历 ......");
        testTraversalEnum();
        // 2.演示EnumMap对象的使用
        System.out.println("演示EnmuMap对象的使用和遍历.....");
        testEnumMap();
        // 3.演示EnmuSet的使用
        System.out.println("演示EnmuSet对象的使用和遍历.....");
        testEnumSet();
    }

    /**
     * 
     * 演示枚举类型的遍历
     */

    private static void testTraversalEnum() {
        Light[] allLight = Light.values();
        for (Light aLight : allLight) {
            System.out.println("当前灯name:" + aLight.name());
            System.out.println("当前灯ordinal:" + aLight.ordinal());
            System.out.println("当前灯:" + aLight);
        }
    }

    /**
     * 
     * 演示EnumMap的使用,EnumMap跟HashMap的使用差不多,只不过key要是枚举类型
     */
    private static void testEnumMap() {
        // 1.演示定义EnumMap对象,EnumMap对象的构造函数需要参数传入,默认是key的类的类型
        EnumMap<Light, String> currEnumMap = new EnumMap<Light, String>(Light.class);
        currEnumMap.put(Light.RED, "红灯");
        currEnumMap.put(Light.GREEN, "绿灯");
        currEnumMap.put(Light.YELLOW, "黄灯");
        // 2.遍历对象
        for (Light aLight : Light.values()) {
            System.out.println("[key=" + aLight.name() + ",value="+ currEnumMap.get(aLight) + "]");
        }
    }

    /**
     * 
     * 演示EnumSet如何使用,EnumSet是一个抽象类,获取一个类型的枚举类型内容<BR/>
     * 
     * 可以使用allOf方法
     */
    private static void testEnumSet() {
        EnumSet<Light> currEnumSet = EnumSet.allOf(Light.class);
        for (Light aLightSetElement : currEnumSet) {
            System.out.println("当前EnumSet中数据为:" + aLightSetElement);
        }
    }
}

 

执行结果如下:

演示枚举类型的遍历 ......
当前灯name:RED
当前灯ordinal:0
当前灯:1
当前灯name:GREEN
当前灯ordinal:1
当前灯:3
当前灯name:YELLOW
当前灯ordinal:2
当前灯:2
演示EnmuMap对象的使用和遍历.....
[key=RED,value=红灯]
[key=GREEN,value=绿灯]
[key=YELLOW,value=黄灯]
演示EnmuSet对象的使用和遍历.....
当前EnumSet中数据为:1
当前EnumSet中数据为:3
当前EnumSet中数据为:2

读后有收获可以支付宝请作者喝奶茶 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值