Java 反射

内容纲要

反射

  • 允许程序在运行时来进行自我检查并且对内部的成员进行操作
  • 反射主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义

反射机制的作用

  • 在运行时判断任意一个对象所属的类
  • 在运行时获取类的对象
  • 在运行时访问java对象的属性,方法,构造方法等

java.lang.reflect类库里面主要的类

  • Field :表示类中的成员变量
  • Method :表示类中的方法
  • Constructor :表示类的构造方法
  • Array :该类提供了动态创建数组和访问数组元素的静态方法

反射依赖的class

用来表示运行时类型信息的对应类

  • 每个类都有唯一一个与之相对应的Class对象
  • Class类为类类型,而Class对象为类类型对象

Class类的特点

  • Class类也是类的一种,class则是关键字
  • Class类只有一个私有的构造函数,只有JVM能够创建Class类的实例
  • JVM中只有唯一一个和类相对应的Class对象来描述其类型信息

获取Class对象的三种方式

  • Object-> getClass()
  • 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
  • 通过Class类的静态方法: forName(String className)(常用)
    public static void main(String[] args) throws ClassNotFoundException {
    //第一种方式获取Class对象
    ReflectTarget reflectTarget = new ReflectTarget();
    Class reflectTargetClass1 = reflectTarget.getClass();
    System.out.println("1st : " + reflectTargetClass1.getName());
    //第二种方式获取Class对象
    Class reflectTargetClass2 = ReflectTarget.class;
    System.out.println("2nd: " + reflectTargetClass2.getName());
    //判断第一种方式获取的class对象和第二种方式获取的是否是同一个
    System.out.println(reflectTargetClass1 == reflectTargetClass2);
    //第三种方式来获取Class对象
    Class reflectTargetClass3 = Class.forName("demo.reflect.ReflectTarget");
    System.out.println("3rd: " + reflectTargetClass3.getName());
    System.out.println(reflectTargetClass2 == reflectTargetClass3);
    }

    在运行期间,一个类,只有一个与之对应的Class对象产生
    Class对象就像一面镜子,透过这面镜子可以看到类的结构

反射的主要用法

  • 如何获取类的构造方法并使用
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/*
 * 通过Class对象可以获取某个类中的:构造方法;
 *
 * 获取构造方法:
 *         1).批量的方法:
 *             public Constructor[] getConstructors():所有"公有的"构造方法
              public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
 *         2).获取单个的方法,并调用:
 *             public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
 *             public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
 *
 *             调用构造方法:
 *             Constructor-->newInstance(Object... initargs)
 */
public class ConstructorCollector {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazz = Class.forName("demo.reflect.ReflectTarget");
        //1.获取所有的公有构造方法
        System.out.println("**********************所有公有构造方法*********************************");
        Constructor[] conArray = clazz.getConstructors();
        for(Constructor c : conArray){
            System.out.println(c);
        }
        //2.获取所有构造方法
        System.out.println("************所有的构造方法(包括:私有、受保护、默认、公有)***************");
        conArray = clazz.getDeclaredConstructors();
        for(Constructor c : conArray){
            System.out.println(c);
        }
        //3.获取单个带参数的公有方法
        System.out.println("*****************获取公有、有两个参数的构造方法*******************************");
        Constructor con = clazz.getConstructor(String.class, int.class);
        System.out.println("con = " + con);
        //4.获取单个私有的构造方法
        System.out.println("******************获取私有构造方法*******************************");
        con = clazz.getDeclaredConstructor(int.class);
        System.out.println("private con = " + con);
        System.out.println("******************调用私有构造方法创建实例*******************************");
        //暴力访问(忽略掉访问修饰符)
        con.setAccessible(true);
        ReflectTarget reflectTarget = (ReflectTarget) con.newInstance(1);
    }
}
  • 如何获取类的成员变量并使用
