枚举类

1、枚举类概念

枚举类是JDK1.5之后出现的,允许用常量来表示特定的数据片断,而且全部都以类型安全的形式来表示
1.1、枚举类特点

  • 枚举类是一种特殊的Java类,枚举不可被继承
  • 枚举类中声明的每一个枚举值代表枚举类的一个实例对象;
  • 与java普通类一样,在声明枚举类时可以声明属性,方法,构造方法,但是枚举类必须是私有的
  • 枚举可以实现接口或继承抽象方法
  • 在JDK5之后,switch语句,可以接受int,byte,char,short外,还可以接受枚举类型
  • 若枚举类只有一个枚举值,则可以当作单例设计模式
    1.2、枚举类的一些方法
  • values():获得所有的枚举类
  • valueOf(String str):将一个字符串转为枚举类;
    1.3、枚举类基类
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable{}
// 定义枚举类:
public enum Status{
 START()STOP()RUNNING();
}

除了toString方法,其余方法都不可重写。要么是final方法要么是私有方法。
1.4、枚举比较

Java 枚举类比较使用 == 或者 equals()都一样,因为枚举类 Enum 的 equals()方法的默认实现是通过 
== 来比较的。

在Enum中equals和hashCode方法都是final,所以在枚举类中不可实现这两个方法。类似的Enum的
compareTo方法比较的是Enum的ordinal顺序大小;类似的还有Enum的name方法和toString方法一
样都返回的是Enum的name值

2、枚举类本质

枚举类本质是通过普通类来实现的,只是编译器进行了相应的处理,每个枚举类编译之后的字节码实质都是继承自java.lang.Enum的枚举类类型同名普通类.而每个枚举常量实质是一个枚举类型同名普通类的静态常量对象,所有枚举常量都通过静态代码块进行初始化实例赋值.

public enum Status{
 START(),
 STOP(),
 RUNNING();
}
编译之后通过 javap -verbose 查看字节码文件:
.......
public final class Status extends java.lang.Enum<Status>
.......
{
 // 枚举类型值都成了status类型类的静态常量成员属性
 public static final Status start;
 public static final Status stop;      
 public static final Status running;
 // 静态代码块
 static{};
}
  • 所以从某种意义上可以说 JDK 1.5 后引入的枚举类型是上面枚举常量类的代码封装而已
public enum  EnumSingleton {
    INSTANCE {
        @Override
        public void print() {
            System.out.println("Singleton Enum");
        }
    };
    public abstract void print();
    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

通过Jad反编译后,看如下代码

public abstract class EnumSingleton extends Enum{
    public static EnumSingleton[] values(){
        return (EnumSingleton[])$VALUES.clone();
    }
    public static EnumSingleton valueOf(String name){
        return (EnumSingleton)Enum.valueOf(com/blue/fish/design/pattern/creational/singleton/EnumSingleton, name);
    }
    private EnumSingleton(String s, int i){
        super(s, i);
    }
    public abstract void print();
    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
    public static final EnumSingleton INSTANCE;
    private static final EnumSingleton $VALUES[];
    static {
  // 如果枚举类有抽象方法,对应的枚举中会使用匿名内部类来构建枚举
        INSTANCE = new EnumSingleton("INSTANCE", 0) {
            public void print(){
                System.out.println("Singleton Enum");
            }
        };
        $VALUES = (new EnumSingleton[] {
            INSTANCE
        });
    }
}

3、枚举类与常量

3.1、区别

