反射的深入浅出
刚开始接触反射这个概念,感觉反射这个机制很复杂很难懂,所以在这篇文章中对java的反射机制以个人的理解总结归纳。
1. 什么是反射?
什么是反射?在官方文档中是这样说的:
reflection is commonly used by programs which require the ability to examine ormodify the runtime behavior of applications running in the java virtual machine. this is a relatively advanced feature and should be used only by developers whohave a strong grasp of the fundamentals of the language. with that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible
翻译一下:
反射技术通常被用来检测和改变应用程序在 java 虚拟机中的行为表现。它是一个相对而言比较高级的技术,通常它应用的前提是开发者本身对于 java 语言特性有很强的理解的基础上。值得说明的是,反射是一种强有力的技术特性,因此可以使得应用程序执行一些常规手段无法企及的目的。
个人理解:反射是一种很牛x的技术,使用反射的条件是程序猿是一个大猿,对java的特性非常理解。反射的牛逼之处在于他可以完成一些非常规操作。
举个栗子来说明一下:
稍微想了一下,觉得用煮饭这个栗子来说明吧,不知道准不准确(^,^)。平时我们在家里一般是用电饭煲来煮饭的,煮饭的步骤一般是:淘米——>擦干锅底——>把锅放到电饭煲里——>合上盖子,通电,电饭煲工作——>饭煮熟了,可以吃了,但现在有个需求,我要在煮饭的过程中加个鸡蛋,这时怎么解决呢?是不是打开正在通电煮饭的电饭煲,然后把鸡蛋放进去呢?(来自吃货的需求^_^)其实反射就相当于刚才加鸡蛋的过程。所以反射很牛逼,他不按常规套路出牌,在程序运行的过程中搞一些小动作,以达到“吃货”的目的。补充一下:“淘米——>擦干锅底——>把锅放到电饭煲里——>”这个过程可以看做编码编译过程,“——>合上盖子,通电,电饭煲工作”可以看做是程序运行过程,“——>饭煮熟了,可以吃了”可以看做程序运行结束。
2.java中的反射机制
2.1 反射中常见的类
理解反射机制时,首先熟悉一下几个类:
1)class类
class类实例表示正在运行的java应用程序中的类和接口。class是普通类、接口、枚举类、数组等的抽象,即它们的类型就是class,它们是class的实例。
既然class代表着类和接口,那么我们可以通过他的实例(字节码文件)来获取对应类或接口的信息,如:注解、修饰符、类型、类的名称、属性、方法、构造方法、直接父类和子类等,还有可以创建它的实例,但只能调用无参构造方法来创建。
什么看不懂?举个栗子,我们都知道,生物可以分为动物、植物、微生物和病毒等,而动物又有人、喵星人、小狗等,植物、微生物和病毒也一样。同样,我们可以类比一下,生物就是class,动物是普通类,植物是接口,微生物是枚举类、病毒是数组(枚举和数组是特殊的类),而人、喵星人、小狗是我们熟悉的对象,如图
这下可整明白了吧,普通类、接口、枚举、数组其实都可以当做class的对象。
2)field类
field表示类的属性,属性含有修饰符、类型、属性名称和值。所以可以通过field的实例获取属性的修饰符、类型、属性名称,并且可以修改属性的值。
3)method类
method表示类的成员方法,方法包括注解、修饰符、返回类型、方法名,参数等。所以可以通过method的实例获取方法的的信息,如,注解、修饰符、返回类型、方法名并且可以调用所表示的方法。
4)constructor类
constructor表示构造方法,可以通过constructor的实例获取构造方法的信息,如,修饰符等,并且可以通过它来创建它所在类的的实例。
5)modifier类
modifier表示修饰符,可通过它来获取修饰符的信息,例如何种修饰符等
6)annotation
annotation代表注解
以上类都位于java.lang中
2.2 获取class对象的方法
了解了什么是反射后,是不是也想体验一下反射这种骚操作?
想秀操作,首先要获取class对象吧,因为class对象是代表着各种类,有了它之后才可以得到类的各种信息。获取方法如下:
1)通过object.getclass()
1 public static void main(string[] args) { 2 3 car car = new car(); 4 5 class clazz = car.getclass(); 6 }
注意:此方法不适用于int、float等类型
2)通过(类型名).class、包装类.type
1 public static void main(string[] args) { 2 class clazz = car.class; 3 class cls1 = int.class; 4 class cls2 = string.class; 5 class cls3=iteger.type 6 }
3)通过class.forclass(string 类的全限定名)
1 try { 2 class clz = class.forname("com.frank.test.car"); 3 } catch (classnotfoundexception e) { 4 e.printstacktrace(); 5 }
采 用哪种方法来获取,看实际情况而定。
2.3获取类信息
有了class对象后,就可以获取类的成员(方法+属性)、注解和类的修饰符等。上面也说了,java中方法用method类表示、属性用field类表示、注解用annotation类来表示、修饰符用modifier类表示。class类中有对应的方法来获取他们。如下:
2.3.1 获取属性field的对象
1 //获取所有的属性,但不包括从父类继承下来的属性 2 public field[] getdeclaredfields() throws securityexception 3 //获取自身的所有的 public 属性,包括从父类继承下来的。 4 public field[] getfields() throws securityexception
5 //获取在本类中声明的指定的属性,参数为属性的名称 6 public field getdeclaredfield(string name) 7 //获取指定的公有属性,包括父类的,参数为属性的名称
8 public field getfield(string name)
2.3.2 获取方法method对象
//获取本类声明指定的的方法,第一个参数是方法的名称,后面的参数是方法参数类型的类,
//如获取setname(string name)方法,getdeclaremethod(“setname”,string.class) public method getdeclaredmethod(string name, class<?>... parametertypes) //获取公有的方法,包括父类的 public method getmethod(string name, class<?>... parametertypes) //获取本类中声明的所有方法 public method[] getdeclaredmethods() //获取所有的公有方法,包括父类的 public method[] getmethods()
2.3.3 获取构造器constructor对象
1 //获取本类中指定的构造方法 2 public constructor<t> getdeclaredconstructor(class<?>... parametertypes) 3 //获取指定的公有构造方法 4 public constructor<t> getconstructor(class<?>... parametertypes) 5 //获取本类中所有的构造方法 6 public constructor<?>[] getdeclaredconstructors() throws securityexception 7 //获取本类中所有的公有构造方法 8 public constructor<?>[] getconstructors()
构造方法的获取与普通方法的获取大致是一样的。
------------------------------------------------------------------
以上的方法都是在class类中,别傻傻不知道(别问我怎么知道的>_>),然后通过class对象调用就可以了。
这里只是列举了常用类信息的的获取方法,其他信息的获取方法,看api文档吧,如注解、类的class的对象(额好像有点绕。。。)等.
2.4 获取类成员信息
上面只是获取了类的成员所代表类的对象,我们还要使用他们或者获取成员的信息(名称、修饰符等)。因为有了代表成员的对象,使用对象调用实例方法就可以了。
2.4.1 field类
field类的方法大概可以分为两种,一种是获取属性的信息,另外一种是设置属性的值。
第一种:
1 //返回由此 field对象表示的字段的名称 2 string getname() 3 //返回一个 类对象标识了此表示的字段的声明类型 field对象。 4 class<?> gettype() 5 //返回由该 field对象表示的字段的java语言修饰符,作为整数。把整数作为modifier的构造方法的参数,就可以获取该整数代表的修饰符类的对象了 6 int getmodifiers() 7 ---------------------------------------------------------------- 8 9 //获取类型为 int的静态或实例字段的值,或通过扩展转换转换为类型 int的另一个原始类型的值。 10 int getint(object obj) 11 //获取类型为 long的静态或实例字段的值,或通过扩大转换获得可转换为类型 long的另一个基本类型的值。 12 long getlong(object obj) 13 ......此处省略一堆get**(object obj)的方法,属性是什么基本类型,就get什么就行了 14属性是引用类型,那么就调用以下方法
15 //返回该所表示的字段的 field ,指定的对象上。 16 object get(object obj)
第二种:
1 //设置作为一个字段的值 double指定的对象上。 2 void setdouble(object obj, double d) 3 //设置作为一个字段的值 float指定的对象上。 4 void setfloat(object obj, float f) 5 //设置作为一个字段的值 int指定的对象上。 6 void setint(object obj, int i) 7 ........此处省略一堆set**()方法,属性是什么基本类型就set什么就行了 8 属性是引用类型,那么就调用以下方法 9 //将指定对象参数上的此 field对象表示的字段设置为指定的新值。 10 void set(object obj, object value)
注意啦:如果没有访问权限的话,默认是不能设置属性值的,那怎么办呢?是不是就秀不了操作了?然而,前面也说了,反射很牛逼,可以来一些非常规操作,
这时我们调用class对象的setaccessible(true)方法就可以了!
是不是觉得反射可以很强?
2.4.1 method类
method类的方法主要是获取方法的信息
部分方法:
1 int getmodifiers() //返回由该对象表示的可执行文件的java语言modifiers 。 2 string getname() //返回由此 方法对象表示的方法的名称,作为 string 。 3 annotation[][] getparameterannotations() //返回一个 annotation s的数组数组,表示由该对象表示的executable的形式参数的声明顺序的 executable 。 4 int getparametercount() //返回由此对象表示的可执行文件的形式参数(无论是显式声明还是隐式声明)的数量。 5 class<?>[] getparametertypes() //返回一个 类对象的数组, 类以声明顺序表示由该对象表示的可执行文件的形式参数类型。
2.4.1 constructor类
constructor类的方法主要是获取构方法的信息和创建对象
获取方法信息:
1 int getmodifiers() //返回由该对象表示的可执行文件的java语言modifiers 。 2 string getname() //以字符串形式返回此构造函数的名称。 3 annotation[][] getparameterannotations() //返回的数组的数组 annotation表示的形参进行注释s时,声明顺序的的 executable该对象表示。 4 int getparametercount() //返回由此对象表示的可执行文件的形式参数(无论是显式声明还是隐式声明)的数量。 5 class<?>[] getparametertypes() //返回一个 类对象的数组, 类以声明顺序表示由该对象表示的可执行文件的形式参数类型。
创建对象的方法先不说,放到后面去。
2.5 反射创建对象和调用方法
2.5.1 创建普通类的对象
创建普通类的对象可以分为两种方法
第一种:调用class对象的方法
4 //首先获取class对象 5 class clazz=class.forclass("test.student"); 6 //创建对象 7 student stu=(student)clazz.newinstance();
注:此方法只能创建无参构造函数的类的对象
第二种:通过constructor的newinstance()方法
1 //首先创建class对象 2 class clazz=class.forclass("test.student"); 3 //获取想调用的构造函数 4 constructor constructor=clazz.getconstructor(string.class, int.class); 5 //调用constructor的newinstance()方法 6 student stu=(student)constructor.newinstance("大王",20);
2.5.2 创建数组
数组本质上是一个 class,而在 class 中存在一个方法用来识别它是否为一个数组。
反射创建数组是通过 array.newinstance(t.class,维数) 这个方法。
第一个参数指定的是数组内的元素类型,后面的是可变参数,表示的是相应维度的数组长度限制。
比如,我要创建一个 int[2][3] 的数组。
1 int[][] a=array.newinstance(integer.type, 2, 3);
2.5.3 调用方法
用了上面的方法,就有class对象,有方法method对象,有实例,现在已经万事俱备,只欠东风了。
那我们怎么调用方法呢?在method类有这么一个方法object invoke(object obj, object... args),object为实例对象,args为调用方法的参数
来个栗子:
1 class<?> c = class.forname("com.kal01.reflect05.person");//获取class对象 2 person p1 = (person) c.newinstance();//获取实例 3 method m3 = c.getdeclaredmethod("test");//获取方法 4 m3.setaccessible(true);//当没有访问权限时,设置一下就可以 5 m3.invoke(p1);//调用方法 6 m3.setaccessible(false);//修改了访问权限,记得修改回来
2.6 静态加载与动态加载
看到这里是不是有个疑问,反射调用类的方法好像除了复杂之外,跟我们平时调用没什么区别。何必弄那么花里胡哨?
所以在这里简单说一下静态加载与动态加载。
回想一下之前煮饭的那个栗子,静态加载和动态加载的这个例子有点相似。
静态加载:我们在程序中使用类时,静态加载是要求要使用的类必须要求在编译的时候存在,否则编译器报错,无法运行程序。编码时忘记导包时,经常会出现这种错误。
动态加载:利用反射来加载类(即获得class对象),不要求我们在编译期存在要是用的那个类,在程序运行时,才去寻找类(可以从jar包,网络等寻找),然后把类加载到方法区中,如果没有找到这个类会抛出classnotfoundexception异常。
有图有真相:
这下看出来反射的牛逼之处了吧,利用反射使用类时,并不需要这个类在编译期存在,这就增加了程序的灵活性,可以完成我们的骚操作!
最后总结一下:
使用反射时,记住一句话:老哥,稳住,别翻车!
上一篇: 使用IDEA的maven工程导入ojdbc14 jar包失败
下一篇: 头条后端面经_1面