19、java中枚举

枚举是什么?

枚举就是将一个有限集合中的所有元素列举出来,在java中使用可以使用enum关键字来声明一个枚举类。

为什么使用枚举?

之前当用到一些常量时,便临时声明一个,这样使得代码看起来很乱,这里一个常量,那里一个常量,所以可以想着把一些具有关联性的常量封装到一个类中,类中的每一个变量使用public static final来进行修饰,这样虽然解决了问题,但是不是很优雅,而且还是有点麻烦,这个时候便可以使用枚举,直接使用enum关键字声明一个枚举类,直接将常量在枚举类中声明即可,不需显示使用任何修饰符修饰,因为这些问题编译器帮我们做了,使用枚举更加安全、方便、优雅。

怎么使用枚举?

使用enum声明枚举类即可,我感觉有两种使用枚举的方式,一种是使用默认的方式,声明的变量及是枚举值;另一种是想用几个字段拼成一个字段作为枚举值,这两种使用方式如下:

//字面值及是枚举值
public enum EnumTest{
	
	Cancel,
	Waiting,
	Payed;
	
}


---------------------------------------------------------------------------

//使用自定义的构造方法创建枚举类,已达到多个字段拼接成一个字段作为枚举值
public enum EnumTest{
	
	Cancel("0","000"),
	Waiting("1","111"),
	Payed("4","444");
	
	private final String name;
	private final String msg;
	//在这里自定义一个构造方法即可,注意一定要讲构造方法私有化
	private EnumTest(String name,String msg) {
		this.name = name;
		this.msg = msg;
	}
	//重写构造方法
	public String toString() {
		return this.name+"-"+this.msg;
	}
}


-----------------------------------------------------------------------------

//使用枚举类型
public class Test{
	public static void main(String[] args) {
		
		//使用时会发现多出了values方法(未自定义),这个方法可以获取到枚举类型中所有的枚举值
		EnumTest[] values = EnumTest.values();
		for (int i = 0; i < values.length; i++) {
			System.out.println(values[i]);
		}
		
                //使用时会发现多出了valuesOf方法(未自定义),这个方法可以获取指定的枚举值
		EnumTest cancel = EnumTest.valueOf("Cancel");
		System.out.println(cancel);
                
                //直接使用枚举值
		System.out.println(EnumTest.Cancel);
	}
}

枚举类型的使用非常简单,但是肯定会有很多疑惑,接下来根据源码来解析一下枚举的实现原理。

枚举的实现原理:

在枚举类上使用 Ctrl+T 可以看到自定义枚举类的体系架构,它竟然是java.lang.Enum的子类,但是声明时并没有继承这个Enum类,这时复制java.lang.Enum到API中查看,可以看到解释文档这么写:“Enum是所有Java语言枚举类型的公共基类。 有关枚举的更多信息,包括由编译器合成的隐式声明方法的描述,请参见The Java™ Language Specification的第8.9节,当使用枚举类型作为集合的类型或映射中的键的类型时,可以使用专门且高效的set和map实现。”,这里边有几个关键词:公共基类、编译器合成的隐式声明方法、作为集合的类型。根据这几个词可以推断被enum修饰之后,编译器会隐式的去让当前枚举类继承Enum类,并且隐式的声明一些处理的方法。所以这里先看一下Enum类的源码分析:


/**
 * Enum是所有Java语言枚举类型的公共基类,更多关于枚举的信息,包括对编译器合成的隐式声明方法的描述,可以参见java语言标准文档. 
 * 
 * 在将枚举类型用作集合的类型或映射中的键的类型时,可以使用更高效的:java.util.EnumSet和java.util.EnumMap
 */
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
    
    //枚举常量的名称, 正如枚举声明中声明的那样.应该使用toString方法而不是访问这个字段
    private final String name;

    //返回此枚举常量的名称,与在其枚举声明中声明的名称完全相同。
    public final String name() {
        return name;
    }

    //枚举常数的序号(它在枚举声明中的位置,其中初始常数的序号为0)。
    private final int ordinal;

    //返回此枚举常数的序号(其在枚举声明中的位置,其中初始常数的序号为0)。
    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;
    }

    //返回与枚举常量的枚举类型对应的类对象。
    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);
    }

    //枚举类不能有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");
    }
}

