zl程序教程

您现在的位置是:首页 >  后端

当前栏目

java基础之反射(细节)

2023-09-14 08:59:52 时间

 

一. java 反射初识
反射是重要特性,使用反射可在运行时动态生成对象、获取对象属性及调用对象方法。
与编译期的静态行为相对,所有的静态型操作都在编译期完成,而反射所有行为基本都是在运行时进行的(很重要特性)。它让Java有了动态特性,可以让程序更加灵活强大。
反射的背景和应用?
反射大量运用在框架代码和工具代码中,因为这类工程项目往往对于灵活性的要求较高。
为什么要学习反射原理的原因:为了更加深刻地理解我们所用的工具和框架,了解了反射原理,我们能够在使用框架时优化出更好的性能
原理?
java程序在编译完成后,会把所有class文件中所包含的类的基本元信息装载到JVM内存中,以Class类的形式保存,每一个Class类对象代表一个具体类的基本元信息。反射就是利用存储着类的所有相关信息的Class类对象,就像镜子,故称“反射”。
反射总体流程?
准备阶段:编译期装载所有的类,将每个类的元信息保存至Class类对象中,每一个类对应一个Class对象
获取Class对象:调用x.class/x.getClass()/Class.forName() 获取x的Class对象clz(这些方法的底层都是native方法,是在JVM底层编写好的)
进行实际反射操作:通过clz对象获取Field/Method/Constructor对象进行进一步操作
class类 :代表一个类,是Java反射机制的起源和入口。
用于获取与类相关的各种信息, 提供了获取类信息的相关方法;Class类继承自Object类;
Class类是所有类的共同的图纸:
每个类有自己的对象,同时每个类也看做是一个对象,有共同的图纸Class,存放类的结构信息,能够通过相应方法取出相应的信息:类的名字、属性、方法、构造方法、父类和接口。

反射提供的功能?
在运行时判断任意一个对象所属的类;
在运行时构造任意一个类的对象;
在运行时判断任意一个类所具有的成员变量和方法; 在运行时调用任意一个对象的方法;
反射的使用?
// 通过Class.forName("全类名")
Class actionClass=Class.forName(“com.xx.MyClass”);
Object action=actionClass.newInstance();
Method method = actionClass.getMethod(“myMethod”,null);
method.invoke(action,null)
反射效率提升?
缓存重复用到的对象
void createInstance(String className){
cachedClass = cache.get(className);
if (cachedClass == null){
cachedClass = Class.forName(className);
cache.set(className, cachedClass);
}
return cachedClass.newInstance();
setAccessible(true) ?
accessible 标志被设置为true,那么反射对象在使用的时候,不会去检查Java语言权限控制(private之类的)
使用高效率反射框架?
EventBus原理 https://www.jianshu.com/p/d9516884dbd4

二. java面试题-java反射机制?

Java反射机制的作用:
1)在运行时判断任意一个对象所属的类。
2)在运行时判断任意一个类所具有的成员变量和方法。
3)在运行时任意调用一个对象的方法
4)在运行时构造任意一个类的对象
拓展:
1、什么是反射机制?
简单说,反射机制值得是程序在运行时能够获取自身的信息。在java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。

反射原理:


编译时,Java类都会被编译成一个.class 文件,这些 Class对象承载了这个类型的父类、接口、构造函数、方法、属性等原始信息,这些 class 文件在程序运行时会被ClassLoader加载到虚拟机中,程序在运行时,当一个类被加载以后,Java虚拟机就会在内存中自动产生一个Class对象。


Java中一切都是对象,其中所有类型包括基本类型,数组,都有对应的Class类的对象。java中可以通过类名里来获取该类的class对象。
有个这个class对象,就可以做很多操作了。
2、java反射机制提供了什么功能?


在运行时能够判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时调用任一对象的方法
在运行时创建新类对象
3、哪里用到反射机制?


jdbc中有一行代码:Class.forName('com.mysql.jdbc.Driver.class').newInstance();那个时候只知道生成驱动对象实例,这就是反射,现在
很多框架都用到反射机制,安卓的EventBus框架,hibernate,struts都是用反射机制实现的。
4、反射机制的优缺点?


静态编译:在编译时确定类型,绑定对象,即通过
动态编译:运行时确定类型,绑定对象。

动态编译最大限度的发挥了java的灵活性,体现了多态的应用,有利于降低类之间的耦合性。
一句话,优点就是可以实现动态创建对象和编译,体现出很大的灵活性,它的灵活性就表现的十分明显。
比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编
译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如
这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能
的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功
能。


