本篇博客是笔者的第一篇博客,其实很早之前就有了写博客的想法。但是奈何万事开头难,一直没有下定决心。随着自己积累的知识不断增多,如果不进行总结,不进行思考归纳,有些知识会经常遗忘。对于某些难题,可能当时有了解决方案,但是如果不做笔记,不做记录,过不了多久就会忘记当时的思路,笔者深有体会。这会大大降低我们的工作效率。所以笔者下定决心,坚持写博客,通过博客来记录自己平时的知识积累,通过博客也让自己的知识能够分享给更多的人,同时对自己也是一种督促,更能发现自己对知识点理解的错误。今天第一篇博客,笔者想总结在Android中如何更优雅的定义常量。

在Java中定义常量的方式

1.在类中定义

public final class Constants {private Constants() {}public static final int A = 0;public static final int B = 1;public static final int C = 2;public static final int D = 3;
}

2.在接口中定义

public interface Constants {int A = 0;int B = 1;int C = 2;int D = 3;
}

那么这两种方式区别在哪里呢?
1.类定义常量,需要定义成final且定义一个private的构造方法, 这样做是为了不让其他类继承, 禁止实例化此类,调用时直接以"类.常量"的方式调用。
2. 接口中定义的"变量",其实就是常量,接口中的"变量"默认都是 “public static final"类型,即为常量, 因此接口可以省略"public static final"而直接写成 “type variable”。
3.用如上的类定义常量,不能实例化也不能被继承, 看起了完美无缺。
4.接口定义常量, 虽不能实例化, 确可以被其他类实现。因此有这么一种设计模式"The constant interface pattern”,所谓的"常量接口模式"就使用这些常量的类实现这个接口,以避免用类名修饰常量名。其实这是对接口的不良使用。接口中定义的常量应为所有类频繁使用的常量,但并不是每个类都使用了接口中定义的所有常量,因而导入了不必要的常量到这个类中,并且导入后这个类的子类也会导入基础的常量, 这样会导致混乱,应当避免此种用法。并且Effective Java中第19条也明确指出接口应该只被用来定义类型,他们不应该被用来导出常量。但是呢我们大可不必拘泥于接口这一含义他是对行为的一种抽象。如果该接口仅仅是作为定义常量使用,那么我们可以直接通过"接口名.变量名"调用。这样也就避免了实现改接口,从而将接口中不必要的常量导入其中,但是这也取决于我们合理的使用。
5. 在interface和class中定义相同的常量,interface生成的class文件比class生成的class文件会更小,而且更简洁, 效率更高。
小结:

  1. 不要使用"常量接口模式",此模式会导致类中的常量混乱,增加维护难度。
  2. 不要使用静态导入,import static **。我非常不赞同这种使用常量的方法,因为import static会导致可维护性下降,维护的人看代码时, 不是那么清楚或者不那么迅速的知道这个常量位于哪个文件中。 建议使用常量的地方直接 “接口.常量名” 的方式使用。
  3. 对于用是用interface定义常量还是使用class定义常量,看个人喜好,个人觉得interface定义常量更为优美, 代码更简洁,生成的class文件更小, JVM不要考虑类的附加特性, 如方法重载等, 因而更为高效。虽然这是一种反模式的用法, 很多人不喜欢这种用法,如果我们知道它的优缺点,延长避短, 也是无可厚非的。还有一点是不要把这种用法用成"常量接口模式" , 个人觉得"常量接口模式"确实是一种对interface的"pool use"。

使用枚举代替常量,简化工作!

需求:有一个任务(Task),服务端定义了一个int status来标记任务的状态,包括 未开始、进行中、已完成 这三个状态,分别用 0、1、2 来标记。我们拿到这个status之后,需要根据状态的不同,显示不同的文本(需求仍在挖掘中)。需求很明确,一般我们会这么做:
在全局的常量类中定义定义3个静态常量表示状态常量:

public class Constants {public static final int STATUS_UN_START = 0;public static final int STATUS_PROGRESSING = 1;public static final int STATUS_COMPLETED = 2;
}

接下来在strings.xml定义状态值:未开始、进行中、已完成

<resources><string name="status_un_start">未开始</string><string name="status_progressing">进行中</string><string name="status_completed">已完成</string>
</resources>

最后,在代码中拿到status之后,我们会这么做:

  switch (status) {case Constants.STATUS_UN_START:textView.setText(getResources().getString(R.string.status_un_start));break;case Constants.STATUS_PROGRESSING:textView.setText(getResources().getString(R.string.status_progressing));break;case Constants.STATUS_COMPLETED:textView.setText(getResources().getString(R.string.status_completed));break;default:break;}

