Java--快速了解注解与反射机制的使用

反射机制

反射详解

  1. 定义:反射是java的动态机制,可以在程序[运行期间]确定对象的实例化、方法的调用等操作,动态机制可以提高代码的灵活度,但是运行效率较低并且开销较大,因此不能过度的依赖反射机制

  2. 名称解释:

    • 类对象(Class类的实例): 在JVM中每个被加载的类都有且只有一个Class的实例与之对应 当我们需要使用一个类时,JVM会首先读取该类的.class(字节码)文件,读取后就会同时 实例化一个Class的实例用来记录该类的信息(名字、构造器、方法、属性等)并与该类绑定

    • 类对象(Class):它的每一个实例用于表示一个类 方法对象(Method):它的每一个实例用于表示一个方法 构造器对象(Constructor):它可以得到此类对象的无参或者有参构造器 属性对象(Field) 注解对象(Annotation)

  3. 步骤:

    1. 第一步:获取待操作的类的类对象

      方式1:类名.class

      Class cls = String.class;
      Class cls = int.class;      //注:基本类型只有这一种获取类对象方式

      方式2:Class.forName(String className) 通过指定类的完全限定名(包名.类名)获取

      Class cls = Class.forName("java.lang.String");
      Class cls = Class.forName("java.util.ArrayList");

      方式3:类加载器模式ClassLoader

    2. 通过类对象的newInstance()方法实例化

      //newInstance()方法会调用类的公开的无参构造器
      Object obj = cls.newInstance();
  4. 常用方法:

    扫描二维码关注公众号,回复: 15893963 查看本文章
    1. Class.forName(String className):返回值为Class类型,通过指定类的完全限定名(包名.类名)获取类对象

      Class cls = Class.forName("java.lang.String");
      Class cls = Class.forName("java.util.ArrayList");
    2. 类对象.getName():返回值为String类型,通过类对象获取当前类对象所表示的类的完全限定名

      Class cls = String.class;
      String name = cls.getName();
      System.out.println(name);//java.lang.String
    3. 类对象.getSimpleName():返回值为String类型,通过类对象获取当前类对象所表示的类名

      Class cls = String.class;
      String name = cls.getSimpleName();
      System.out.println(name);//String
    4. 类对象.getMethods():返回值为一个Method类型数组,通过类对象获取当前类对象所表示的类的所有公开方法(包含从Object继承的方法)

      Class cls = String.class;
      Method[] methods = cls.getMethods();
      for(Method method : methods){
          String methodName = method.getName();
          System.out.println(methodName);//输出为String类里面所有公开方法(不是private的方法)
      }
    5. 类对象.getPackage():返回值为Package类型,通过类对象获取当前类对象所表示的类所在包

      Class cls = String.class;
      Package pack = cls.getPackage();
      System.out.println(pack.getName());//java.lang
    6. 类对象.newInstance():返回值为一个新分配对象的类的的实例,创建由此类对象表示的类的新实例,即初始化对象为默认

      Class cls = Class.forName(java.util.HashMap);
      Object obj = cls.newInstance();
      System.out.println(obj);//{}
      Class cls = Class.forName(java.util.Date);
      Object obj = cls.newInstance();// 等同于:Object obj=new Date()
      System.out.println(obj);//Sat Apr 08 11:10:15 CST 2023
    7. 类对象.getConstructor(有参传参):java.lang.reflect.Constructor包下,返回值为Constructor类型,通过类对象获取对应的有参或者无参构造器

      Class cls = Class.forName("reflect.Person");
      Constructor c =  cls.getConstructor(String.class,int.class);
      Object obj = c.newInstance("张三",20); //获取Person中两个参数的构造方法
      System.out.println(obj);//默认调用Person中重写的toString()方法:Person{name='张三', age=22}
    8. 类对象.getMethod(方法名,[有参传参]):java.lang.reflect.Method包下,返回值为Method类型,获取待调用方法的方法对象

      方法对象.invoke():返回值为boolean类型,一般不用接收,通过方法对象来调用此方法

      Class cls = Class.forName("reflect.Person");
      Object obj = cls.newInstance();//Object obj = new Person();
      Method method = cls.getMethod("sayHello");//传入方法名
      method.invoke(obj);//obj.sayHello()
    9. 类对象.getDeclaredMethods():返回值为一个Method类型数组,用来获取类中自己定义的方法(包含私有方法,不包含从超类继承的方法)

      Class cls = Class.forName("reflect.Person");
      Method[] methods = cls.getDeclaredMethods();
      for(Method method : methods){
          String methodName = method.getName();
          System.out.println(methodName);//输出为String类里面所有自己写的方法(如果需要获取的方法权限过低,可以配合setAccessible()修改权限的方法使用)
      }
    10. 类对象.getDeclaredMethod(方法名):返回值为Method类型,获取待调用方法的方法对象,详情参照第8条方法

    11. 方法对象.setAccessible(boolean值):返回值为boolean类型,一般不接收,强行打开权限(当方法为的访问权限不够时,使用此方法,可以把权限设置为最高,不过使用后一定记得把权限还原,不然会破坏封装的私密性)

      Class cls = Class.forName("reflect.Person");
      Method method = cls.getDeclaredMethod("私有方法名");
      method.setAccessible(true);
      method.invoke(obj);
      method.setAccessible(false);
    12. 方法对象.getParameterCount():返回值为int类型,用于获取当前方法对象表示的方法参数个数

      Class cls = Class.forName("reflect.Person");
      Method method = cls.getMethod("方法名");
      int num = method.getParameterCount();
      System.out.println(num);//参数值
    13. 方法对象.getModifiers():返回值为int类型,用于获取取此方法的修饰词的一个代替int值,此返回值可以与Modifier类(此类中定义了许多常量,每个常量有对应的int值,如:PUBLIC对应1)比较,常用于判断方法是否满足条件然后执行后续逻辑

      Class cls = Class.forName("reflect.Person");
      Method method = cls.getMethod("方法名");
      boolean flag = (method.getModifiers()==Modifier.PUBLIC);
      System.out.println(flag);//判断是否为public修饰的方法
    14. 对象.isAnnotationPresent(注解类型.class):根据对象(类对象、方法对象等)判断该类是否被注解标注,常用于逻辑判断

      Class cls = Class.forName("reflect.Person");
      flag = (cls.isAnnotationPresent(AutoRunClass.class));//需要提前自定义注解@AutoRunClass,并在Person类上方进行标记才会返回true
      Method method = cls.getMethod("方法名");
      flag1 = (method.isAnnotationPresent(AutoRunMethod.class));//需要提前自定义注解@AutoRunMethod,并在Person类的该方法上方进行标记才会返回true