--缺点是对性能有影响。使用反射基本上是一种解释操作,告诉JVM,我们希望做什么并且它满足我们的要求。
这类操作总是慢于只直接执行相同的操作。

========
关于反射:
反射是可以在运行时获取类的函数、属性、父类、接口等 Class 内部信息的机制。通过反射还可以在运行期实例化对象,调用方法,通过调用 get/set 方法获取变量的值,(包括私有的方法也可以)。这种机制在框架源码中很常见。
一个比较常见的场景就是编译时对于类的内部信息不可知,必须得到运行时才能获取类的具体信息。
比如ORM 框架,在运行时才能够获取类中的各个属性,然后通过反射的形式获取其属性名和值,存入数据库。

java中提供的API:
1 获取Class对象:
// 加载指定的 Class 对象,参数 1 为要加载的类的完整路径 :包名+类名 ( 常用方式 )
public static Class<?> forName (String className)
// 加载指定的 Class 对象,参数 1 为要加载的类的完整路径;
// 参数 2  为是否要初始化该 Class 对象,
参数 3 为指定加载该类的 ClassLoader.
public static Class<?> forName (String className, boolean shouldInitialize, ClassLoader classLoader)
注意:在调用 Class.forName()方法时,没有在编译路径下(classpath)找到对应的类,那么将会抛出 ClassNotFoundException。

2 通过反射构造对象,首先要获取类的 Constructor(构造器)对象,然后通过 Constructor 来创建目标类的对象。
Constructor<?> constructor = clz.getConstructor(String.class);
// 设置 Constructor 的Accessible
constructor.setAccessible(true);
注意:通过反射获取到 Constructor、Method、Field 后,在反射调用之前将此对象的 accessible 标志设置为 true,以此来提升反射速度。
值为 true 则指示反射的对象在使用时应该取消Java语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。

3 反射获取类中函数
// 获取 Class 对象中指定函数名和参数的函数,参数一为函数名,参数 2 为参数类型列表
public Method getDeclaredMethod (String name, Class...<?> parameterTypes)
// 获取该 Class 对象中的所有函数( 不包含从父类继承的函数 )
public Method[] getDeclaredMethods ()
// 获取指定的 Class 对象中的**公有**函数,参数一为函数名,参数 2 为参数类型列表
public Method getMethod (String name, Class...<?> parameterTypes)
// 获取该 Class 对象中的所有**公有**函数 ( 包含从父类和接口类集成下来的函数 )
public Method[] getMethods ()
注意: getDeclaredMethod 和 getDeclaredMethods 包含 private、protected、default、public 的函数,并且通过这两个函数获取到的只是在自身中定义的函数,从父类中集成的函数不能够获取到。而 getMethod 和 getMethods 只包含 public 函数,父类中的公有函数也能够获取到。

4 反射获取类中的属性

public Method getDeclaredField (String name) // 获取 Class 对象中指定属性名的属性,参数一为属性名
public Method[] getDeclaredFields ()// 获取该 Class 对象中的所有属性( 不包含从父类继承的属性 )
public Method getField (String name)// 获取指定的 Class 对象中的**公有**属性,参数一为属性名
public Method[] getFields ()// 获取该 Class 对象中的所有**公有**属性 ( 包含从父类和接口类集成下来的公有属性 )
5 反射获取指定类型的注解
// 获取指定类型的注解
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) ;
// 获取 Class 对象中的所有注解
public Annotation[] getAnnotations() ;

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

2 反射是如何实现的

1.简单介绍Class
——java基于此基础,才能够实现反射。
Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识。这项信息纪录了每个对象所属的类。
虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类。
也就是说,ClassLoader找到了需要调用的类时(java为了调控内存的调用消耗,类的加载都在需要时再进行,很抠但是很有效),就会加载它,然后根据.class文件内记载的类信息来产生一个与该类相联系的独一无二的Class对象。该Class对象记载了该类的字段,方法等等信息。以后jvm要产生该类的实例,就是根据内存中存在的该Class类所记载的信息(Class对象应该和我所了解的其他类一样会在堆内存内产生、消亡)来进行。
而java中的Class类对象是可以人工自然性的(也就是说开放的)得到的(虽然你无法像其他类一样运用构造器来得到它的实例,因为Class对象都是jvm产生的。不过话说回来,客户产生的话也是无意义的),而且,更伟大的是,基于这个基础,java实现了反射机制。
我的理解:在JVM中,根据.class文件加载后,产生了独一无二的Class对象(这个是由JVM创建的,根据
图中所示,Class内部的私有构造器),而后如果还要产生该类的实例,就根据刚才生成的Class对象,通过newInstance得到,但是不能利用构造器得到实例了。

