Java_编程机制_6_反射及注解

反射及注解

类加载机制

  1. 单个类自身的加载步骤:

    • 类的加载:

      • 将 .class 文件读入内存,并为之创建一个 java.lang.Class 对象。
    • 类的连接:

      • 验证阶段:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致;
      • 准备阶段:负责为类的类变量分配内存,并设置默认初始化值;
      • 解析阶段:将类的二进制数据中的符号引用替换为直接引用。
    • 类的初始化:

      • 主要是对类变量进行初始化。

    JVM 会连续完成这三个步骤,所以这 3 个步骤也会统称为 类加载 或 类初始化 。

  2. 类加载/类初始化(统称)的总体步骤:

    • 假如类还未被加载和连接,则程序先加载并连接该类。
    • 假如该类的直接父类还未被初始化,则先初始化其直接父类。
    • 假如类中有初始化语句,则系统依次执行这些初始化语句。

    其中,在执行第 2 个步骤的时候,系统对直接父类的初始化步骤也是遵循上面的初始化步骤 1-3 。

  3. 一个类被加载的时机:

    • 自己的静态方法被调用时;
    • 自己的静态属性被访问时;
    • 被创建实例时;
    • 自己的子类被初始化时;
    • 被使用反射方式来强制创建 java.lang.Class 对象时;
    • 若自己是一个入口类,被直接使用 java 命令运行时;

反射

概念

  1. 反射的概念:

    • 指在运行时去获取一个类的变量和方法信息,然后通过获取到的信息来创建对象、调用方法的一种机制。
    • 由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就要完成确定,而在运行期仍然可以扩展,因此反射被称为框架设计的灵魂。
  2. 要想通过反射去使用一个类,首先要获取到该类的字节码文件对象(类型为 Class 的对象),有如下三种常见的获取方式:

    • 使用类的 class 属性来获取该类对应的 Class 对象,在代码中即为:类名.class

      注意:基本数据类型也是这样,如 int.class ,并且它和 Integer.class 并不等价!

    • 使用对象调用 getClass() 方法来返回其所属类的 Class 对象,在代码中即为:对象引用.getClass()

    • 使用 Class 类静态方法传入类的完整路径名的字符串来返回该类的 Class 对象,在代码中即为:Class.forName(String className)

常用方法

  1. 反射获取构造方法并使用:

    Class<T> 类常用方法说明
    Constructor<?>[] getConstructors()返回所有公共构造方法对象的数组
    Constructor<?>[] getDeclaredConstructors()返回所有构造方法对象的数组
    Constructor<T> getConstructor(Class<?>… parameterTypes)返回单个公共构造方法对象
    Constructor<T> getDeclaredConstructor(Class<?>… parameterTypes)返回单个构造方法对象
    Constructor<T> 类常用方法说明
    T newInstance(Object… initargs)根据指定的构造方法创建对象
    void setAccessible(boolean flag)设置此 Constructor 对象权限为可访问
  2. 反射获取成员变量并使用:

    Class<T> 类常用方法说明
    Field[] getFields()返回所有公共成员变量对象的数组
    Field[] getDeclaredFields()返回所有成员变量对象的数组
    Field getField(String name)返回单个公共成员变量对象
    Field getDeclaredField(String name)返回单个成员变量对象
    Field 类常用方法说明
    void set(Object obj, Object value)将 obj 对象的成员变量赋值为 value
    void setAccessible(boolean flag)设置此 Field 对象权限为可访问
  3. 反射获取成员方法并使用:

    Class<T> 类常用方法说明
    Method[] getMethods()返回所有公共成员方法对象的数组(含继承得来的)
    Method[] getDeclaredMethods()返回所有成员方法对象的数组(不含继承得来的)
    Method getMethod(String name, Class<?>… parameterTypes)返回单个公共成员方法对象
    Method getDeclaredMethod(String name, Class<?>… parameterTypes)返回单个成员方法对象
    Method 类常用方法说明
    Object invoke(Object obj, Object… args)调用 obj 对象的成员方法,参数是 args,返回值是 Object 类型
    void setAccessible(boolean flag)设置此 Method 对象权限为可访问

注解

概念

  • 注解( Annotation ),也称元数据,是一种代码级别的说明。

    • 它与类、接口、枚举是在同一个层次。

    • 它可以声明在包、类、字段、方法、局部变量、方法参数等前面,来对这些元素进行说明。

      使用方式:

      @注解名称
      
    • 实际上我们大多数时候认为注解不是程序的一部分,而认为它就是一个标签,这个标签要么给编译器用,要么给解析程序用。

      解析程序就是用反射来得到类的 Class 对象(及 MethodField 等对象),调用类似 getAnnotation(...) 方法获取该类上写的注解,从而进行逻辑操作。

  • 注解与注释:

    • 注释:用文字描述程序,给程序员看的;
    • 注解:说明程序,给计算机看的。
  • 按作用分类:

    • 编写文档:通过代码里标识的注解生成文档 ( JDK 预定义的 )

      如:在文档注释中使用 @author@since@param@return 等,使用 javadoc 命令生产文档后,分别对应作者信息、起始版本信息、方法的形参信息、方法的返回值信息等。

    • 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查 ( JDK 预定义的 )

      如: @Override 可以检查一个方法是否是重写的父类/接口的方法,若不是则编译失败。

    • 代码分析:通过代码里标识的注解对代码进行分析 ( 自定义的 )

      如: @自定义注解名,再使用反射抽取注解。

