吃透Java基础十一:枚举

一:枚举类

枚举是JDK1.5添加的,在枚举类型出来之前,我们都是以定义常量来代替,比如:

public class Date {
    public static final int ONE = 0;
    public static final int TWO = 1;
    public static final int THREE = 2;
    public static final int FOUR = 3;
    public static final int FIVE = 4;
}

这种定义常量的方式也是有一些不足的,如int值相同的情况下,编译期并不会提示我们的,所以很容易造成混淆。
定义枚举类的一般语法:任意两个枚举成员不能具有相同的名称,多个枚举成员之间使用逗号分隔,枚举表示的类型其取值是必须有限的

public enum Date {
    ONE,TWO,THREE,FOUR,FIVE
}

二:枚举类原理

把刚才定义的Date枚举类编译后再反编译class文件来看一下:
在这里插入图片描述
可以看到,使用关键字enum定义的枚举类型,在编译期后,也将转换成为一个实实在在的类,而在该类中,会存在每个在枚举类型中定义好变量的对应实例对象,如上述的ONE枚举类型对应public static final Date ONE;,同时编译器会为该类创建两个方法,分别是values()和valueOf()。

  1. 枚举类实际上也是一个类,默认继承于java.lang.Enum类,并且此类是final的,不可被继承,又由于java是单继承已经默认继承Enum所以我们定义的Date枚举类是不允许继承其他类的。
  2. 默认生成私有构造函数,只能由虚拟机去创建,不允许外部直接创建枚举类对象的。
  3. 定义的枚举实际上是枚举类型的常量。

java.lang.Enum源码

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;
    }

    //比较的是ordinal值
    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;//根据ordinal值比较大小
    }
    
    //返回与此枚举常量的枚举类型相对应的 Class 对象
    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
    	//获取class对象引用,getClass()是Object的方法
        Class<?> clazz = getClass();
        //获取父类Class对象引用
        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) {
        //enumType.enumConstantDirectory()获取到的是一个map集合,key值就是name值,value则是枚举变量值 
        //enumConstantDirectory是class对象内部的方法,根据class对象获取一个map集合的值
        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);
    }

 	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();
    }
    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");
    }
}

下面例子测试其方法:

public class MyTest {
    public static void main(String[] args) {
        Date[] dates = {Date.ONE, Date.TWO, Date.THREE, Date.FOUR, Date.FIVE};
        for (Date date : dates) {
            System.out.println("name:" + date.name() + " ordinal:" + date.ordinal() + " class:" + date.getDeclaringClass());
        }
        System.out.println("-----------------------------");

        System.out.println("dates[0].compareTo(dates[1]):" + dates[0].compareTo(dates[1]));

        System.out.println("-----------------------------");

        Date d1 = Date.valueOf(Date.class, dates[0].name());
        Date d2 = Enum.valueOf(Date.class, dates[0].name());
        System.out.println("d1:" + d1 + " d2:" + d2);
    }
}

运行输出:

name:ONE ordinal0 class:class Date
name:TWO ordinal1 class:class Date
name:THREE ordinal2 class:class Date
name:FOUR ordinal3 class:class Date
name:FIVE ordinal4 class:class Date
-----------------------------
dates[0].compareTo(dates[1])-1
-----------------------------
d1:ONE d2:ONE

Values()方法与ValueOf()方法

values()方法和valueOf(String name)方法是编译器生成的static方法,在Enum类中并没出现values()方法,但valueOf()方法还是有出现的,只不过编译器生成的valueOf()方法需传递一个name参数,而Enum自带的静态方法valueOf()则需要传递两个方法,其实,编译器生成的valueOf方法最终还是调用了Enum类的valueOf方法:

public class MyTest {
    public static void main(String[] args) {
        System.out.println(Arrays.toString(Date.values()));
        System.out.println(Date.valueOf("ONE"));
    }
}

运行输出:

[ONE, TWO, THREE, FOUR, FIVE]
ONE

