Java基础加强01-注解3
1、注解概念
概念:说明程序的。注解是给计算机看的。
注释:用文字描述程序的。注释是给程序员看的。
定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
使用注解:@注解名称
2、注解作用分类
①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
package com.itheima.day01.annotation; /** * 注解javadoc演示 * * @author lj * @since 1.5 * @version 1.0 */ public class AnnoDemo1 { /** * 计算两数的和 * @param a 整数 * @param b 整数 * @return 两数的和 */ public int add(int a,int b){ return a+b; } }
把AnnoDemo1.java单独放到一个文件夹里,命令提示符到文件夹位置,执行javadoc AnnoDemo1.java命令就会生成文档,打开index.html就会进入到文档中,查看到AnnoDemo1类的文档(跟Java api帮助文档类似),Java api也是这样生成的
如果文档中文出现乱码,使用notepad++中的“Encode in ANSI”编码后重新执行命令生成文档,这时打开文档就会出现中文。
②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
@Override就是对代码进行编译检查的。限定重写父类方法。对于子类中被@Override 修饰的方法,如果存在对应的被重写的父类方法,则正确;如果不存在,则报错。@Override 只能作用于方法,不能作用于其他程序元素。
package com.itheima.day01.reflect; public class Person { private String name; private int age; public Person() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", a='" + a + '\'' + ", b='" + b + '\'' + ", c='" + c + '\'' + ", d='" + d + '\'' + '}'; } }
toString改成toString1,@Override会检查父类中有没有此方法,没有则会报错:Method does not override method from its superclass
![]()
3、JDK中预定义的一些注解
- @Override:检测被该注解标注的方法是否是继承自父类(父接口)的
- @Deprecated:该注解标注的内容,表示已过时,仍旧可使用,编译器会发出警告。
- @SuppressWarnings:抑制编译器警告。指示被@SuppressWarnings修饰的程序元素(以及该程序元素中的所有子元素,例如类以及该类中的方法.....)取消显示指定的编译器警告。
一般传递参数all @SuppressWarnings("all")
@SuppressWarnings("all")放在类上,则此类右侧不会出现警告信息
SuppressWarnings注解的常见参数值的简单说明:
1.deprecation:使用了不赞成使用的类或方法时的警告(使用@Deprecated使得编译器产生的警告);
2.unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型; 关闭编译器警告
3.fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
4.path:在类路径、源文件路径等中有不存在的路径时的警告;
5.serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
6.finally:任何 finally 子句不能正常完成时的警告;
7.all:关于以上所有情况的警告。
package com.itheima.day01.annotation;
import java.util.Date;
/**
* JDK中预定义的一些注解
* * @Override :检测被该注解标注的方法是否是继承自父类(接口)的
* * @Deprecated:该注解标注的内容,表示已过时
* * @SuppressWarnings:压制警告
*/
@SuppressWarnings("all")
public class AnnoDemo2 {
@Override
public String toString() {
return super.toString();
}
@Deprecated
public void show1(){
//有缺陷
}
public void show2(){
//替代show1方法
}
public void demo(){
show1();//此代码被横线划掉了表示此方法已过时了,但仍旧可用
Date date = new Date();
date.getDay();//getDay()被划掉表示不建议使用,建议用Calendar日历类
}
}
4、自定义注解
- 格式:元注解 public @interface 注解名称 { 属性列表 }
package com.itheima.day01.annotation; public @interface MyAnno { }
package com.itheima.day01.annotation; import java.util.Date; @SuppressWarnings("all") public class AnnoDemo2 { @MyAnno public void show2(){ } }
- 本质:注解本质上就是一个接口,该接口默认继承Annotation接口。它的实现类在jvm运行时会自动帮我们创建它的实现类。
public interface MyAnno extends java.lang.annotation.Annotation { }
javac MyAnno.java
编译生成MyAnno.class文件,然后反编译
javap MyAnno.class
反编译输出
- 注解属性:接口中可以定义的成员方法,即接口中的抽象方法就是注解的属性。
要求:1、属性的返回值类型有下列取值:基本数据类型、String、枚举、注解、以上类型的数组
2、定义了属性,在使用时需要给属性赋值
a. 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时可以不进行属性的赋值
b. 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
c. 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}省略
package com.itheima.day01.annotation; public enum Person { P1,P2; }
package com.itheima.day01.annotation; public @interface MyAnno2 { }
package com.itheima.day01.annotation; public @interface MyAnno { int value(); String name() default "zhangsans"; Person per(); MyAnno2 anno2(); String[] strs(); }
如果MyAnno中只有一个属性value或有value和name(有默认初始化值)两个属性,那么使用如下
package com.itheima.day01.annotation;
@MyAnno(23)
public class Worker {
}
有多个属性时则都需要赋值
package com.itheima.day01.annotation; @MyAnno(value = 1,per = Person.P1,anno2 = @MyAnno2,strs = {"abc","bb"}) public class Worker { }
- 元注解:用于描述注解的注解
a. @Target:描述注解能够作用的位置
TYPE:可以作用于接口、类、枚举、注解上
METHOD:可以作用于方法上
FIELD:可以作用于成员变量上
PARAMETER:方法参数
CONSTRUCTOR:构造函数
LOCAL_VARIABLE:局部变量
ANNOTATION_TYPE:注解
PACKAGE:包
package com.itheima.day01.annotation; import java.lang.annotation.*; //@Target(value={ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})//表示MyAnno3注解只能作用于类上 @Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})//value省略 public @interface MyAnno3 { }
package com.itheima.day01.annotation; @MyAnno(value = 1,per = Person.P1,anno2 = @MyAnno2,strs = {"abc","bb"}) @MyAnno3 public class Worker { @MyAnno3 public String name = "aaa"; @MyAnno3 public void show(){ } }
b. @Retention:描述注解被保留的阶段
@Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到。一般使用该注解。即注解会在class字节码文件中存在,在运行时可以通过反射获取到。
@Retention(RetentionPolicy.CLASS):当前被描述的注解,会保留到class字节码文件中,但是不会被JVM读取到。即默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得。
@Retention(RetentionPolicy.SOURCE):当前被描述的注解,不会保留到class字节码文件中。即注解仅存在于源码中,在class字节码文件中不包含
首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。
package com.itheima.day01.annotation; import java.lang.annotation.*; @Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})//value省略 @Retention(RetentionPolicy.RUNTIME)//MyAnno3描述Worker类时,将来运行Worker类时JVM会读取到注解 public @interface MyAnno3 { }
c. @Documented:描述注解是否被抽取到api文档中,即该注解将被包含在javadoc中
package com.itheima.day01.annotation; import java.lang.annotation.*; //@Target(value={ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})//表示MyAnno3注解只能作用于类上 @Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})//value省略 @Retention(RetentionPolicy.RUNTIME)//MyAnno3描述Worker类时,将来运行Worker类时JVM会读取到注解 @Documented public @interface MyAnno3 { }
把MyAnno3.java、Worker.java放到一个空的文件夹中,编辑去掉package一栏,去掉name上的注解,执行javadoc Worker.java命令生成文档,可以看到show方法中有@MyAnno3注解,name没有注解
去掉@Documented,那么show方法上没有@MyAnno3注解
d. @Inherited:描述注解是否被子类继承,即子类可以继承父类中的该注解
package com.itheima.day01.annotation; import java.lang.annotation.*; @Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})//value省略 @Retention(RetentionPolicy.RUNTIME)//MyAnno3描述Worker类时,将来运行Worker类时JVM会读取到注解 @Documented @Inherited public @interface MyAnno3 { }
package com.itheima.day01.annotation; @MyAnno3 public class Worker { @MyAnno3 public String name = "aaa"; @MyAnno3 public void show(){ } }
package com.itheima.day01.annotation; //注解被Inherited描述了,那么Teacher类会自动继承Worker类上的MyAnno3的注解 public class Teacher extends Worker { }
5、在程序内使用(解析)注解
获取注解中定义的属性值
1. 获取注解定义位置的对象(Class,Method,Field)。注解定义在类上,获取的是类的字节码文件对象;注解定义在方法上。。。
2. 获取指定的注解:注解名 注解对象名称 = 1中获取的对象.getAnnotation(注解对象Class)
//其实就是在内存生成了一个该注解接口的子类实现对象
public class ProImpl implements Pro{
public String className(){
return "com.itheima.day01.annotation.Demo1"
}
public String methodName(){
return "show";
}
}
3. 调用注解中的抽象方法获取配置的属性值
package com.itheima.day01.annotation; public class Demo1 { public void show(){ System.out.println("demo1... show ..."); } }
package com.itheima.day01.reflect; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 描述需要执行的类名和方法名 */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Pro { String className(); String methodName(); }
package com.itheima.day01.annotation; import com.itheima.day01.reflect.Pro; import java.io.InputStream; import java.lang.reflect.Method; import java.util.Properties; /** * 框架类 */ @Pro(className = "com.itheima.day01.annotation.Demo1",methodName = "show") public class ReflectTest { public static void main(String[] args) throws Exception { //前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法 //1.解析注解 //1.1获取该类的字节码文件对象 Class<ReflectTest> reflectTestClass = ReflectTest.class; //2.获取上边的注解对象 //其实就是在内存去生成了一个该注解接口的子类实现对象 /* public class ProImpl implements Pro{ public String className(){ return "com.itheima.day01.annotation.Demo1" } public String methodName(){ return "show"; } } */ Pro an = reflectTestClass.getAnnotation(Pro.class); //3.调用注解对象中定义的抽象方法,获取返回值 String className = an.className(); String methodName = an.methodName(); System.out.println(className);//输出:com.itheima.day01.annotation.Demo1 System.out.println(methodName);//输出:show //3. 加载该类进内存 Class cls = Class.forName(className); //4. 创建对象 Object obj = cls.newInstance(); //5. 获取方法对象 Method method = cls.getMethod(methodName); // method.getAnnotation(); //6. 执行方法 method.invoke(obj);//Demo1类的show方法被执行,输出:demo1... show ... } }
6、注解案例
package com.itheima.day01.annotation.Demo; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Check { }
package com.itheima.day01.annotation.Demo; /** * 定义的计算器类 */ public class Calculator { //加法 @Check public void add(){ String str = null; str.toString(); System.out.println("1+0=" +(1+0)); } //减法 @Check public void sub(){ System.out.println("1-0=" +(1-0)); } //乘法 @Check public void mul(){ System.out.println("1*0=" +(1*0)); } //除法 @Check public void div(){ System.out.println("1/0=" +(1/0)); } public void show(){ System.out.println("永无bug..."); } }
package com.itheima.day01.annotation.Demo; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * 简单的测试框架 * * 当主方法执行后,会自动执行被检测的所有方法(加了Check注解的方法),判断方法是否有异常,记录到文件中 */ public class TestCheck { public static void main(String[] args) throws IOException { //1.创建计算器对象 Calculator c = new Calculator(); //2.获取计算器对象的字节码文件对象 Class cls = c.getClass(); //3.获取所有public方法 Method[] methods = cls.getMethods(); int number = 0;//出现异常的次数 BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt")); //遍历方法数组,输入iter快捷键可以快速写出for for(Method method: methods){ //4.判断方法上是否有Check注解 if(method.isAnnotationPresent(Check.class)){ //是,需要校验方法是否有异常 try { //5.有,执行方法 method.invoke(c); } catch (Exception e) { //6.捕获异常 number++; //记录到文件中 bw.write(method.getName()+"方法出现异常了"); bw.newLine(); bw.write("异常名称:"+e.getCause().getClass().getSimpleName()); bw.newLine(); bw.write("异常原因:"+e.getCause().getMessage()); bw.newLine(); bw.write("--------------"); bw.newLine(); } } } if(number>0){ bw.write("本次测试一共出现"+number+"个异常"); }else { bw.write("恭喜您,所有方法均通过本次测试"); } bw.flush(); bw.close(); } }
输出结果并自动在项目下生成一个bug.txt文件:
7、小结
1. 以后大多数时候,我们会使用注解,而不是自定义注解
2. 注解给谁用? 编译器;给解析程序用(TestCheck用);
3. 注解不是程序的一部分(相当于给方法加个标签),可以理解为注解就是一个标签