这样写,所有的状态和状态值都统一了,规范的很,效果也不错,毫无违和感!
突然有一天,产品汪找到你:Hi,我们把文本前面的颜色设置成动态的吧,不同的状态颜色不一样,你根据效果图改一下吧…”。还好这个简单,分分钟的事。在colors.xml中,加几个色值:

<resources><color name="status_un_start">#3F51B5</color><color name="status_progressing">#303F9F</color><color name="status_completed">#FF4081</color>
</resources>

然后修改switch语句,这样:

switch (status) {case Constants.STATUS_UN_START:view.setBackgroundResource(R.color.status_un_start);textView.setText(getResources().getString(R.string.status_un_start));break;case Constants.STATUS_PROGRESSING:view.setBackgroundResource(R.color.status_progressing);textView.setText(getResources().getString(R.string.status_progressing));break;case Constants.STATUS_COMPLETED:view.setBackgroundResource(R.color.status_completed);textView.setText(getResources().getString(R.string.status_completed));break;default:break;}

So easy,很快解决了问题!慢慢的,随着开发的深入,你发现许多地方需要这么显示,于是就封装了一个自定义ViewGroup,一切都朝着好的方向发展。但除此之外,在其他地方,也需要判断这个状态值,以需要获取该状态下的属性,万一哪天产品汪跟你说:“我要多加一个状态,未知(UN_KONW)。服务端和iOS已经同步了,你这边赶紧吧”,意味着之前涉及状态判断的都需要找到加上,岂不傻眼了?
所以,大多数人还是会选择将这一部分写到全局的静态方法中,这样加字段、加状态再也不怕了,在对应文件中加几个常量【status、颜色、状态文本】,改一下方法就搞定!由于这块不是本文重点,就不贴代码了。至此,我们的程序一步步完善了,一些基本的套路已经无法难倒我们了,完美!功能基本实现了,但是回过头来想想,这样做并不优雅!一方面需求变动时我们还是需要改很多地方,另一方面我们并不能很直观的看出状态值status与状态文本、色值之间的关系,比如 0代表未开始,颜色为黄色。当然,可以向上文一样,依靠命名规范去解决这个问题。可是,在命名规范的基础上,有没有更好的办法进一步强化他们之间的关系呢?当然有!接下来,就到了今天的主题,一起来借助枚举完成这一任务。

—————–枚举登场—————————–

刚开始,我们可以定义一个枚举类,这么写

public enum TaskStatus {UN_START(0, "未开始"),PROGRESSING(1, "进行中"),COMPLETED(2, "已完成");private int status;private String desc;TaskStatus(int status, String desc) {this.status = status;this.desc = desc;}public int getStatus() {return status;}public void setStatus(int status) {this.status = status;}public String getDesc() {return desc;}public void setDesc(String desc) {this.desc = desc;}public static TaskStatus getStatus(int status) {for (TaskStatus taskStatus : values()) {if (status == taskStatus.status) {return taskStatus;}}return UN_START;}
}

用的时候简直不要太简单(status是从服务端获取的状态值):

 TaskStatus taskStatus = TaskStatus.getStatus(status);textView.setText(taskStatus.getDesc());

然后后来产品说加一个色值,你很坦然就加上了。这么写:

public enum TaskStatus {UN_START(0, "未开始", "#3F51B5"),PROGRESSING(1, "进行中", "#303F9F"),COMPLETED(2, "已完成", "#FF4081");private int status;private String desc;private String color;TaskStatus(int status, String desc, String color) {this.status = status;this.desc = desc;this.color = color;}public int getStatus() {return status;}public void setStatus(int status) {this.status = status;}public String getDesc() {return desc;}public void setDesc(String desc) {this.desc = desc;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}public static TaskStatus getStatus(int status) {for (TaskStatus taskStatus : values()) {if (status == taskStatus.status) {return taskStatus;}}return UN_START;}
}

用的时候毫不影响:

 TaskStatus taskStatus = TaskStatus.getStatus(status);textView.setText(taskStatus.getDesc());view.setBackgroundColor(Color.parseColor(taskStatus.getColor()));   

重点来了,该到加未知(UN_KNOW)状态的时候了,按照上文的做法,我们要改、加的东西很多,但现在只需要加一行代码,是的,一行代码:

public enum TaskStatus {UN_KNOWN(-1, "未知", "#34D6B5"),UN_START(0, "未开始", "#3F51B5"),PROGRESSING(1, "进行中", "#303F9F"),COMPLETED(2, "已完成", "#FF4081");private int status;private String desc;private String color;TaskStatus(int status, String desc, String color) {this.status = status;this.desc = desc;this.color = color;}public int getStatus() {return status;}public void setStatus(int status) {this.status = status;}public String getDesc() {return desc;}public void setDesc(String desc) {this.desc = desc;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}public static TaskStatus getStatus(int status) {for (TaskStatus taskStatus : values()) {if (status == taskStatus.status) {return taskStatus;}}return UN_KNOWN;}
}

