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() && // optimizationself.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=07: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V  //将构造的对象赋给变量Cancel10: putstatic     #19                 // Field Cancel:Lcom/czp/enumeration/EnumTest;  13: new           #1                  // class com/czp/enumeration/EnumTest16: dup17: ldc           #21                 // String Waiting19: iconst_120: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V23: putstatic     #22                 // Field Waiting:Lcom/czp/enumeration/EnumTest;26: new           #1                  // class com/czp/enumeration/EnumTest29: dup30: ldc           #24                 // String Payed32: iconst_233: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V36: putstatic     #25                 // Field Payed:Lcom/czp/enumeration/EnumTest;//-------------------  创建数组,将所有枚举值放入到数组中  ---------------------//	     39: iconst_3// 相当于创建一个数组,数组引用入栈,长度为栈顶元素340: anewarray     #1                  // class com/czp/enumeration/EnumTest //复制栈顶数值,并且复制值进栈           43: dup//int型常量值0入栈,作为数组下标44: iconst_0//获取到Cancel45: getstatic     #19                 // Field Cancel:Lcom/czp/enumeration/EnumTest; // 依次取出栈顶元素,根据类型进行数组赋值操作  48: aastore49: dup50: iconst_151: getstatic     #22                 // Field Waiting:Lcom/czp/enumeration/EnumTest;54: aastore55: dup56: iconst_257: getstatic     #25                 // Field Payed:Lcom/czp/enumeration/EnumTest;60: aastore//将构建的数组对象赋给VALUES61: putstatic     #27                 // Field ENUM$VALUES:[Lcom/czp/enumeration/EnumTest;64: return //返回值为空//创建一个values方法,用于获取所有枚举值(大致操作就是新弄一个数组,将上边VALUES中数据复制到新数组,返回新数组)public static com.czp.enumeration.EnumTest[] values();Code://获取到静态域中数组VALUES0: getstatic     #27                 // Field ENUM$VALUES:[Lcom/czp/enumeration/EnumTest;3: dup4: astore_0    // 该指令的行为类似于astore指令index为0的情况。5: iconst_06: aload_0			// 当前frame的局部变量数组中下标为0的引用型局部变量进栈7: arraylength //栈顶的数组引用(arrayref)出栈,该数组的长度进栈。8: dup9: istore_1		//将栈顶int型数值存入第一个局部变量10: anewarray     #1                  // class com/czp/enumeration/EnumTest13: dup14: astore_215: iconst_016: iload_1		//第二个int型局部变量进栈//调用静态方法17: invokestatic  #35                 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V20: aload_2 21: areturn 	//从方法中返回一个对象的引用//根据枚举值的字面值获取到对应的枚举元素public static com.czp.enumeration.EnumTest valueOf(java.lang.String);Code:0: ldc           #1                  // class com/czp/enumeration/EnumTest2: aload_03: invokestatic  #43                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;//类型转换检查,如果该检查未通过将会抛出ClassCastException异常6: checkcast     #1                  // class com/czp/enumeration/EnumTest9: 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);elsereturn 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实例-------------------------//    }

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/321723.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Hangfire使用ApplicationInsigts监控

起因我司目前使用清真的ApplicationInsights来做程序级监控。&#xff08;ApplicationInsights相关文档: https://azure.microsoft.com/zh-cn/services/application-insights/ &#xff09;其实一切都蛮好的&#xff0c;但是我们基于Hangfire的Job系统却无法被Ai所监控到&#…

nssl1446-小智的旅行【dp】

正题 题目大意 求一条最大的权值严格上升的路径。 解题思路 将边权排序&#xff0c;然后从fxf_xfx​转移到fy1f_y1fy​1即可&#xff0c;要注意的是因为严格上升&#xff0c;所以此次转移用的fff不能是相同权值转移时转移的。 codecodecode #include<cstdio> #include…

2017西安交大ACM小学期数据结构 [树状数组]

Problem C 发布时间: 2017年6月28日 11:38 最后更新: 2017年6月28日 16:38 时间限制: 1000ms 内存限制: 32M 描述 给定一个长度为n的序列a1, a2, ..., an, 其中ai∈[1,10]给出q个操作, 操作分为两种 对于形如1xy的操作, 将ax改为y, 满足1≤x≤n, 1≤y≤10对于形如2xyz的操…

NET主流ORM框架分析

接上文我们测试了各个ORM框架的性能&#xff0c;大家可以很直观的看到各个ORM框架与原生的ADO.NET在境删改查的性能差异。这里和大家分享下我对ORM框架的理解及一些使用经验。ORM框架工作原理所有的ORM框架的工作原理都离不开下面这张图&#xff0c;只是每个框架的实现程度不同…

20、java中的类加载机制

1、类加载机制是什么&#xff1f; 类加载机制指的就是jvm将类的信息动态添加到内存并使用的一种机制。 2、那么类加载的具体流程是什么呢&#xff1f; 一般说类加载只有三步&#xff1a;加载、连接和初始化&#xff0c;其中连接包括验证、准备和解析&#xff0c;用于将运行时加…

nssl1447-小智的糖果【dp】

正题 题目大意 长度为nnn的序列&#xff0c;mmm个位置要求两边都比他大&#xff0c;kkk个位置要求两边都比他小。求序列个数。 解题思路 若第xxx个位置为山峰&#xff0c;那么ax−1<ax>ax1a_{x-1}<a_x>a_{x1}ax−1​<ax​>ax1​&#xff0c;我们用upiup_iu…

21、java中的反射机制

