转载

Java注解详解

JAVA 注解在Think in Java 这本书里是这样定义的

“注解(也成为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后的某个时刻非常方便的使用这些数据”。

这个定义有点儿抽象,而且根据我的经验,什么事情只要一跟“元”沾上边就变得复杂了,比如“元编程”。

我希望你尽快忘了这么晦涩的定义,下面我将给出一个简单的类比,帮你理解注解。

举个例子,比如你新爷的女儿有很多彩色小球,都散落在地上了,你新爷跟她说,把红色的球都放到箱子里。然后你新爷的女儿屁颠儿屁颠儿的就去找红色的球,然后放到箱子里。这个过程就是Java注解的工作过程。这个红色就是球的标签,JAVA注解就是一段代码的标签。

注解的用处:

  1. 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息。
  2. 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
  3. 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取。

基本注解。

JAVA1.8之前,内置了三个基本注解,1.7新增了 @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以及以上类型的数组 ,如果你使用了其他类型,那编译器就会报错。注意,也不允许使用其他任何包装类型,不过由于自动打包的存在,这算不上是什么限制。注解也可以作为元素的类型,也就是说注解可以嵌套。

注解默认值限制

编译器对元素的默认值是有限制的。

  1. 元素不能有不确定的值。要么在元素定义的时候提供默认值,要么在使用注解时提供元素的值。
  2. 对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以null作为其值。

为了表示元素确实为空这种定义,我们可以使用一些其他自定义的特殊值来表示某个元素不存在,比如空字符串或者负数。例如

@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注解的基本信息,以后日常开发中,可以任性的定义自己的注解了。

原文  https://hellofrank.github.io/2019/09/05/Java注解详解/
正文到此结束
Loading...