#[Android]使用 Typedef 注解代替枚举
假设我们现在需要通过代码实现这个功能:
1. 定义 1 个变量 x
2. 定义几个常量,将其中一个常量的值赋给 x
3. 根据 x 的值来执行不同的方法
那么我们可以这样做:
~~~
public class Main{
//这里定义一些关于“星期几”的常量
public static final int SUNDAY = 0;
public static final int MONDAY = 1;
public static final int TUESDAY = 2;
public static final int WEDNESDAY = 3;
public static final int THURSDAY = 4;
public static final int FRIDAY = 5;
public static final int SATURDAY = 6;
//这个就是我们的变量 x
private int x ;
public int getX() { return x; }
public void setX(int x){ this.x = x; }
//main 方法
public static void main(String[] args){
Main obj = new Main();
//通过set方法来为 x 赋值,
obj.setX(SUNDAY);
int today = obj.getX();
//根据不同的 x 的值,来输出不同的内容
switch(today){
case SUNDAY:
System.out.println("Today is Sunday");
break;
case MONDAY:
System.out.println("Today is Monday");
break;
case TUESDAY:
System.out.println("Today is Tuesday");
break;
case WEDNESDAY:
System.out.println("Today is Wednesday");
break;
case THURSDAY:
System.out.println("Today is Thursday");
break;
case FRIDAY:
System.out.println("Today is Friday");
break;
case SATURDAY:
System.out.println("Today is Saturday");
break;
default:
break;
}
}
}
---------------------
run result:
Today is Sunday
~~~
编译器没有警告,程序运行后,因为我们传入的是 `SUNDAY` ,所以这里输出了 "Today is Sunday" ,我们成功实现了要求的功能,任务完成了。但是仔细看过以后,代码仍然存在问题:`obj.setX(SUNDAY)` 中传入的值可以是任何值,假设我们这样写 `obj.set(100)`,编译器可以正常编译,但是在 `switch` 语句中就不会执行任何 `case` 了。为了限定传入的值,Java 为我们提供了解决这一问题的方法:**Enum** ,就是**枚举**。
让我们使用枚举来重写原来的代码:
~~~
public class Main{
//定义枚举类
public enum WeekDays{
SUNDAY , MONDAY , TUESDAY , WEDNESDAY , THURSDAY , FRIDAY , SATURDAY
}
//定义我们的 x ,这里的 x 的类型已经从 int 变为了 WeekDays
private WeekDays x ;
public WeekDays getX() { return x; }
public void setX(WeekDays x){ this.x = x; }
//main 方法
public static void main(String[] args){
Main obj = new Main();
obj.setX(WeekDays.SUNDAY);
WeekDays today = obj.getX();
//同样使用 switch 来控制输出不同的语句
switch(today){
case SUNDAY:
System.out.println("Today is Sunday");
break;
case MONDAY:
System.out.println("Today is Monday");
break;
case TUESDAY:
System.out.println("Today is Tuesday");
break;
case WEDNESDAY:
System.out.println("Today is Wednesday");
break;
case THURSDAY:
System.out.println("Today is Thursday");
break;
case FRIDAY:
System.out.println("Today is Friday");
break;
case SATURDAY:
System.out.println("Today is Saturday");
break;
default:
break;
}
}
}
--------------
run result :
Today is Sunday
~~~
我们既实现了效果,又防止了 `obj.setX()` 方法中传入错误的值。现在当你写 `obj.setX(100)` 的时候,编译器就会发出警告。枚举确实很好用,但是在 Android 中,枚举又会造成新的问题:
因为 Enum 是一个完整的 Java 类,每个枚举的变量都是 Enum 类的对象,比方说我们刚才的代码中:
~~~
System.out.println(WeekDays.SUNDAY instanceof Enum);
-----------
run result:
true
~~~
所以使用枚举比我们一开始使用的 `public static final int SUNDAY = 0;` 形式消耗更多的内存。不过即使是在 Android 老的设备上(系统<=2.2),一些有关枚举性能的问题已经通过 JIT 编译器([简单了解 JIT]) 解决了,所以我们其实可以在 Android 上使用枚举。但是如果你的应用程序需要消耗很多的内存,或者你所开发的是游戏应用程序,那我们最好还是使用 `static final int` 常量的形式,不过正如开头所说,它容易出错,且没有办法限制传入的值。好在 Android 提供了一个支持库:
`com.android.support:support-annotations`
里面包括了很多好用的注解,也包含了可以解决我们目前的问题的注解:`IntDef` 和 `StringDef` ,它们可以代替枚举,可以在编译时就发现错误,并警告,现在我们来演示一下用法:
* 之前一直在写纯 Java 代码,所以可能使用的是 Android Studio 中的 Java 模块,记得切换到正常的 Android App 模块。
* 可根据[Android文档:导入支持库]
* 定义我们需要的常量:
~~~
public static final int SUNDAY = 0;
public static final int MONDAY = 1;
public static final int TUESDAY = 2;
public static final int WEDNESDAY = 3;
public static final int THURSDAY = 4;
public static final int FRIDAY = 5;
public static final int SATURDAY = 6;
~~~
* 声明注解**@WeekDays**,并为 **@IntDef** 传入这些常量,和枚举的做法有些像:
~~~
//这里使用 @IntDef 和 @Retention 来定义一个注解 @WeekDays
@IntDef({SUNDAY, MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY})
@Retention(RetentionPolicy.SOURCE)
public @interface WeekDays {}
~~~
* 声明 x 变量
~~~
@WeekDays
int x ;
~~~
* get/set 方法也使用 @WeekDays
~~~
public void setX(@WeekDays int x) { this.x = x;}
@WeekDays
public int getX() { return x;}
~~~
* 测试一下
~~~
@WeekDays int x ;
x = 60;//编译器会发出警告。
x = SUNDAY;//编译正常。
这里还有个问题要注意:
@WeekDays
int y =100;
//按刚才的说法,这里应该会报错,因为用 @WeekDays 修饰了变量 y ,而 100 明显不是 @IntDef 里所设定的值,
//但是经过测试,这里并不会报错。
//所以声明变量时,进行赋值是可以的。
//但是如果再想给 y 赋值,比如 y = 50; 这样的话是不行的。
//通过 setX 的方法赋值,也必须是 @IntDef 中所限定的值。
setX(60);//编译器会发出警告。
setX(SUNDAY);//编译正常。
//通过 getX 的方法 return 的值,也必须是 @IntDef 中所限定的值。
@WeekDays public int getX() { return x ;}//编译正常,因为 x 已经用 @WeekDays 修饰了。
@WeekDays public int getX() { return 60;}//编译器会发出警告。
@WeekDays public int getX() { return SUNDAY;}//编译正常。
@StringDef 和 @IntDef 的用法一样,只是适用的数据类型不同,所以就不演示用法了。
~~~
* 关于 @IntDef 的 flag 的含义
需要先了解 **Java 位运算符**,例如: >> , & , |
~~~
//这是 @IntDef 的源码,可以看到,我们还可以为 flag 赋值,默认是 false 的。
@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {
/** Defines the allowed constants for this element */
long[] value() default {};
/** Defines whether the constants can be used as a flag, or just as an enum (the default) */
boolean flag() default false;
}
//那我们定义 flag=true 作用是什么呢?
@IntDef(flag=true, value = {SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY})
//举个例子
public void setX(@WeekDays int x){}
//当 flag = false 的时候:
//注意:这里使用的 | ,&,^ 是位运算符,要和逻辑运算符区分开,不要搞混了。
setX(SUNDAY | MONDAY)//编译报错
//当 flag = true 的时候:
//我们可以将 value 使用位运算符计算,计算结果赋值给被 @WeekDays 修饰的变量
setX(SUNDAY | MONDAY)//编译通过
~~~
* 小问题
经过阅读 **关于 @IntDef 的 flag 的含义** ,我建议你可以先猜一下,**@StringDef** 可以设定类似的 `flag=true`或者是 `flag=false`吗?看一下 **@StringDef** 的源码,证实你的猜想。
##推荐扩展阅读
由于 Android 注解支持库也提供了其他很多注解,本篇文章介绍到的内容非常少,只能算是引出了 `com.android.support:support-annotations` 所以大家可以先学习注解的知识,然后再学习支持库中的注解的用法,共勉!
[Android 文档:使用注解改进代码检查](https://developer.android.com/studio/write/annotations.html)
[还在用枚举?我早就抛弃了!(Android 注解详解](http://www.jianshu.com/p/1fb27f46622c)
[Android文档:导入支持库]:https://developer.android.com/studio/write/annotations.html#adding-library
[简单了解 JIT]:http://blog.dontcareabout.us/2013/03/jit-compiler.html
[Java 枚举和 Android Typedef 注解]:https://noobcoderblog.wordpress.com/2015/04/12/java-enum-and-android-intdefstringdef-annotation/