再看一下一个简单枚举类编译后的字节码解析(枚举的实现原理在解析中解释了):

//被enum修饰的代码,会被编译成一个继承了Enum类的最终类,所以被enum修饰的枚举类不可被继承也不可继承其他类
public final class com.czp.enumeration.EnumTest extends java.lang.Enum<com.czp.enumeration.EnumTest> {
  //声明的枚举值会声明成EnumTest类型的变量
  public static final com.czp.enumeration.EnumTest Cancel;
  public static final com.czp.enumeration.EnumTest Waiting;
  public static final com.czp.enumeration.EnumTest Payed;

  //使用一个静态代码块进行声明的枚举值的赋值操作
  static {};
	Code:
	//--------------------- 处理三个枚举值 ----------------------------//
	       //创建一个EnumTest对象,并且其引用进栈
	       0: new           #1                  // class com/czp/enumeration/EnumTest
	       //复制栈顶数值,并且复制值进栈 
	       3: dup
	       //将Cancel作为一个String类型值放在栈顶					
	       4: ldc           #14                 // String Cancel  
	       //将0压入栈顶
	       6: iconst_0	
	       //使用Enum中的构造方法 Enum(String name, int ordinal) 创建一个对象,name=Cancel,ordinal=0
	       7: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V  
	       //将构造的对象赋给变量Cancel
	      10: putstatic     #19                 // Field Cancel:Lcom/czp/enumeration/EnumTest;  
	      
	      13: new           #1                  // class com/czp/enumeration/EnumTest
	      16: dup
	      17: ldc           #21                 // String Waiting
	      19: iconst_1
	      20: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
	      23: putstatic     #22                 // Field Waiting:Lcom/czp/enumeration/EnumTest;
	      
	      26: new           #1                  // class com/czp/enumeration/EnumTest
	      29: dup
	      30: ldc           #24                 // String Payed
	      32: iconst_2
	      33: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
	      36: putstatic     #25                 // Field Payed:Lcom/czp/enumeration/EnumTest;

//-------------------  创建数组,将所有枚举值放入到数组中  ---------------------//	     
	      39: iconst_3
	      // 相当于创建一个数组,数组引用入栈,长度为栈顶元素3
	      40: anewarray     #1                  // class com/czp/enumeration/EnumTest 
	      
	      //复制栈顶数值,并且复制值进栈           
	      43: dup
	      //int型常量值0入栈,作为数组下标
	      44: iconst_0
	      //获取到Cancel
	      45: getstatic     #19                 // Field Cancel:Lcom/czp/enumeration/EnumTest; 
	      // 依次取出栈顶元素,根据类型进行数组赋值操作  
	      48: aastore
	   		
	      49: dup
	      50: iconst_1
	      51: getstatic     #22                 // Field Waiting:Lcom/czp/enumeration/EnumTest;
	      54: aastore
	     
	      55: dup
				56: iconst_2
	      57: getstatic     #25                 // Field Payed:Lcom/czp/enumeration/EnumTest;
	      60: aastore
	      
	       //将构建的数组对象赋给VALUES
	      61: putstatic     #27                 // Field ENUM$VALUES:[Lcom/czp/enumeration/EnumTest;
	      64: return //返回值为空
      
      
                //创建一个values方法,用于获取所有枚举值(大致操作就是新弄一个数组,将上边VALUES中数据复制到新数组,返回新数组)
		public static com.czp.enumeration.EnumTest[] values();
		    Code:
		    	 //获取到静态域中数组VALUES
		       0: getstatic     #27                 // Field ENUM$VALUES:[Lcom/czp/enumeration/EnumTest;
		       3: dup
		       4: astore_0    // 该指令的行为类似于astore指令index为0的情况。
		       5: iconst_0
		       6: aload_0			// 当前frame的局部变量数组中下标为0的引用型局部变量进栈
		       7: arraylength //栈顶的数组引用(arrayref)出栈,该数组的长度进栈。
		       8: dup
		       9: istore_1		//将栈顶int型数值存入第一个局部变量
		      10: anewarray     #1                  // class com/czp/enumeration/EnumTest
		      13: dup
		      14: astore_2
		      15: iconst_0
		      16: iload_1		//第二个int型局部变量进栈
		      //调用静态方法
		      17: invokestatic  #35                 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
		      20: aload_2 
		      21: areturn 	//从方法中返回一个对象的引用
		