自定义注解

定义注解
  1. 定义格式:

    元注解
    public @interface 注解名称 {
        属性列表...
    }
    
    • 注解本质上就是一个接口,该接口默认继承 java.lang.annotation.Annotation 接口;
    • 所以,注解里面能定义的内容就是接口里面能定义的内容是一种类型的。
  2. 注解中的 “属性” (这种特殊的接口中的抽象方法)

    之所以 “方法” 在这里会被叫做 “属性” ,是因为在使用注解时的语法如:

    @注解名(抽象方法名 =)
    ...
    

    在形式上类似给属性赋值,所以就被称为 “属性” 了。

    • 注解内定义的 “属性” 的返回值类型只能是以下几种:

      • 基本数据类型或其数组;

      • String或其数组;

      • 枚举或其数组;

      • 注解或其数组。

    • 注解内定义的 “属性” 在注解被使用时必须被赋值,并且:

      • 如果定义属性时,使用 default 关键字给属性默认初始化值(如 int age() default 18; )则使用注解时,可以不进行属性的赋值。
      • 如果只有一个属性需要赋值,并且属性的名称是 value ,则在赋值时的 value= 可以省略,直接传值即可。
      • 数组赋值时,值使用 {} 包裹。如果数组中只有一个值,则 {} 可以省略不写。
  3. 元注解:

    它是描述注解的注解,是由 JDK 预定义的。常见以下几种:

    • @Target : 描述注解能够作用的位置。

      该注解的属性被定义为一个枚举类型的数组: java.lang.annotation.ElementType ,该枚举的常用取值有:

      • TYPE :传入该值则代表被注解的注解可以作用于类上;
      • METHOD :传入该值则代表被注解的注解可以作用于方法上;
      • FIELD :传入该值则代表被注解的注解可以作用于成员变量上;
      • ……
    • @Retention : 描述注解被保留到的阶段。

      该注解的属性被定义为一个枚举类型的数组: java.lang.annotation.RetentionPolicy ,该枚举的取值有:

      • SOUECE :传入该值则代表被注解的注解只保留在源码阶段,即只给编译器看,编译完的 .class 文件中就没有该注解的信息了;
      • CLASS :传入该值则代表被注解的注解保留到字节码阶段,编译完的 .class 文件中有它的信息,但在运行时就没有该注解的信息了;(默认值)
      • RUNTIME :传入该值则代表被注解的注解的信息一直保留到运行时,可以在运行时用反射机制读取到该注解。
    • @Inherited : 描述该注解是否会被子类继承。

    • @Documented : 描述注解是否被抽取到api文档中。

解析注解
  • 解析注解,需要使用反射机制,从一个类的 Class 对象中(及 MethodField 等)中得到它上面的注解信息(注解实现对象),从而进一步的判断分析操作等。

  • 常用方法如:

    • 判断是否具有某注解:boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

      如:

      • 注解的定义:

        ...
        @Target(ElementType.METHOD)
        @Retention(RetentionPolicy.RUNTIME)
        public @interface Check {
        }
        
      • 使用该注解的类:

        ...
        public class Calculator {
            @Check
            public void add() {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 class Test {
            public static void main(String[] args) {
                Calculator calculator = new Calculator();
                Class<? extends Calculator> calculatorClass = calculator.getClass();
                Method[] methods = calculatorClass.getMethods();
                for (Method m : methods) {
                    if (m.isAnnotationPresent(Check.class)) {
                        try {
                            m.invoke(calculator);
                        } catch (Exception e) {
                            System.out.println(m.toString() + "方法出现异常:" + e);
                        }
                    }
                }
            }
        }
        
        /* 控制台输出:
        1+0=1
        1-0=1
        public void com.qiong.Calculator.div()方法出现异常:java.lang.reflect.InvocationTargetException
        1*0=0
        */
        
    • 提取使用注解时标记的信息:<A extends Annotation> A getAnnotation(Class<A> annotationClass)

      如:

      • 注解的定义:

        ...
        @Target(ElementType.TYPE)
        @Retention(RetentionPolicy.RUNTIME)
        public @interface GroupInfo {
            int number();
            String major();
        }
        
      • 使用该注解的类:

        ...
        @GroupInfo(number = 5, major = "IT")
        public class JavaLearner {
            private String major;
        
            public JavaLearner(String major) {
                this.major = major;
            }
        }
        
      • 解析程序:

        ...
        public class Test {
            public static void main(String[] args) {
                GroupInfo anno = JavaLearner.class.getAnnotation(GroupInfo.class);
                /* 上述的操作相当于 Java 在内部生产了一个 GroupInfo 接口的实现类对象并返回,实现类相当于:
                    public class GroupInfoImpl_xxx {
                        public int number() {
                            return 5;
                        }
                        public String major() {
                            return "IT";
                        }
                    }
                 */
                String major = anno.major();
                int number = anno.number();
                ArrayList<JavaLearner> list = new ArrayList<>();
                for (int i = 0; i < number; i++) {
                    list.add(new JavaLearner(major));
                }
                // .....
            }
        }
        
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值