Java反射,就是这么简单

一文带你完全理解反射

前言

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

静态语言和动态语言python

对于咱们来讲,接触作多的就是高级语言,包括C、C++、Java、python、JavaScript等。这些高级语言能够大概分为两大类,即动态语言静态语言编程

  • 静态语言

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

  • 动态语言

动态语言通常指的是脚本语言,如python、JavaScript等,这类语在编写代码是没必要指定类型安全

  • 动态语言VS静态语言

从直观上看,静态语言在代码编译时须要指定变量类型;而动态语言则是在运行期间才会检查变量类型。因此,针对动态语言来讲,咱们能够在运行时改变其结构,即运行时的代码能够根据某些条件改变自身的结构。markdown

按照划分,Java是属于静态语言的,可是因为Java提供了反射机制,使得Java成为了一种准动态语言,利用反射能够得到相似动态语言的特性,使得编程更加的灵活。数据结构

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

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

  • 什么是反射?

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

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

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

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

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

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

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

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

获取Class类三种方式

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

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

  • 建立Person类
class People{
    private int id;
    private int age;
    private String name;
....
}

复制代码
  • 测试类1
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
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());
    }
}
复制代码

测试类的输出以下:

class reflection.People 460141958 460141958 460141958 复制代码

所以,咱们能够判定,同一个类的不一样的实例对象反射出class对象是同一个,是惟一的。

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

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:表明类的构造器

反射机制提供的主要功能

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

四、建立运行时类对象

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

public final Class getClass
复制代码

4.1获取Class对象的三种方式

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

  • 使用Class.forName静态方法

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

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

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

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

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

//方式二:调用该实例的getClass()获取
       People people=new People();
复制代码
  • 代码验证
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对象

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

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

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

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

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

5.1获取运行时类的属性

  • 方法
Field[] getFields()
Filed[] getDeclaredFields()
复制代码

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

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

  • 代码实践
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得到运行时类的方法

  • 方法
Method[] = new getMethods[]
Method[] =new getDeclaredMethods[]
复制代码

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

  • 代码实践
/** * 获取类的运行时结构 * 包括方法、属性、构造器 */
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建立运行时类的构造器

  • 方法
Constructor[] getConstructors()
Constructor[] getDeclaredConstructors()
复制代码

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

  • 代码实践
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);
    }
}

复制代码

六、动态建立类的对象

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

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

类必须有一个无参构造器,由于当操做时,若没有明确调用类中的构造器,则会调用无参构造器,若要实例化对象,须要使用构造器

类的构造器访问权限须要足够

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

测试代码

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语言的访问检查

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

相关文章
相关标签/搜索