注解

  1. 定义:注解是一种能被添加到计算机的元数据,可以修饰一些类、方法、字段等,常配合反射机制使用

  2. 简介:

    • @interface自定义注解一被创建就会自动继承java.lang.annotation.Annotation接口

    • 注解常与反射机制配合使用

    • 注解可以不定义成员,只做一个标识的作用,但一旦定义了元素,必须由public abstract修饰,与接口里面一样可以省略不写

    • 注解中的方法类似于抽象方法,区别在于多了一个默认值,所有注解中这些方法被称为:注解类型元素

    • 注解里面的元素可以通过default声明一个默认值,如果此元素没有默认值,那么在使用该注解时需要为未赋值的元素赋值(注意:元素长得像方法,但不是方法,与反射机制配合使用,可以通过反射机制调用相关方法获取注解元素的值进行一系列的操作,反射机制中可以直接根据定义的值定位对应的类、方法、字段等,然后进行调用)

    • 注解类型元素只能是:基本数据类型、String、Class、枚举、注解类型

    • 注解的定义涉及到一个新的名词:元注解

      元注解:专门修饰注解的注解(jdk1.8API帮助文档中定义)

      1. @Documented:使用此类型来注释其注释影响其客户端使用注释元素的类型的声明,表示是否此注解的相关信息添加到javadoc文档(Sun公司提供的一个技术,它从程序源代码中抽取类、方法、成员等注释形成一个和源代码配套的API帮助文档)

        定义:@Documented

        实质:是否需要在Javadoc文档中生成此注解的内容

      2. @Inherited:表示注释类型自动继承,如果在注释类型声明中存在继承的元注释,并且用户在类声明上查询注释类型,并且类声明没有此类型的注释,则该类的超类将自动查询注释类型

        定义:@Inherited

        实质:如果注解中有继承关系,可以用于定义此注解与子注解的关系(当此注解声明是注解的类时有效,方法、字段等无效)

      3. @Retention:指示要注释具有注释类型的注释的保留时间

        定义:@Retention(RetentionPolicy.SOURCE)

        • RetentionPolicy.SOURCE:注解仅保留在.java文件的源代码中,编译后不会加入.class字节码文件中

        • RetentionPolicy.CLASS:默认保留级别,编译后在.class字节码文件中会存在,但运行时无法获取,并且反射机制无法访问

        • RetentionPolicy.RUNTIME:自定义注解常使用方式,保留在字节码文件中,并且可以被反射机制访问

        实质:可以定义注解生命周期(即在什么时候存在该注解)

      4. @Target:指示注释类型适用的上下文,注释类型可能适用的声明上下文和类型上下文在JLS 9.6.4.1中指定,并以源代码通过枚举常数java.lang.annotation.ElementType表示。

        定义:@Target(ElementType.TYPE)

        • ElementType.TYPE: 声明在类、接口、注解、枚举上

        • ElementType.CONSTRUCTOR: 声明在构造函数上

        • ElementType.FIELD: 声明在成员变量、对象、属性、枚举的常量上

        • ElementType.LOCAL_VARIABLE: 声明在局部变量上

        • ElementType.METHOD: 声明在方法上

        • ElementType.PACKAGE: 声明在包上

        • ElementType.PARAMETER: 声明在参数上

        • ElementType.ANNOTATION_TYPE): 声明在注解上(用于嵌套注解时)

        实质:限定此自定义注解能够被应用在哪些Java元素上面

      5. @Repeatable:注释类型java.lang.annotation.Repeatable用于表示其(meta-)注释声明的注释类型是可重复的

        定义:@Repeatable(注解类型.class)

        实质:使用@Repeatable注解可以让一个注解是否可以重复使用

  3. 自定义注解的流程:

    1. 定义注解:相当于定义标记

      1. 声明一个由@interface修饰的注解类型

        public @interface TestAnnotation{
           
        }
      2. 为注解类型添加注解类型元素

        public @interface TestAnnotation{
           int value() default 1;
        }
      3. 指定保留级别

        @Retention(RetentionPolicy.RUNTIME)
        public @interface TestAnnotation{
           int value() default 1;
        }
      4. 指定应用位置

        @Target(ElementType.Class)
        @Retention(RetentionPolicy.RUNTIME)
        public @interface TestAnnotation{
           int value() default 1;
        }
    2. 配置注解:即使用注解,就是把标记写在需要的地方

      @TestAnnotation
      public class Person{
          /**
          方法以及属性
          */
      }
    3. 解析注解:在编译期或运行期检测标记的地方,并进行相应操作(注解如果没经过解析,将会毫无作用)

  4. 注意:

    在为注解类型添加注解类型元素时

    • 如果注解仅仅只定义了一个参数,一般采用value命名该元素名,此做法有个好处,可以在配置(使用)注解时直接:@注解名(元素值)-----例:@TestAnnotation(12)

    • 如果注解定义了两个及以上参数,或者一个参数时没有采用value命名元素名,那么在配置(使用)注解时需要:@注解名(元素名=元素值,元素名=元素值...)-----例:@TestAnnotation(age=12)

    • 但声明了默认值情况下对应的元素(有默认值的元素)可以不用写出来,此时对应元素会采用默认值-----例:一个注解中有两个元素(age默认值为18,name无默认值),使用注解时:@注解(name="张三"),实质上传入了name="张三",name=18

猜你喜欢

转载自blog.csdn.net/m0_69270622/article/details/130031365