2.如何做到反射?
反射其实就是一个文本扫描器,扫描.class文件。

3.Reflection动态相关机制
3.1动态语言
Reflection是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过ReflectionAPIs取得任何一个已知名称的class的内部信息。
包括其modifiers(诸如public、static等)、superclass(例如Object)、实现了的 interfaces (例如Serializable)、也包括其fields和methods的所有信息,并可于运行时改变fields内容或调用methods。
换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。

这种“看透”class的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflection和introspection是常被并提的两个术语。

3.2Java反射机制提供的功能

1.在运行时判断任意一个对象所属的类。
2.在运行时构造任意一个类的对象。
3.在运行时判断任意一个类所具有的成员变量和方法。
4.在运行时调用任意一个对象的方法。

3.3Java Reflection API 简介和使用
在JDK中,主要由以下类来实现Java反射机制,这些类(除了第一个)都位于java.lang.reflect包中:
Class类:代表一个类,位于java.lang包下。
Field类:代表类的成员变量(成员变量也称为类的属性)。
Method类:代表类的方法。
Constructor类:代表类的构造方法。
Array类:提供了动态创建数组,以及访问数组的元素的静态方法。

3.3.1Class对象

要想使用反射,首先需要获得待操作的类所对应的Class对象。
Java中,无论生成某个类的多少个对象,这些对象都会对应于同一个Class对象。
这个Class对象是由JVM生成的,通过它能够获悉整个类的结构。

获取Class对象的3种方式(以User类为例):
使用Class类的静态方法,如:
Class<?> clazz = Class.forName("com.bobo.User");
2.使用类的.class语法,如
Class clazz = User.class;
3.使用对象的getClass()方法,如
User user =new User();
Class<?> clazz =user.getClass();
注:getClass()方法定义在Object类中,不是静态方法,需要通过对象来调用,并且它声明为final,表明不能被子类所覆写。

3.3.2生成对象
若想通过类的不带参数的构造方法来生成对象,我们有两种方式:
1.先获得Class对象,然后通过该Class对象的newInstance()方法直接生成即可:
Class<?> classType = User.class;
Object obj = classType.newInstance();

2.先获得Class对象,然后通过该对象获得对应的Constructor对象,再通过该Constructor对象的newInstance()方法生成 (其中User是一个自定义的类,有一个无参数的构造方法,也有带参数的构造方法):

Class<?> classType = User.class;

// 获得Constructor对象,此处获取第一个无参数的构造方法的
Constructor cons = classType.getConstructor(new Class[] {});
// 通过构造方法来生成一个对象
Object obj = cons.newInstance(new Object[] {});

若想通过类的带参数的构造方法生成对象,只能使用下面这一种方式:
(User为一个自定义的类,有无参数的构造方法,也有一个带参数的构造方法,传入字符串和整型)
Class<?> classType = User.class;
Constructor cons2 = classType.getConstructor(new Class[] {String.class, int.class});
Object obj2 = cons2.newInstance(new Object[] {"ZhangSan",20});

小结:可以看出调用构造方法生成对象的方法和调用一般方法的类似,不同的是从Class对象获取Constructor对象时不需要指定名字,而获取Method对象时需要指定名字。

3.3.3获取类的构造器
首先介绍一下Constructor类,这个类用来封装反射得到的构造器,Class有四个方法来获得Constructor对象
public Constructor<?>[] getConstructors() 返回类中所有的public构造器集合,默认构造器的下标为0
public Constructor<T> getConstructor(Class<?>... parameterTypes) 返回指定public构造器,参数为构造器参数类型集合
public Constructor<?>[] getDeclaredConstructors() 返回类中所有的构造器,包括私有
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 返回任意指定的构造器
从名字来看,还是很好懂的,带上Declared的都是获得所有的构造方法,包括私有,哈,这下我们就可以调用原本不允许调用的私有构造器了,看代码。

例:
//指定参数列表获取特定的方法
Constructor con = cls1.getDeclaredConstructor(new Class[]{String.class});
con.setAccessible(true); //设置可访问的权限
Object obj = con.newInstance(new Object[]{"liyang"});
System.out.println(obj); //打印一下这个对象的信息

//获取所有的构造方法集合
Constructor con1[] = cls1.getDeclaredConstructors();
con1[1].setAccessible(true);
Object obj1 = con1[1].newInstance(new Object[]{"tom"});
System.out.println(obj1);