		  //根据枚举值的字面值获取到对应的枚举元素
		  public static com.czp.enumeration.EnumTest valueOf(java.lang.String);
		    Code:
		       0: ldc           #1                  // class com/czp/enumeration/EnumTest
		       2: aload_0
		       3: invokestatic  #43                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
		       //类型转换检查,如果该检查未通过将会抛出ClassCastException异常
		       6: checkcast     #1                  // class com/czp/enumeration/EnumTest
		       9: areturn      

可以看到枚举类型被编译器编译成一个同名的java类,所以这里这可以看出枚举实质就是类,这个类继承Enum类,另外每一个枚举值都会被声明成一个当前类类型的变量,默认使用Enum中的构造方法初始化这个变量,并且编译器还自定义添加了几个方法用于操作枚举类。

有的时候想利用枚举类中的枚举值作为键,然后存储键值对到Map中,如何实现呢?很简单,使用HashMap集合,泛型中键使用枚举类作为类型即可,但是呢,java为了追求更好的效果,提供了EnumMap类来处理这类问题,看一下EnumMap的使用和基本原理(底层使用数组来记录元素的添加):

public static void main(String[] args) {
	//使用EnumMap来处理枚举集合的问题
	EnumMap<EnumTest, String> enumMap = new EnumMap<>(EnumTest.class);
	//正好每一个枚举值的类型是一个枚举类型,所以直接用即可
	enumMap.put(EnumTest.Cancel, "Cancel");
	enumMap.put(EnumTest.Waiting, "Waiting");
	enumMap.put(EnumTest.Cancel, "Cancel");
	Set<EnumTest> keySet = enumMap.keySet();
	for (EnumTest enumTest : keySet) {
		System.out.println(enumMap.get(enumTest));
	}
}

---------------------------------------------------------------------

//EnumMap实现的基本原理,底层用的是一个数组来存储元素
// Map implementation for use with enum type keys. Enum maps are represented internally as arrays.
// 一个用于处理枚举类型的Map集合,底层的实现方式是用的数组
//这里只看一下EnumMap是如何存储键值对的
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements java.io.Serializable, Cloneable
{
    //集合键的枚举类型的类对象
    private final Class<K> keyType;

    //键值对中所有值的数组
    private transient Object[] vals;

    //映射个数
    private transient int size = 0;

		//判断key的类型是否正确
    private void typeCheck(K key) {
        Class<?> keyClass = key.getClass();
        if (keyClass != keyType && keyClass.getSuperclass() != keyType)
            throw new ClassCastException(keyClass + " != " + keyType);
    }
    
    //判断value的值
    private Object maskNull(Object value) {
        return (value == null ? NULL : value);
    }
		
		//添加元素
		//将枚举值对应的编号作为key,value是传入的值
    public V put(K key, V value) {
        typeCheck(key);
				//获取到当前key,也就是枚举值对应的编号
        int index = key.ordinal();
        //将编号作为下标在vals中获取对应的值
        Object oldValue = vals[index];
        //替换或者添加一个元素
        vals[index] = maskNull(value);
        if (oldValue == null)
            size++;
        return unmaskNull(oldValue);
    }
    
}

还有一个单列集合EnumSet专门用于处理枚举类型的集合,其使用和基本原理如下(底层用一个long类型的值来标记哪个元素已经添加):

public static void main(String[] args) {
	EnumSet<EnumTest> enumSet = EnumSet.allOf(EnumTest.class);
	enumSet.add(EnumTest.Cancel);
	enumSet.add(EnumTest.Waiting);
	enumSet.add(EnumTest.Payed);
	for (EnumTest e : enumSet) {
		System.out.println(e);
	}
}

--------------------------------------------------------------


public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E> implements Cloneable, java.io.Serializable
{
    //类型
    final Class<E> elementType;

    //枚举中所有元素
    final Enum<?>[] universe;

//-------------------------------创建一个EnumSet实例-------------------------//
    //创建一个空的集合
    public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
        //可以看到实例化的是RegularEnumSet或者JumboEnumSet类的对象
        if (universe.length <= 64)
            return new RegularEnumSet<>(elementType, universe);
        else
            return new JumboEnumSet<>(elementType, universe);
    }
    