方法返回值 方法名称 方法说明
Field getDeclaredField(String name) 获取指定name名称的(包含private修饰的)字段,不包括继承的字段
Field[] getDeclaredField() 获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段
Field getField(String name) 获取指定name名称、具有public修饰的字段,包含继承字段
Field[] getField() 获取修饰符为public的字段,包含继承字段
/*
 * 获取成员变量并调用:
 *
 * 1.批量的
 *      1).Field[] getFields():获取所有的"公有字段"
 *      2).Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;
 * 2.获取单个的:
 *      1).public Field getField(String fieldName):获取某个"公有的"字段;
 *      2).public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
 *   设置字段的值:
 *      Field --> public void set(Object obj,Object value):
 *                  参数说明:
 *                  1.obj:要设置的字段所在的对象;
 *                  2.value:要为字段设置的值;
 */

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class FieldCollector {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //获取Class对象
        Class reflectTargetClass = Class.forName("demo.reflect.ReflectTarget");
        //1.获取所有公有的字段
        System.out.println("************获取所有公有的字段********************");
        Field[] fieldArray = reflectTargetClass.getFields();
        for (Field f : fieldArray){
            System.out.println(f);
        }
        //2.获取所有的字段
        System.out.println("************获取所有的字段(包括私有、受保护、默认的)********************");
        fieldArray = reflectTargetClass.getDeclaredFields();
        for (Field f : fieldArray){
            System.out.println(f);
        }
        //3.获取单个特定公有的field
        System.out.println("*************获取公有字段并调用***********************************");
        Field f = reflectTargetClass.getField("name");
        System.out.println("公有的field name : " + f);
        ReflectTarget reflectTarget = (ReflectTarget)reflectTargetClass.getConstructor().newInstance();
        //4.给获取到的field赋值
        f.set(reflectTarget, "待反射一号");
        //5.验证对应的值name
        System.out.println("验证name : " + reflectTarget.name);
        //6.获取单个私有的Field
        System.out.println("**************获取私有字段targetInfo并调用********************************");
        f = reflectTargetClass.getDeclaredField("targetInfo");
        System.out.println(f);
        f.setAccessible(true);
        f.set(reflectTarget, "13810592345");
        System.out.println("验证信息" + reflectTarget);
    }
}
  • 如何获取类的成员方法并使用
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/*
 * 获取成员方法并调用:
 *
 * 1.批量的:
 *      public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
 *      public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
 * 2.获取单个的:
 *      public Method getMethod(String name,Class<?>... parameterTypes):
 *      参数:
 *         name : 方法名;
 *         Class ... : 形参的Class类型对象
 *      public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
 *
 *   调用方法:
 *      Method --> public Object invoke(Object obj,Object... args):
 *        参数说明:
 *            obj : 要调用方法的对象;
 *            args:调用方式时所传递的实参;
):
 */
public class MethodCollector {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //1、获取Class对象
        Class reflectTargetClass = Class.forName("demo.reflect.ReflectTarget");
        //2、获取所有公有方法
        System.out.println("***************获取所有的public方法,包括父类和Object*******************");
        Method[] methodArray = reflectTargetClass.getMethods();
        for (Method m : methodArray) {
            System.out.println(m);
        }
        //3、获取该类的所有方法
        System.out.println("***************获取所有的方法,包括私有的*******************");
        methodArray = reflectTargetClass.getDeclaredMethods();
        for (Method m : methodArray) {
            System.out.println(m);
        }
        //4、获取单个公有方法
        System.out.println("***************获取公有的show1()方法*******************");
        Method m = reflectTargetClass.getMethod("show1", String.class);
        System.out.println(m);
        //5、调用show1并执行
        ReflectTarget reflectTarget = (ReflectTarget) reflectTargetClass.getConstructor().newInstance();
        m.invoke(reflectTarget, "待反射方法一号");
        //6、获取一个私有的成员方法
        System.out.println("***************获取私有的show4()方法******************");
        m = reflectTargetClass.getDeclaredMethod("show4", int.class);
        System.out.println(m);
        m.setAccessible(true);
        String result = String.valueOf(m.invoke(reflectTarget, 20));
        System.out.println("返回值 : " + result);
    }
}

反射的获取源

用XML来保存类相关的信息以供反射调用

用注解来保存类相关的信息以供反射调用

注解

提供一种为程序元素设置元数据的方法

  • 元数据是添加到程序元素如方法、字段、类和包上的额外信息
  • 注解是一种分散式的元数据设置方式,XML是集中式的设置方式
  • 注解不能直接干扰程序代码的运行
  • 核心package为 java.long.annotation
  • 注解均继承自Annotation接口

注解的功能

  • 作为特定的标记,用于告诉编译器一些信息
  • 编译时动态处理,如动态生成代码
  • 运行时动态处理,作为额外信息的载体,如获取注解信息

注解的分类

元注解

