欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java注解(通俗易懂)

程序员文章站 2022-04-19 20:58:37
...

java注解

  • 概念:说明程序的,是给计算机看的。

  • 注释:用文字描述程序的,给程序员看的。

  • 定义:注解(Annotation),也叫做元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次【因为其声明是public @interface xxx{}】。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面用来对这些元素进行说明、注释。

  • 概念描述:

    1. JDK1.5以后的新特性
    2. 用来说明程序的
    3. 使用注解的格式:@注解名称
  • 作用分类

    1. 编写文档:通过代码里标识的注解生成文档【生成文档doc文档】

    2. 代码分析:通过代码里标识的注解对代码进行分析【使用反射】

    3. 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【例:@Override】

  • JDK中预定义的一些注解

    1. @Override:检测被该注解标注的方法是否是继承父类(接口)的

    2. @Deprecated:该注解标注的内容,表示已经过时

    3. @SuppressWarnings:压制警告

      • 一般传递参数all : @SuppressWarnings(“all”)代表压制.java文件中所有警告信息
      • idea中java文件警告展现方式:

java注解(通俗易懂)

package annocation;


@SuppressWarnings("all")
//压制所有警告,主要是右侧的黄色警告全部消失
public class AnnoDemo2 {
    @Override
    public String toString() {
        return super.toString();
    }

    @Deprecated
    public void show1(){
        //有缺陷
    }

    public void show2(){
        //替换show1方法
    }

