JAVA 注解在Think in Java 这本书里是这样定义的
“注解(也成为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后的某个时刻非常方便的使用这些数据”。
这个定义有点儿抽象,而且根据我的经验,什么事情只要一跟“元”沾上边就变得复杂了,比如“元编程”。
我希望你尽快忘了这么晦涩的定义,下面我将给出一个简单的类比,帮你理解注解。
举个例子,比如你新爷的女儿有很多彩色小球,都散落在地上了,你新爷跟她说,把红色的球都放到箱子里。然后你新爷的女儿屁颠儿屁颠儿的就去找红色的球,然后放到箱子里。这个过程就是Java注解的工作过程。这个红色就是球的标签,JAVA注解就是一段代码的标签。
@SafeVarargs
,1.8新增了 @FunctionalInterface
@Overried
:表示当前的方法定义将覆盖超类中的方法。如果你不小心拼写错误,或者方法中签名对不上被覆盖的方法,编译器就会发出错误提示。
@Deprected
: 如果你使用了该注解标记的元素,那么编译器会发出警告信息。
@SuppressWarnings
: 关闭不当的编译器警告信息。
@SafeVarargs
:参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。
@FunctionalInterface
:用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错。
仅用上面的几种内置的基本注解显然不能满足我们日常开发需求,也不是JAVA提供注解的真正目的。我们日常开发过程中还是要定义自己的注解,帮助我们提高代码开发效率。
下面我们就通过自定义一个注解,来详细说说JAVA注解的知识点。
首先定义一个 @FrankTest
注解,我希望标注了 @FrankTest
标签的方法都执行一下,没有标注该注解的方法则不执行。
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface FrankTest { }
就这么三行代码就定义完成了一个注解,如果没有@符号你还以为我定义了一个接口呢,除了@符号以外,相信你对 @Target
, @Retention
一定也很好奇。这两个是JAVA的元注解(注解的注解)。同样被称为元注解的还有 @Documented
, @Inherited
。如果你想自定义注解,就离不开这四个元注解。
@Target
,表示该注解可以用于什么地方。
ElementType参数包括: –CONSTRUCTOR,构造器的声明 –FIELD,域声明,包括[enum实例] –LOCAL_VARIABLE, 局部变量声明 –METHOD,方法声明 –PACKAGE,包声明 –PARAMETER,参数声明 –TYPE:类,接口(包括注解类型)或enum声明 -TYPE_PARAMETER:Java1.8新增,表示该注解能写在类型参数的声明语句中 -TYPE_USE:Java1.8新增,能标注任何类型名称
@Retention
,表示需要在什么级别保存该注解信息。
RetentionPolicy参数包括: –SOURCE,注解将被编译器丢弃 –CLASS,注解在class文件中可用 ,但会被VM丢弃[不读入内存] –RUNTIME, VM将在运行期也保留注解,因此可以通过反射机制读取注解的信息
@Documented
,将此注解包含在Javadoc中。
@Repeatable
,Java1.8新增,标记的注解可以多次应用于相同的声明或类型
@Inherited
,允许子类型继承父类中的注解。
@Inherited @Retention(RetentionPolicy.RUNTIME) @interface Test {} @Test public class A {} public class B extends A {}
注解 Test 被 @Inherited
修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。
回到刚才我们自定义的 @FrankTest
的例子,我们使用了 @Target
,其中ElementType是Method,表示该注解可以作用在方法上面,如果ElementType是TYPE,则表示可以作用在类上。 @Retention
表示在JVM运行时保留注解信息。
以上,介绍了如何定义一个注解,下面我们该使用注解了。看代码
public class FrankAnnotationUtil { @FrankTest public void frankMethod(){ System.out.println("Frank"); } @FrankTest public void jackMethod(){ System.out.println("Jack"); } public void maryMethod(){ System.out.println("Mary"); } }
我在其中两个方法上面标注了注解,另外一个没有标注。我希望JVM能够运行frankMethod方法和jackMethod方法。那么你会问了,JVM怎么知道要怎么处理这些标记了@FrankTest的方法呢。答案就是注解处理器。这才关健
public class FrankTestProcessor { public static void processor(Class<?> tClass) throws IllegalAccessException, InstantiationException, InvocationTargetException { for (Method method : tClass.getDeclaredMethods()){ FrankTest frankTest = method.getAnnotation(FrankTest.class); Object object = tClass.newInstance(); if (frankTest != null){ System.out.println("Found FrankTest Annotation " + method.getName()); method.invoke(object); }else { System.out.println("Not Found FrankTest Annotation " + method.getName()); } } } public static void main(String args[]) throws IllegalAccessException, InvocationTargetException, InstantiationException { processor(FrankAnnotationUtil.class); } }
这段代码的输出是这样的
Not Found FrankTest Annotation maryMethod Found FrankTest Annotation frankMethod Frank Found FrankTest Annotation jackMethod Jack
标记标签的方法都执行了一次,没有标注的没有执行。其实没有什么神奇的,注解处理器的基本原理就是利用JAVA的反射原理。
在看另外一个例子,定义另外一个注解 @FrankInfo
,希望它能够提供更多信息。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface FrankInfo { public int id(); public String describe() default "no description"; }
这个注解比上面那个多了int元素id,以及一个String元素description。
注解里面的元素可使用如下类型,所有的基本类型[int, float,boolean],String,Class,enum,Annotation以及以上类型的数组 ,如果你使用了其他类型,那编译器就会报错。注意,也不允许使用其他任何包装类型,不过由于自动打包的存在,这算不上是什么限制。注解也可以作为元素的类型,也就是说注解可以嵌套。
编译器对元素的默认值是有限制的。
为了表示元素确实为空这种定义,我们可以使用一些其他自定义的特殊值来表示某个元素不存在,比如空字符串或者负数。例如
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SimulatingNull { public int id() default -1; public String description() default ""; }
最后,注解不支持继承。不能使用extends来继承某个 @interface
回到上面的例子,我们定义了包含元素的注解,那么如何使用注解呢,看例子:
public class FrankAnnotationUtil { @FrankTest @FrankInfo(id = 01,describe = "Frank's Method") public void frankMethod(){ System.out.println("Frank"); } @FrankTest @FrankInfo(id = 02) public void jackMethod(){ System.out.println("Jack"); } @FrankInfo(id = 03,describe = "Mary's Method") public void maryMethod(){ System.out.println("Mary"); } }
JVM如何拿到注解标注中的信息呢?还是利用反射机制,看代码。
public class FrankInfoProcessor { public static void processor(Class<?> cl){ for (Method method : cl.getDeclaredMethods()){ FrankInfo frankInfo = method.getAnnotation(FrankInfo.class); if (frankInfo != null){ System.out.println("Found FrankInfo " + frankInfo.id() + " " + frankInfo.describe()); } } } public static void main(String[] args){ processor(FrankAnnotationUtil.class); }
上面代码输出如下内容:
Found FrankInfo 2 no description Found FrankInfo 3 Mary's Method Found FrankInfo 1 Frank's Method
以上就是有关Java注解的基本信息,以后日常开发中,可以任性的定义自己的注解了。