用于修饰注解的注解,通常用在注解的定义上

  • @Target:注解的作用目标
    • packages,types (类、接口、枚举、Annotation类型)
    • 类型成员(方法、构造方法、成员变量、枚举值)
    • 方法参数和本地变量(如循环变量、catch参数)
  • @Retention:表明注解的生命周期
    • RetentionPolicy枚举说明
    • SOURCE:编译后不会在,class文件中出现
    • CLASS:编译后存在
    • RUNTIME:运行时可获得
  • @Documented:注解是否应当被包含在javaDoc文档中
  • @Inherited:是否运行子类继承该注解

自定义注解格式

public @interface 注解名{
    修饰符 返回值 属性名() 默认值;
    修饰符 返回值 属性名() 默认值;
    ...
}

注解属性支持的类型

  • 所有基本数据类型:int,float, boolean, byte,double,char,long,short
  • Enum类型
  • String类型
  • Annotation类型
  • Class类型
  • 以上类型数组

自定义注释的获取与使用

package demo.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class AnnotationParser {
    //解析类的注解
    public static  void  parseTypeAnnotation() throws ClassNotFoundException {
        Class clazz = Class.forName("demo.annotation.ImoocCourse");
        //这里获取的是class对象的注解,而不是其里面的方法和成员变量的注解
        Annotation[] annotations = clazz.getAnnotations();
        for(Annotation annotation : annotations){
            CourseInfoAnnotation courseInfoAnnotation =  (CourseInfoAnnotation) annotation;
            System.out.println("课程名:" + courseInfoAnnotation.courseName() + "\n" +
                    "课程标签:" + courseInfoAnnotation.courseTag() + "\n" +
                    "课程简介:" + courseInfoAnnotation.courseProfile() + "\n" +
                    "课程序号:" + courseInfoAnnotation.courseIndex() );
        }
    }
    //解析成员变量上的标签
    public static void parseFieldAnnotation() throws ClassNotFoundException {
        Class clazz = Class.forName("demo.annotation.ImoocCourse");
        Field[] fields = clazz.getDeclaredFields();
        for(Field f : fields){
            //判断成员变量中是否有指定注解类型的注解
            boolean hasAnnotation = f.isAnnotationPresent(PersonInfoAnnotation.class);
            if(hasAnnotation){
                PersonInfoAnnotation personInfoAnnotation = f.getAnnotation(PersonInfoAnnotation.class);
                System.out.println("名字:" + personInfoAnnotation.name() + "\n" +
                        "年龄:" + personInfoAnnotation.age() + "\n" +
                        "性别:" + personInfoAnnotation.gender() + "\n");
                for(String language : personInfoAnnotation.language()){
                    System.out.println("开发语言:" + language);
                }

            }
        }
    }
    //解析方法注解
    public static void parseMethodAnnotation() throws ClassNotFoundException{
        Class clazz = Class.forName("demo.annotation.ImoocCourse");
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            /*
             * 判断方法中是否有指定注解类型的注解
             */
            boolean hasAnnotation = method.isAnnotationPresent(CourseInfoAnnotation.class);
            if(hasAnnotation){
                CourseInfoAnnotation courseInfoAnnotation = method.getAnnotation(CourseInfoAnnotation.class);
                System.out.println("课程名:" + courseInfoAnnotation.courseName() + "\n" +
                        "课程标签:" + courseInfoAnnotation.courseTag() + "\n" +
                        "课程简介:" + courseInfoAnnotation.courseProfile() + "\n"+
                        "课程序号:" + courseInfoAnnotation .courseIndex() + "\n");
            }
        }
    }

    public static void main(String[] args) throws ClassNotFoundException {
        //parseTypeAnnotation();
        parseFieldAnnotation();
        //parseMethodAnnotation();
    }
}

注解的工作原理

//保存jdk动态代理为注解生成的代理类
-Djdk.proxy.ProxyGenerator.saveGenerator.Files= true
//跟踪类加载
-XX:+TraceClassLoading
  1. 通过键值对的形式为注解属性赋值
  2. 编译器检查注解的使用范围,将注解信息写入元素属性表
  3. 运行时JVM将RUNTIME的所有注解属性取出并最终存入map里
  4. 创建AnnotationInvocationHandler实例并传入前面的map
  5. JVM使用JDK动态代理为注解生成代理类,并初始化处理器
  6. 调用invoke方法,通过传入方法名返回注解对应的属性值
THE END
分享
二维码
< <上一篇
下一篇>>