3.3.4获取类的成员变量
了解了构造器,其实你可以猜到成员变量的获取方法了,成员变量用Field类进行封装。
主要的方法非常的类似:
public Field getDeclaredField(String name) 获取任意指定名字的成员
public Field[] getDeclaredFields() 获取所有的成员变量
public Field getField(String name) 获取任意public成员变量
public Field[] getFields() 获取所有的public成员变量
例:
Field mem = cls1.getDeclaredField("name");
mem.setAccessible(true); // 设置可访问的权限
System.out.println("we get form field :"+mem.get(obj));

3.3.5获取类的方法
封装类的方法的类是Method.获取method也有四个方法:
public Method[] getMethods() 获取所有的共有方法的集合
public Method getMethod(String name,Class<?>... parameterTypes) 获取指定公有方法 参数1:方法名 参数2:参数类型集合
public Method[] getDeclaredMethods() 获取所有的方法
public Method getDeclaredMethod(String name,Class<?>... parameterTypes) 获取任意指定方法
例:
Method f = clazz.getMethod("getName", null);
Object name = f.invoke(obj, null);
System.out.println("we invoke method : "+ name);
这个很简单吧,无参的时候我们只要传null就行了。

4反射的实际应用

配置文件:
通过配置文件就可以让我们获得指定的方法和变量,在我们创建对象的时候都是通过传进string实现的,就好像你需要什么,我们去为你生产,还有我们一直在用Object,这就说明java语言的动态特性,依赖性大大的降低了。
AOP,Struts就是应用了这个。

5反射的优缺点
5.1优点
反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。

5.2缺点
对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

 

四. Java语言的反射机制初步学习

基本概念:

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

       Java 反射机制主要提供了以下功能: 

       ①:在运行时判断任意一个对象所属的类。
       ②:在运行时构造任意一个类的对象。   
       ③:在运行时判断任意一个类所具有的成员变量和方法。
       ④: 在运行时调用任意一个对象的方法

      反射机制允许程序在运行时通过反射的API获取类中的描述,方法,并且允许我们在运行时改变fields内容或者去调用methods

(二)Java Reflection APIs简介:

    在JDK中,主要由以下类来实现Java反射机制,这些类都
    位于java.lang.reflect包中
     ①:Class类:代表一个类。【注:这个Class类进行继承了Object,比较特别】
     ②:Field 类:代表类的成员变量(成员变量也称为类的属性)。
     ③:Method类:代表类的方法。
     ④:Constructor 类:代表类的构造方法。
     ⑤:Array类:提供了动态创建数组,以及访问数组的元素的静态方法

