枚举--让盗版美国总统wcc给你整明白哈哈
1.为什么要有枚举
Java中的枚举其实是一种语法糖,在 JDK 1.5之后出现,用来表示固定且有限个的对象。比如一个季节类有春、夏、秋、冬四个对象;一个星期有星期一到星期日七个对象。这些明显都是固定的,且有限个。那jdk5之前如果想表示有限个对象,代码是怎么写的嘞?这样写有什么缺点,最终让jdk官方人员(大部分是美国人)看不下去了呐,我这里给大家举一个2020火热的事件,来体会,话不多说,我wcc直接上代码:
package com.huawei.subtitle.portal.com;
/*
为什么需要Enum
*/
public class AmericanFamily {
public static final String bigSon = "特朗普";
public static final String smallSon = "拜登";
public static final String daughter = "奥巴马";
//public static final String daughter = "罗斯福";
public static void main(String[] args) {
String person = "拜登";
ourPresident(person);
System.out.println("--------------------");
String chineseBoyWccNickName = "特朗普";
ourPresident(chineseBoyWccNickName);
System.out.println("你看到了,我是中国小子wcc,可不是AmericanFamily成员");
}
public static void ourPresident(String person) {
if (AmericanFamily.bigSon.equals(person)) {
System.out.println(" our president 川建国,2020 我挺你");
}
if (AmericanFamily.smallSon.equals(person)) {
System.out.println(" our president 拜登");
}
if (AmericanFamily.daughter.equals(person)) {
System.out.println(" our president 奥巴马");
}
/*
如果上面定义了常量罗斯福,这里就要增加个if'判断',也就是就要改代码哈
if (AmericanFamily.daughter.equals(person)) {
System.out.println(" our president 奥巴马");
}
*/
}
}
大家看到了吧,结果竟然是中国小子wcc因为昵称叫特朗普
,当美国人将我进行校验的时候,得到的结果是这是美国总统特朗普哈哈。于是jdk官网人员开始反思,我目前主要是看出了2点:
- 美国人肯定不干呀最后,jdk官方这些人更是看不下去,这验证方式不对呀,这家伙都不会美国人,这也通过了校验。这不行,我得把类型也得校验住了,最好在编译期就提示这个有问题,冒牌货
- 这个代码写的不好呀,随着时间的更替,美国总统族谱上的人越来越多,每次增加了常量,ourPresident()这个方法就要增加判断逻辑,这代码水呀,我可是jdk官方,这得改。
为了让编译器能自动检查某个值在枚举的集合内,并且,不同用途的枚举需要不同的类型来标记,不能混用,我们可以使用enum来定义枚举类,于是终于在jdk1.5版本,Enum枚举出来了,接下来我们看下怎么使用,以及它的原理。
2.Enum定义和常用方法
一般的定义形式:
在定义枚举类型时我们使用的关键字是enum,与class关键字类似,enum 枚举名{ 枚举值表 }; 在枚举值表中应罗列出所有可用值。这些值也称为枚举元素。例如:
enum Color {
RED, GREEN, BLUE;
}
枚举类型Color中定义了颜色的值,这里要注意,值一般是大写的字母,多个值之间以逗号分隔。同时我们应该知道的是枚举类型可以像类(class)类型一样,定义为一个单独的文件,当然也可以定义在其他类内部,更重要的是枚举常量在类型安全性和便捷性都很有保证,如果出现类型问题编译器也会提示我们改进,但务必记住枚举表示的类型其取值是必须有限的,也就是说每个值都是可以枚举出来的,比如上述描述的一周共有七天。
使用枚举直接用枚举名.枚举值即可,例如Color.RED
enum Color {
RED, GREEN, BLUE;
}
public class Test {
// 执行输出结果
public static void main(String[] args) {
Color c1 = Color.RED;
System.out.println(c1);
}
}
2.1Enum类常见的方法:
我们使用enum关键字(注意是小写的),创建对应的枚举类时,默认是继承了Enum类的,而Enum类里自带了一些方法并且进行了实现,大家可以看下Enum源码。
(1) ordinal()方法: 返回枚举值在枚举类种的顺序。这个顺序根据枚举值声明的顺序而定。(顺序是以0开始的,即0,1,2......)
Color.RED.ordinal(); //返回结果:0
Color.BLUE.ordinal(); //返回结果:2
(2) compareTo()方法: Enum实现了java.lang.Comparable接口,因此可以比较对象与指定对象的顺序。Enum中的compareTo返回的是两个枚举值的顺序之差。当然,前提是两个枚举值必须属于同一个枚举类,否则会抛出ClassCastException()异常。(具体可见源代码)
Color.RED.compareTo(Color.BLUE); //返回结果 -2,即(0-2)
(3) values()方法: 静态方法,返回一个包含全部枚举值的数组。
Color[] colors=Color.values();
for(Color c:colors){
System.out.print(c+",");
}//返回结果:RED,GREEN,BLUE
(4) toString()方法: 返回枚举常量的名称。和.name一样的效果
Color c=Color.RED;
System.out.println(c);//返回结果: RED
(5) valueOf()方法: 这个方法和toString方法是相对应的,返回带指定名称的指定枚举类型的枚举常量。
Color.valueOf("BLUE"); //返回结果: Color.BLUE
(6) equals()方法: 比较两个枚举类对象的引用是否相同,每一个枚举值其实都是一个类的实例,这个接下来我会讲下。
因为这些api比较简单,就不写例子一一举例了,我们把美国总统这个例子改写下,让大家体会下使用枚举后有么有将我们第一部分的问题给解决:
public enum AmericanFamily {
teRuPu,baiDeng,obama,wccHaHa;
public static void ourPresident(AmericanFamily person) {
//大家看这块代码是不是以后都不用管改了哈
for (AmericanFamily human:AmericanFamily.values()){
if (human.equals(person)){
System.out.println("this is our president" +" "+ person);
}
}
}
public static void enumMethodTest(){
AmericanFamily teRuPu = AmericanFamily.teRuPu;
int ordinal = teRuPu.ordinal();
int i = teRuPu.compareTo(AmericanFamily.wccHaHa);
boolean same = teRuPu.equals(AmericanFamily.wccHaHa);
System.out.println(teRuPu);
System.out.println(ordinal);
System.out.println(i);
System.out.println(same);
}
public static void main(String[] args) {
AmericanFamily person = AmericanFamily.teRuPu;
ourPresident(person);
System.out.println("--------------------");
String chineseBoyWccNickName = "特朗普";
System.out.println("你看到了,我是中国小子wcc,可不是AmericanFamily成员,强行使用,我异常了");
// ourPresident(chineseBoyWccNickName);编译器检查就通不过了
// 不兼容的类型: java.lang.String无法转换为com.huawei.subtitle.portal.com.AmericanFamily
ourPresident(AmericanFamily.wccHaHa);
System.out.println("--------------------");
enumMethodTest();
}
}
3.枚举的原理
我们来看下,我们简单的创建一个枚举类,Java底层为我们做了什么,为了搞反编译,试了很多,因为Java的发展,很多反编译软件可能不支持Java的新的语法特性,或者支持不友好:
这里仅附上我使用的反编译工具下载地址:jad反编译下载,
**直接使用jad命令反编译,具体如下:**
- 1. 打开命令,将当前位置指向xjad所在目录
- 2. 使用Jad -sjava xxx.class
即可在同目录下生成xxx.java的反编译后的文件
public enum Day {
MONDAY, TUESDAY, WEDNESDAY,THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}
这里直接展示反编译的结果,具体使用不同工具反编译的差别,请查看我另一篇文章--暂时起名反编译工具使用吧嘻嘻。话不多说,附上反编译后代码,从反编译代码中看这个类的真实样子:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Day.java
package com.huawei.subtitle.portal;
public final class Day extends Enum {
public static Day[] values() {
return (Day[])$VALUES.clone();
}
public static Day valueOf(String s) {
return (Day)Enum.valueOf(Day, s);
}
private Day(String s, int i) {
super(s, i);
}
public static final Day MONDAY;
public static final Day TUESDAY;
public static final Day WEDNESDAY;
public static final Day THURSDAY;
public static final Day FRIDAY;
public static final Day SATURDAY;
public static final Day SUNDAY;
private static final Day $VALUES[];
static {
MONDAY = new Day("MONDAY", 0);
TUESDAY = new Day("TUESDAY", 1);
WEDNESDAY = new Day("WEDNESDAY", 2);
THURSDAY = new Day("THURSDAY", 3);
FRIDAY = new Day("FRIDAY", 4);
SATURDAY = new Day("SATURDAY", 5);
SUNDAY = new Day("SUNDAY", 6);
$VALUES = (new Day[] {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
});
}
}
首先我们从第一行
- 可以知道enum定义的枚举本身也是一个class,和我们常见的类使用上没有差别
- 这个类默认继承了Enum接口,由于Java的单继承,所以enum不能再继承别的类,同时由于继承,也就有了Enum接口中的已经实现的方法。
- 这个类被final修饰,也就是太监类,没有子类,不允许继承
接下来我们看成员变量和static代码块这块:
- static静态代码块的特性大家应该都知道哈,这里会随着类的加载使用而执行,且仅执行一次,为我们创建了Day这个对象的实例,共计7个,每一个枚举值都是一个对象的实例,这点要时刻记住,后面会用到
- 通过私有构造器,并调用父构造器初始化name和ordinal属性,name值默认就是我们定义的枚举名称的字符串形式,如"Monday"
- VALUS常量是一个Day类型数组,也是在静态代码块中进行的初始化,数组中的值为对应的枚举类对象
到此我们也就明白了,使用关键字enum定义的枚举类型,在编译期后,也将转换成为一个实实在在的类,而在该类中,会存在每个在枚举类型中定义好变量的对应实例对象,如上述的MONDAY枚举类型对应public static final Day MONDAY;,同时编译器会为该类创建两个方法,分别是values()和valueOf()。ok~,到此相信我们对枚举的实现原理也比较清晰啦。接下来我们来看下枚举的高级用法:
4.枚举的高级用法
向enum类添加方法与自定义属性和构造函数 重新定义一个日期枚举类,带有desc成员变量描述该日期的对于中文描述,同时定义一个getDesc方法,返回中文描述内容,自定义私有构造函数,在声明枚举实例时传入对应的中文描述,代码如下:
public enum DayDesc {
MONDAY("星期一", 1),
TUESDAY("星期二", 2),
WEDNESDAY("星期三", 3),
THURSDAY("星期四", 4),
FRIDAY("星期五", 5),
SATURDAY("星期六", 6),
SUNDAY("星期日", 7);
private String desc;//文字描述
private Integer code; //对应的代码
/**
* 私有构造,防止被外部调用,默认就是私有构造,且只允许私有构造
*
* @param desc
*/
private DayDesc(String desc, Integer code) {
this.desc = desc;
this.code = code;
}
/**
* 定义方法,返回描述,跟常规类的定义没区别
*
* @return
*/
public String getDesc() {
return desc;
}
/**
* 定义方法,返回代码,跟常规类的定义没区别
*
* @return
*/
public int getCode() {
return code;
}
public static void main(String[] args) {
for (DayDesc day : DayDesc.values()) {
System.out.println("name:" + day.name() +
",desc:" + day.getDesc());
}
}
}
输出结果:
枚举类中定义抽象方法,由于每个枚举值都是一个对象实例,在枚举类中定义的抽象方法,会由每个对应的枚举对象去实现,在第一个枚举值后面打上{},报红后alt+enter即可实现方法。先上代码,给大家看看
public enum Human {
MEN{
@Override
public void say() {
System.out.println("男:我可以约你出来看月亮么");
}
},WOMEN {
@Override
public void say() {
System.out.println("女:好哒,月亮不睡我们不睡");
}
};
public abstract void say();
public static void main(String[] args) {
Human.MEN.say();
Human.WOMEN.say();
}
}
探索:可能大家还是有疑问哈,这个就简单的使用Idea内嵌的反编译工具叫啥flower给大家看下,大概就知道原理了:
首先执行javac Human.java 对该类进行编译,我们会发现得到了三个class文件:
直接用Idea内置的反编译,默认已经嵌入,大家什么都不用干,直接打开.class文件即可。代码如下:
package com.huawei.subtitle.portal;
public enum Human {
MEN {
public void say() {
System.out.println("男:我可以约你出来看月亮么");
}
},
WOMEN {
public void say() {
System.out.println("女:好哒,月亮不睡我们不睡");
}
};
private Human() {
}
public abstract void say();
public static void main(String[] var0) {
MEN.say();
WOMEN.say();
}
}
---
enum Human$1 {
Human$1() {
}
public void say() {
System.out.println("男:我可以约你出来看月亮么");
}
}
---
enum Human$2 {
Human$2() {
}
public void say() {
System.out.println("女:好哒,月亮不睡我们不睡");
}
}
首先我们看到由于我们定义了抽象方法say(),所以Human这个类是抽象的,要不违背了Java基础有抽象方法的类肯定是抽象类,然后两个枚举对象,分别实现了Human这个类并重写了say()方法,看到这里大家应该懂了吧。好吧,今天就写到这里吧,我有点累了,准备去公司楼下健身房锻炼下。
有什么问题留言即可,基本每天都会登录我的博客。谁让我笨但是勤劳嘞嘻嘻
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构