就是这么简单!同时,这么写的话,就不需要在 string.xml、colors.xml、常量类中加一堆值了,直接放在枚举类中很直观有木有?!当然,有时候我们需要在某个地方使用常量来判断,下图是常见的做法。现在我们把状态值写到枚举里面了,还能这么直观的用吗?答案是肯定的!

        //使用常量if(status==Constants.STATUS_PROGRESSING){//do something}//使用枚举if(status=TaskStatus.PROGRESSING.getStatus()){//do something}

声明一点,我所说的使用枚举替换常量,是针对类似于“常量之间存在关联”的情况,如状态值status与状态文本、色值,此时使用枚举能大大简化我们的工作,并不是说以后所有常量都写成枚举,毕竟官方是不推荐使用枚举的:
Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
但也只是尽量避免使用!所以在实际开发中,还需要根据实际使用场景去斟酌,杜绝滥用。像本文描述的场景,建议使用,战斗力翻倍!

Android使用枚举注解代替枚举

需求:我们仅仅需要根据一组状态中的某一状态执行相应的操作,不需要多个常量之间相互对应。你自然会想到枚举的简化版:

public enum TaskStatus {UN_KNOW,UN_START,PROGRESSING,COMPLETED
}

然后定义了一个任务辅助类:

import com.ruicb.enumdemo.TaskStatus;
public class TaskHelper {public static void doSth(TaskStatus status){switch (status){case UN_KNOW://do somethingbreak;case UN_START:break;case PROGRESSING:break;case COMPLETED:break;}}
}

然后如此调用:

TaskHelper.doSth(TaskStatus.COMPLETED);

这样使用枚举,十分优雅:保证了类型安全:调用者无法随意传一个 int 值;代码可读性非常高;但是…枚举对于性能的损耗,事实上你也知道,官方是不建议使用枚举的,这个在文章开头简单说了。想详细了解的,可以参考该博文:http://blog.csdn.net/hp910315/article/details/48975655。总之,与静态常量相比,枚举会增大应用程序编译后的 dex 文件,同时应用在运行时的内存占用也会升高。在资源有限的移动设备上,大量的使用枚举无疑是致命的。
替代方案
既然如此,我们不使用枚举不就行了吗?反正只是简单的单一状态,我单独定义一个常量类,实现分组:

/*** 任务状态常量*/
public class TsakContants {public static final int UN_KNOW = -1;public static final int UN_START = 0;public static final int PROGRESSING = 1;public static final int COMPLETED = 2;
}

然后修改 doSth() 方法:

public class TaskHelper {public static void doSth(int status){switch (status){case TsakContants.UN_KNOW://do somethingbreak;case TsakContants.UN_START:break;case TsakContants.PROGRESSING:break;case TsakContants.COMPLETED:break;}}
}

调用起来也没有区别:

TaskHelper.doSth(TsakContants.COMPLETED);

其次代码可读性很差,IDE 只是提示传入 int 类型的参数。此时如果不看方法体,调用者根本不知道该传什么值。那么问题来了?在一组常量的情况下,我们使用枚举太重,使用常量不安全、可读性差,我们该怎么办?当然是使用注解了!哈哈,兜了一圈终于轮到注解,是时候为其正名啦。

@IntDef(定义int型) @StringDef(定义String类型) 是Android提供的注解@IntDef({TaskStatus.UN_KNOW,TaskStatus.UN_START,TaskStatus.PROGRESSING,TaskStatus.COMPLETED
})
@Retention(RetentionPolicy.SOURCE)
public @interface TaskStatus {int UN_KNOW = -1;int UN_START = 0;int PROGRESSING = 1;int COMPLETED = 2;
}

修改 doSth() 方法:

public class TaskHelper {public static void doSth(@TaskStatus int status){switch (status){case TaskStatus.UN_KNOW://do somethingbreak;case TaskStatus.UN_START:break;case TaskStatus.PROGRESSING:break;case TaskStatus.COMPLETED:break;}}
}

可以看出,doSth() 方法的参数是 int 类型的,但是使用 @TaskStatus 进行了注解,这样外界就无法传递 TaskStatus 之外的成员作为参数了。
调用

TaskHelper.doSth(TaskStatus.UN_START);

在调用时,IDE 会提示 @TaskStatus int status,提醒我们传入 TaskStatus 类型的值。同时,调用者如果再随便传入一个 int 值,虽然可以运行,但代码会爆红,lint 检查将会给与警告。

总结

现在看完本篇文章之后再思考,你应该清楚什么场景下该使用那种方案了吧。在类中定义常量可能更规范一点,但是书写起来以及class文件大小也要考虑。在接口中定义常量更加方便,class文件也相对较小,但是避免写成常量接口模式,要合理使用。所以两者的取舍看个人喜好吧,没有谁对谁错。此外如果需要定义一组常量,如果我们使用枚举,阅读性很好,但是性能缺损。如果单纯的使用常量,类型安全、可读性差,这时候我们可以考虑Android中提供的枚举注解,相当的优雅。此外如果定义多组常量,我们可以考虑枚举定义,这样阅读性和扩展性也是非常好的,虽然有性能缺损,但是我们也要度之。在性能还行的情况下,考虑使用。

Android中如何优雅的定义常量相关推荐