先推荐安装一个 eclipse 的反编译插件 Enhanced Class Decompiler 是什么&#xff1f; 在说反射之前先说一下编译时类型和运行时类型&#xff0c;大家都知道List是一个接口&#xff0c;它是不可以被实例化的&#xff0c;但是可以通过多态实现&#xff1a;List list new Arra…

【北京】BXUG第12期活动基于 .NET Core构建微服务和Xamarin

分享主题&#xff1a;基于 .NET Core构建微服务实战分享分享者&#xff1a;薛锋 北京切尔思科技架构师 兼任东北大学信息安全工程师和技术主播&#xff0c;行业内专注于研究 .NET Core和Web应用&#xff0c;具有比较扎实的技术基础和数年的从业经历。在GitHub上主持数个开…

2017西安交大ACM小学期数据结构 [树状数组,极大值]

Problem D 发布时间: 2017年6月28日 10:51 最后更新: 2017年6月28日 16:38 时间限制: 1000ms 内存限制: 32M 描述 给定一个长度为n的序列a1, a2, ..., an当k满足2≤k≤n−1, ak>ak−1且ak>ak1时, 将元素k称为极大值点, 给出q个操作, 操作分为两种 对于形如1xy的操作…

nssl1448-小智过马路【模拟】

正题 题目大意 nnn个横向道&#xff0c;若干辆车&#xff0c;每辆车速度恒定&#xff0c;给出方向位置长度。 过马路的速度&#xff0c;最早开始时间&#xff0c;最晚开始时间。求最长的可以通过马路的时间段。 解题思路 计算出每辆车限制的时间区间&#xff0c;然后排序找到…

22、java中的注解

注解是什么&#xff1f; 注解可以理解成注释、标记、标签的意思&#xff0c;用来标记类、方法等。就相当于现实生活中的一些事物&#xff0c;上边贴一个标签或者写一些注释性文字来描述它可以用来做什么、怎么用、何时用等信息。Java中的注解也是一样的&#xff0c;用来表示被标…

谈谈ASP.NET Core中的ResponseCaching

前言前面的博客谈的大多数都是针对数据的缓存&#xff0c;今天我们来换换口味。来谈谈在ASP.NET Core中的ResponseCaching&#xff0c;与ResponseCaching关联密切的也就是常说的HTTP缓存。在阅读本文内容之前&#xff0c;默认各位有HTTP缓存相关的基础&#xff0c;主要是Cache-…

2017西安交大ACM小学期数据结构 [树状数组 离散化]

Problem E 发布时间: 2017年6月28日 12:53 最后更新: 2017年6月29日 21:35 时间限制: 1000ms 内存限制: 64M 描述 给定一个长度为n的序列a1, a2, ..., an给定两个整数L, R输出有多少个二元组(x,y),x≤y, 满足L≤∑yixai≤R9104≤n≤105, −109≤ai≤109, −109≤L≤R≤10…

使用 dynamic 类型让 ASP.NET Core 实现 HATEOAS 结构的 RESTful API

上一篇写的是使用静态基类方法的实现步骤: 使用dynamic (ExpandoObject)的好处就是可以动态组建返回类型, 之前使用的是ViewModel, 如果想返回结果的话, 肯定需要把ViewModel所有的属性都返回, 如果属性比较多, 就有可能造成性能和灵活性等问题. 而使用ExpandoObject(dynamic)就…

23、java中的网编基础

什么是网络编程&#xff1f; 在说网络编程之前要先知道什么是网络&#xff0c;网络是一种实现资源共享和数据传输的系统。而网络编程就是使用代码编写程序来进行网络之间数据的传输。使用java进行网络之间数据的传输是比较简单的&#xff0c;java中提供了一些现成的类供我们使…

2017西安交大ACM小学期数据结构 [又是树状数组、异或]

Problem F 发布时间: 2017年6月28日 10:31 最后更新: 2017年6月29日 21:35 时间限制: 2000ms 内存限制: 64M 描述 给定一个nm的矩形, 初始时所有元素都为0给出q个操作, 操作有三种 对于形如1x的操作, 将第x行的所有元素异或1对于形如2y的操作, 将第y列的所有元素异或1对于…

P2717-寒假作业【逆序对,树状数组】

正题 题目链接:https://www.luogu.com.cn/problem/P2717 题目大意 nnn个数&#xff0c;求有多少个连续子序列的平均值大于等于kkk。 解题思路 因为长度会十分干扰&#xff0c;所以我们将所有数减去kkk。问题就变为了求有多少连续子序列的和非负。用前缀和逆序对求就好了。 co…

使用 BenchmarkDotnet 测试代码性能

先来点题外话&#xff0c;清明节前把工作辞了&#xff08;去 tm 的垃圾团队&#xff0c;各种拉帮结派、勾心斗角&#xff09;。这次找工作就得慢慢找了&#xff0c;不能急了&#xff0c;希望能找到个好团队&#xff0c;好岗位吧。顺便这段时间也算是比较闲&#xff0c;也能学习…

24、jdbc操作数据库(1)

什么是jdbc&#xff1f; 看一下官方怎么说&#xff0c;JDBC 英文名Java DataBase Connectivity&#xff0c;使用java连接数据库的工具&#xff0c;就是一组使用java代码来执行SQL语句的API。 Jdbc有什么用&#xff1f; 数据库有多种&#xff0c;并且不同数据库操作时的方式和…

jzoj3918-蛋糕【二分】

正题 题目链接:https://jzoj.net/senior/#contest/show/2953/0 题目大意 n∗mn*mn∗m的矩阵&#xff0c;有数字&#xff0c;横着三刀竖着三刀分成16份使得最小那份最大。 解题思路 暴力枚举竖着的三刀&#xff0c;然后二分答案判定即可。 codecodecode #include<cstdio&g…