Java中的反射机制详解

文章目录

一、简介

  • 在Java 运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?
  • 对于任意一个对象,能否调用它的任意一个方法?
  • 答案是 肯定的
  • 这种动态的获取类的信息以及动态调用对象的方法的功能都来自于Java语言的反射机制

1.Java反射机制提供的功能

  • 运行时判断任意一个对象所属的类
  • 运行时构造任意一个类的对象
  • 运行时判断任意一个类所具有的成员变量和方法
  • 运行时调用任意一个对象的方法

2反射让Java具有动态语言的性质

  • Reflection是Java被视为动态(准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称class的内部信息,其中包括
    • modifiers:public/static
    • superclass:Object
    • 实现的接口:Serializable
    • 也包括fieldsmethods的所有信息
    • 并且可以在运行时改变fields内容或调用methods包括私有变量和私有方法。也就说类的所有包装机制在反射机制面前都失效了
  • 一般认为动态语言的定义是:程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言,从这个观点看Python,JavaScript是动态语言,Java,C/C++是静态语言
  • 虽然从严格定义来看,Java是一门静态语言,但是反射机制让Java具有了动态相关的机制
  • Reflection用在Java身上表示我们可以于 运行时加载、探知、使用编译期完全未知的classes
  • 也就是说,Java可以加载一个运行时才得知名称的class,获取其完整的构造,( 但不包括methond定义),并生成其对象实体、或对其fields设值、唤起器methods
  • 这种“看透class”的能力又被称为introspection(内观/反省)。(Reflectionintrospection)是常被并提的两个术语

3.java.lang.reflect(关于反射的API介绍)

JDK中主要由以下的类来实现反射机制,这些类大都在java.lang.reflect

  • Class类:代表一个类(位于java.lang下)
    • 代表类本身,每一个类都有一个与之对应的Class
  • Field类:代表类的成员变量/属性
  • Method类:代表类的方法
  • Constructor类:代表类的构造方法
  • Array类:提供了动态创建数组,以及访问数组的元素的静态方法

3.1基础示例:如何通过反射获取一个类的所有方法?

  • 代码
   public static void main(String[] args) throws ClassNotFoundException {
        //一定要理解到反射是一个运行期的行为
        //在下面中 Class.forName("java.lang.String");
        // 中的内容在编译期只是一个字符串而已,不代表任何实际意义
        //注意 这里的参数需要是 类的全限定名
        Class<?> classType = Class.forName("java.lang.String");
        //获取该类的所有方法
        Method[] methods = classType.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
    }
  • 输出:可以获取到该类的所有方法,包括私有方法
    在这里插入图片描述
3.1.1 一定要理解反射是运行期的行为(重要)!!
  • 例1
    在这里插入图片描述- 例2
    在这里插入图片描述

3.2通过反射调用类中的方法

最常见的反射的使用,一定要掌握和理解每一行代码的意思

  • 代码
    • 一个类中定义了两个带参数的方法,想要通过反射机制来调用这些方法
    • 下面的代码是一般的调用步骤
public class Demo03_InvokTester {

    public int add(int a, int b){
        return a +b;
    }

    public String out(String str){
        return "hello:" + str;
    }


    public static void main(String[] args) throws Exception {

        //1.首先获取类对应的Class对象
        Class<?> classType = Demo03_InvokTester.class;
        //2.根据Class对象获取对应类的 实例
        Object invokeTester = classType.newInstance();
        /*3.获取方法对象: 根据Class类 获取方法对象,这个方法对象 是和该类中 的某个方法是一一对应的
             3.1前面展示了获取类的所有的方法
             3.2这里是获取某个具体的方法
             3.3参数解读:
                "add" :方法名 new Class[]{int.class,int.class}:该方法参数列表 中每一个参数的数据类型对应的Class类 组成的Class对象数组
             3.4 为何需要两个参数?因为方法重载的存在,无法通过方法名来唯一的确定一个方法,只有方法名+参数类型+参数个数
              才能唯一的确定一个方法
             3.5那么最终的返回值就是 获取classType对应类中的名为add且参数列表为两个整型的方法 对象
        */
        Method addMethod = classType.getMethod("add", new Class[]{int.class,int.class});
        /*4.方法调用: 通过方法对象的invoke方法  来调用类中的方法
            4.1invokeTester: 类对应的Class对象,在哪个对象上调用方法
            4.2new Object[]{1,2}:传入方法的参数
            4.3注意invoke的返回值是一个Object对象,后面需要强制转换为真正的对象
            4.4最终的返回值就是该方法运行后的返回值
            4.5注意和上述获取方法时参数的异同
                上面的new Class[]{int.class,int.class}可以认为是一个抽象的,描述类型的信息,可认为是形参
                这里的new Object[]{1,2} 在方法调用的时候当然需要一个具体的参数信息,可以认为是实参
                这两个数组的长度一定是一样的

         */

        Object res = addMethod.invoke(invokeTester, new Object[]{1,2});
        System.out.println((Integer)res);

        System.out.println("-------------------------------");
        //下面用同样的方法来调用out方法
        //注意这里就算方法只有一个参数 这里也仍然需要传入Class对象数组
        Method outMethod = classType.getMethod("out", new Class[]{String.class});

        Object out = outMethod.invoke(invokeTester, new Object[]{"world"});

        System.out.println((String)out);
    }
}

3.3类,Class类,对象之间的关系是?

先记住,后理解

Java中,无论生成某个类的多少个对象,这些对象都对应于同一个Class对象

二、深入理解Class类,Method类,Field类

Class类对应 类,Class类是反射的基础,它包含了这个类在运行时的所有想要的信息
Method类 对应类中的一个方法
Field类 对应类中的一个属性

1.获取某个类/对象所对应的Class对象常用的3中方式(非常重要)

  • 1.使用Class类的静态方法forname
 Class<?> classType = Class.forName("java.lang.String");
  • 2.使用类的.class语法:类.class
  Class<?> classType = String.class;
  • 3.使用对象的getClass()方法:
    • 既然是通过对象来获取的,那就一定先要有对象
String s = "aa";
Class<?> clazz = s.getClass();

2.通过Class类对象来获取实例(对象)的2种情况

在反射中,第一步一定是获取某类/对象对应的Class对象,第二步一般就是通过Class对象获取某类的实例(对象),在通过Class对象获取某类的实例的时候会有两种情况,根据该类是否提供无参的构造方法。

2.1通过类的不带参数的构造方法来生成对象的2种方式

  • 1.获取Class对象,直接通过该Class对象的newInstance()方法获取实例
Class<?> classType = String.class;
Object obj = classType.newInstance();
  • 2.先获取Class对象,再通过Class对象来获取对应的Constructor对象,再通过Constructor对象的newInstance()方法生成
//1.获取类对象的Class对象
Class<?> classType = object.getClass();
/2.现在需要通过Class对象 获取该对象的实例
Constructor cons =  classType.getConstructor(new Class[]{});
Object obj = cons.newInstance(new Object[]{});
  • 在这种通过类的不带参数的构造方法获取实例的时候,上述两种方法是等价的,而且方法1更为简便
  • 但是如果某类没有不带参数的构造方法或者说有时候必须使用带构造参数的方法来获取实例,就只能使用方法2了

2.2若想通过类的带参数的构造方法生成对象,只能用1种方式

这种方式就是上述的方式2

  • 核心步骤
//1.获取类对象的Class对象
        Class<?> classType = object.getClass();
        //2.现在需要通过Class对象 获取该对象的实例
        //2.1通过Class对象先获取 Constructor类,注意参数列表的传入
        Constructor cons =  classType.getConstructor(new Class[]{String.class,int.class});
        //2.2调用Constructor类的newInstance获取实例
        Object obj = cons.newInstance(new Object[]{"hello",13});
  • 完整示例代码

public class ReflectTester {

    public static void main(String[] args) throws Exception {
        ReflectTester tester = new ReflectTester();
        tester.copy(new Customer("a",1));
    }

    //该方法实现对Customer对象的拷贝
    public Object copy(Object object) throws Exception {
        //1.获取类对象的Class对象
        Class<?> classType = object.getClass();
        //2.现在需要通过Class对象 获取该对象的实例
        //2.1通过Class对象先获取 Constructor类,注意参数列表的传入
        Constructor cons =  classType.getConstructor(new Class[]{String.class,int.class});
        //2.2调用Constructor类的newInstance获取实例
        Object obj = cons.newInstance(new Object[]{"hello",13});
        System.out.println(obj);
        return obj;
    }

}
class Customer{

    private Long id;
    private String name;
    private int age;

    public Customer(String name, int age){
        this.name = name;
        this.age = age;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    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 "Customer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

2.2.1如果在这种情况下使用Class类的newInstance()方法来获取实例,在运行时会报错
  • 代码:只需要修改上述copy方法为下面
 public Object copy(Object object) throws Exception {
        //1.获取类对象的Class对象
        Class<?> classType = object.getClass();
        //通过Class对象的newInstance()方法获取实例
        Object obj = classType.newInstance();
        System.out.println(obj);
        return obj;
    }
  • 可以观察到在编译期是没有任何报错的
    在这里插入图片描述- 运行结果

在这里插入图片描述

2.3API文档解读

上面的内容更多的是从结果直接说出来了,下面尝试从API文档,看看对相应方法的描述

  • Class.newInstance
    在这里插入图片描述
  • Constructor.newInstance

在这里插入图片描述

3.利用反射完成对象的拷贝

接着上述的例子,利用反射完成对一个Customer对象的反射

3.1代码

认真理解代码中的每一行,特别是copy函数

public class ReflectTester {
    public static void main(String[] args) throws Exception {
        ReflectTester tester = new ReflectTester();
        Customer c0 = new Customer("xpt",18);
        //注意id类型是Long
        c0.setId(1L);
        Customer c1 = (Customer) tester.copy(c0);
        System.out.println("被拷贝对象:" + c0);
        System.out.println("拷贝目标对象:" + c1);
    }

    //该方法实现对Customer对象的拷贝
    public Object copy(Object object) throws Exception {
        //1.获取 被拷贝对象的Class对象
        Class<?> classType = object.getClass();
        //2.通过classType实例化一个 拷贝 目标对象
        Object objectCopy = classType.getConstructor(new Class[]{}).newInstance(new Object[]{});

        //3.获取 对象的所有成员变量
        Field[] fields = classType.getDeclaredFields();

        //4.遍历成员变量 逐一拷贝
        for (Field field : fields) {
            //4.1获取成员变量的名字(Field类提供的方法)
            String name = field.getName();

            /*4.2获取 每一个成员变量名 对应的getter/setter方法
                4.2.1将属性名的第一个字母大写
                4.2.2 这是因为getter/和setter方法的规律性决定的
                4.2.3 然后拼接字符串
            */
            String firstLetter = name.substring(0,1).toUpperCase();
            String getMethodName = "get" + firstLetter + name.substring(1);
            String setMethodName = "set" + firstLetter + name.substring(1);

            //4.3获取Method 对象(get方法参数列表为空)
            Method getMethod = classType.getMethod(getMethodName, new Class[]{});
            //4.3.1 set方法参数就是成员变量,而这个成员变量 刚刚就是 field,获取它的类型即可
            Method setMethod = classType.getMethod(setMethodName, new Class[]{field.getType()});

            //4.4调用get方法 获取值
            Object value = getMethod.invoke(object, new Object[]{});
            //4.5调用set方法 为 拷贝目标对象赋值
            setMethod.invoke(objectCopy, new Object[]{value});
        }
        //5.完成拷贝  返回拷贝目标对象即可
        return objectCopy;
    }

}
class Customer{

    private Long id;
    private String name;
    private int age;

    public Customer(){

    }
    public Customer(String name, int age){
        this.name = name;
        this.age = age;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    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 "Customer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

3.2结果

在这里插入图片描述

三、java.lang.Array类

提供了动态创建和访问数组元素的各种静态方法

1.Array类的基本使用

创建Array实例,调用方法

  • 注意基本方法的使用即可

public class ArrayTest1 {

    public static void main(String[] args) throws ClassNotFoundException {

        //1.获取Sting类的Class对象
        Class<?> classType = Class.forName("java.lang.String");
        //2.新建array数组 长度为10的字符串数组
        Object array = Array.newInstance(classType, 10);
        //3.为数组的指定index赋予指定的值
        Array.set(array, 5, "hello");
        String str = (String) Array.get(array, 5);
        System.out.println(str);

    }

}

2 Integer.TYPE和Integer.class的区别?

  • Integer.TYPE返回int
  • Integer.class返回的是Integer类Class对象
    在这里插入图片描述

3.Arrays创建多维数组/为多维数组中的具体位置赋值以及理解数组的组件类型

  public static void main(String[] args) throws Exception {

//        System.out.println(Integer.TYPE);
//        System.out.println(Integer.class);

        int[] dims = new int[]{5,10,15};
        /*1. 创建多维数组对象
          1.1 Integer.TYPE:数组元素类型
          1.2 dims 规定了数组的形状,dims本身是个数组,这个数组的长度代表 数组array的维度,数组的每一个元素代表每一个维度的长度
          1.3 简单的说就是  这个句话创建了一个 5 * 10 * 15的三维数组
        */
        Object array = Array.newInstance(Integer.TYPE, dims);
        System.out.println(array instanceof int[][][]);

        /*
            2.获取数组的组件类型
            2.1数组的组件类型就是: 二维数组 是有 多个一维数组构成的 那二维数组的组件类型就是一维数组
                                 三维数组 是由 二维数组构成的    三维数组的组件类型就是二维数组
            2.2 array是一个三维数组,那它的组件类型肯定就是一个二维数组
         */
        Class<?> classType = array.getClass().getComponentType();
        //3.获取三维数组array中index = 3的元素,显然是个二维数组
        Object arrayObj = Array.get(array, 3);
        System.out.println(arrayObj instanceof int[][]);
        //4.获取二维数组 arrayObj的index = 5的元素 , 当然是一个一维数组
        arrayObj = Array.get(arrayObj, 5);
        System.out.println( arrayObj instanceof int[]);
        //5.给一维数组 arrayObj的index = 10赋值37
        Array.setInt(arrayObj, 10, 37);

        //6.把最开始的array数组强转为三维整型数组
        int[][][] arrayCast = (int[][][]) array;
        //7.这个值就是赋值给它的37
        System.out.println(arrayCast[3][5][10]);
        
    }

四、利用反射机制调用对象的私有方法,访问对象的私有成员变量

1.如何获取对象的私有方法?

  • 查找Class类的相关文档,发现和获取方法对象有关的方法,我们阅读文档,比较一下各自的区别

在这里插入图片描述

  • 从文档中可以很清楚的看到获取方法对象的相关方法
  • 那么显然想要获取到某个特定的私有方法,需要用:
    • getDeclaredMethod(String name, Class<?>... parameterTypes)

2.如何调用私有方法?(错误示例以及分析)

先按照我们前面学习过的调用对象的方法,看会发生什么

  • 先定义一个类以及提供一个私有方法
public class PrivateTest {
    private String sayHello(String name){

        return "hello: " + name;
    }
}
  • 创建一个类,目标是调用上述的私有方法
public class TestPrivateMethod {

    public static void main(String[] args) throws Exception {
        //1.创建对象
        PrivateTest p = new PrivateTest();
        //2.获取对象p对应的Class对象
        Class<?> classType = p.getClass();
        //3.获取方法对象
        Method method = classType.getDeclaredMethod("sayHello", new Class[]{String.class});
        //4.调用方法
        String str = (String) method.invoke(classType, new Object[]{"world"});
        System.out.println(str);
    }
}
  • 运行结果:
    在这里插入图片描述- 错误信息分析

在这里插入图片描述

  • 这里提示我们无法访问该私有方法
  • 但是反射不是无所不能吗?问题出在哪里呢?
  • 阅读文档
    在这里插入图片描述在这里插入图片描述

3.调用私有方法(正确示例)

  • 代码
public class TestPrivateMethod {

    public static void main(String[] args) throws Exception {
        //1.创建对象
        PrivateTest p = new PrivateTest();
        //2.获取对象p对应的Class对象
        Class<?> classType = p.getClass();
        //3.获取方法对象
        Method method = classType.getDeclaredMethod("sayHello", new Class[]{String.class});
        //4.设置 以压制Java的访问控制检查
        method.setAccessible(true);
        //5.调用方法
        String str = (String) method.invoke(p, new Object[]{"world"});
        System.out.println(str);
    }
}
  • 结果
    在这里插入图片描述

  • 重点
    在这里插入图片描述

4.反射调用私有方法小结

  • 1.要能获取到私有方法对象:getDeclaredMethod(String name, Class<?>... parameterTypes)
  • 2.要设置压制Java的访问检查:method.setAccessible(true);

5.反射对象反问私有成员变量

理解了如何访问私有方法,那访问私有对象也很容易了,只是这里需要使用Field类来完成相关操作

  • 含有私有属性的类
public class PrivateTest {

    String name = "xpt";

    public String getName() {
        return name;
    }

    private String sayHello(String name){

        return "hello: " + name;
    }
}
  • 利用放射访问私有属性
public class TestPrivateField {

    public static void main(String[] args) throws Exception {
        //1.创建对象
        PrivateTest p = new PrivateTest();
        //2.获取Class对象
        Class<?> classType = p.getClass();
        //3.获取特定 私有成员变量 类
        Field field = classType.getDeclaredField("name");
        //4.压制Java语言的访问检查
        field.setAccessible(true);
        //5.调用Field类的set方法
        field.set(p, "abc");
        System.out.println(p.getName());
    }

}
  • 结果
    在这里插入图片描述

6.反射访问私有成员变量小结

  • 1…获取特定 私有成员变量 类 Field field = classType.getDeclaredField("name");
  • 2.压制Java语言的访问检查 field.setAccessible(true);
    在这里插入图片描述

6.1一个经典的题目

  • 提供了下述的类和属性以及方法,问是否能够改变属性的初始值
public class PrivateTest {

    String name = "hello";

    public String getName() {
        return name;
    }
}
  • 通过上面如何访问私有成员变量的分析,这里应该很容易回答以及如何实现

五、反射知识点总结

前面的内容侧重于细节的讲解,以及API文档的阅读,这里对前面的内容做一个简单清晰的总结

1.获取Class对象的三种方式?

2.如何获取所有的成员变量?如何获取某个特定的成员变量?某个特定的私有成员变量?如何访问某个特定的私有私有成员变量?

3.如何获取所有的构造函数?如何获取某个特定的构造函数?私有构造函数?

  • 获取所有构造方法的getDeclaredConstructors
  • 获取公有构造方法的 getConstructors

4.如何获取所有的方法对象?如何获取特定方法对象?私有方法?如何调用方法?如何调用私有方法?

5.如何实例化一个对象?有几种情况?

6.常用API的命名规律

  • 上述的内容本质上都是API的使用,并且这些API都是非常有规律的,一看名字就明白含义
  • s结尾的表示复数,返回值都是复数
  • Declared表示获取声明的方法,也就是所有的方法/属性。
  • 不带s表示访问某个具体方法/属性:需要传入方法名,参数列表类型
  • 根据下面的API应该很容易识别各自的作用和需要传递的参数
classType.getConstructors();
        classType.getDeclaredConstructors();

        classType.getMethods();
        classType.getDeclaredMethods();

        classType.getFields();
        classType.getDeclaredMethods();

        classType.getMethod(, );
        classType.getDeclaredMethod();
        classType.getConstructor();
        classType.getDeclaredConstructor();
        classType.getFields();
        classType.getDeclaredField();
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值