前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >反射都不会,就别学所谓的框架了

反射都不会,就别学所谓的框架了

作者头像
麦洛
发布2021-08-23 13:55:37
6940
发布2021-08-23 13:55:37
举报

一文带你彻底理解反射

  • 前言
  • 1、Java反射机制的基本概述
  • 2 、理解Class类并获取Class实例
  • 3、类的加载过程以及反射创建对象时的内存分析
    • 3.1类的加载过程分析
    • 3.2 使用反射创建对象的内存分析
    • 3.3反射相关API和提供的主要功能概述
  • 4、创建运行时类对象
    • 4.1获取Class对象的三种方式
    • 总结
  • 5、获取运行时类的完整结构
    • 5.1获取运行时类的属性
    • 5.2获得运行时类的方法
    • 5.3创建运行时类的构造器
  • 6、动态创建类的对象

前言

人与人交流要用语言,人与机器人的交互同样需要语言,从计算机诞生至今,计算机语言经历了机器语言汇编语言高级语言。在所有的程序设计语言中,只有机器语言能够被计算机直接理解和执行,而其他程序语言都必须先翻译成与机器语言,才能和计算机交互。

静态语言和动态语言

对于我们来说,接触做多的就是高级语言,包括C、C++、Java、python、JavaScript等。这些高级语言可以大概分为两大类,即动态语言静态语言

  • 静态语言

通俗来讲,如果在编译时就知道变量的类型,该可认为该语言是静态的,如我们所熟知的Java、C、C++等,它们在写代码时必须指定每个变量的类型。

  • 动态语言

动态语言一般指的是脚本语言,如python、JavaScript等,这类语在编写代码是不必指定类型

  • 动态语言VS静态语言

从直观上看,静态语言在代码编译时需要指定变量类型;而动态语言则是在运行期间才会检查变量类型。所以,针对动态语言来说,我们可以在运行时改变其结构,即运行时的代码可以根据某些条件改变自身的结构。

按照划分,Java是属于静态语言的,但是由于Java提供了反射机制,使得Java成为了一种准动态语言,利用反射可以获得类似动态语言的特性,使得编程更加的灵活。

下面,我们就认真学习下Java反射是什么怎么使用为什这么使用

1、Java反射机制的基本概述

  • 什么是反射?

官方解释:JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

看到官方解释,大家也许会有点懵,不要着急,我们想一下在日常的开发过程中,我们经常会遇到某个类中的成员变量、方法是私有的,这些成员、方法是不对外开发的,但是我们可以通过Java的反射机制在运行期间动态的获取。所以,我们对Java反射可以重新理解如下:反射就是程序在运行时,可以根据类的全限定名称,动态地加载该类,创建对象,并可以调用该对象中地任意属性方法

那么,问题来了,为什么要学习反射呢?

我们想象这样一个场景,当我们在程序中需要一些功能的时候,我们一般采用的方式就是先new一个对象,然后从对象中获取我们所需功能的方法,但是我们有没有想过,如果一个我们的程序支持插件,但是我们并不知道这个插件都有哪些类,这种情况下该怎么办呢?还好反射可以解决这个问题,使用反射可以在程序运行期间从配置文件中读取类名,然后动态的获取对象类型的信息。所以,反射的最大好处就是在运行期间动态的构建组件,使得代码更具有灵活性和通用性。

正常方式:①引入需要的“包类”名称②通过new实例化③获得实例化对象

反射方式:①实例化对象②getClass方法③得到完整的“包类”名称

2 、理解Class类并获取Class实例

既然我们要使用反射创建对象,那么我们是如何创建Class呢?针对不同的实例对象反射出的对象是否是同一个呢?

获取Class类三种方式

  • 已知一个类的全限定类名,可通过Class类的静态方法forName获取
代码语言:javascript
复制
Class c=Class.forName("java.Lang.String")
  • 已知具体的类,可以通过该类的class属性获取
代码语言:javascript
复制
Class c=Person.class;
  • 已知某个类的实例,调用该实例的getClass()方法获取Class对象
代码语言:javascript
复制
Class c=person.getClass();

实例代码(以第一种方法为例)

  • 创建Person类
代码语言:javascript
复制
class People{
    private int id;
    private int age;
    private String name;
....
}

  • 测试类1