values()方法的作用就是获取枚举类中的所有变量,并作为数组返回。
valueOf(String name)方法与Enum类中的valueOf方法的作用类似根据名称获取枚举变量。

三:枚举类使用

  • enum 与 class、interface 具有相同地位,但是不能继承与被继承,也不能创建实例,只能由JVM去创建。
  • 可以实现多个接口,也可以拥有构造器、成员方法、成员变量。

实现接口

public interface ClickListener {
    void onClick();
}
public enum Date implements ClickListener{
    ONE, TWO, THREE, FOUR, FIVE;
    @Override
    public void onClick() {
    }
}

定义构造函数

注意:只能是私有的构造函数或默认

public enum Date{
    ONE, TWO, THREE, FOUR, FIVE;
    private Date(){}
}

定义成员方法和成员变量

public enum Date{
    ONE, TWO, THREE, FOUR, FIVE;
    private String a;
    public String getContext(){
        return Arrays.toString(values());
    }
}

EnumMap
java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumMap继承了AbstractMap类,因此EnumMap具备一般map的使用方法,主要通过一个保存key的数组K[] keyUniverse和一个保存value的数组Object[] vals来操作。
源码:

public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>
    implements java.io.Serializable, Cloneable
{
    //Class对象引用
    private final Class<K> keyType;

    //存储Key值的数组
    private transient K[] keyUniverse;

    //存储Value值的数组
    private transient Object[] vals;

    //map的size
    private transient int size = 0;

    //空map
    private static final Enum<?>[] ZERO_LENGTH_ENUM_ARRAY = new Enum<?>[0];

    //构造函数
    public EnumMap(Class<K> keyType) {
        this.keyType = keyType;
        keyUniverse = getKeyUniverse(keyType);
        vals = new Object[keyUniverse.length];
    }

	 //返回枚举数组
	private static <K extends Enum<K>> K[] getKeyUniverse(Class<K> keyType) {
        //最终调用到枚举类型的values方法,values方法返回所有可能的枚举值
        return SharedSecrets.getJavaLangAccess().getEnumConstantsShared(keyType);
    }
    
    public V put(K key, V value) {
        typeCheck(key);//检测key的类型
        //获取存放value值得数组下标
        int index = key.ordinal();
        //获取旧值
        Object oldValue = vals[index];
        //设置value值
        vals[index] = maskNull(value);
        if (oldValue == null)
            size++;
        return unmaskNull(oldValue);//返回旧值
    }
    
    private void typeCheck(K key) {
    Class<?> keyClass = key.getClass();//获取类型信息
    if (keyClass != keyType && keyClass.getSuperclass() != keyType)
       throw new ClassCastException(keyClass + " != " + keyType);
	}
	//代表NULL值得空对象实例
    private static final Object NULL = new Object() {
        public int hashCode() {
            return 0;
        }

        public String toString() {
            return "java.util.EnumMap.NULL";
        }
    };

    private Object maskNull(Object value) {
        //如果值为空,返回NULL对象,否则返回value
        return (value == null ? NULL : value);
    }

    @SuppressWarnings("unchecked")
    private V unmaskNull(Object value) {
        //将NULL对象转换为null值
        return (V)(value == NULL ? null : value);
    }
    public V get(Object key) {
        return (isValidKey(key) ?
                unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);
    }

    //对Key值的有效性和类型信息进行判断
    private boolean isValidKey(Object key) {
      if (key == null)
          return false;

      // Cheaper than instanceof Enum followed by getDeclaringClass
      Class<?> keyClass = key.getClass();
      return keyClass == keyType || keyClass.getSuperclass() == keyType;
    }
    public V remove(Object key) {
        //判断key值是否有效
        if (!isValidKey(key))
            return null;
        //直接获取索引
        int index = ((Enum<?>)key).ordinal();

        Object oldValue = vals[index];
        //对应下标元素值设置为null
        vals[index] = null;
        if (oldValue != null)
            size--;//减size
        return unmaskNull(oldValue);
    }
    //判断是否包含某value
	public boolean containsValue(Object value) {
    value = maskNull(value);
    //遍历数组实现
    for (Object val : vals)
        if (value.equals(val))
            return true;

    return false;
	}
	//判断是否包含key
	public boolean containsKey(Object key) {
    return isValidKey(key) && vals[((Enum<?>)key).ordinal()] != null;
	}
}