  1. Android 中封装优雅的 MediaPlayer 音频播放器,支持多个播放器

    Android 中封装优雅的 MediaPlayer 音频播放器,支持多个播放器实例的示例: public class AudioPlayer implements MediaPlayer.OnPre ...

  2. [翻译]PHP中define()和const定义常量的区别

    在PHP中可以通过define()和const两种方式定义常量 可是在开发中我们应该什么时候用define()定义常量,什么时候用const定义常量? 这两种方式定义常量的主要区别是什么? 从5.3版 ...

  3. android 比较符合android构架,优雅的定义全局变量

    android.app.Application api 是这样描述的. Base class for those who need to maintain global application sta ...

  4. 【Android】 Android中适配器简介

    1. BaseAdapter的使用实例 BaseAdapter baseAdapter = new BaseAdapter() {@Overridepublic View getView(int po ...

  5. 从零开始学android:Android中的基本控件(上)

    从零开始学android:Android中的基本控件(上) 本章内容较多,下面只贴代码,大家只需要贴到自己eclipse里就知道作用^^! View组件简介 Android中的View组件包含了几乎所 ...

  6. android 触摸 唤醒屏幕,Android中屏幕保持唤醒

    1.锁的类型 PowerManager中各种锁的类型对CPU .屏幕.键盘的影响: PARTIAL_WAKE_LOCK : 保持CPU 运转,屏幕和键盘灯有可能是关闭的. SCREEN_DIM_WAK ...

  7. 如何使方法行数达到最优、常量与变量如何优雅的定义?

    温馨提示:阅读本文需要1-2分钟(无代码) 来源:<码出高效Java开发手册> (ps:以后文章都会以这种形式发布,希望大家能适应) 今天,我们来解决一个问题: 在日常编码中,如何使方法行 ...

  8. android手机 scala环境,在Android中使用Scala中的Java常量

    我在Android上的Scala中开发了一个奇怪的问题.我正在使用sbt android插件,现在我正在尝试让内容提供商工作,但是...在Android中使用Scala中的Java常量 刚刚用Scal ...

  9. java中的关键字static(静态变量)和final定义常量

    package point;class Point {int x = 0;int y = 0;static int z = 100; // 定义静态变量z,类变量static final double ...

最新文章

  1. 带你重读Youtube深度学习推荐系统论文,惊为神文
  2. oracle pl sql注意问题,Oracle PL/SQL编写PL/SQL代码的注意事项
  3. FileReader对象和FormData对象
  4. RabbitMQ安装方法 安装完成已验证方法步骤可行性
  5. linux QT 结束当前进程_软件特攻队|为什么 Qt 成为 c++ 界面编程的第一选择?
  6. 服务器显示配置命令,linux查看服务器配置命令
  7. 看《Linux入门讲座》随记
  8. 萌宠主题页面设计灵感
  9. 大二第二学期周学习进度总结(十三)
  10. android 获取service 实例化,在Activity中,如何获取service对象?a.可以通过直接实例化得到。b.可以通过绑定得到。c.通过star - 众答网问答...
  11. 【软件开发知识积累】深入理解HTTP 原理基础与变迁
  12. 在校外,如何免费下载知网上的文献论文的方法
  13. 解决在iOS9上安装的软件显示未受信任的企业级开发者
  14. python爬取云顶之弈官网排名数据
  15. ai描边工具怎么打开_ai描边面板怎么调出来? ai找不到描边面板的解决办法
  16. XXL-JOB任务调度中心后台默认弱口令漏洞
  17. 论文解读:Prefix-Tuning: Optimizing Continuous Prompts for Generation
  18. Python Web 框架:Tornado
  19. 初探无水印信息图片加密技术
  20. 仙人掌之歌——投石问路(2)

热门文章

  1. mongodb基本命令
  2. Oracle数据类型转换
  3. 鹰眼海量级分布式日志系统上云的架构和实践
  4. MyBatis-Plus 多表查询
  5. “智能协同 赋能发展” 第三届智能协同云技术与产业发展高峰论坛召开
  6. C语言--结构体初始化
  7. OFDM系统的基本原理以及PAPR问题
  8. 【01Studio MaixPy AI K210】11.摄像头
  9. 微信已停止访问该网页的原因及预防域名被微信封杀的措施
  10. 语言编译程序若按软件分类则是属于()