摘要:
谈起枚举(enum),我脑海里立即呈现出一周有七天(Mon、Tues、Wed、Thur、Fri、Sat、Sun)和一年有四季(Spring、Summer、Autumn、Winter)的例子,除此之外,关于什么时候使用枚举、枚举支持调用哪些方法、如何用类实现枚举的功能以及枚举在实际项目中的应用,我是一无所知,经过一番学习后,本篇文章就从这几方面入手,揭开枚举(enum)的真实面纱,学习枚举(enum)的高级用法。
一、 什么时候使用枚举?
枚举不同于类和接口,类用class
标识,接口用interface
标识,而枚举用enum
标识,枚举名建议带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开。
以一年四季为例,学习枚举的基本用法,创建枚举SeasonEnum
,包括成员:SPRINT
、SUMMER
、AUTUMN
、WINTER
,代码如下:
public enum SeasonEnum {
SPRINT,SUMMER,AUTUMN,WINTER
}
调用的方式,代码如下:
SeasonEnum season=SeasonEnum.AUTUMN
printDesc(season);
判断两个枚举成员是否相等,用等号(==),代码如下:
private void printDesc(SeasonEnum season) {
if (SeasonEnum.SPRINT == season) {
Log.d(TAG, "printDesc: 春天,春来江水绿如蓝");
} else if (SeasonEnum.SUMMER == season) {
Log.d(TAG, "printDesc: 夏天,牧童骑黄牛,歌声振林樾");
} else if (SeasonEnum.AUTUMN == season) {
Log.d(TAG, "printDesc: 秋天,夕阳西下,断肠人在天涯");
} else {
Log.d(TAG, "printDesc: 冬天,遥知不是雪,为有暗香来");
}
}
运行demo,控制台输出:秋天,夕阳西下,断肠人在天涯
到此,我们学会了枚举的基本用法,接下来再看一周七天的例子,制定一周的工作计划,创建枚举WeeklyPlanEnum
,包括成员:MON
、TUES
、WED
、THUR
、FRI
、SAT
、SUN
,代码如下:
/**
* Created by https://www.teachcourse.cn on 2017/9/5.
*/
public enum WeeklyPlanEnum {
MON("周一:了解反编译apk文件的方法"),
TUES("周二:学习防止反编译的技术——代码混淆、压缩、优化"),
WED("周三:参考代码混淆官方文档"),
THUR("周四:学习混淆普通的包名、类名、方法名的方法"),
FRI("周五:学习混淆Android项目的资源文件、属性文件"),
SAT("周六:休息、睡懒觉"),
SUN("周日:打球");
private String plan;
WeeklyPlanEnum(String plan) {
this.plan = plan;
}
public String getPlan() {
return plan;
}
}
因为我们创建了带参数的构造方法,所以可以往MON
里面传入参数(也必须传参),制定好一周的工作计划,就老老实实按计划行事,调用getPlan()
方法,查看具体的工作内容,代码如下:
WeeklyPlanEnum friday=WeeklyPlanEnum.FRI;
String plan=friday.getPlan();
Log.d(TAG, "onCreate: "+plan);
运行demo,控制台输出:周五:学习混淆Android项目的资源文件、属性文件
从上面两个例子,你是否可以总结出一些共同的特点,是否明白什么时候使用枚举?如果还是不明白,我们再看一个例子:本学期,计算机科学与技术专业学院已经评选出三名优秀学生干部,学生信息包括:学号(num)、姓名(name)、专业(profession),现在需要根据学号查询学生的其他信息,使用枚举实现,代码如下:
/**
* Created by https://www.teachcourse.cn on 2017/9/5.
*/
public enum ExcellentStudentEnum {
ZHAOYUN(2011110924, "赵云", "网络工程方向"),
ZHANGFEI(2011110925, "张飞", "信息工程方向"),
LIUBEI(2011110926, "刘备", "数字媒体方向"),
NONE(0, "匿名", "你猜我学什么专业?");
private int num;
private String name;
private String profession;
ExcellentStudentEnum(int num, String name, String profession) {
this.num = num;
this.name = name;
this.profession = profession;
}
public int getNum() {
return num;
}
public String getName() {
return name;
}
public String getProfession() {
return profession;
}
public static String query(int num){
for (ExcellentStudentEnum student:values()){
if(student.getNum()==num)
return "姓名:"+student.getName()+","+
"专业:"+student.getProfession();
}
return "姓名:"+NONE.getName()+","+
"专业:"+NONE.getProfession();
}
}
调用如下方法,查询三好学生的其他信息,代码如下:
Log.d(TAG, "onCreate: "+ ExcellentStudentEnum.query(2011110924));
Log.d(TAG, "onCreate: "+ ExcellentStudentEnum.query(2011110927));
运行demo,控制台打印出:
姓名:赵云,专业:网络工程方向
姓名:匿名,专业:你猜我学什么专业?
从上面三个例子,可以总结:当你需要在一个可选区域内,选择某一个选项,获取该选项对应的属性或方法,可以考虑用枚举
例子一:一年有四个季节,是一个可选区域,可以选择其中一项,然后获取描述该季节的诗句。
例子二:一周有七天,是一个可选区域,选择其中一天,获取当天的工作安排。
例子三:评选出的三个优秀学生,是一个可选区域,可以根据学号查询某个学生是否是优秀学生干部,并返回学生的其他信息。
二、枚举支持调用哪些方法?
查看枚举支持调用哪些方法,在Android Studio快速按下两下shift
,查找Enum
类源码
所有使用enum
标识的,统一继承自Enum
抽象类,也就继承Enum
类的方法和属性,枚举支持调用的方法除了继承的方法外,还可以调用添加的方法。
查看JDK 1.8包的Enum
源码,如下图:
绿色符合标识public
修饰的方法,表示Enum
的子类可以调用的方法,现在来学习每个继承方法的作用,以第三个例子作为测试,看到Enum
抽象类的构造方法,代码如下:
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
开发者不能在外部包中调用protected
修饰的构造方法,所以可以猜测它可能在程序内部调用,实际上代码被编译器执行,为响应我们用enum
标识的枚举类型时调用Enum(String,int)
构造方法,而我们声明的成员:ZHAOYUN
、ZHANGFEI
、LIUBEI
,将被以字符串name的形式传入,ordinal从0开始依次分配,其他方法的使用情况如下:
-
name()
,返回枚举常量的名字,测试代码如下:Log.d(TAG, "test: "+ExcellentStudentEnum.LIUBEI.name());
运行demo,控制台打印:
D/SeasonInfoActivity: test: LIUBEI
-
ordinal()
,返回枚举序数,测试代码如下:Log.d(TAG, "test: "+ExcellentStudentEnum.LIUBEI.ordinal());
运行demo,控制台打印:
D/SeasonInfoActivity: test: 2
-
toString()
,返回枚举常量的名字,测试代码如下:Log.d(TAG, "test: "+ExcellentStudentEnum.LIUBEI.toString());
运行demo,控制台打印:
D/SeasonInfoActivity: test: LIUBEI
-
equals(Object other)
,比较传入的对象是否等于当前的枚举,测试代码如下:Log.d(TAG, "test: "+ExcellentStudentEnum.LIUBEI.equals("LIUBEI")); Log.d(TAG, "test: "+ExcellentStudentEnum.LIUBEI.equals(ExcellentStudentEnum.LIUBEI));
运行demo,控制台打印:
D/SeasonInfoActivity: test: false D/SeasonInfoActivity: test: true
-
hashCode()
,返回当前枚举常量的哈希码,测试代码如下:Log.d(TAG, "test: "+ExcellentStudentEnum.LIUBEI.hashCode());
运行demo,控制台打印:
D/SeasonInfoActivity: test: -2049136912
-
compareTo(E o)
,比较当前枚举对象和传入的对象的顺序对象,返回一个负整数、0或正整数Log.d(TAG, "test: "+ExcellentStudentEnum.LIUBEI.compareTo(ExcellentStudentEnum.ZHAOYUN)); Log.d(TAG, "test: "+ExcellentStudentEnum.ZHAOYUN.compareTo(ExcellentStudentEnum.ZHAOYUN)); Log.d(TAG, "test: "+ExcellentStudentEnum.ZHAOYUN.compareTo(ExcellentStudentEnum.ZHANGFEI));
运行demo,控制台打印:
D/SeasonInfoActivity: test: 2 D/SeasonInfoActivity: test: 0 D/SeasonInfoActivity: test: -1
-
getDeclaringClass()
,返回enum标识的枚举的Class名称,测试代码如下:Log.d(TAG, "test: "+ExcellentStudentEnum.LIUBEI.getDeclaringClass());
运行demo,控制台打印:
D/SeasonInfoActivity: test: class cn.teachcourse.enums.original.ExcellentStudentEnum
-
valueOf(Class<T>,String)
, 这是一个类方法,返回枚举常量的名字,测试代码如下:Log.d(TAG, "test: "+ExcellentStudentEnum.valueOf(ExcellentStudentEnum.LIUBEI.getDeclaringClass(),"LIUBEI")); Log.d(TAG, "test: "+ExcellentStudentEnum.valueOf(ExcellentStudentEnum.LIUBEI.getDeclaringClass(),"ZHANGFEI")); Log.d(TAG, "test: "+ExcellentStudentEnum.valueOf(ExcellentStudentEnum.LIUBEI.getDeclaringClass(),"ZHAOYUN"));
运行demo,控制台打印:
D/SeasonInfoActivity: test: LIUBEI D/SeasonInfoActivity: test: ZHANGFEI D/SeasonInfoActivity: test: ZHAOYUN
-
values()
,这是一个隐藏起来的类方法,因为并不继承Enum
类,同时在enum
标识的枚举,有没有声明该方法,却可以使用,所以说values()
是一个隐藏的类方法,还是很合适的,测试代码如下:for (ExcellentStudentEnum student : ExcellentStudentEnum.values()) { Log.d(TAG, "test: " +student.getProfession()); }
运行demo,控制台打印:
D/SeasonInfoActivity: test: 网络工程方向 D/SeasonInfoActivity: test: 信息工程方向 D/SeasonInfoActivity: test: 数字媒体方向 D/SeasonInfoActivity: test: 你猜我学什么专业?
-
valueOf(String)
,这是一个隐藏起来的类方法,返回指定枚举常量名字的枚举对象,测试代码如下:Log.d(TAG, "test: "+ExcellentStudentEnum.valueOf("LIUBEI").getNum()); Log.d(TAG, "test: "+ExcellentStudentEnum.valueOf("ZHAOYUN").getProfession());
运行demo,控制台打印:
D/SeasonInfoActivity: test: 2011110926 D/SeasonInfoActivity: test: 网络工程方向
到此,我们完成enum
标识的枚举支持调用方法的学习,其中包括继承自Enum
抽象类的1-8个方法,以及隐藏的两个类方法:values()
、valueOf(String)
,对枚举的用法有了更深的认识。
三、 如何用类实现枚举的功能?
阅读Enum
抽象类的构造方法说明,我们知道了编译器在编译期间,读取enum
关键字标识的枚举,内部调用Enum
构造方法,将成员名称name以字符串的形式传入,ordinal从0开始分配序数,最终转换普通的类。
结合TeachCourse个人的经验,枚举可以说是一个固定的模板,编译器负责转换,转换的过程在编译器内部发生,我们无法觉察和看到,更增加了枚举的神秘感,从本质上来说,枚举也是一个类,接下来我们用第三个例子测试,用类实现枚举的功能,代码如下:
/**
* Created by https://www.teachcourse.cn on 2017/9/5.
*/
public class ExcellentStudent {
public static final ExcellentStudent ZHAOYUN = new ExcellentStudent(2011110924, "赵云", "网络工程方向");
public static final ExcellentStudent ZHANGFEI = new ExcellentStudent(2011110925, "张飞", "信息工程方向");
public static final ExcellentStudent LIUBEI = new ExcellentStudent(2011110926, "刘备", "数字媒体方向");
public static final ExcellentStudent NONE = new ExcellentStudent(0, "匿名", "你猜我学什么专业?");
private int num;
private String name;
private String profession;
ExcellentStudent(int num, String name, String profession) {
this.num = num;
this.name = name;
this.profession = profession;
}
public int getNum() {
return num;
}
public String getName() {
return name;
}
public String getProfession() {
return profession;
}
public String query(int num) {
for (ExcellentStudent student : values()) {
if (student.getNum() == num)
return "姓名:" + student.getName() + "," +
"专业:" + student.getProfession();
}
return "姓名:" + NONE.getName() + "," +
"专业:" + NONE.getProfession();
}
public static ExcellentStudent[] values() {
return new ExcellentStudent[]{ZHAOYUN, ZHANGFEI, LIUBEI,NONE};
}
public static ExcellentStudent valueOf(String name) {
for (ExcellentStudent student : values()) {
if (name.equals(student.getName()))
return student;
}
return NONE;
}
}
除了没有继承Enum
抽象类的方法外,ExcellentStudent
实现了枚举自身的其他方法,同时添加两个类方法::values()
、valueOf(String)
,最终实现的功能和枚举差不多哈哈。
调用ExcellentStudent
的方法过程如下所示:
/*1、测试values()方法,返回当前类成员属性*/
for (ExcellentStudent student:ExcellentStudent.values()) {
Log.d(TAG, "testExcellent: " + student.getProfession());
}
/*2、测试valueOf(),返回指定名称的实例对象*/
Log.d(TAG, "testExcellent: "+ExcellentStudent.valueOf("赵云").getNum());
运行demo,控制台打印:
D/SeasonInfoActivity: testExcellent: 网络工程方向
D/SeasonInfoActivity: testExcellent: 信息工程方向
D/SeasonInfoActivity: testExcellent: 数字媒体方向
D/SeasonInfoActivity: testExcellent: 你猜我学什么专业?
D/SeasonInfoActivity: testExcellent: 2011110924
到此,完成了用类实现枚举的功能,对比了之后,你会发现枚举的简单、方便。
四、 枚举在实际项目中的应用
我们知道在一个可选区域内选择其中一个成员,或者从可选区域查询符合的一个成员,这个时候考虑使用枚举,阅读一些开源项目源码,多少会发现枚举的影子,比如图片加载框架:universal-image-loader,在ImageDownloader
这个接口内声明了一个枚举Scheme
,源码如下:
...
public enum Scheme {
HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN("");
private String scheme;
private String uriPrefix;
Scheme(String scheme) {
this.scheme = scheme;
uriPrefix = scheme + "://";
}
public static Scheme ofUri(String uri) {
if (uri != null) {
for (Scheme s : values()) {
if (s.belongsTo(uri)) {
return s;
}
}
}
return UNKNOWN;
}
private boolean belongsTo(String uri) {
return uri.toLowerCase(Locale.US).startsWith(uriPrefix);
}
public String wrap(String path) {
return uriPrefix + path;
}
public String crop(String uri) {
if (!belongsTo(uri)) {
throw new IllegalArgumentException(String.format("URI [%1$s] doesn't have expected scheme [%2$s]", uri, scheme));
}
return uri.substring(uriPrefix.length());
}
}
...
该枚举列举了universal-image-loader支持加载的几种类型图片,以http
开头的、https
开头的、file
开头的、content
开头的和assets
开头的,添加的ofUri(String)
,用于查询当前传入的图片地址是否属于可选区域内的一种,将加深我们对枚举的理解、使用。
枚举在实际项目中应用的例子,还有很多,比如TeachCourse项目中引用的友盟分享功能:SHARE_MEDIA
public enum SHARE_MEDIA {
QQ("qq") {
public boolean isSupportAuthorization() {
return true;
}
public int getReqCode() {
return 5658;
}
},
WEIXIN("weixin") {
public int getReqCode() {
return 10086;
}
public boolean isSupportAuthorization() {
return true;
}
};
private String a;
private SHARE_MEDIA(String var3) {
this.a = var3;
}
public boolean isSupportAuthorization() {
return false;
}
public int getReqCode() {
return 0;
}
}
网易即时通信demo Tab导航:MainTab
public enum MainTab {
RECENT_CONTACTS(0, ReminderId.SESSION, SessionListFragment.class, R.string.main_tab_session, R.layout.activity_session_list),
CONTACT(1, ReminderId.CONTACT, ContactListFragment.class, R.string.main_tab_contact, R.layout.activity_contacts_list),
CHAT_ROOM(2, ReminderId.INVALID, ChatRoomListFragment.class, R.string.chat_room, R.layout.activity_chat_room_tab);
public final int tabIndex;
public final int reminderId;
public final Class<? extends Fragment> clazz;
public final int resId;
public final int fragmentId;
public final int layoutId;
MainTab(int index, int reminderId, Class<? extends Fragment> clazz,
int resId, int layoutId) {
this.tabIndex = index;
this.reminderId = reminderId;
this.clazz = clazz;
this.resId = resId;
this.layoutId = layoutId;
this.fragmentId = index;
}
public static final MainTab fromReminderId(int reminderId) {
for (MainTab value : MainTab.values()) {
if (value.reminderId == reminderId) {
return value;
}
}
return null;
}
public static final MainTab fromTabIndex(int tabIndex) {
for (MainTab value : MainTab.values()) {
if (value.tabIndex == tabIndex) {
return value;
}
}
return null;
}
}
阅读源码后,你会发现枚举在项目中的应用涉及的用法基本一样,都包含在前面的例子中。
五、总结
本文从什么时候使用枚举、枚举支持调用的方法、用类实现枚举的功能和枚举在实际项目的应用四个方面,深入理解枚举的用法,加深了对枚举的认识,demo包含多个枚举的例子,可以方便下载阅读。
你可能感兴趣的文章
转载请注明出处: https://www.teachcourse.cn/2482.html ,谢谢支持!