ja反射机制

java反射机制

前言

对于Ja反射,虽然在日常工作中经常用到,但是从来没有系统的总结过,所以我就趁有空的时候总结一下,加深理解。

如发现有谬误,欢迎批评指正。

本文大部分相关知识点都是从甲骨文官方文档中总结出来的。对于英语好的朋友,建议直接看原文。

例如,首先描述定义:

反射使Ja代码能够发现关于已加载类的字段、方法和构造函数的信息,并在安全限制内使用反射的字段、方法和构造函数来操作它们的底层对应物。

通过反射,Ja代码可以发现关于已加载类的字段、方法和构造函数的信息,并可以在安全限制内对这些字段、方法和构造函数进行操作。

简而言之,您可以通过运行状态中的反射机制来实现:

对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;

动态获取信息和动态调用对象的方法的功能称为ja语言的反射机制。

在我看来,我们通常使用的Ja反射主要涉及两个类(接口)类和成员。如果把这两类讲清楚,反思基本就ok了。

Class详解

说到反思,就不得不提课,课可以说是反思的基础。注意,这里说的class和Class关键字不是一回事。声明ja类时使用class关键字;Class是ja JDK提供的类,完整路径是ja.lang.Class,本质上和Math、String或者自己定义的各种类没什么区别。

公共最终类Class & ltT & gt实现ja.io.serializable、泛型声明、类型、批注元素{…}课堂在反思中起什么作用?

对于每种类型的对象,Ja虚拟机实例化ja.lang.Class的不可变实例,该实例提供检查对象的运行时属性的方法,包括其成员和类型信息。类还提供了创建新的类和对象的能力。最重要的是,它是所有反射API的入口点。

对于每个类,Ja虚拟机将初始化一个类类型的实例。每当我们编写和编译一个新创建的类时,都会生成一个对应的类对象,这个类对象会保存在一个同名的. Class文件中。当我们创建一个新的对象或者引用一个静态成员变量时,Ja虚拟机(JVM)中的类加载器系统会将相应的类对象加载到JVM中,然后JVM会根据这个类型信息相关的类对象创建我们需要的实例对象或者提供静态变量的引用值。

例如,如果创建并编译一个Shapes类,JVM将创建一个对应于Shapes类的类实例,该实例存储与Shapes类相关的类型信息,包括属性、方法、构造方法等。通过此类实例,您可以在运行时访问Shapes对象的属性和方法。此外,可以通过Class类创建新的Shapes对象。这就是反思能够实现的原因。可以说,类是反射操作的基础。

需要注意的是,每个类(注意Class是小写的,代表普通类)类,不管创建了多少个实例对象,都对应于JVM中的同一个类对象。

这里有一个简单的例子来说明如何通过反射实例化一个对象。