下面例子测试其方法:

public class MyTest {
    public static void main(String[] args) {
        EnumMap enumMap=new EnumMap(Date.class);
        //添加元素
        enumMap.put(Date.ONE,1);
        enumMap.put(Date.TWO,2);
        enumMap.put(Date.THREE,3);
        //删除元素
        enumMap.remove(Date.TWO);
        //遍历输出
        System.out.println(enumMap.toString());
    }
}

运行输出:

{ONE=1, THREE=3}

EnumSet

EnumSet是与枚举类型一起使用的专用 Set 集合,EnumSet 中所有元素都必须是枚举类型。注意EnumSet不允许使用 null 元素。试图插入 null 元素将抛出 NullPointerException,但试图测试判断是否存在null 元素或移除 null 元素则不会抛出异常,与大多数collection 实现一样,EnumSet不是线程安全的,因此在多线程环境下应该注意数据同步问题。

public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
    implements Cloneable, java.io.Serializable
{
	//元素的class
    final Class<E> elementType;
	
    final Enum<?>[] universe;

    private static Enum<?>[] ZERO_LENGTH_ENUM_ARRAY = new Enum<?>[0];

    EnumSet(Class<E>elementType, Enum<?>[] universe) {
        this.elementType = elementType;
        this.universe    = universe;
    }

    //创建一个具有指定元素类型的空EnumSet。
    public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
        Enum<?>[] universe = getUniverse(elementType);
        if (universe == null)
            throw new ClassCastException(elementType + " not an enum");

        if (universe.length <= 64)
            return new RegularEnumSet<>(elementType, universe);
        else
            return new JumboEnumSet<>(elementType, universe);
    }

    //创建一个指定元素类型并包含所有枚举值的EnumSet
    public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {
        EnumSet<E> result = noneOf(elementType);
        result.addAll();
        return result;
    }
    
    abstract void addAll();
	
	创建一个包含参数容器中的所有元素的EnumSet
    public static <E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s) {
        return s.clone();
    }
    public static <E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c) {
        if (c instanceof EnumSet) {
            return ((EnumSet<E>)c).clone();
        } else {
            if (c.isEmpty())
                throw new IllegalArgumentException("Collection is empty");
            Iterator<E> i = c.iterator();
            E first = i.next();
            EnumSet<E> result = EnumSet.of(first);
            while (i.hasNext())
                result.add(i.next());
            return result;
        }
    }

    // 初始集合包括指定集合的补集
    public static <E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s) {
        EnumSet<E> result = copyOf(s);
        result.complement();
        return result;
    }

    // 创建一个包括参数中所有元素的EnumSet
    public static <E extends Enum<E>> EnumSet<E> of(E e) {
        EnumSet<E> result = noneOf(e.getDeclaringClass());
        result.add(e);
        return result;
    }
    public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2) {
        EnumSet<E> result = noneOf(e1.getDeclaringClass());
        result.add(e1);
        result.add(e2);
        return result;
    }
    public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3) {
        EnumSet<E> result = noneOf(e1.getDeclaringClass());
        result.add(e1);
        result.add(e2);
        result.add(e3);
        return result;
    }
    public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4) {
        EnumSet<E> result = noneOf(e1.getDeclaringClass());
        result.add(e1);
        result.add(e2);
        result.add(e3);
        result.add(e4);
        return result;
    }
    public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4,
                                                    E e5)
    {
        EnumSet<E> result = noneOf(e1.getDeclaringClass());
        result.add(e1);
        result.add(e2);
        result.add(e3);
        result.add(e4);
        result.add(e5);
        return result;
    }
    @SafeVarargs
    public static <E extends Enum<E>> EnumSet<E> of(E first, E... rest) {
        EnumSet<E> result = noneOf(first.getDeclaringClass());
        result.add(first);
        for (E e : rest)
            result.add(e);
        return result;
    }

    // 创建一个包括枚举值中指定范围元素的EnumSet
    public static <E extends Enum<E>> EnumSet<E> range(E from, E to) {
        if (from.compareTo(to) > 0)
            throw new IllegalArgumentException(from + " > " + to);
        EnumSet<E> result = noneOf(from.getDeclaringClass());
        result.addRange(from, to);
        return result;
    }

    abstract void addRange(E from, E to);

    @SuppressWarnings("unchecked")
    public EnumSet<E> clone() {
        try {
            return (EnumSet<E>) super.clone();
        } catch(CloneNotSupportedException e) {
            throw new AssertionError(e);
        }
    }

    abstract void complement();

    final void typeCheck(E e) {
        Class<?> eClass = e.getClass();
        if (eClass != elementType && eClass.getSuperclass() != elementType)
            throw new ClassCastException(eClass + " != " + elementType);
    }
    
    private static <E extends Enum<E>> E[] getUniverse(Class<E> elementType) {
        return SharedSecrets.getJavaLangAccess()
                                        .getEnumConstantsShared(elementType);
    }
}

