理解
注解就是标签
这里先不引出概念
具体描述
- 注解是在JavaSE 5.0版本引入的一个概念
注解的定义
-
使用@interface关键字进行定义
public @interface DemoAnnotation{ }
- 上述的代码创建了一个名字叫做DemoAnnotation的注解,可以理解创建了一个名字叫做DemoAnnotation的标签。
注解的应用
- 创建一个类Demo,在类定义的地方加入 @DemoAnnotation就可以使用这个注解类了。
- 从我们说的标签角度说 就是为Demo贴上了一个名字叫做DemoAnnotation的标签。
元注解
- 元注解是可以注解到注解上的注解,元注解也是一种注解,但是能够用到其他注解上面。
- 从标签的角度说,元注解也是一种标签,只不过是用来给其他标签进行解释说明的。
- 元标签:@Retention @Documented @Target @Inherited @Repeatable
Retention
当Retention注解应用到一个注解上的时候,它解释说明了这个注解的存活时间。
有如下几种取值
RetentionPolicy.SOURCE 注解只在源代码阶段保留,在编译器编译的时候会被忽略。
RetentionPolicy.CLASS 注解只被保留到编译进行时,不会被加载到JVM中。
RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载到JVM中,所以在程序运行的时候可以拿到它们。
@Retention去给一张标签解释的时候,它指定了这张标签张贴的时间。
-
@Retention相当于给一张标签上面盖了一张时间戳,时间戳指明了标签张贴的时间周期
@Retention(RetentionPolicy.RUNTIME) public @interface DemoAnnotation{ }
- 上述代码我们给DemoAnnotation注解指定了Retention注解,该注解指明了注解DemoAnnotation可以在程序运行期间获取到,生命周期很长。
@Documented
- 这个注解和文档的注释有关,能够将注解中的元素包含到Javadoc中去。
@Target
- Target 目标的意思
- @Target注解指定了注解运用的地方
- 当一个注解被@Target注解时,这个注解就被限定了运用场景。
- 从我们标签的角度来说就是,我们给标签限定了张贴的地方了,比如这个注解是在方法上使用,类上等等
@Target有如下的取值
- ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
- ElementType.CONSTRUCTOR 可以给构造方法进行注解
- ElementType.FIELD 可以给属性进行注解
- ElementType.LOCAL_VERIABLE 可以给局部变量进行注解
- ElementType.METHOD 可以给方法进行注解
- ElementType.PACKAGE 可以给一个包进行注解
- ElementType.PARAMETER 可以给一个方法内的参数进行注解
- ElementType.TYPE 可以给一个类型进行注解 比如类、接口、枚举
@Inherited
遗传、继承的意思
并不是说注解本身是可以遗传的。而是说一个超类被@Inherited注解过的注解注解了,那么如果子类没有被任何注解应用的话,那么这个子类就继承了超类的注解
- 代码演示
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface DemoAnnotation{}
@DemoAnnotation
public class Demo{}
public class Demo1 extends Demo{}
如上述代码,@DemoAnnotation被注解@Inherited修饰,Demo被注解@DemoAnnotation所修饰,Demo1继承至Demo,Demo1没有被其他注解所修饰,那么Demo1就拥有了@DemoAnnotation的注解了
@Repeatable
Repeatable是可重复的 在Java1.8时加进来的,算是一个新特性
-
一个人他既可以是一个程序员也是一个产品总监,也可以是一个运动员
@interface Persons{ Person [] values(); } @Repeatable(Persons.class) @interface Person{ String role defaule ""; } @Person(role="coder") @Person(role="PM") @Person(role="sportsman") public class SuperMan{ }
- @Repeatable注解了 Person,而@Repeatable后面的括号中的类相当于一个容器注解
- 所谓的容器注解就是用来存放其他注解的地方,本身也是一个注解
- 从标签的角度来说,Persons是一张总的标签。上面贴满了Person这种类型的标签但是内容是不一样的。
注解的属性
注解的属性也叫做成员变量。注解只有成员变量,没有方法。
注解的成员变量在注解的定义中以“无形参的方法”形式声明的
其方法名定义了该成员变量的名字,其返回值定义了成员变量的类型
-
代码示例
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface DemoAnnotation { //定义了两个注解属性 分别为 id和name int id(); String name(); } //在使用注解的时候,需要把注解属性给声明了,中间以 “,”分隔 @DemoAnnotation(id = 3,name = "DashingQi") public class Demo { }
- 在注解中定义属性时。它们的类型必须是8种基本数据类型,外加接口、类、注解以及8种基本数据类型的数组
- 注解中声明的属性可以有默认值,声明默认值使用default关键字
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface DemoAnnotation { int id() default 1; String name() default "ZhangQi"; }
- 当注解中声明的属性有默认值的时候,在使用注解的时候,可以不用声明属性值
@DemoAnnotation() public class Demo { }
- 当声明的注解没有属性的时候,使用注解可以不用写括号
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface DemoAnnotation { } @DemoAnnotation public class Demo { }
Java中预置的注解
@Deprecated
该注解用来表示过时的方法、类、成员变量
编译器在编译阶段遇到这个注解会发出警告,告诉我们此时正在调用一个过时
-
代码模拟
public class Test { @Deprecated public void speak() { System.out.println("out "); } public void say() { System.out.println(" now "); } } public class MyTest { public static void main(String[] args) { Test test = new Test(); //此时会标注调用的方法过时了 test.speak(); test.say(); } }
@Override
提示字类要复写父类中被@Override修饰的方法
@SuppressWarnings
阻止警告的意思,之前被@Deprecated修饰方法在调用的时候,会发出警告,如果我要忽略掉这种警告可以使用@SuppressWarnings 修饰
@SuppressWarnings("deprecation")
public static void main(String[] args) {
Test test = new Test();
//就不会提示过时
test.speak();
test.say();
}
@SafeVarargs
参数安全类型的注解
目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生unchecked这样的警告。
在Java1.7版本中加入的。
@FunctionalInterface
函数式接口注解
java1.8版本引入的新特性
函数式接口标记,可以很容易转换成Lambda表达式
-
函数式接口就是一个具有一个方法的普通接口
@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
注解的提取
形象的比喻就是你把这些注解标签在合适的时候撕下来,然后检阅上面的内容信息。
想要正确检阅注解,离不开一个手段,那就是反射。
注解与反射
注解通过反射获取
-
通过Class对象的isAnnotationPresent()方法判断它是否应用了某个注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) { return GenericDeclaration.super.isAnnotationPresent(annotationClass); }
-
通过getAnnotation()方法或者getAnnotations()来获取Annotation对象
前者是返回指定类型的注解,后者是返回注解到这个元素上的所有注解
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) { } public Annotation[] getAnnotations() { }
如果获取的Annotation对象存在,那么我们可以调用它们的属性方法了。
-
代码实操
// 定义一个注解 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface TestAnnotation { int id() default 1; String msg() default "DashingQi"; } //将定义好的注解应用到元素上 @TestAnnotation(id = 4, msg = "zhangqi") public class Test { } //执行代码 public class MainTest { public static void main(String[] args) { Class<Test> testClass = Test.class; //判断Test类对应的Class中是否应用了TestAnnotation注解 boolean annotationPresent = testClass.isAnnotationPresent(TestAnnotation.class); if (annotationPresent) { //获取到应用的注解 TestAnnotation annotation = testClass.getAnnotation(TestAnnotation.class); //获取到对应的注解之后,获取注解的成员变量 System.out.println("id = " + annotation.id()); System.out.println("msg = " + annotation.msg()); } } } //运行结果如下 id = 4 msg = zhangqi
上述代码是注解到类上的提取,属性、方法上的注解也是可以提取处理。同样还要借助于反射
-
代码实操
@Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation { int id() default 1; String msg() default "DashingQi"; } @Retention(RetentionPolicy.RUNTIME) public @interface Check { String value() default "heihie"; } @Retention(RetentionPolicy.RUNTIME) public @interface Perform { } public class MainTest { public static void main(String[] args) { try { Field filedA = Test.class.getDeclaredField("a"); filedA.setAccessible(true); Check check = filedA.getAnnotation(Check.class); if (check != null) { System.out.println("check value = " + check.value()); } Method testMethod = Test.class.getDeclaredMethod("testMethod", null); if (testMethod != null) { Annotation[] annotations = testMethod.getAnnotations(); for (int i = 0; i < annotations.length; i++) { System.out.println(annotations[i].getClass().getSimpleName()); } } } catch (NoSuchFieldException e) { e.printStackTrace(); System.out.println(e.getMessage()); } catch (NoSuchMethodException e) { e.printStackTrace(); System.out.println(e.getMessage()); } } } //注意想要在代码运行期间获取到注解,那么@Retention(RetentionPolicy.RUNTIME)是必须的 //运行结果如下 check value = haha $Proxy2
注解的用处
- 提供信息给编译器:编译器可以利用注解来探测错误和警告信息。
- 编译阶段的处理:软件工具可以利用注解信息来生成代码、Html文档或者做其他相应处理。(@Document)
- 运行时的处理:某些注解可以在程序运行时接受代码的提取
官方解释:注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
值得注意的是 注解不是代码本身的一部分
注解是给编译器或者APT(Annotation Processing Tool)用的
注解到底有什么用?取决于你像用它来干什么