简要说下是使用方法的步骤:

     要想使用使用反射,我们要去获取我们需要进行去处理的类或者对象的Class对象,其中我们主要有三种方法去获取

      ①:使用Class的静态方法forName():例如:Class.forName("java.lang.Class");

      ②:使用XXX.Class语法:例如:String.Class;

      ③:使用具体某个对象.getClass()方法:例如String str="abc"; Class<?> tClass=str.getClass();

     先看一个例子:这个例子对于指定的类名,使用反射来获取该类中的所有声明的方法,(使用第一种获取Class对象的方法)(主要代码如下:):

  1. /** 
  2.  * 使用反射来获取Class中的生命的方法,包括私有的方法 
  3.  */  
  4. import java.lang.reflect.Method;  
  5. public class Reflection1 {  
  6.     public static void main(String[] args) throws Exception {  
  7.         //使用Class去调用静态方法forName()获得java.lang.Class的Class对象  
  8.         Class<?> tClass = Class.forName("java.lang.Class");  
  9.         //获取该class中声明的所有方法  
  10.         Method[] methods = tClass.getDeclaredMethods();  
  11.         for (Method method : methods) {  
  12.             System.out.println(method);  
  13.         }  
  14.     }  
  15. }  

     (三)查看Class的API发现Class类是Reflection API 中的核心类,它有以下几个常用的方法

            ①: getName():获得类的完整名字。
            ②: getFields():获得类的public类型的属性。
            ③: getDeclaredFields():获得类的所有属性。
            ④: getMethods():获得类的public类型的方法。
            ⑤: getDeclaredMethods():获得类的所有方法。

            ⑥:getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字parameterTypes参数指定方法的参数类型。

            ⑦:getConstructors():获得类的public类型的构造方法。

            ⑧:getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes参数指定构造方法的参数类型。

            ⑨:newInstance():通过类的不带参数的构造方法创建这个类的一个对象。

         先看上面的⑧和⑨其中都能生成对象,但是因为构造函数有无参和有参构造函数两种,所以我们分两种情况考虑

 情况一:如果是无参的构造函数来生成对象:

    首先我们去获取Class对象,然后直接通过Class对象去调用newInstance()方法就可以

  1. Class<?> tclass = Reflection2.class;  
  2. Object reflection2 = classType.newInstance();  

        首先我们也是去获取Class对象,然后去去调用getConstructor()得到Constructor对象,接着直接调用newInstance()即可

  1. Class<?> classType = Reflection2.class;  
  2. t reflection2 = classType.newInstance();  
  3. Constructor<?> constructor = classType.getConstructor(new Class[] {});  
  4.  reflection2 = constructor.newInstance(new Object[] {});              

 情况二:现在是有参构造函数,那我们只有一种方法来通过反射生成对象:          

  1. Class<?> tClass = Person.class;    
  2. Constructor cons = classType.getConstructor(new Class[]{String.class, int.class});     
  3. Object obj = cons.newInstance(new Object[]{“zhangsan”, 19});   

   接下来根据以上的一些常用的方法,使用反射举几个例子(使用反射来访问类中的方法):

  1. /** 
  2.  * 反射练习二,使用反射访问类中的方法 
  3.  */  
  4. public class Reflection2 {  
  5.     public int sum(int a, int b) {  
  6.         return a + b;  
  7.     }  
  8.     public String addStr(String str) {  
  9.         return "This is the:" + str;  
  10.     }  
  11.     public static void main(String[] args) throws Exception {  
  12.         Class<?> classType = Reflection2.class;  
  13.         // Object reflection2 = classType.newInstance();  
  14.         Constructor<?> constructor = classType.getConstructor(new Class[] {});  
  15.         Object reflection2 = constructor.newInstance(new Object[] {});  
  16.         // 通过反射进行反射出类中的方法  
  17.         Method sumMethod = classType.getMethod("sum", new Class[] { int.class,   int.class });  
  18.         //invoke方法的值永远只是对象  
  19.         Object result1 = sumMethod.invoke(reflection2, new Object[] { 6, 10 });  
  20.         System.out.println((Integer) result1);  
  21.         Method addStrMethod = classType.getMethod("addStr",   new Class[] { String.class });  
  22.         Object result2 = addStrMethod.invoke(reflection2,   new Object[] { "tom" });  
  23.         System.out.println((String) result2);  
  24.     }  
  25. }  

           ④:通过反射机制调用对象的私有方法,访问对象的私有变量....

我们大家都知道,在Java语言中,如果我们对某些变量,或者方法进行private的声明,然后我们在其他类中进行不能去调用这些方法和变量,但是通过反射机制,这些私有声明将不复存在【提醒一点:在写程序的时候,我们最好不要故意经常去使用反射机制来打破这种私有保护...】

            要实现这种功能,我们需要用到AccessibleObject类中的public void setAccessible(boolean flag)方法:

 使用这个方法,把参数flag设置成true,然后我们的field或者method就可以绕过Java语言的语法访问的检查

  使用反射去访问私有方法

  1. public class Test01 {  
  2.     private String getName(String name) {  
  3.         return "This i:" + name;  
  4.     }  
  5. }  
  6. public class TestPrivate01 {  
  7.     public static void main(String[] args) throws Exception {  
  8.         Test01 p = new Test01();  
  9.         Class<?> classType = p.getClass();  
  10.         Method method = classType.getDeclaredMethod("getName",  
  11.                 new Class[] { String.class });  
  12.         method.setAccessible(true);  
  13.         Object object = method.invoke(p, new Object[] { "tom" });  
  14.         System.out.println((String)object);  
  15.     }  
  16. }  

使用反射机制去访问私有变量:

  1. public class Test02 {  
  2.   private String name="张三";  
  3.   private String getName()  
  4.   {  
  5.       return name;  
  6.   }  
  7. }    
  8. public class TestPrivate02 {  
  9.     public static void main(String[] args) throws Exception {  
  10.         Test02 p = new Test02();  
  11.         Class<?> classType = p.getClass();  
  12.         Field field = classType.getDeclaredField("name");  
  13.         //设置true,使用可以绕过Java语言规范的检查  
  14.         field.setAccessible(true);  
  15.         //对变量进行设置值  
  16.         field.set(p, "李四");  
  17.         Method method = classType.getDeclaredMethod("getName", new Class[] {});  
  18.         method.setAccessible(true);  
  19.         Object object = method.invoke(p, new Object[] {});  
  20.         System.out.println((String) object);  
  21.     }  
  22. }