Java 中的注解(上)

最近因为在项目中用到了很多注解,所以找时间看了相关的文章,然后大致对注解有了一个浅显的认识,所以在此记录一下,以防以后再需要的时候再去 Google。

提到注解,我们见到最后的应该就是@Override了,每次我们重写父类的方法的时候都会加上这个注释,它是不是必须要的呢?答案是否定的,你也可以直接重写父类的方法不加@Overvide注解,不仅是因为这样代码的可读性不是很好,更重要的是@Override有时候可以帮你避免很多错误,具体可以参考:Effective Java 第36条:坚持使用 Override 注解,幸运的是 Android Studio 会自动为我们添加@Override,除非你非得要手动删掉。

其实在Java 1.5 添加注解的时候,除了@Override,还有两个我们也会经常用到的:@Deprecated@SuppressWarnings,下面我们以@Override为例,看下定义一个注解都需要哪些知识:

1
2
3
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}

其实也就三行代码,而且其中最重要的也就最后一行,我们看到用@interface关键字标记的Override,那上面那两个注解是做什么的呢?它们就是元注解,除了@Target@Retention,还有两个用的不是很多的@Inherited@Documented,接下来会着重介绍前两个:

@Target

我们知道@Override只能作用于方法,就是因为:

1
@Target(ElementType.METHOD)

@Target就是确定注解的使用方式,它具体的值可以有:

  • ElementType.ANNOTATION_TYPE(注:修饰注解)
  • ElementType.CONSTRUCTOR
  • ElementType.FIELD
  • ElementType.LOCAL_VARIABLE
  • ElementType.METHOD
  • ElementType.PACKAGE
  • ElementType.PARAMETER
  • ElementType.TYPE(注:任何类型,即上面的的类型都可以修饰)

如果定义的注解没有@Target,那么理论上它是可以出现在代码的任何地方。

@Retention

继续拿@Override举例子,因为我们只需要@Override在编译期生效,所以:

1
@Retention(RetentionPolicy.SOURCE)

这样@Override只会保留在源码阶段,不会出现在.classs文件中,更不会在运行时可见。

@Retention除了SOURCE,还有其他两个值可选:

  • RetentionPolicy.CLASS:会在.class文件中保留注解,可以做一些字节码相关的操作,如果注解没有定义Rentention,那么默认就是它。
  • RetentionPolicy.RUNTIME:表示在运行时仍然可以获取这个注解,通常在做一些反射相关的操作时,会选择这种保留策略。

@Documented 和 @Inherited

@Documented是和生成 JavaDoc 文档相关的,一般情况下是用不到的,但是你应该知道它的存在。

@Inherited用于表示这个这个注解是否会被自动继承,如果想要被注解标记的父类它所有的子类都自动带有这个注解,就可以使用@Inherited,下面举个例子:

1
2
3
4
5
6
7
8
9
10
@Inherited
public @interface MyAnnotation {
}
@MyAnnotation
class SuperClass {
}
class SubClass extends SuperClass {
}

可以看到父类SuperClass@MyAnnotation注解,而且这个注解被@Inherited,是自动继承的,所以子类SubClass也会自动带上这个注解。

自定义注解

了解完一些注解的基本知识以后,我们就可以自定义自己的注解了,从一个最简单的版本开始,只需要一行代码就可以搞定:

1
public @interface Author { }

这样我们就定义了Author注解,没有带什么标记,根据上面的知识,我们可以知道,这个注解是可以用在任何地方,保留策略是保留在.class文件中,不会出现在运行时,不是自动继承,不会被文档化。

仅仅是这样的话,这个注解是没有什么意义的,既然是一个Author的注解,我们可能还需要给它增加一条名字的属性:

1
2
3
4
public @interface Author {
String value();
}

然后就可以在项目中使用:

1
2
3
@Author(value = "shaohui.me")
public class ExampleClass {
}

这样我们就一眼看出来,这个类是一个叫shaohui.me的作者写的。而且在注解中还有一个规则就是,名字为value的那个元素是名字可以简写,然后就成了这个样子:

1
2
3
@Author( "shaohui.me")
public class ExampleClass {
}

不仅如此,我们还可以给注解的元素定义默认值:

1
2
3
public @interface Author {
String value() default "shaohui.me";
}

这样在使用的时候就更简单:

1
2
3
@Author()
public class ExampleClass {
}

如果我们不想这个注解被到处滥用,可以定义它的可作用域和保留策略:

1
2
3
4
5
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Author {
String value() default "shaohui.me";
}

这样一个简单的注解就定义好了。但是这个注解其实只简单提供了一个声明,这种被称为标记注解。注解除了能在编译期生效,还能在构建时以及运行时生效,后面的两个情况需要借助apt和反射的帮助,我会在下半部分详细解释注解如何和apt工具以及反射搭配。

参考链接

Fork me on GitHub