    public void demo(){
        show1();//虽然过时但是仍然可以使用
    }
}

  • 自定义注解

    • 格式:

      ​ 元注解

      ​ public @interface 注解名称{}

    • 本质:本质上是一个接口,这个接口继承下面的Annocation接口,故接口中可以定义什么,注解中就可以定义什么。

      public interface MyAnno extends java.lang.annotation.Annotation {
      }
      
      1. 将一个自定义注解编译成字节码文件(.class文件)。

        javac MyAnno.java
        

        java注解(通俗易懂)

      2. 将字节码文件反编译成.java文件。

        javap MyAnno.class
        

    java注解(通俗易懂)

    • 属性:接口中的抽象方法

      1. 属性的返回值类型有下列取值:

        • 基本数据类型
        • String
        • 枚举
        • 注解
        • 以上数据类型的数组
        package annocation;
        public @interface MyAnno {
        
            int age();
            String name() default "皮皮虾";
            Person per();
            MyAnno2 anno2();
            String[] str();
        
        }
        
        
      2. 定义了属性,在使用注解的时候要为注解中的属性赋值。

        1. 如果在定义属性的时候,使用default关键字给属性默认初始化值,则使用注解的时候,可以不进行属性的赋值。
        2. 如果只有一个属性的时候,并且属性的名称为value则value可以省了,直接定义值即可。
        3. 数组赋值的时候,值使用{}包裹,如果数组中的值只有一项则可以省了{}。
        package annocation;
        @MyAnno(age = 12,name = "张三",per = Person.MAN,anno2 = @MyAnno2,str = {"abc","sdsd"})
        public class Worker {
        }
        
        
    • 元注解:用于描述注解的注解

      • @Target:描述注解能够作用的位置【常用】

        • ElementType(@Target注解的枚举属性)取值
          1. TYPE:可以作用于类上
          2. METHOD:可以作用于方法上
          3. FIELD:可以作用于成员变量上
        # 自定义注解@MyAnno3
        package annocation;
        import java.lang.annotation.ElementType;
        import java.lang.annotation.Target;
        
        @Target(value = {ElementType.TYPE,ElementType.METHOD})
        public @interface MyAnno3 {
        }
        
        
        #测试自定义注解
        package annocation;
        @MyAnno3
        public class Worker {
        
            @MyAnno3//下划线警告:因为该注解中未描述其可以作用于成员变量上
            public String name = "aaa";
        
            @MyAnno3
            public void show(){
        
            }
        }
        
      • @Retention:描述注解被保留的阶段(java代码的三个阶段)【常用】

        • RetentionPolicy(@Retention注解的枚举属性)取值
          1. **SOURCE:**当前被描述的注解不会保留到class字节码文件中
          2. **CLASS:**当前被描述的注解会被保留到class字节码文件中不会被JVM读取到
          3. RUNTIME:当前被描述的注解会被保留到class字节码文件中,并被JVM读取到【常用】
      • @Documented:描述注解是否被抽取到api文档中

        # 该注解没有属性
        @Documented
        @Retention(RetentionPolicy.RUNTIME)
        @Target(ElementType.ANNOTATION_TYPE)
        public @interface Documented {
        }
        
        

        将java文件生成为javadoc文档:由于Worker.java文件中的成员变量未带有自定义的注解,所以在javadoc文档中不会保留注解样式。

      java注解(通俗易懂)

      java注解(通俗易懂)

      • @Inherited:描述注解是否被子类继承
  • 在程序中使用(解析)注解:获取注解中定义的属性值

    • 案例01:创建框架类,要求不能改变该类的任何代码,可以创建任意类的对象,可以执行任意方法。

      1. 获取注解定义位置的对象 (Class、Method、Field)
      2. 获取指定的注解
        • getAnnotation(Class)
      3. 调用注解中抽象方法获取配置的属性值
      #  框架类java文件
      package annocation;
      
      import java.io.InputStream;
      import java.lang.reflect.Method;
      import java.sql.Ref;
      import java.util.Properties;
      
      /**
       * @ClassName ReflectUtil
       * @Description TODO 创建任意类的对象,可以执行任意方法
       * @Author ROG
       * @Date 2019/10/24 16:00
       * @Version 1.0
       */
      @Pro(ClassName = "annocation.Demo",MethodName = "test")
      public class ReflectUtil {
      
          public static void main(String[] args) throws Exception {
      
      //       1. 解析注解
      //        1.1获取该类的字节码文件对象
              Class<ReflectUtil> reflectUtilClass = ReflectUtil.class;
      
      //        2.获取上边Pro的注解对象,实际上就是在内存中生成了该注解接口的子类实现对象(类似于内部类的用法)
              Pro annotation = reflectUtilClass.getAnnotation(Pro.class);
      
      //        调用注解对象中定义的抽象方法,获得返回值
              String className = annotation.ClassName();
              String methodName = annotation.MethodName();
      
              System.out.println(className + " " + methodName);
      
      //        根据全类名获得字节码文件对象
              Class aClass = Class.forName(className);
      //        创建实际Runtime时对象
              Object o = aClass.newInstance();
              System.out.println(o);
      
      //        获取字节码文件对象中的Method对象
              Method method = aClass.getMethod(methodName);
      //        执行方法
              method.invoke(o);
      
          }
      }
      
      
      #  注解@Pro文件
      package annocation;
      //描述需要执行的类名、方法名
      
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      
      @Target(value = ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface Pro {
      
          String ClassName();
          String MethodName();
      }
      
      
      #  实体类Demo.java文件
      package annocation;
      
      
      public class Demo {
          public String name = "我是个好人";
      
          @Override
          public String toString() {
              return "Demo{" +
                      "name='" + name + '\'' +
                      '}';
          }
      
          public void test(){
              System.out.println("我爱你");
          }
      }
      
      
    • 案例02:检测一个封装类中的部分方法是否出现异常,如果出现异常将这些异常信息记录到bug.txt文件中。

      • 思路:

        1. 创建受检测封装类的对象。
        2. 获取封装类的字节码对象。
        3. 根据字节码对象获取封装对象的方法对象列表(使用反射)。
        4. 检测方法是否带有注解。
        5. 如果方法带有注解,执行方法。
        6. 如果方法执行之后出现异常,进行异常捕获,并把异常信息记录到bug.txt文件中。
      • 代码:

        #  @Check注解
        package annocation.demo;
        
        
        import java.lang.annotation.ElementType;
        import java.lang.annotation.Retention;
        import java.lang.annotation.RetentionPolicy;
        import java.lang.annotation.Target;
        
        @Target(value = ElementType.METHOD)
        @Retention(RetentionPolicy.RUNTIME)
        public @interface Check {
        }
        
        
        #  封装类
        package annocation.demo;
        
        /**
         * @ClassName Calculateor
         * @Description TODO 自定义的计算器
         * @Author ROG
         * @Date 2019/10/25 13:57
         * @Version 1.0
         */
        public class Calculateor {
        
            @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("永不报错");
            }
        }
        
        
        
        #  测试类
        package annocation.demo;
        
        import java.io.BufferedWriter;
        import java.io.FileWriter;
        import java.io.IOException;
        import java.lang.reflect.InvocationTargetException;
        import java.lang.reflect.Method;
        
        /**
         * @ClassName CheckTest
         * @Description TODO
         * @Author ROG
         * @Date 2019/10/25 14:02
         * @Version 1.0
         */
        public class CheckTest {
            public static void main(String[] args) throws IOException {
                //1.创建Calcuator对象
                Calculateor calculateor = new Calculateor();
        
                //2.获取其字节码文件对象
                Class calculateorClass = Calculateor.class;
        
                //3.获取其所有方法
        
                Method[] methods = calculateorClass.getMethods();
        
                int number = 0;
                BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
        
                //4.对方法进行检测,查看方法是否被@Check注解修饰
        
                for (Method method : methods) {
                    //5.如果方法被注解修饰,执行
                    if(method.isAnnotationPresent(Check.class)){
                        try {
                            method.invoke(calculateor);
                        } catch (Exception e) {
                            //6.方法报错,捕获异常,将异常信息打印在txt文件中
                            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();
        
                        }
                    }
                }
        
        
                bw.write("本次测试一共出现" + number + "次异常");
                bw.flush();
                bw.close();
        
            }
        }
        
        
        #  bug.txt内容
        add方法出现异常了
        异常的名称是:NullPointerException
        异常的原因是:null
        ------------------
        div方法出现异常了
        异常的名称是:ArithmeticException
        异常的原因是:/ by zero
        ------------------
        本次测试一共出现2次异常
        
    • 小结

      1. 以后大多数的时候我们会使用注解,很少自定义注解。
      2. 注解给谁用?
        • 给编译器用:编译器识别注解,检测编译看有没有问题,有问题就在代码中会有提示,一般为红色下划曲线。
        • 给解析程序用(上述两个案例中的ReflectUtil.java 、CheckTest.java)
      3. 注解不是程序的一部分,可以理解为注解是一个标签,它不会影响程序代码的执行逻辑。