Java枚举enum入门

2023年 10月 2日 118.5k 0

枚举

枚举某种程度上也是一种语法糖。枚举主要是代替过多的public static final修饰的全局常量。

枚举快速开始

搞个demo看看:

public enum MyEnum {
    enum1("123");
    String name;
​
    MyEnum(String name) {
        this.name = name;
    }
​
    public static void main(String[] args) {
        System.out.println(MyEnum.enum1.name);
    }
​
}

输出“123”。要了解“语义”,不错的办法是看看字节码,javap搞一下:

透过字节码看语义

public final class entity.MyEnum extends java.lang.Enum {
  public static final entity.MyEnum enum1;
​
  java.lang.String name;
​
  public static entity.MyEnum[] values();
  public static entity.MyEnum valueOf(java.lang.String);
  static {};
    Code:
       0: new           #4                  // class entity/MyEnum
       3: dup
       4: ldc           #11                 // String enum1
       6: iconst_0
       7: ldc           #12                 // String 123
       9: invokespecial #13                 // Method "":(Ljava/lang/String;ILjava/lang/String;)V
      12: putstatic     #9                  // Field enum1:Lentity/MyEnum;
      15: iconst_1
      16: anewarray     #4                  // class entity/MyEnum
      19: dup
      20: iconst_0
      21: getstatic     #9                  // Field enum1:Lentity/MyEnum;
      24: aastore
      25: putstatic     #1                  // Field $VALUES:[Lentity/MyEnum;
      28: return
}

我们发现:我们的枚举被final修饰了,并且继承自Enum,所有的枚举类实例都被public static final修饰,还多了俩方法:values和valueOf,猜一下:是因为继承Enum的原因,还是编译器?另外有一个static代码块。

那我们现在可以知道很多事情了:

枚举与继承

  • 枚举是无法继承的。因为它们已经继承了Enum,而Java是单继承的。
  • 枚举是无法被继承的。因为它们自己被final修饰,因此class类不能继承枚举

当然枚举是可以实现接口的

下面看一下父类Enum

Enum:枚举的父类

public abstract class Enum
        implements Comparable, Serializable {
    // name 就是你Enum实例对象的名字,在上述demo中是enum1
    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;
    }
// ---------------------------------------------
    // 不可clone
    protected final Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
// ---------------------------------------------
    // 重写compareTo方法,默认是根据ordinal比较
    public final int compareTo(E o) {
        Enum other = (Enum)o;
        Enum self = this;
        return self.ordinal - other.ordinal;
    }
    public static  T valueOf(Class enumType,String name) {
        T result = enumType.enumConstantDirectory().get(name);
        return result;
    }
// ---------------限制序列化------------------------------
    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也可以看出枚举有如下特点:

  • 限制了序列化
  • 每个枚举实例都有自己的名字和索引,索引从0开始

静态代码块

观察static字节码,会生成一个String实例对象并初始化enum1实例。

那枚举能自定义static代码块吗?依然可以,在上述初始化流程结束之后,再执行你自定义的static代码块。

编译器提供的两个value方法

这两个方法都是编译器提供的,因此你在enum中找不到values方法。虽然Enum有valueOf方法,但编译器提供的valueOf方法内部会调用Enum的valueOf方法。

  • values()方法的作用就是获取枚举类中的所有变量,并作为数组返回
  • valueOf(String name)方法的作用类似根据名称获取枚举变量
  • MyEnum[] myEnums = MyEnum.values();
    MyEnum myEnum = MyEnum.valueOf("enum1")
    

    valueOf(Class enumType, String name)方法是根据枚举类的Class对象和枚举名称获取枚举常量,这个方法由Enum类提供,本质是对map的get,是一张HashMap,key为String,存储在Class对象中。而valueOf(String name)是编译器生成的

    枚举的构造方法

    默认就是private修饰的,也不能被别的关键字修饰。

    因为枚举要生成的对象已经被确定了,不允许由外部再生成实例对象。

    枚举与反射

    能否通过反射调用枚举的私有构造方法呢?不可以的。

    newInstance方法中有如下判断:

    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    

    因此即便是反射,也无法调用枚举的构造方法。因此该构造方法只有枚举实例初始化时才能调用。

    枚举与JDK序列化

    枚举序列化是由jvm保证的,每一个枚举类型和定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定:在序列化时Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。

    在writeObject中有这样一个判断

    if (obj instanceof Enum) {
            writeEnum((Enum) obj, desc, unshared);
    

    枚举实现单例模式

    一般的单例模式仍然会被反射/序列化破坏,而枚举同时限制了这两个破坏单例的手段。

    public class Singleton {
        // 构造函数私有化
        private Singleton(){
        }   
        public static enum SingletonEnum {
            SINGLETON;
            private Singleton instance = null;
            private SingletonEnum(){
                instance = new Singleton();
            }
            public Singleton getInstance(){
                return instance;
            }
        }
        // 对外提供获取实例实例的方法
        public static User getInstance(){
            return SingletonEnum.SINGLETON.getInstnce();
        }
    }
    

    枚举与class的比较

    枚举更像是一个抽象类,但是:

    • 不允许有继承关系
    • 所有枚举的实例必须定义在枚举中,无法通过别的方式生成

    枚举的抽象方法需要每个实例去实现,demo如下:

    public enum MyEnum {
        enum1{
            @Override
            public String dosth() {
                return null;
            }
        };
    public abstract String dosth();
    

    EnumMap

    EnumMap是枚举的专属map,效率比通常的HashMap更高。

    EnumMap只能接收同一枚举类型的实例作为键值且不能为null,通过ordinal方法(声明枚举对象的顺序的索引)获取枚举key对应的数组下标。

    在使用上EnumMap和HashMap区别不大,只是key全部为枚举类型的实例,看下get方法:

    public V get(Object key) {
        // 保证key是枚举的实例
        return (isValidKey(key) ?
                // 下标为 key 的 ordinal索引值
                unmaskNull(vals[((Enum)key).ordinal()]) : null);
    }
    

    更多的就不介绍了,源码并不复杂,感兴趣的可以自己去看一下。

    当然还有EnumSet,这篇文章的分析很到位了

    参考文档

    Java枚举类型(enum)详解

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论