对于Java这门语言,我的态度应该是讨厌多于喜欢。整个大学我只上过一门Java的选修课算是有了Java的入门知识,之后用得更多的则是C#。在研究生阶段也是以C++和C#为主。直到工作需要做Android开发才拾起Java作为主打开发武器,所以很多Java的知识对我而言可能只是懂一个肤浅的表面。好在身边有很多的大神总是愿意耐心提供帮助,引导我进入Java新的知识领域,再加上网上的一些搜索和补充,让我的知识变得更加全面。
比如WeakReference
在一定程度上帮助解决了Activity和Fragment泄露的问题;多线程时要注意的同步问题;内部类调用其外部类的私有方法和变量产生了合成方法(Synthetic Methods)造成的性能影响和Android的dex溢出。
最近又得益大神们的教诲,被反复强调一定要善用Java的注解(annotation)。这让我感觉,当我还在思索要怎么写代码的时候,别人已经在考虑着怎么告诉编译器和其他开发者他们写了什么东西。
不过在坚持使用Annotation数月之后,我也深深体会到了它对写代码带来的好处而停不下来。比如方法上加个@Override
就可以大大减少把重载方法名字的或者变量参数写错的几率,同时在浏览文件时一眼就能看出哪些是新增方法,哪些来自父类;在对象变量上放一个@Nullable
,IDE就能帮助我们快速指出哪里有可能会出现NullPointerException
这个恼人却又简单能解决的问题,不用等到运行时异常被抛出了才去解决;而加上@NonNull
就可以随心所意的直接使用变量而无需加if语句了检查null
。
还有在Android开发中最常用到的那些指示资源的标示(Resource Id),全部都是整型数,其实有很多的类型如R.id
、R.string
、R.drawable
。如果不小心,就很容易弄错而得到了非所需的结果。特别是R.style
和R.styleable
两个常常会被IDE的自动补全所迷惑;还有整型的Color
值和R.color
也很容易出错。现在只要在变量前加上对应类型的Android Support Annotation,就可以大大地规避这些问题出现的几率。
虽然对于有经验的开发者来说这些小问题都会自动避免,再不小心运行时也会发现问题。但不是所有接触和使用这些代码的人都能一样的小心。万一别人的失误落到自己头上修复,也只能无奈抱怨一句:这人怎么这么不小心呢。
简单的加上一些annotation,那么一旦在代码中出现错误的使用,IDE就会高亮显示错误的地方让人一目了然,也能及时修改这些错误,不用等到运行后才去debug:
这些Android的Support Annotation如同C/C++的typedef一般让整型有了新的别名,同时也保持了java赋值类型的要一致的特征。
目前在开发中用到的annotation基本来自三类:
- 语言和系统
这部分主要指Java自带的Annotation和Android的Support Annotation。(在最近的Google I/O大会上,还展示了更多新增的Support Annotation,相信在Android Studio 1.3发布时我们也会把这些注释添加到代码中)
- 第三方库
这部分用得其实比较少,但更有针对性,比如LoganSquare库用来生成JSON模块的注释;JUnit4中标示测试的@Before
、@Test
、@After
。还有我们转用jetbrains下的@NotNull
和@Nullable
,而非Android提供的注解,虽然Android Studio会抱怨参数注解不一致,但是在其他工具下jetbrains的注解会对我们更有帮助。
- 自定义
这部分大多是扩展于Android的Support Annotation。作为性能的考虑代码中很少使用enum类型,而是直接使用整型,虽然就失去了类型保证,但通过@IntDef又重新保证了在编译期就发现类型不匹配。
对于如何定义和使用Java和Android的annotation,官方网站和各种指南都有详细的介绍,文末引用列表里也有相关的链接。这里我想分享公司大神里教我的一种定义@IntDef
的方式。当大神告诉我可以用这种方式定义时,我深深体会到大神对Java的理解是要有多透彻才会想到这么偏门却又简便的方式。
先来看看Java文档里告诉我们申明自定义的annotation的方式:
[codesyntax lang=”java” lines=”normal”]
@Retention(CLASS) @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; }
[/codesyntax]
在annotation里类似接口(interface)申明一些偏向于只读器的方法,也可以为这些方法提供默认的返回值。
然后Android的开发文档中教我们应该这样定义@IntDef
:
[codesyntax lang=”java” lines=”normal”]
@IntDef({CHARACTER_COUNTER_MODE_NONE, CHARACTER_COUNTER_MODE_NORMAL, CHARACTER_COUNTER_MODE_COUNTDOWN}) @Retention(RetentionPolicy.SOURCE) public @interface CharacterCounterMode {} public static final int CHARACTER_COUNTER_MODE_NONE = 0; public static final int CHARACTER_COUNTER_MODE_NORMAL = 1; public static final int CHARACTER_COUNTER_MODE_COUNTDOWN = 2;
[/codesyntax]
定义一些整型常量,再定义一个annotation同时用指定了这些常量的@IntDef
来注解那个新的annotation。
不过大神告诉我,其实可以把定义改成如下的方式:
[codesyntax lang=”java” lines=”normal”]
@IntDef({CharacterCounterMode.NONE, CharacterCounterMode.NORMAL, CharacterCounterMode.COUNTDOWN}) @Retention(RetentionPolicy.SOURCE) public @interface CharacterCounterMode { int NONE = 0; int NORMAL = 1; int COUNTDOWN = 2; }
[/codesyntax]
把整型变量去掉前缀放在annotation中,这样子其定义看起来就非常的紧凑,添加新的类型时也不用再把前缀复制过来。使用时就变成了counter.setCharacterCounterMode(CharacterCounterMode.NORMAL)
效果与之前一致,但少了那些下划线则让代码看起来更加的整洁,也可以更好的利用自动补全,因为可以先输入注解名,再慢慢选值。另外代码看起来也更像是在用enum。
也许只是因为我对Java的了解还太少才显得无知,但是当我在网上搜索该定义方式时,却没找到任何例子是在annotation中直接定义变量的,因为annotation本来不是这么用的。最后我只在Java对于annotation的指南中看到下边这句话,我才接受了这样的方式:
Annotation types are a form of interface
另外,查看Java语言规范,也能看到annotation的解释是放在interface的章节里的。
按照前边那句话的表述,“注解(annotation)类型是接口(interface)的一种形式”,annotation就应该拥有interface的所有特性,于是乎在annotation里面定义变量也就完全合情合理了。因为我们知道在interface里面定义的变量自动默认是public static final
,同理annotation里定义的变量也应该是常量。同样的,这也是为什么有时候会看到enum被定义在annotation内部,因为那也是符合语法的。
虽然有些annotation在编译后不会出现在class文件中,但因为编译器对常量的处理也是直接将常量变量使用的地方替换成实际的常量,所以一点也不会影响到最后的运行。
感谢大神让我也掌握了这项技能,也让我对annotation有了更多的了解。
其实本文中提到的annotation的用法还只局限于编译期的检测功能,其实还有利用APT来实现代码的生成功能,希望以后在更深入地了解之后再写一篇总结分享。
3 Comments