代码语言:javascript
复制
public class TestReflrction01 {
    public static void main(String[] args) throws ClassNotFoundException {
        //通过反射获取类的Class对象
        Class c1=Class.forName("reflection.People");
        System.out.println(c1);
    }
}

测试类的输出为:class reflection.People

那么,问题又来了,对于不同的实例对象获取的Class类时否是同一个呢?我们采用这样的方式,我们获取不同实例对象获取的Class的hashCode,如果hashCode相同,则可证明他们是同一个Class

  • 测试类2
代码语言:javascript
复制
public class TestReflrction01 {
    public static void main(String[] args) throws ClassNotFoundException {
        //通过反射获取类的Class对象
        Class c1=Class.forName("reflection.People");
        System.out.println(c1);
        //内存中只存在一个类的Class对象
        //一个类被加载后,类的整个结构都会封装在Class对象中
        Class c2=Class.forName("reflection.People");
        Class c3=Class.forName("reflection.People");
        Class c4=Class.forName("reflection.People");

        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());
        System.out.println(c4.hashCode());
    }
}

测试类的输出如下:

代码语言:javascript
复制
class reflection.People
460141958
460141958
460141958

因此,我们可以断定,同一个类的不同的实例对象反射出class对象是同一个,是唯一的。

3、类的加载过程以及反射创建对象时的内存分析

3.1类的加载过程分析

上面我们学习了如何创建Class类,但是我们肯定会有这样的疑惑,为什么可以动态创建Class类呢,它的原理是什么呢?要想了解它的原理,我们必须先了解下JVM内存

我们先看这样一张流程图

这张图详细的描述了我们编写的Java文件的执行流程,因为这里涉及了很多JVM的知识点,感兴趣的同学可以先看看我以前的一篇文章一文入门JVM虚拟机,后面还会继续补充相关知识点,在这里,我们主要分析类加载过程

我们都了解java程序都是放在虚拟机上执行的,Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验转换解析初始化,最终形成可以被虚拟机直接使用的Java类型。

其中验证准备解析统称为连接,下面我们详细分析下类的加载过程

  • 加载

  • 通过一个类的全限定名来获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区运行时数据结构
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为这个方法区这个类的各种数据的访问入口
  • 链接:将Java类的二进制代码合并到JVM的运行态之中的过程

  • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
  • 准备:正式为了类变量(static)分配内存并设置类变量默认初始值阶段,这些内存都将在方法区中分配
  • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程

Note:对于常量池中的符号引用解析,要结合具体的实际情况自行判断,到底是在类加载器加载时就对常量池中的符号引用解析,还是等到一个符号引用将要被使用前采取解析它。

  • 初始化

初始化阶段是类加载过程的最后一个阶段,在这个阶段时,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。初始化步骤如下:

  • 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生。(类构造器是构造类信息的,不是构造该类对象的构造器)
  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
  • 虚拟机会保证一个类的()方法在多线程环境下被正确加锁和同步。

这时,我们可能会有疑惑,什么时候会发生类的初始化呢?事实上,只要当类主动引用时才会发生初始化。

  • 类的主动引用

  • 当虚拟机启动,先初始化main方法所在的类
  • new一个类的对象
  • 调用类的静态成员(除了final常量)和静态方法
  • 使用java.lang.reflect包的方法对类进行反射调用
  • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类

那么,是不是可以理解为,类的被动引用就不会发生初始化了,是的,下面列出的这几种情况就不会发生类的初始化

  • 类的被动引用

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类的初始化
  • 通过数组定义类引用,不会触发此类的初始化
  • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中)

3.2 使用反射创建对象的内存分析

上面我们详细分析了Java的内存分布和类的加载流程,此时,我们编写的代码已经处于在运行期了,我们知道利用反射可以在运行期动态的创建对象,那么它的工作机制到底是什么样的呢?在下面的文章中,我们详细分析

上图是我们类加载过程结束后的内存分布,每个类都在堆中创建了代表自己本类的Class类。记住,这个Class类是用于创建Class对象的,我们继续向下分析。

当我们在栈中new A时,它首先会找到堆中的Class类,因为Class类是访问方法区类A中各种数据的访问入口。然后将相应的类信息带到堆中完成实例化。

这也就不难理解为为什么可以反射可以在运行时期动态的获取的对象。在下面的文章中,我们将详细讲解如何使用反射,即怎样利用反射创建运行时类对象,怎么获取运行时类的完整结构,如何调用运行时类的指定结构。