下面例子测试其方法:
创建EnumSet并不能使用new关键字,因为它是个抽象类,而应该使用其提供的静态工厂方法,EnumSet的静态工厂方法比较多,如下:

public class MyTest {
    public static void main(String[] args) {
        EnumSet<Date> enumSet= EnumSet.noneOf(Date.class);
        enumSet.add(Date.ONE);
        enumSet.add(Date.TWO);
        enumSet.add(Date.THREE);
        System.out.println("添加后:"+enumSet.toString());

        EnumSet<Date> enumSet1= EnumSet.allOf(Date.class);
        System.out.println("add后:"+enumSet1.toString());

        //初始集合包括枚举值中指定范围的元素
        EnumSet<Date> enumSet2= EnumSet.range(Date.ONE,Date.THREE);
        System.out.println("指定范围:"+enumSet2.toString());

        //指定补集,也就是从全部枚举类型中去除参数集合中的元素,如下去掉上述enumSet2的元素
        EnumSet<Date> enumSet3= EnumSet.complementOf(enumSet2);
        System.out.println("指定补集:"+enumSet3.toString());

        //初始化时直接指定元素
        EnumSet<Date> enumSet4= EnumSet.of(Date.ONE);
        System.out.println("指定Date.ONE元素:"+enumSet4.toString());
        EnumSet<Date> enumSet5= EnumSet.of(Date.ONE,Date.FOUR);
        System.out.println("指定Color.BLACK和Color.GREEN元素:"+enumSet5.toString());

        //复制enumSet5容器的数据作为初始化数据
        EnumSet<Date> enumSet6= EnumSet.copyOf(enumSet5);
        System.out.println("enumSet6:"+enumSet6.toString());


        List<Date> list = new ArrayList<Date>();
        list.add(Date.ONE);
        list.add(Date.ONE);//重复元素
        list.add(Date.TWO);
        list.add(Date.THREE);
        System.out.println("list:"+list.toString());

        //使用copyOf(Collection<E> c)
        EnumSet enumSet7=EnumSet.copyOf(list);
        System.out.println("enumSet7:"+enumSet7.toString());
    }
}

运行输出:

添加后:[ONE, TWO, THREE]
add后:[ONE, TWO, THREE, FOUR, FIVE]
指定范围:[ONE, TWO, THREE]
指定补集:[FOUR, FIVE]
指定Date.ONE元素:[ONE]
指定Color.BLACK和Color.GREEN元素:[ONE, FOUR]
enumSet6:[ONE, FOUR]
list:[ONE, ONE, TWO, THREE]
enumSet7:[ONE, TWO, THREE]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吃透Java

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值