Java反射机制

一、反射机制

1、一个需求引出反射

-根据配置文件re.properties指定信息,创建Cat对象并调用相关方法

-这样的要求在学习框架时特别多,即通过外部文件配置,在不修改源码的情况下控制程序,也符合设计模式的ocp(开闭原则:不修改源码,来扩展功能)

-在类ReflectionQuestion中调用

传统方式:自己new对象(不符合设计原则)

IO流的方式,可以读写配置文件

但是第二步创建类的时候会出现问题,因为读入的classfullpath是一个字符串:

这里就要用到反射机制:

-加载类:返回一个Class类型的对象

-通过cls得到加载的类的对象的实例(可以强转为Cat类,也可以不转,转不转用cls得到的加载对象的运行类型都是Cat)

-通过cls得到加载的类的方法对象method(在放射机制中,可以把方法视为对象,万物皆对象)

-通过方法对象来实现调用方法(传统方式是 对象.方法,这里反射是 方法.invoke(对象))(传统方法应该是对象调用方法,但在反射机制中是通过对象方法来调用方法的)

为什么要有这种麻烦的机制?

-因为可以在不修改源码的情况下,只通过配置文件来控制程序。(例如,这里我不调用hi方法,而是cry方法,不得已只能动源码,重新掉cry方法,但是使用刚才的第二种方式,只需要修改配置文件的method即可)

2、反射机制介绍

-反射机制允许程序在执行器件借助于Reflection API取得任何类的内部信息(比如成员变量,构造方法,成员方法等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到

-加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息,通过这个对象得到类的结构。这个Class对象像一面镜子,通过这个镜子看到类的结构,所以称之为反射

例如:我加载了一个p对象,类型是Persion类,在底层创建的时候会产生一个cls对象,类型是Class,这里有点绕,因为Class本身意思是类型,这里的Class也就是一个类型,如果Java设计者把Class改成ReflectionClass就会好理解很多

反射底层机制 具体可看这个图:

-为什么前面用反射时是用方法对象调用对象实现对象的方法,看这个图,是因为在加载器的作用下,类的方法被加载为了各种方法对象数组/集合

-在面试时喜欢问类加载阶段的相关问题,这属于JVM的范畴,但实际工作中用到的很少

Java反射机制可以完成:

-在运行时判断任意一个对象所属的类

-在运行时构造任意一个类的对象

-在运行时得到任意一个类锁具有的成员变量和方法

-在运行时调用任意一个对象的成员变量和方法

-生成动态代理

反射相关的主要的类:

-java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象

-java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法

-java.lang.reflec.Field:代表类的成员变量,Field对象表示某个类的成员变量

-java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示某个类的构造器

这些类在java.lang.reflection中

前面演示过了Method对象,这里演示Field与Constructor

反射优点和缺点:

-优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就会失去底层支持

-缺点:反射本身是解释执行,对执行速度有影响

用传统方法调用对象方法9千万次耗时0毫秒 , 用反射机制调用9千万次耗时180毫秒

用传统方法调用对象方法9亿次耗时10毫秒,反射机制调用耗时1545毫秒

反射调用优化-关闭访问检查

-Method和Field、Constructor对象都有setAccessible()方法

- setAccessible()作用是启动和禁用访问安全检查的开关

-参数值为true表示 反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查

关闭访问检查关闭后 1545 –> 1418

二、Class

Class类介绍:

-Class也是类,因此也继承Object类

-Class对象不是new出来的,而是系统创建的

-对于某个类的Class对象,在内存中只有一份,因为类只加载一次

-每个类的实例都会记得自己是由哪个Class实例所生成

-通过Class对象可以得到一个类的完整结构

-Class对象是存放在堆里的

-类的字节码二进制数据是放在方法区的,有的地方称为类的元数据

Class<?> 的<?>比送到hi不确定的Java类型

获取Class类的对象

-在代码/编译阶段:用Class.forName()

-在类加载阶段:类.class

-在运行阶段:对象.getClass()

-通过类加载器过的Class类对象

通过int得到的class和通过Integer得到的class是同一个类对象,它们的hashcode一样,这里在java底层体现了一个自动装箱机制

哪些类型有Class对象

-外部类、成员内部类、静态内部类、局部内部类、匿名内部类

-interface:接口

-数组

-enum:枚举

-annotation:注解

-基本数据类型

-void

三、类加载

反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载

类加载的基本说明:

-静态加载:编译时加载相关的类,如果没有则报错,依赖性太强

-动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性

类的加载时机:

-当创建对象时(new)//静态加载

-当子类被加载时,父类也被加载 //静态加载

-调用类中的静态成员时 //静态加载

-通过反射//动态加载

类的加载过程:

这里的初始化不是new,因为这里说的是类加载阶段,不是对象的创建阶段

各阶段所做的事

-加载阶段:JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件、也可能是jar包,甚至网络)转化为二进制字节流记载到内存中,并生成一个代表该类的java.lang.Class对象。