3.3反射相关API和提供的主要功能概述

反射相关的API

  • java.lang.Class:代表一个类
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类成员变量
  • java.lang.reflect.Constructor:代表类的构造器

反射机制提供的主要功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型的信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理

4、创建运行时类对象

在程序运行期间,Java运行时系统始终为所有对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。Java中提供了专门的类访问这些信息,保存这些信息的类称为Class。这个名称很容易让人产生混淆,因为在Object类中有一个方法用获取Class实例,此方法可以被所有的子类继承

代码语言:javascript
复制
public final Class getClass

4.1获取Class对象的三种方式

在Java API中,提供了获取Class类对象的三种方式

  • 使用Class.forName静态方法

使用这种的方法的前提是要知道类的全路径名

代码语言:javascript
复制
//方式一:通过类的全类名获取,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
       Class c1= Class.forName("reflection.People");
  • 使用类的.class方法

该方法适用于在编译前就已经明确要操作的Class

代码语言:javascript
复制
  //方式二:若一致具体的类,通过类的class属性获取
       Class c3=People.class;
  • 使用类对象的getClass()方法

该方法适用于有对象实例的情况下

代码语言:javascript
复制
//方式二:调用该实例的getClass()获取
       People people=new People();
  • 代码验证
代码语言:javascript
复制
public class TestReflection {
    public static void main(String[] args) throws ClassNotFoundException {

        //方式一:通过类的全类名获取,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
       Class c1= Class.forName("reflection.People");
       System.out.println(c1);

       //方式二:调用该实例的getClass()获取
       People people=new People();
       Class c2=people.getClass();
       System.out.println(c2);

        //方式三:若一致具体的类,通过类的class属性获取
       Class c3=People.class;
       System.out.println(c3);
    }
}

总结

实际上,对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含特定某个结构的有关信息。Class总结如下:

  • Class本身也是一个类
  • Class对象只能由系统建立对象
  • 一个加载的类在JVM中只会有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的一个.Class文件
  • 每个类的实例都会记得自己是由哪个Class实例所生成的
  • 通过Class可以完整地得到一个类中的所有被加载的结构
  • Class类时是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

5、获取运行时类的完整结构

在上面的文章中,我们讲解了如何使用反射机制来创建Class类对象,当有了实际的对象后,我们可以做哪些事情呢?反射机制为我们提供了哪些具体的操作方法呢?

java.lang.reflect包中有三个类FieldMethodConstructor分别用于描述类的属性方法构造器。这三个类都有一个叫做getName的方法,

Class类中的getFieldsgetMethodsgetConstructord方法将分别返回类提供的public(属性、方法和构造器的数组),其中也包括了父类的public成员。

Class类中的getDeclaredFieldsgetDeclaredMethodsgetDeclaredConstructors方法将分别返回类中声明的(全部)属性、方法和构造器,其中包括了私有成员和受保护成员,但不包括父类的成员

5.1获取运行时类的属性

  • 方法
代码语言:javascript
复制
Field[] getFields()
Filed[] getDeclaredFields()

getFileds方法将返回一个包含Field对象的数组,这些对象记录这个类或其父类的public属性;getDeclaredFileds也将返回一个包含Field对象的数组,这些对象记录这个类的全部属性。

Note:如果类中没有定义属性,或者Class对象描述的是基本类型或者数组类型,这些方法将返回一个长度为0的数组

  • 代码实践
代码语言:javascript
复制
public class TestReflection04 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class c1=Class.forName("reflection.People");

        //获得类的名字
        System.out.println(c1.getName());//包名+类名
        System.out.println(c1.getSimpleName());//类名

        //获得类的属性
        Field[] fields=c1.getFields(); //只能找到public
        fields=c1.getDeclaredFields();  //可以找到全部的属性
        for (Field field:fields){
            System.out.println(field);
        }

        //获得指定属性的值
        Field name=c1.getDeclaredField("name");
        System.out.println(name);

    }
}

5.2获得运行时类的方法

  • 方法
代码语言:javascript
复制
Method[] = new getMethods[]
Method[] =new getDeclaredMethods[]

getMethods方法将返回一个包含Method对象的数组,这些对象记录这个类或其父类的public方法;getDeclaredMethods[]也将返回一个包含Method对象的数组,这些对象记录这个类或接口的全部方法。

  • 代码实践