    //使用指定的枚举类型创建一个集合
    public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {
        EnumSet<E> result = noneOf(elementType);
        result.addAll();
        return result;
    }
    
    //添加元素时,调用的是子类中的方法,因为EnumSet中没有添加的方法,代码如下:
    //说一下RegularEnumSet和JumboEnumSet(这部分代码是在别处贴过来的)
			    
			    //---------------RegularEnumSet实现代码---------------------//
			    class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {
			    		
				  		//仅仅使用这个值来记录集合中的元素,long类型占8个字节,下边只用最低8位计算
				  		private long elements = 0L;
				
					    RegularEnumSet(Class<E>elementType, Enum<?>[] universe) {
					        super(elementType, universe);
					    }
					    
					    //添加方法
					    public boolean add(E e) {
					        typeCheck(e);
					
					        long oldElements = elements;
					        //假设原本集合为空,则elements为0(0000 0000),这是添加编号为1的枚举值,计算如下:
					        // 先将 1 左移1位 ,则变成
					        // 0000 0001  ----》 0000 0010
					        // 然后与之前的elements做与运算
					        // 0000 0000
					        // 0000 0010 |
					        //----------------
					        // 0000 0010
					        
					        //这个地方设计的比较巧妙,因为long型一共占用64位,所以这里就用每一位表示当前集合中是否包含对应的枚举值
					        //例如:0000 0010,低的第二位为1,此时枚举中的第一个枚举值添加进来了,
					        //依次类推;当第三位上是1时,则表示第二个枚举值添加进来了
					        //而最多只能到64位,这也是为什么只有小于或等于64才创建RegularEnumSet对象
					        elements |= (1L << ((Enum<?>)e).ordinal());
					        return elements != oldElements;
					    }
			    }
			    //---------------umboEnumSet实现代码---------------------//
			    
			    class JumboEnumSet<E extends Enum<E>> extends EnumSet<E> {
					    
							//这个集合的位向量表示。这个数组的第j个元素的第i位表示这个集合中存在宇宙[64*j +i]。
					    private long elements[];
					
					    JumboEnumSet(Class<E>elementType, Enum<?>[] universe) {
					        super(elementType, universe);
					        elements = new long[(universe.length + 63) >>> 6];
					    }
					    
					    //实现的基本思想:将是按着64位分组,当枚举值的个数大于64时,就开始分,使用枚举个数除以64看可以分几组
					    //然后每一组再用如RegularEnumSet中添加的方法进行记录哪些元素已经添加了
					    public boolean add(E e) {
					        typeCheck(e);
					
					        int eOrdinal = e.ordinal();
					        //判断一下要添加的枚举值应该在第几组
					        int eWordNum = eOrdinal >>> 6;
									//获取到当前组的long类型的值,它是64位,按着RegularEnumSet中的add方法继续分析
					        long oldElements = elements[eWordNum];
					        elements[eWordNum] |= (1L << eOrdinal);
					        boolean result = (elements[eWordNum] != oldElements);
					        if (result)
					            size++;
					        return result;
					    }
			  	}
    
    
//-------------------------------创建一个EnumSet实例-------------------------//    
    
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值