Java 进阶——枚举enum使用小结及使用枚举替代你的常量类

文章大纲

  • 引言
  • 一、enum 枚举概述
  • 二、枚举类实现背后的故事
  • 三、枚举的典型使用
    • 1、替换你的常量类
    • 2、switch case 条件分支
    • 3、实现接口
    • 4、使用接口组织枚举
    • 5、遍历枚举
    • 6、EnumMap
    • 7、EnumSet

引言

枚举类型是Java 1.5中新增特性的一部分,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性,同时合理使用枚举不仅能让你的代码可读性增强,而且在某些场景下还能让你的性能更好。

一、enum 枚举概述

Java 中的枚举类型采用关键字enum 来定义,从JDK 1.5才有的新类型,所有的枚举类型都是继承自Enum 类型,所以枚举类不能指定继承的父类,但是可以实现多个接口,枚举类默认实现了Comparable接口和Serializable接口,而且与普通的类最大的区别在于:枚举类的构造方法的访问权限只能为私有的且对应的实例必须显示列举出来(即对应的实例有限个数)。

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
	//枚举类对应实例的名称,对应默认{@link #toString()}方法返回的值
    private final String name;
	//原始的枚举实例的序号,从0开始,声明在第一个的为0,依次类推
    private final int ordinal;
    
    public final String name() {
        return name;
    }
    public final int ordinal() {
        return ordinal;
    }

	//注意protected 修饰符,只可由子类访问
    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;
    }

    //返回与此枚举常量对应的Class对象的枚举类型。
    public final Class<E> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }
    //通过枚举类对应Class类和实例的名称获取到枚举实例
    {
        // Android-changed: Use a static BasicLruCache mapping Enum class -> Enum instance array.
        if (enumType == null) {
            throw new NullPointerException("enumType == null");
        }
        if (name == null) {
            throw new NullPointerException("name == null");
        }
        T[] values = getSharedConstants(enumType);
        if (values == null) {
            throw new IllegalArgumentException(enumType.toString() + " is not an enum type.");
        }

        // Iterate backwards through the array to retain historic Android behavior in the
        // unexpected / likely invalid case where there are multiple values with the same name.
        for (int i = values.length - 1; i >= 0; --i) {
            T value = values[i];
            if (name.equals(value.name())) {
                return value;
            }
        }
        throw new IllegalArgumentException(
                "No enum constant " + enumType.getCanonicalName() + "." + name);
    }

    private static final BasicLruCache<Class<? extends Enum>, Object[]> sharedConstantsCache
            = new BasicLruCache<Class<? extends Enum>, Object[]>(64) {
        @Override protected Object[] create(Class<? extends Enum> enumType) {
            if (!enumType.isEnum()) {
                return null;
            }
            try {
                Method method = enumType.getDeclaredMethod("values", EmptyArray.CLASS);
                method.setAccessible(true);
                return (Object[]) method.invoke((Object[]) null);
            } catch (NoSuchMethodException impossible) {
                throw new AssertionError("impossible", impossible);
            } catch (IllegalAccessException impossible) {
                throw new AssertionError("impossible", impossible);
            } catch (InvocationTargetException impossible) {
                throw new AssertionError("impossible", impossible);
            }
        }
    };

    public static <T extends Enum<T>> T[] getSharedConstants(Class<T> enumType) {
        return (T[]) sharedConstantsCache.get(enumType);
    }
    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 类有两个重要的成员变量:用于存储枚举实例名称的name和用于存储枚举实例序号的ordinal,而两个成员变量都被final 修饰这意味着枚举类在构造时必须调用其父类的构造方法完成赋值等操作(这些不需要我们在代码中自己调用,编译器会在编译过程中动态完成,)。除此之外还重写了几个来自Object 的成员方法。

二、枚举类实现背后的故事

定义枚举类型时我们使用的关键字是enum,与class关键字类似,只不过前者是定义枚举类型,后者是定义类类型,要调用枚举类的方法必须通过枚举实例名,而且枚举类实例本身也不能作为方法参数。左边是我们代码中定义了一个枚举类型的原始代码,经过IDE进行编译之后,使用jad 等其他工具反编译查看可以得到右边最终的代码。
Java 进阶——枚举enum使用小结及使用枚举替代你的常量类_第1张图片
如上图所示,可以看到IDE 会对枚举类进行一些特殊的处理:

  • 修改当前枚举类的构造方法的签名,在原有的参数列表前增加两个重要的成员变量:用于存储枚举实例名称的name和用于存储枚举实例序号的ordinal,并使用super调用父类java.lang.Enum的构造方法用于初始化。

  • 添加values方法和valueOf方法