-连接阶段/验证:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。包括:文件格式验证(是否以魔数ox cafebabe开头)、元数据验证、字节码验证和符号引用验证。可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机加载的时间。(加载阶段生成一个SecurityManager来进行各种操作)

-连接阶段-准备:JVM回在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值,如0、0L、null、false等)。这些变量所使用的内存都将在方法区中进行分配

准备阶段实例:

-连接阶段-解析:虚拟机将常量池内的符号引用替换为直接引用的过程(就是相对引用变成直接的内存地址引用,这个过程全部由JVM机完成,举个例子就是小明的位置开始是相对小王东100米变成地球具体经度维度确定位置)

-初始化:1.到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行<clint>()方法的过程。2. <clint>()方法是由编译器按语句在源文件中出现的顺序,一次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。3.虚拟机会保证一个类的<clint>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clint>()方法,其它线程都需阻塞等待,知道活动线程执行<clint>()方法完毕。

举例:B类中含有静态代码块和静态变量:

这里没new,所以不会输出构造器里面的语句

可以看到,因为有这个加锁机制,才能保证某个类在内存中,只有一份Class对象,如果没加锁,这个类会加载多次,内存里面Class对象就乱了。

这里更详细的机制和内容可以查看JVM虚拟机的各种技术卷

四、反射获取类的结构信息

前面说了,只要我们拿到了一个类的class对象,我们就可以获取类的很多信息常用的有:

Java.lang.Class类:

-getName:获取全类名

-getSimpleName:获取简单类名

-getFields:获取所有public修饰的属性 , 包含本类以及父类的

-getDeclaredFields:获取本类中所有的属性包括private修饰的属性

-getMethods:获取所有pubilc修饰的方法,包含本类以及父类

-getDeclaredMethods:获取本类的所有方法包括private修饰的方法

-getConstructors:获取本类所有public修饰的构造器

-getDeclaredConstructors:获取本类所有构造器

-getPackage:以package形式返回包信息

-getSuperClass以Class形式返回父类信息

-getInterfaces:以Class[]形式返回接口信息

-getAnnontations:以Annotation[]形式返回注解信息

Java.lang.reflect.Constructor类:

-getModifiers

-getName

getParameterTypes

通过反射创建对象:

-调用类中的public修饰的无参构造器

-调用类中指定的构造器

Class类中相关方法:

-newInstance:调用类中的无参构造器,获取对应类的对象

-getConstructor:根据参数列表,获取对应的pubilc构造器对象

-getDelaredConstructor:根据参数列表,获取对应所有构造器对象

Constructor类相关方法:

-setAccessible:爆破

-newInstance(Object obj)调用构造器

案例:

通过反射访问类中的成员:访问属性:

-根据属性名获取Field对象                                

 Field f  = class对象.getField(属性名);

-爆破:f.setAccessible(true) ; //f是Field,爆破的定义:类中有些属性是private属性不能用反射直接访问,但我们爆破这个属性之后就可以访问了,前面直接用getDeclareField也可以,就不用爆破

-访问:

 f.set(o , value) o表示对象

 syso(f.get(o));

-如果是静态属性,则set和get中的参数o可以写成null

访问方法:

-根据方法名和参数列表获取Method方法对象:Method m = class.getMethod(方法名 , xx.class)//得到所有pubic修饰的方法

-获取对象:Object o = class.newInstance();

-爆破:m.setAccessible(true)

-访问:Object returnValue = m.invoke(o , 实参列表)

注意:如果是静态方法,则invoke参数o可以写成null

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值