Advertisement

【JAVA】枚举类到底是什么?(上)

阅读量:

枚举入门

提一句丢脸的话,在学习Java已有若干年的时间内我对枚举类始终不太熟悉。后来深入学习后才了解到其用途极为广泛。随后我们通过一个具体的实例深入探究了枚举的发展历程与应用价值。

  • 在java没有枚举类的时候,我们可以创建一个普通类来表示性别。
复制代码
    // 1.普通类
    class SexEnum{
    private String s;
    private SexEnum(String s){
        this.s = s;
    }
    }
    // 使用:new SexEnum("男")、new SexEnum("女")
  • 经过简化处理后发现,在表示性别时每次都必须调用new关键字确实非常不方便。因此决定对相关操作进行优化:将所有性别相关的操作统一转换为使用类的静态变量来表示,并且在设计过程中特别强调了对继承链和构造函数的严格控制措施;这些措施的目的在于确保实例不会被外部代码随意破坏掉。
复制代码
    // 2.使用静态变量优化
    final class SexEnum{
    public final static SexEnum MAN = new SexEnum("男人");
    public final static SexEnum WOMAN = new SexEnum("女人");
    private String s;
    private SexEnum(String s){
        this.s = s;
    }
    }
  • 随后, Java 1.5增加了枚举类型的语法,使得表示有限类的对象更加简便。其语法形式为:枚举名+构造函数符号,例如:Enum("男人"), Enum("女人");用逗号分隔,以分号结束。需要注意的是,构造函数也可以省略。
  • 这些对象实际上是枚举类的静态成员变量,它们在后续生成的字节码文件中表现得更加直观。
复制代码
    // 3.性别枚举
    public enum SexEnum {
    MAN("男人"),
    WOMAN("女人");
    private String s;
    
    SexEnum(String s) {
        this.s = s;
    }
    }

总结:可以将枚举类视为普通类别中的一个特殊形式,在这种情况下尤其适用于处理具有有限且固定的实例数量的对象或情况。需要注意的是:枚举类同样是C++中的一个类别,在实现过程中包括但不限于:定义成员函数、静态成员变量以及构造函数等操作都是可行的。然而由于其特殊的声明方式容易让人产生混淆,在进一步研究其二进制码后会发现其实并没有那么复杂。

枚举类字节码

特别提醒:在Java语言中枚举类型(即枚举)其实属于普通类。我们可以通过jad命令对枚举类进行反编译以获取其底层信息

复制代码
    public final class SexEnum extends Enum
    {
    
    public static SexEnum[] values()
    {
        return (SexEnum[])$VALUES.clone();
    }
    
    public static SexEnum valueOf(String name)
    {
        return (SexEnum)Enum.valueOf(jdk8/lang/_enum/SexEnum, name);
    }
    
    private SexEnum(String s1, int i, String s)
    {
        super(s1, i);
        this.s = s;
    }
    
    public static final SexEnum MAN;
    public static final SexEnum WOMAN;
    private String s;
    private static final SexEnum $VALUES[];
    
    static 
    {
        MAN = new SexEnum("MAN", 0, "\u7537\u4EBA"); //男人
        WOMAN = new SexEnum("WOMAN", 1, "\u5973\u4EBA"); //女人
        $VALUES = (new SexEnum[] {
            MAN, WOMAN
        });
    }
    }

拆分要点:

1.枚举类继承了Enum类

通过分析字节码文件得知,在Java语言中,默认情况下枚举类会继承其父类的属性和行为,并采用final修饰符来限制其可继承性。

基于Java单继承机制,枚举类无法允许枚举类继承其他类别;然而由于枚举本身就是一种类别,在设计时可以选择性地实现多个接口。

复制代码
    public final class SexEnum extends Enum{....}

2.全局静态变量以及初始化

JVM负责生成并关联对应WOMAN和MAN对象的全局静态变量,并采用枚举数组进行存储。每个枚举对象的创建都在类加载阶段完成,从而防止了潜在的线程不安全问题。

复制代码
    //静态变量
    public static final SexEnum MAN;
    public static final SexEnum WOMAN;
    private String s;
    private static final SexEnum $VALUES[];
    
    //初始化
    static 
    {
    MAN = new SexEnum("MAN", 0, "\u7537\u4EBA"); //男人
    WOMAN = new SexEnum("WOMAN", 1, "\u5973\u4EBA"); //女人
    $VALUES = (new SexEnum[] {
        MAN, WOMAN
    });
    }

3.构造函数

经过编译后,在Java中定义枚举类时所使用的构造函数相较于其java文件版本多出两个参数:一个是字符串类型的一个int索引值。其中字符串与所引用的枚举变量名称保持一致,而int索引值则与实际创建该枚举实例的具体顺序相匹配。这些新增的参数其作用在于引导其调用枚举类父类所具有的构造函数。为了进一步了解这些细节我们建议深入研究Enum类的核心代码结构。

复制代码
    private SexEnum(String s1, int i, String s)
    {
    super(s1, i);
    this.s = s;
    }

Enum类源码

概述

该枚举类包含两个参数:名称和序号(根据定义的顺序)。在构造函数中传递这两个参数即可。同时实现了toString方法以返回名称,并认为这种设计更加直观。

复制代码
    public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {
    private final String name;  
    
    public final String name() {
        return name;
    }
    
    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;
    }
    
    public final boolean equals(Object other) {
        return this==other;
    }
    
    public final int hashCode() {
        return super.hashCode();
    }
    
    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;
    }
    
    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }
    
    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);
    }
    
    /** * enum classes cannot have finalize methods.
     */
    protected final void finalize() { }
    
    /** * prevent default deserialization
     */
    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");
    }
    }

要点

Enum类有以下要点:

  • 禁止对Enum类进行克隆(Cloneable),任何调用clone()方法的行为都会导致直接抛出异常并确保枚举类是单例。
  • 为了避免反序列化构造对象的情况发生并违反单例设计原则,请避免调用readObject()方法。
  • 该实现已成功实现了Comparable接口,并采用了基于字节码进行比较的方式作为初步比较策略,在最终比较时则会通过ordinal属性进行最终比较。
  • valueOf()方法基于类型和名称实现了功能,并能够生成对应的Enum实例作为返回结果。

枚举类特点总结

枚举类就是普通类,只是写法有点花里胡哨

枚举类不允许被继承,因为已经隐式继承Enum类

枚举类中的实例遵循单例模式,并且在静态代码块内进行实例化操作时会立即生成。这表明该机制能够确保线程间的互斥性与安全性。

该枚实现支持序列化编码功能,并包含读取对象相关的方法readObject()。由于继承自Enum类,在编写过程中特别考虑到了保护对象实例不被重复编码的问题。具体来说,在这一设计下,默认情况下所有基于该枚的对象实例都被正确解码以保证系统的稳定性和数据完整性。

枚举类的一些特性,用于单例模式特别友好,这里先占坑。

全部评论 (0)

还没有任何评论哟~