  • 枚举相对于常量类来说定义更简单,其不需要定义枚举值,而常量类中每个常量必须手动添加值.
    枚举作为参数使用时可以避免在编译时避免弱类型错误,而常量类中的常量作为参数使用时无法避免类型错误.
  • 枚举类自动具备内置方法,如 values() 方法可以获得所有值的集合遍历,ordinal
    方法可以获得排序值,compareTo方法可以给予ordinal比较,而常量类不具备这些方法。
  • 枚举的缺点是不能被继承(编译后生成的类是 final class),也不能通过 extends 继承其他类(枚举编译后实质是继承了 Enum 类,java是单继承的)。但是定义的枚举类也通过 implements 实现其他接口;
  • 枚举值定义完毕后除非重构,否则无法做扩展,而常量类可以随意继承.
    3.2、枚举与静态常量内存消耗比
    Java枚举会比静态常量更消耗内存,一般场景下不仅编译后的字节码会比静态常量多,而且运行时也会比静态常量需要更多的内存,不过这个多取决于场景和枚举的规模等等

4、枚举类是如何保证线程安全的

Java 类加载与初始化是 JVM 保证线程安全,而Java enum枚举在编译器编译后的字节码实质是一个 final 类,每个枚举类型是这个 final 类中的一个静态常量属性,其属性初始化是在该final类的static块中进行,而 static的常量属性和代码块都是在类加载时初始化完成的, 所以自然就是 JVM 保证了并发安全;
也就是说,我们定义的一个枚举,在第一次被真正用到的时候,会被虚拟机加载并初始化,而这个初始化过程是线程安全的。解决单例的并发问题,主要解决的就是初始化过程中的线程安全问题

5、枚举与单例模式

除枚举实现的单例模式以外的其他实现方式都有一个比较大的问题是一旦实现了Serializable接口后就不再是单例了,因为每次调用readObject()方法返回的都是一个新创建出来的对象(当然可以通过使用 readResolve() 方法来避免))

  • Java规范中保证了每一个枚举类型及其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上Java做了特殊处理。序列化时 Java 仅仅是将枚举对象的 name 属性输出到结果中,反序列化时则是通过 java.lang.Enum的valueOf方法来根据名字查找枚举对象;同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、
writeReplace和readResolve等方法;
   // remaining cases
if (obj instanceof String) {
 writeString((String) obj, unshared);
} else if (cl.isArray()) {
 writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
 writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
 writeOrdinaryObject(obj, desc, unshared);
} else {
 if (extendedDebugInfo) {
  throw new NotSerializableException(
   cl.getName() + "\n" + debugInfoStack.toString());
 } else {
  throw new NotSerializableException(cl.getName());
 }
}
private void writeEnum(Enum<?> en,ObjectStreamClass desc, boolean unshared) throws IOException {
      bout.writeByte(TC_ENUM);
      ObjectStreamClass sdesc = desc.getSuperDesc();
      writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false);
      handles.assign(unshared ? null : en);
 // 这里是将name属性输出到结果中
      writeString(en.name(), false);
  }    
  • 普通的Java类的反序列化过程中,会通过反射调用类的默认构造函数来初始化对象。所以即使单例中构造函数是私有的,也会被反射给破坏掉。由于反序列化后的对象是重新new出来的,所以破坏了单例;但是枚举的反序列化并不是通过反射实现的,它是通过name去找实例的,所以,也就不会发生由于反序列化导致的单例破坏问题;
  • Java 枚举序列化需要注意的点:
    如果我们枚举被序列化本地持久化了,那我们就不能删除原来枚举类型中定义的任何枚举对象,否则程序在运行过程中反序列化时JVM就会找不到与某个名字对应的枚举对象了,所以我们要尽量避免多枚举对象序列化的使用

6、迭代器和枚举器区别

  • Enumeration 枚举器接口是1.0开始提供,适用于传统类,而Iterator迭代器接口是1.2提供,适用于Collections
  • Enumeration只有两个方法接口,我们只能读取集合的数据而不能对数据进行修改,而Iterator有三个方法接口,除了能读取集合的数据外也能对数据进行删除操作
  • Enumeration不支持fail-fast机制,而Iterator支持fail-fast机制(一种错误检测机制,当多线程对集合进行结构上的改变的操作时就有可能会产生fail-fast机制,譬如ConcurrentModificationException异常)尽量使用Iterator迭代器而不是Enumeration枚举器;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值