//返回一个数组,包含了该枚举类所有的实例(并不是返回枚举类自己保存实例的数组,而是通过对其复制,以确保实例不被篡改)。
public static Season[] values()
//通过当前枚举类的名称返回对应的实例。比如valueOf(“SPRING”)返回Season.SPRING)。
public static Season valueOf(String name)
  • 增加一个静态代码初始化块和一个静态数组,静态代码初始化块负责构造枚举类的实例并将其保存到数组中。

三、枚举的典型使用

public class SeasonClient {
	public static void main(String[] args){
		Season season=Season.SPRING;
		System.out.println(season.ordinal());//0 int类型
		System.out.println(season.getClass().getSimpleName());//Season
		System.out.println(season.toString());//SPRING
		System.out.println(season.toString().getClass().getSimpleName());//String
		System.out.println(season.getText().equals("春天"));//true
		System.out.println(Season.SPRING.toString());//SPRING
		System.out.println(Season.SPRING.name());//SPRING
	}
}
enum Season {
	SPRING("春天"), SUMMER("夏天"), AUTUMN("秋天"), WINTER("冬天");//用逗号连接各实例,并用分号结束。
	private final String text;
	private Season(String txt) {
		this.text = txt;
	}
	public String getText() {
		return text;
	}
}

1、替换你的常量类

在这里插入图片描述

2、switch case 条件分支

	//调用:getSeason(Season.WINTER);
	public static void getSeason(Season season){
		switch(season){
			case SPRING:
				System.out.println("春天到了");
				break;
			case SUMMER:
				System.out.println("夏天来了");
				break;
			case AUTUMN:
				System.out.println("秋天也近了");
				break;
			case WINTER:
				System.out.println("冬天不远了");
				break;
			default:
					break;
		}
	}

3、实现接口

/**
*使用:System.out.println(Color.BLUE.getText());//蓝色
		Color.RED.show();//1:红色
*/
interface ITest {
	void show();
	String getText();
}

enum Color implements ITest {
	RED("红色", 1), GREEN("绿色", 2),BLUE("蓝色", 4);
	//新增的成员变量
	private String text;
	private int index;

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

	@Override
	public String getText() {
		return this.text;
	}

	@Override
	public void show() {
		System.out.println(this.index + ":" + this.text);
	}
}

4、使用接口组织枚举

	/**
	*  使用: VoiceBrand.TTS.CRUZR;
	*        VoiceBrand.ASR.CRUZR
	*/
    public interface VoiceBrand {
        enum TTS implements VoiceBrand {
            CRUZR("crz"), IFLY("ifly"), GOOGLE("google");
            private final String text;
            TTS(String txt) {
                this.text = txt;
            }

            public String getText() {
                return text;
            }
        }

        enum ASR implements VoiceBrand {
            CRUZR("crz"), IFLY("ifly");
            private final String text;
            ASR(String txt) {
                this.text = txt;
            }
            public String getText() {
                return text;
            }
        }
    }

5、遍历枚举

	public static void traversal(){
		Season[] seasons=Season.values();
		for(Season s : seasons){
			System.out.println( " 现在是 : " + s.getText()+"实例名为:"+s);
			System.out.println( " 当前季节 的[ordinal:name]=" +"["+s.name()+":"+ s.ordinal()+"]");
		}
	}
	

Java 进阶——枚举enum使用小结及使用枚举替代你的常量类_第2张图片

6、EnumMap

EnumMap 跟 HashMap 的使用大同小异,只不过EnumMap 的 key 必须是枚举类型。EnumMap 是专门为枚举类型量身定做的Map实现,同样的场景虽然使用其它的Map实现(如HashMap)也能完成枚举类型实例到值得映射,但是使用EnumMap会更加高效,因为它只能接收同一枚举类型的实例作为键值,并且由于枚举类型实例的数量相对固定并且有限,而EnumMap内部是使用数组来存放与枚举类型对应的值,毕竟数组是一段连续的内存空间,根据程序局部性原理,效率会相当高。