公共类动物{私有字符串名称;私人年龄;公共动物(String name,int age){ this . name = name;this.age =年龄;} @Override公共字符串toString(){ return ” Animal:name = “+name+” age = “+age;}}公共类test Reflection { private static final String TAG = ” Reflection “;Public void testReflection(){ //获取动物类的类对象class c = Animal.classTry {//通过类对象反射获取Animal类的构造函数= c.getconstructor (string.class,int . Class);//调用构造函数获取动物实例Animal =(Animal)constructor . new instance(” jack “,3);//打印出构造好的动物对象Log.d(TAG,Animal . tostring());} catch(nosuchmethodeexception e){ e . printstacktrace();} catch(IllegalAccessException e){ e . printstacktrace();} catch(instantiation exception e){ e . printstacktrace();} catch(InvocationTargetException e){ e . printstacktrace();}}}我们来看看打印值。

03-28 20:12:00.958 2835-2835/?d/reflection:Animal:name = jackage = 3可见我们已经成功的构造了Animal对象,而Class在这个过程中起到了重要的作用。有人说对你来说太麻烦了。每个人都知道动物物体。我可以在几分钟内给你一个新的。

动物动物=新动物(“杰克”,3);没错!但是如果不能直接导入Animal类呢?如果构造函数都是私有的呢?这时候反思才能大显身手。

如何获取Class

说class是反射基础的另一个原因是,Ja反射包ja.lang.reflect中的所有类都没有公共构造函数,这些类实例只能通过Class类获取。所以如果你想使用反射,你必须得到类对象。

这里有几种获取类对象的方法。

Object.getClass()通过对象实例获取对应 Class 对象,如

//返回StringClass c = “foo “的类。getClass();enum E { A,B }//返回枚举类型E对应的类,Class c = A . getclass();byte[] bytes =新字节[1024];//返回组件类型为byte的数组对应的类。class c = bytes . getclass();设置& lt字符串& gts = new HashSet & lt字符串& gt();//返回Ja . util . hashset . Class c = s . getclass()对应的类;但是,此方法不能用于基本类型。

布尔b;class c = b . getclass();//编译时错误。Classsyntax通过类的类型获取类对象,这种方法也可以用于基本类型,比如//the`。class `语法返回更正为` Boolean `类型的类。c类=布尔型。类;//返回StringClass c = String.class的类;Class.forName()通过类的全限定名获取类对象,基本类型不能使用这种方法class c = class。forname(” Ja . lang . string “);它对于数组来说是特殊的。

class cDoubleArray = class . forname(“[D “);//相当于双[]。classclasscstringgarray = class。for name(“[[ljalang . string;);//等效于string [] []的基本类型和void类型的包装类。基元类型包装的Classtypefield可以使用类型字段来获得Class c = Double。类型;//相当于double.class.Class c = Void。类型;返回类的方法有其他的反射方法来获得一个类对象,但前提是你已经有了一个类对象。比如你已经获得了一个类的类对象,那么你就可以通过反射获得这个类的父类的类对象。获取给定类的父类。

//jax . swing . utton的父类是jax . swing . abstract button class c = jax . swing . utton . class . get超类();类似的方法包括:

class . get classes()class . getdeclaredclasses()class . getdeclaringclass()class . getenclosingclass()Ja . lang . reflect . field . getdeclaringclass()Ja . lang . reflect . method . getdeclaringclass()Ja . lang . reflect . constructor . getdeclaringclass()通过类获取类修饰符和类型。我们知道类的声明一般如下。

让我们以HashMap为例,通过一个演示来说明如何获取这些信息。

公共类test Reflection { private static final String TAG = ” Reflection “;public void test reflection(){ Class & lt;?& gtc = HashMap.class//获取类名log.d (tag,” class:”+c . getcanonical name());//获取类限定符log.d (tag,” modifiers:”+modifier . tostring(c . Get modifiers()));//获取类type variable[]TV = c . gettype parameters()的泛型信息;if (tv.length!= 0){ StringBuilder parameter = new StringBuilder(” Parameters:”);for(type variable t:TV){ parameter . append(t . getname());parameter . append(” “);} Log.d(标记,参数. tostring());} else { Log.d(TAG,”-无类型参数-“);}//获取类[] intfs = c.getgenericinterfaces()实现的所有接口类型;if (intfs.length!= 0){ StringBuilder Interfaces = new StringBuilder(” Implemented Interfaces:”);for(Type intf:intfs){ interfaces . append(intf . tostring());interfaces . append(” “);} Log.d(标记,接口. tostring());} else { Log.d(TAG,”-没有实现的接口-“);}//获取所有父类列表

03-29 15:04:23.070 27826-27826/com . example . Ming . test project D/Reflection:Class:Ja . util . hashmap 03-29 15:04:23.070 27826-27826/com . example . Ming . test project D/Reflection:Modifiers:public 03-29 15:04:23.071 27826-27826/comk,V & gt接口ja.lang.Cloneable接口Ja . io . serializable 03-29 15:04:23.071 27826-27826/com . example . Ming . test project D/Reflection:继承路径:Ja . util . abstract map Ja . lang . object 03-29 15:04:23.071 27826-27826/com . example . Ming . test project D/Reflection:-无批注- MemberReflection定义了一个接口ja.lang.reflect

可能有人不知道成员接口是做什么的,但是如果提到实现它的三个实现类,估计用过反射的人都能知道。我们知道类成员主要包括构造函数、变量和方法,Ja中的操作基本都和这三个有关,member的这三个实现类分别对应。

Ja.lang.reflect.Field:对应的类变量ja.lang.reflect.Method:对应的类方法ja.lang.reflect.Constructor:正是通过这三个类可以在运行时改变对象的状态。下面我们通过一些例子来说明如何通过反射来操作它们。

首先,构建一个测试类

public class Cat { public static final String TAG = Cat . class . get simplename();私有字符串名称;@已弃用的public int agepublic Cat(String name,int age){ this . name = name;this.age =年龄;} public String getName(){ return name;} public void eat(String food){ log . d(TAG,“吃货”+food);} public void eat(字符串…foods){ StringBuilder s = new StringBuilder();for(String food:foods){ s . append(food);s . append(” “);} Log.d(TAG,”吃货”+s . tostring());} public void sleep(){ Log.d(TAG,” sleep “);} @覆盖公共字符串toString(){ return ” name = “+name+” age = “+age;} }字段

通过字段,可以访问给定对象的类变量,包括获取变量类型、修饰符、注释、变量名、变量值或重置变量值,即使变量是私有的。

获取 Field

类提供了四种方法来获取给定类的字段。

getDeclaredField(String name) 获取指定的变量(只要是声明的变量都能获得,包括 private)getField(String name) 获取指定的变量(只能获得 public 的)getDeclaredFields() 获取所有声明的变量(包括 private)getFields()获取所有的 public 变量获取变量类型、修饰符、注解

一个例子说明了这个问题。

public void testField(){ Class c = cat . Class;field[]fields = c . getdeclaredfields();for(Field f:fields){ StringBuilder builder = new StringBuilder();//获取名称builder . append(” filed name = “);builder . append(f . getname());//获取类型builder . append(” type = “);builder . append(f . gettype());//获取修改器builder . append(” modifiers = “);builder . append(modifier . tostring(f . get modifiers()));//Get annotation[]ann = f . Get annotations();如果(ann.length!= 0){ builder . append(” annotations = “);for(批注a:ann){ builder . append(a . tostring());builder . append(” “);} } else { builder.append(” -无批注-“);} Log.d(TAG,builder . tostring());}}打印结果:

filename = age type = int modifiers = public Annotations = @ Ja . lang . deprecated()filename = name type = class Ja . lang . string modifiers = private-No Annotations-filename = TAG type = class Ja . lang . string modifiers = pub Lic static final-No Annotations-获取并设置变量值。给定一个对象及其成员变量名,您可以通过反射获取和更改变量值。什么都不说,没有什么是一个例子解决不了的,容易~

还是上面的测试类,通过反射得到并更改猫的名字和年龄。

public void testField(){ Cat Cat = new Cat(” Tom “,2);class c = cat . getclass();Try {//注意,获取私有变量时,需要使用getdeclaredfieldname = c . getdeclaredfieldname(” name “);field field age = c . get field(” age “);//Reflect获取名称,age string name =(string)field name。get(猫);int age = field age . getint(cat);Log.d(标签,“设置前,猫名= ” + name +”年龄= ” +年龄);//Reflect重置姓名和年龄fieldName.set(cat,” Timmy “);fieldAge.setInt(cat,3);Log.d(TAG,” after set,Cat “+Cat . tostring());} catch(NoSuchFieldException e){ e . printstacktrace();} catch(IllegalAccessException e){ e . printstacktrace();}}嗯?居然报错了?

system . err:Ja . lang . illegalaccessexception:Class Ja . lang . Class & lt;com . example . Ming . testnesscrollview . test reflection & gt;无法访问ja.lang.Class的私有字段Ja . lang . string com . example . Ming . testnesscrollview . cat . name & lt;com . example . Ming . testnesscrollview . cat & gt;System . err:at Ja . lang . reflect . field . get(Native Method)System . err:at com . example . Ming . testnesscrollview . test reflection . test field(test reflection . Ja:22)System .err:at com . example . Ming . testnesscrollview . main activity . oncreate(main activity . Ja:17)看异常消息Ja . lang . illegalaccessexception,说我们无权操纵变量名;;回到Cat类,看看name变量。

私有字符串名称;原来name变量是私有的,Ja运行时会检查访问权限。私有类型的变量是不能直接访问的,而刚刚进行的反射操作并没有打破这种封装,所以我们仍然没有权利直接访问私有属性。

没有办法打破这个限制吗?肯定有!强大的倒影早已为我们暗中准备好了一切。反射包为我们提供了一个强大的类。

Ja . lang . reflect . accessible object

AccessibleObject为我们提供了一个方法setAccessible(布尔标志),它的作用是取消Ja语言的访问权限检查。因此,任何继承AccessibleObject类的对象都可以使用此方法取消Ja语言访问检查。(final类型的变量也可以通过这种方式访问。)

public final class字段扩展AccessibleObject实现memberfield是accessible object的子类,所以在访问私有变量之前调用filed.setAccessible(true)就很简单。

…field name . set accessible(true);//Reflect获取名称,age string name =(string)field name。get(猫);…打印结果

TestReflection:设置前猫名= Tom age = 2TestReflection:设置后猫名= Timmy age = 3Bingo!

请注意,方法和构造函数都继承AccessibleObject,因此如果私有方法和私有构造函数不可访问,请记住对它们进行相同的处理。

方法

ja.lang.reflect.Method类提供API来访问关于方法的修饰符、返回类型、参数、注释和抛出的异常的信息。它也可以用来调用方法。

本节主要介绍如何通过反射访问对象。

获取 Method

类仍然提供了四种方法来获取方法:

getDeclaredMethod(String name, Class<?>… parameterTypes)根据方法名获得指定的方法, 参数 name 为方法名,参数 parameterTypes 为方法的参数类型,如 getDeclaredMethod(“eat”, String.class)getMethod(String name, Class<?>… parameterTypes)根据方法名获取指定的 public 方法,其它同上getDeclaredMethods()获取所有声明的方法getMethods()获取所有的 public 方法

注意:获取带参数的方法时,如果参数类型错误,将会报告为NoSuchMethodException。如果参数是泛型,则泛型必须被视为对象(类)。

获取方法返回类型getReturnType() 获取目标方法返回类型对应的 Class 对象getGenericReturnType() 获取目标方法返回类型对应的 Type 对象

这两种方法有什么区别?

getReturnType()返回类型为 Class,getGenericReturnType() 返回类型为 Type; Class 实现 Type。返回值为普通简单类型如 Object, int, String 等,getGenericReturnType() 返回值和 getReturnType() 一样例如 public String function1(),那么各自返回值为:getReturnType() : class ja.lang.StringgetGenericReturnType() : class ja.lang.String返回值为泛型例如 public T function2(),那么各自返回值为:getReturnType() : class ja.lang.ObjectgetGenericReturnType() : T返回值为参数化类型例如public Class<String> function3(),那么各自返回值为:getReturnType() : class ja.lang.ClassgetGenericReturnType() : ja.lang.Class<ja.lang.String>

其实反射中getGenericXXX()的所有方法规则都和上面说的差不多。

获取方法参数类型getParameterTypes() 获取目标方法各参数类型对应的 Class 对象getGenericParameterTypes() 获取目标方法各参数类型对应的 Type 对象返回值为数组,它俩区别同上 “方法返回类型的区别” 。获取方法声明抛出的异常的类型getExceptionTypes() 获取目标方法抛出的异常类型对应的 Class 对象getGenericExceptionTypes() 获取目标方法抛出的异常类型对应的 Type 对象返回值为数组,区别同上获取方法参数名称.class 文件中默认不存储方法参数名称,如果想要获取方法参数名称,需要在编译的时候加上 -parameters 参数。(构造方法的参数获取方法同样)

//M这里可以是一个普通的方法,也可以是一个构造函数// Get方法的所有参数parameter[]params = M . Get parameters();for(int I = 0;我& ltparams.lengthi++){ Parameter p = params[I];p . gettype();//获取参数类型p . getname();//获取参数名。如果编译时没有添加“-parameters ”,则返回的名称是“argx”的形式,其中x是参数在方法声明中的位置,从0开始,p . get modifiers();//获取参数修饰符p . isname present();是否在//中保存参数名。类文件,编译时添加`-parameters ‘返回true,否则请参考oracle官方示例MethodParameterSpy获取方法参数名的flase}详细信息。

获取方法修饰符

该方法类似于领域等。

method . get modifiers();Ps:顺便再介绍几个方法方法

method.isVarArgs() //判断方法参数是否是可变参数

公共构造函数& ltT & gtget constructor(Class & lt;?& gt…parameterTypes) //返回真正的公共构造函数

通过反射调用方法

反射通过方法的invoke()方法调用目标方法。第一个参数是要调用的目标类对象。如果方法是静态的,则参数为null。以下参数是目标方法的参数值,顺序与目标方法声明中的参数顺序一致。

公共本机对象调用(对象对象,对象…args)throwsillegalaccessexception、illegalargumentexception、invocationtargetexception或者以上面的测试类Cat为例。

注意:如果方法是私有的,可以使用method.setAccessible(true)绕过权限检查。

Class & lt?& gtc = Cat.classTry {//构造一个Cat实例构造函数= c.getconstructor (string.class,int . class);object cat = constructor . new instance(” Jack “,3);//调用无参数方法method sleep = c . getdeclaredmethod(” sleep “);sleep.invoke(猫);//调用参数方法method eat = c . getdeclaredmethod(” eat “,string . class);eat.invoke(猫,“草”);//调用不定参数方法//不定参数可以作为数组处理;class [] argtypes =新类[] {string []。class };方法varargsEat = c . getdeclaredmethod(” eat “,arg types);String[] foods = new String[]{“草”、“肉”};varargsEat.invoke(cat,(Object)foods);} catch(instantiation exception | IllegalAccessException | NoSuchMethodException | InvocationTargetException e){ e . printstacktrace();被调用方法本身抛出的异常将在反射中作为InvocationTargetException抛出。换句话说,如果在反射调用过程中抛出了异常InvocationTargetException,就意味着反射调用本身是成功的,因为这个异常是目标方法本身抛出的异常。

构造器

本节主要介绍如何通过反射访问构造函数,以及如何通过构造函数构建新的对象。

获取构造方法

像方法一样,类也为构造函数提供了四个方法来获取。

getDeclaredConstructor(Class<?>… parameterTypes)获取指定构造函数,参数 parameterTypes 为构造方法的参数类型getConstructor(Class<?>… parameterTypes)获取指定 public 构造函数,参数 parameterTypes 为构造方法的参数类型getDeclaredConstructors()获取所有声明的构造方法getConstructors()获取所有的 public 构造方法

构造函数的名称、限定符、参数、声明的异常和其他获取方法与方法类似。请参考方法。

创建对象

通过反射创建对象有两种方法:

ja.lang.reflect.Constructor.newInstance()Class.newInstance()

一般来说,我们优先考虑第一种方法;那么这两种方法有什么异同呢?

Class.newInstance()仅可用来调用无参的构造方法;Constructor.newInstance()可以调用任意参数的构造方法。Class.newInstance()会将构造方法中抛出的异常不作处理原样抛出; Constructor.newInstance()会将构造方法中抛出的异常都包装成 InvocationTargetException 抛出。Class.newInstance()需要拥有构造方法的访问权限; Constructor.newInstance()可以通过 setAccessible(true) 方法绕过访问权限访问 private 构造方法。

方法部分已经写了例子,这里直接截取。

Class & lt?& gtc = Cat.classtry { Constructor Constructor = c . get Constructor(string . class,int . class);Cat Cat =(Cat)constructor . new instance(” Jack “,3);} catch(instantiation exception | IllegalAccessException | NoSuchMethodException | InvocationTargetException e){ e . printstacktrace();}注意:反射不支持自动密封,所以传入参数时要小心(自动密封是在编译时,反射是在运行时)。

数组和枚举

数组和枚举也是对象,但是在反射中,数组和枚举的创建和访问与普通对象是如此的不同,所以Ja反射为数组和枚举提供了一些特定的API接口。

排列

数组类型

数组类型:数组本质上是一个对象,所以它也有自己的类型。

例如,对于int[] intArray,数组类型为class [I i .数组类型中的数字表示数组的维数,例如,[表示一维数组,[[表示二维数组;【后面的字母代表数组元素类型,I代表int,通常大写为类型的首字母(long类型除外,为J)。

Class [B //byte型一维数组class [S //short型一维数组class [I //int型一维数组class [C //char型一维数组class [J //long型一维数组,J代表long型,因为l被引用的对象类型占用,class [F //float型一维数组class [D //double型一维数组class [Lcom.dada.Season //引用型一维数组class [[Lja.lang.String //引用型二维数组//获取a的类型类& gtc = field . gettype();//判断变量是否为数组if (c.isarray ()) {/}获取数组的元素类型c.getComponentType()}创建并初始化数组Ja反射为我们提供了ja.lang.reflect.Array类来创建并初始化数组。

//创建一个数组。参数componentType是数组元素的类型,后面不确定参数的个数代表数组的维数。参数值是数组长度array.newinstance(类

object array = array . new instance(int . class,2);Array.setInt(array,0,1);Array.setInt(array,1,2);注意:反射支持数据自动加宽,但不允许数据缩小(缩小?真的很难翻译)。意思是对于上面的set方法,可以在int数组中设置短数据,但是不能设置长数据,否则会报错IllegalArgumentException。

多维数组

Ja反射没有提供可以直接访问多维数组元素的API,但是你可以把多维数组当作数组的数组。

object matrix = array . new instance(int . class,2,2);Object row0 = Array.get(matrix,0);Object row1 = Array.get(matrix,1);Array.setInt(row0,0,1);Array.setInt(row0,1,2);Array.setInt(row1,0,3);Array.setInt(row1,1,4);或者

object matrix = array . new instance(int . class,2);object row 0 = array . new instance(int . class,2);object row 1 = array . new instance(int . class,2);Array.setInt(row0,0,1);Array.setInt(row0,1,2);Array.setInt(row1,0,3);Array.setInt(row1,1,4);Array.set(matrix,0,row 0);Array.set(matrix,1,row 1);列举

Enumeration隐式继承自ja.lang.Enum,Enum继承自Object,所以enumeration本质上也是一个类,它还可以有成员变量、构造函数、方法等。枚举可用于普通类可以使用的反射方法;此外,ja反射为枚举服务提供了几个额外的方法。

Class.isEnum()Indicates whether this class represents an enum typeClass.getEnumConstants()Retrieves the list of enum constants defined by the enum in the order they’re declaredja.lang.reflect.Field.isEnumConstant()Indicates whether this field represents an element of an enumerated type总结

没有技术是完美的。Ja反射功能强大,但也带来了一些副作用。

性能开销反射涉及类型动态解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。安全限制使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。内部曝光由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

使用反射的一个原则:如果可以用常规方法实现,那么就不要使用反射。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。

发表回复

登录后才能评论