代码语言:javascript
复制
/**
 * 获取类的运行时结构
 * 包括方法、属性、构造器
 */
public class TestReflection04 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class c1=Class.forName("reflection.People");

   
       //获得类的方法
        Method[] methods=c1.getMethods();//获得本类及其父类的全部的public方法
        for(Method method:methods){
            System.out.println(method);
        }
        //获得本类的所有方法
        methods=c1.getDeclaredMethods();
        for(Method method:methods){
            System.out.println(method);
        }

        //获得指定的方法
        Method getName=c1.getMethod("getName",null);
        System.out.println(getName);
    }
}

5.3创建运行时类的构造器

  • 方法
代码语言:javascript
复制
Constructor[] getConstructors()
Constructor[] getDeclaredConstructors()

getConstructors方法将返回一个包含Constructor对象的数组,这些对象记录这个类或其父类的public公有构造器;getDeclaredConstructors也将返回一个包含Constructor对象的数组,这些对象记录这个类的所有构造器。

  • 代码实践
代码语言:javascript
复制
public class TestReflection04 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class c1=Class.forName("reflection.People");
        Constructor[] constructors=c1.getConstructors();
        for (Constructor constructor:constructors){
            System.out.println(constructor);
        }
        constructors=c1.getDeclaredConstructors();
        for (Constructor constructor:constructors){
            System.out.println(constructor);
        }
        //获得指定的构造器
        Constructor declareConstructor=c1.getDeclaredConstructor(int.class,int.class,String.class);
        System.out.println(declareConstructor);
    }
}

6、动态创建类的对象

上面的文章中,我们讲解了如何获取类的运行时结构,如果我们要使用,必须创建类的对象

创建类的对象:调用Class对象newInstance()方法

类必须有一个无参构造器,因为当操作时,若没有明确调用类中的构造器,则会调用无参构造器,若要实例化对象,需要使用构造器 类的构造器访问权限需要足够

  • 通过Class类的getDeclaredConstructor(Class.... parameTypes)获得本类的指定形参类型的构造器
  • 向构造器的形参中传递一个对象数组中去,里面包含了构造器中所需的各个参数
  • 通过Constructor实例化对象

测试代码

代码语言:javascript
复制
public class TestRelection05 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class c1=Class.forName("reflection.People");
        //构造一个对象,本质上调用了无参构造器
        People people=(People) c1.newInstance();
        System.out.println(people);

        //通过构造器创建对象
        Constructor constructor=c1.getDeclaredConstructor(int.class,int.class,String.class);
        People people2=(People) constructor.newInstance(23,25,"Simon");
        System.out.println(people2);
        //通过反射调用普通方法
        People people3=(People) c1.newInstance();
        Method setName=c1.getDeclaredMethod("setName", String.class);
        //invoke:激活的意思(对象,"方法的值")
        setName.invoke(people3,"Simon Lang");
        System.out.println(people3.getName());

        //通过反射调用属性
        People people4=(People) c1.newInstance();
        Field name=c1.getDeclaredField("name");
        //不能直接操作私有属性,我们需要关闭程序的安全监测,属性或者方法的setAccessible(true)
        name.setAccessible(true);
        name.set(people4,"Simon snow");
        System.out.println(people4.getName());
    }
}

setAccessible

  • Method和Field、Constructor对象都setAccessible()方法
  • setAccessible作用是启动和禁用访问安全检查的开关
  • 参数值为true则指示反射的对象在使用时用该取消Java语言的访问检查

  • 使得原本无法访问的私有成员也可以访问
  • 参数值为false则指示反射的对象应该实施Java语言的访问检查

山腰太拥挤了,我想去山顶看看

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-08-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 今日Java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一文带你彻底理解反射
    • 前言
      • 1、Java反射机制的基本概述
        • 2 、理解Class类并获取Class实例
          • 3、类的加载过程以及反射创建对象时的内存分析
            • 3.1类的加载过程分析
            • 3.2 使用反射创建对象的内存分析
            • 3.3反射相关API和提供的主要功能概述
          • 4、创建运行时类对象
            • 4.1获取Class对象的三种方式
            • 总结
          • 5、获取运行时类的完整结构
            • 5.1获取运行时类的属性
            • 5.2获得运行时类的方法
            • 5.3创建运行时类的构造器
          • 6、动态创建类的对象
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档