构造方法 说明
EnumMap(Class< K > keyType) 创建一个具有指定键类型的空枚举映射,Map enumMap=new EnumMap<>(Color.class);
EnumMap(EnumMap m) 创建一个其键类型与指定枚举映射相同的枚举映射,最初包含相同的映射关系(如果有的话),Map enumMap2=new EnumMap<>(enumMap);
EnumMap(Map m) 创建一个枚举映射,从指定映射对其初始化,Map enumMap = new EnumMap<>(hashMap);
EnumMap<Color, String> enumStringEnumMap = new EnumMap<Color, String>(Color.class);
		enumStringEnumMap.put(Color.RED,"红色2");
		enumStringEnumMap.put(Color.GREEN,"绿色2");
		enumStringEnumMap.put(Color.BLUE,"蓝色2");
		 
		for(Map.Entry<Color,String> enumStringEntry : enumStringEnumMap.entrySet()){
		    System.out.println("enum map key: " +enumStringEntry.getKey().name()+ " value " + enumStringEntry.getValue());		
		}

在这里插入图片描述

7、EnumSet

EnumSet 是枚举类型的高性能Set实现,它要求放入它的枚举常量必须属于同一枚举类型。它的使用与其他的Set大同小异,不过内部实现就大有不同,与其他Set接口的实现类HashSet/TreeSet(内部都是用对应的HashMap/TreeMap实现的)不同的是,EnumSet在内部实现是位向量(稍后分析),它是一种极为高效的位运算操作,由于直接存储和操作都是bit,因此EnumSet空间和时间性能都十分可观,足以媲美传统上基于 int 的“位标志”的运算,重要的是我们可像操作set集合一般来操作位运算,EnumSet不允许使用 null 元素,在试图插入 null 元素时将抛出 NullPointerException,但试图测试判断是否存在null 元素或移除 null 元素则不会抛出异常,与大多数Collection 实现一样,EnumSet不是线程安全的,因此在多线程环境下应该注意数据同步问题。而且创建EnumSet并不能使用new关键字,因为它是个抽象类,而应该使用其提供的静态工厂方法

创建一个具有指定元素类型的空EnumSet。
EnumSet<E>  noneOf(Class<E> elementType)       
//创建一个指定元素类型并包含所有枚举值的EnumSet
<E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType)
// 创建一个包括枚举值中指定范围元素的EnumSet
<E extends Enum<E>> EnumSet<E> range(E from, E to)
// 初始集合包括指定集合的补集
<E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s)
// 创建一个包括参数中所有元素的EnumSet
<E extends Enum<E>> EnumSet<E> of(E e)
<E extends Enum<E>> EnumSet<E> of(E e1, E e2)
<E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3)
<E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4)
<E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4, E e5)
<E extends Enum<E>> EnumSet<E> of(E first, E... rest)
//创建一个包含参数容器中的所有元素的EnumSet
<E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s)
<E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c)

简单使用:

//创建一个之类enum类型的空集合
		EnumSet<Color> enumEnumSet = EnumSet.noneOf(Color.class);
		System.out.println("enum set value is : "+ Arrays.toString(enumEnumSet.toArray()));
		enumEnumSet.add(Color.RED);
		enumEnumSet.add(Color.BLUE);
		System.out.println("enum set value is : "+ Arrays.toString(enumEnumSet.toArray()));
		 
		//创建一个指定enum类型的所有实例的集合
		EnumSet<Color> enumEnumSet1 = EnumSet.allOf(Color.class);
		System.out.println("enum set1 value is : "+ Arrays.toString(enumEnumSet1.toArray()));
		//创建一个指定enum初始化内容的集合
		EnumSet<Color> enumEnumSet2 = EnumSet.of(Color.GREEN,Color.BLUE);
		System.out.println("enum set2 value is : "+ Arrays.toString(enumEnumSet2.toArray()));
		 
		//创建指定类型,指定范围的集合,包含边界数据
		EnumSet<Color> enumEnumSet3 = EnumSet.range(Color.GREEN,Color.BLUE);//(Color.BLUE,Color.GREEN)出错因为BLUE>GREEN
		System.out.println("enum set3 value is : "+ Arrays.toString(enumEnumSet3.toArray()));

Java 进阶——枚举enum使用小结及使用枚举替代你的常量类_第3张图片

你可能感兴趣的:(Java 进阶——枚举enum使用小结及使用枚举替代你的常量类)