目录
二.获取Class对象的三种方法:(Three ways to get a Class object)
newInstance(Object... initargs):(重点)
invoke (Object obj, Object... args) :(invoke本身就是调用的意思)
set (Object obj, Object value):
前言
大家好,今天给大家带来一篇详解JAVA反射基础的博文(将来会出JAVA反射进阶的讲解,准备放在JAVA进阶专栏),我会从字节码文件对象,构造器对象,方法对象,属性对象一一进行代码演示,由于Constructor,Method,和Field三者的用法大同小异,于是up决定重点把Constructor构造器这块儿讲得通透点,注意:注释内容也是重点,(注释内容也是重点,注释内容也是重点,)有利于大家举一反三。不要眼高手低,跟着up一块儿练习,看完就会用反射。学习反射基础需要有一定的javaSE基础,尤其是file类和Exception类。我之后会陆续出一系列专门讲javaSE基础的文章,把javaSE基础篇全讲一遍。所有文章都会适时补充完善,良工不示人以朴。PS:使用IEDA讲解,点击目录可以跳转。
一.反射及其相关概念
1.什么是反射?
反射指的是在程序运行的过程中分析类的一种能力。你可以这么理解,在程序运行过程中,我可以查看并使用其他类的构造器,方法和属性,这就是反射的能力。
光这么说还是过于抽象,我们直接上图片:
如图,我们可以得知反射的第一步是获取字节码文件对象 。
2.反射的用途:
①分析类:
加载并初始化一个类(反射创建类的字节码文件对象时会加载类)
查看类的所有属性和方法(即Constructor, Method, Field,etc)
②查看并使用对象:
查看一个对象的全部属性和方法
使用对象的任意属性和方法
3.反射的应用场景:
构建通用的工具
搭建具有高度灵活性和扩展性的系统框架。
4.类加载器:
类加载器,(ClassLoader)
.java的源文件经过javac.exe编译后,会生成.class的字节码文件,而类加载器负责将类的字节码文件(.class)加载到内存中,并生成对应的Class对象。
类的加载时机:
①创建类的实例时:
eg:Dog dog = new Dog();
注意:一个类的字节码文件,只会被加载一次
②访问类的静态成员时:
eg:Collections.shuffle();
③初始化类的子类时:(要先加载其父类)
eg:假设Honor类继承自Phone类,
即:class Honor extends Phone {
Honor h = new Honor();
}//那么,在加载子类Honor类之前,必须先加载它的父类Phone类。
④反射方式创建类的Class对象时:(重点)
eg:class c = Class.forName("类的正名"); //类的正名 = 包名 + 类名。 forName() 方法我们后面马上讲。
5.Class对象:
Class对象,即java.lang.Class类的对象,也叫字节码文件对象。每个Class对象对应一个字节码文件。
联系:
①如果.java的源文件中只有一个类,那么一个.java的源文件对应一个.class的字节码文件,对应一个Class对象。
②如果.java的源文件中不止一个类,那么编译后,每一个类都对应一个字节码文件。
二.获取Class对象的三种方法:(Three ways to get a Class object)
1.通过Object类的getClass() 方法:
Class c = 对象名.getClass(); //用Class类型(首字母大写) 来作接收,即得到一个Class对象。
注意:这里要用对象名. 的形式来调用,所以一定要先创建一个对象。
2.通过类的静态属性:
Class c2 = 类名.class; //eg:Integer.class
3.通过Class类的静态方法:
Class c3 = Class.forName("类的正名"); //类的正名 = 包名 + 类名。中间用点'.'来连接
4.代码演示:(注意注释)
我们先在包下新建一个类,创建一个标准的javabean学生公共类。
packageknowledge.reflect; publicclassStudent { //私有属性privateStringname; privateStringsex; privateintage; //空参构造publicStudent() { } //带参构造publicStudent(Stringname, Stringsex, intage) { this.name=name; this.sex=sex; this.age=age; } //setter,getter方法publicStringgetName() { returnname; } publicvoidsetName(Stringname) { this.name=name; } publicStringgetSex() { returnsex; } publicvoidsetSex(Stringsex) { this.sex=sex; } publicintgetAge() { returnage; } publicvoidsetAge(intage) { this.age=age; } }
好的,创建好学生类后,借机再说一下类的正名。注意看,这个学生类最顶部的那一串去掉头尾,即去掉package和分号就是包名,所以up创建的这个学生类的包名就是knowledge.reflect,类的正名是包名+类名,所以这个学生类的正名就是knowledge.reflect.Student
🆗,明白什么是正名以后,我们直接上代码,演示一下如何获取学生类的Class对象:
packageknowledge.reflect; publicclassGetClassObject { //需求:使用上述提到的三种方法分别获取字节码文件对象publicstaticvoidmain(String[] args) throwsClassNotFoundException { //1.通过Object类的getClass() 方法:Studentst=newStudent(); Classc1=st.getClass(); /** 这里的Class类型一定要记得大写 *///2.通过类的静态属性:Classc2=Student.class; //创建好的Student类可以现用Classc2ex=Integer.class; Classc2ex2=Object.class; //这里多获取两个Class对象,作输出结果的对比。//3.通过Class类的静态方法:Classc3=Class.forName("knowledge.reflect.Student"); /*这里报错异常原因: 是因为这个正名有可能是你瞎写的,计算机不知道。解决方案:抛出异常。IDEA可以将光标放在报错处,快捷键Alt+Enter,选第一个即可快速抛出异常,或者直接在本类的类名后加上throws Exception,抛出它的父类,也可以解决。*//**可以去复制所需类顶部的包名,加上类名即是类的正名*///思考: 如何判断获取到的三个Class对象是不是同一个对象System.out.println("查看获取到的Class对象c1:"+c1); System.out.println("查看获取到的Class对象c2:"+c2); System.out.println("查看获取到的Class对象c2ex:"+c2ex); System.out.println("查看获取到的Class对象c2ex2:"+c2ex2); System.out.println("查看获取到的Class对象c3:"+c3); System.out.println("-------------------------------"); System.out.println("你们说的c1和c2会不会是同一个Class对象?"+ (c1==c2)); System.out.println("你们说的c2和c3会不会是同一个Class对象?"+ (c2==c3)); System.out.println("你们说的c1和c3会不会是同一个Class对象?"+ (c1==c3)); System.out.println("你意思是三个Class对象其实是同一个?"+ (c1==c2&&c2==c3&&c1==c3)); } }
IDEA抛出异常快捷键Alt + Enter 演示:将光标浮在报错处,使用快捷键,效果如下:
直接继续回车,或者鼠标点击第一个选项即可抛出异常。
输出结果:
根据输出的结果,我们也能得到一些信息,比如说:
①Class对象的格式为:小写class 后面跟着该类的正名
②验证了我们上方说的:编译后,每一个类都对应一个字节码文件,且一个类只对应一个Class对象。c1,c2,c3这三个都是Student类的Class对象,所以它们的输出结果才相同。即,一个类只能有一个字节码文件对象。
三.获取构造器(Constructor)
1.前言(重要):
①不管是通过反射获取类的构造器(Constructor) ,方法(Method) ,还是属性(Field) , 第一步都需要先获取字节码文件对象,然后再通过字节码文件对象来分别获取构造器对象,方法对象,和属性对象。
②Constructor,Method,和FIeld都属于java.base模块下的 java.lang.reflect包,来张示意图就是这样:
③建议在用反射分析类之前,先导包!其目的是一劳永逸,以绝后患。 例如,要获取构造器对象Constructor时,就先导包:import java.lang.reflect.Constructor;
④遇到异常没必要见一个抛一个,建议直接抛出它们的父类Exception类,省时省力。 如图所示:
⑤前言看不懂没关系,之后代码演示会更直观。
2.三种方式获取构造器对象:
方式一:
getConstructor(Class<?>... parameterTypes)
说明(重要):
①该方法返回一个Constructor类型的对象,因此需要用Constructor类型来做接收,该方法仅限于获取公共的构造函数。
②parameterTypes是参数类型的意思,
③该方法 需要传入你要获取的构造器参数的字节码文件对象,大白话就是说,假如我要获取的构造器是个无参构造,那我就不需要传入任何字节码文件对象了,假如我要获取的构造器是个带参构造,那带什么参数,我用这方法时就传入什么参数对应的字节码文件对象,说这么复杂,其实实操时很简单,直接参数.class就搞定了,构造器本身有几个参数就传入几个参数.class。
④ ?表示通配符,代表不确定的任意类型。
⑤假如本该传入对应参数的字节码文件对象,你没有传入,默认输出的是所分析类的公有空参构造。
方式二:
getDeclaredConstructor(Class<?>... parameterTypes)
说明:
用法和方式一没什么区别,唯一一点不同就是方式二是用来获取私有构造器的。
方式三:
getConstructors()
说明:
①该方法返回目标类所有非私有的构造函数的数组,因此需要用一个构造器类型的数组作接收。即,Constructor[] cons = ......
②可使用增强for循环来遍历获得的构造器数组(IDEA快捷:输入iter)
3.Constructor类的常用方法:
getName():
该方法可以查看该构造器位于哪个类,并返回该类的正名,用String类型做接收。
newInstance(Object... initargs):(重点)
说明:
①使用获得的构造器和对应的参数可以创建并初始化对象,本质上几乎可以说,获取Constructor对象的根本目的就是去使用newInstance() 方法。
②该方法返回一个Object类型的对象,理应用Object类型作接收,但实际开发中,往往直接利用强制类型转换来向下转型,即直接用所分析类类型的对象来作接收,
举个栗子:Student1 stu = (Student1) constructor2.newInstance(.....);
③至于括号里那一大堆,没必要说是非去弄明白init是初始化,args是arguments的缩写,是参数的意思。现在去扣这些没什么用,搞清楚自己的目的,是先去掌握反射的用法。实际操作中简单的一塌糊涂,就是构造器本身需要什么参数,你就直接传入什么类型的参数就好了,比获取构造器省事得多,回顾一下:(当初获取构造器是传入对应参数的字节码文件对象)。
4.代码演示:(注释给我好好看)
老规矩,我们先在本包下创建一个演示类,模拟我们要分析的类,这里我们创建了一个学生类Student1,如图所示:(后面加1是为了与之前的Student类做区分,之后的代码演示也同理)
看一下学生类Student1的代码:
packageknowledge.reflect.constructor; publicclassStudent1 { //公共的无参构造publicStudent1() { } //公共的带参构造publicStudent1(Stringname) { System.out.println("What's 👴's name :"+name); } //私有的带参构造privateStudent1(intage) { System.out.println("What's 👴's age:"+age); } //公共的空参方法publicvoidshow1() { System.out.println("👴是公有的空参方法!"); } //公共的带参方法publicvoidshow2(inta) { System.out.println("👴是公有的带参方法!您输入的值是:"+a); } //私有的带参方法privateintshow3(intc, intd) { System.out.println("👴是私有的带参方法!"); return (2*c+3*d); } }
正片开始,获取构造器对象的三种方式及Constructor类的常用方法,代码演示:
packageknowledge.reflect.constructor; importjava.lang.reflect.Constructor;//别忘了导包/*** 需求: 通过反射的方式创建: Student1 类型的对象。* 其最终目的是要调用newInstance方法*/publicclassGetConstructor { publicstaticvoidmain(String[] args) throwsException { //1.首先获取字节码文件对象(注意为了见名知意,这里变量名用了clazz,别看错了)Classclazz=Class.forName("knowledge.reflect.constructor.Student1"); /*注意刚刚写到这里已经报了ClassNotFoundException异常,原因我们在前面获取字节码文件对象那里就已经讲过了,这里不再赘述。建议直接抛出异常的顶层父类Exception,一招毙命。*///2.通过获取的字节码文件对象来获取构造器对象(三种方式)//方式一: getConstructor(Class<?>... parameterTypes)//①获取公有的空参构造(不需要传入参数)Constructorc1=clazz.getConstructor(); System.out.println("输出空参构造器c1,注意观察输出构造器的形式:"+c1); /*如果前面没有抛出父类Exception异常,此处就会报NoSuchMethodException异常,所以还是建议一劳永逸。*///②获取公有的带参构造(传入对应参数的字节码文件对象)/*因为我们定义Student1类时,公共的带参构造器所需的参数是String类型,所以此处需要传入String类型的字节码文件对象,即String.class.(class小写)*/Constructorc2=clazz.getConstructor(String.class); //又是NoSuchMethodException异常,写到这里你要是还没抛出Exception你可真头铁System.out.println("输出带参构造器c2,注意观察输出构造器的形式:"+c2); //方式二: getDeclaredConstructor(Class<?>... parameterTypes)/*因为我们定义Student1类时,私有的带参构造器所需的参数是int类型,所以此处需要传入int类型的字节码文件对象,即int.class.(class小写)*/Constructorc3=clazz.getDeclaredConstructor(int.class); //NoSuchMethodException异常三杀System.out.println("输出带参构造器c3,注意观察输出构造器的形式:"+c3); System.out.println("------------------------------------------"); //方式三: getConstructors() ,获取所有非私有的构造器,用构造器类型的数组来作接收,注意仅限非私有的构造器Constructor[] cons=clazz.getConstructors(); //使用增强for循环来遍历获得的构造器数组for (Constructorcon : cons) { System.out.println("遍历构造器数组,注意要和之前的c1,c2作比较:"+con); } System.out.println("------------------------------------------"); //3.Constructor类的常用方法://①getName():Stringc1name=c1.getName(); System.out.println("来看看公有的空参构造是属于哪个类的:"+c1name); //②newInstance(): 实际开发中,这里往往直接利用强制类型转化向下转型Student1stu1= (Student1) c1.newInstance(); Student1stu2= (Student1) c2.newInstance("五爷"); /*1)这里调用了带参构造,别忘了传入参数,此处不需要传入字节码文件对象,直接传入对应参数本身类型即可。2)如果之前没抛出Exception异常,这里会报一堆异常错误,怎么说,抛一个要比抛一堆省事儿的多*/System.out.println("👴看看用反射创建的第一个对象长什么样子:"+stu1); System.out.println("👴看看用反射创建的第二个对象长什么样子:"+stu2); /** 最后打印的地址值代表学生对象 */ } }
5.输出结果及分析:
I.注意观察前三行输出,看它们开头的修饰符public,public,private,以及结尾的参数列表,发现和Student1类中定义的三个构造保持一致,即一个公有空参构造,一个公有带参构造,还有一个私有带参构造,我们再来回顾一下之前的Student1类中对构造器的定义,我直接截张图,省着大家来回翻页,如图:
嗯,雀氏一致。接着看呗:
II.遍历获得的构造器数组时 ,也雀氏只输出了全部的公有构造,符合我们的预期!
III.当通过getName方法获取第一个空参构造所在类的名字时,也成功打印!(看结果其实就是类的正名给打印出来了。)
IV.当我们用公有的带参构造来newInstance时,成功输出了该构造器中的输出语句中的内容。
V.最后打印出通过反射创建的对象,由于没有重写toString() 方法,所以默认打印出对象的地址值,可以看到两个对象的地址值并不相同。
这时候,可能有同学要问了,***凭什么没有通过私有构造器来创建对象?
我只能说,你**牛逼,好问题,那我们现在就来试一试:
我们在第63行后面,也就是我们用newInstance创建对象的后面,悄悄加上两条代码:
Student1 stu3 = (Student1) c3.newInstance(19); System.out.println("👴看看用反射创建的第三个对象长什么样子:" + stu3);
诶巧了,IDEA这时候也不报错,那我们就运行呗。
这时候,一运行你傻眼了,如下图:
你可能会想:靠,***这咋回事儿啊?
别急,注意看, 出现了IllegalAccessException,即非法访问异常。这是因为我们无权过问Student1类的私有构造方法。怎么办?这辈子就栽在这上头了?Of Course Not!
解决方法:开启暴力反射:
你不让我用?我偏要!我就任性!(bushi) 这时我们需要用到setAccessible (boolean flag) 方法(这个方法在讲到获取Field对象时会更详细地讲解),我们这里直接说怎么用:即谁的访问权限受限,谁就调用setAccessible() 方法,传入的boolean值为true。
上代码:
c3.setAccessible(true); Student1stu3= (Student1) c3.newInstance(19); System.out.println("👴看看用反射创建的第三个对象长什么样子:"+stu3);
没错,我们在创建stu3对象之前,先开启暴力反射,这样一来就能输出了,
输出结果:
Ohhhhhhhhhhhhhhh!成功!
四.获取成员方法(Method)
1.前言:
①其实Constructor那里已经讲过了,我再重申一遍是怕大家忘了,我只把最重要的一条复制过来:不管是通过反射获取类的构造器(Constructor) ,方法(Method) ,还是属性(Field) ,第一步都需要先获取字节码文件对象,然后再通过字节码文件对象来分别获取构造器对象,方法对象,和属性对象。 这叫——万变不离其宗!
②前言真的很重要,特别是对于新人们,前言是我把易犯的错误和需要注意的要点进行的总结,大家千万不要置若罔闻。
2.三种方式获取Method对象:
方式一:
getMethod (String name, Class <?>... parameterTypes):
说明(重要):
①仔细看这三种方式,其实你会发现和Constructor的获取方式真的大同小异,唯一一个需要注意的变化是, 使用方式一和方式二需要先传入一个String类型的变量, 这个String类型的变量指的是所调用方法的名字。没错,就是方法的名字,注意是以字符串的形式传入。然后就和Constructor一样,传入方法形参对应的字节码文件对象就可以了,如果方法的参数列表为空,就只传入方法名。
②该方法会返回一个Method对象,因此需要用Method类型来作接收,注意方式一仅限公共的成员方法。
方式二:
getDeclaredMethod (String name, Class <?> ...):
说明:
同上,但方式二可用于获取私有的成员方法。
方式三:
getMethods():
说明:
还是大同小异,注意要用Method类型的数组作接收,即Method[] methods = .......;
3.Method类的常用方法:
getName () :
该方法可以返回对应函数的方法名,返回值是String类型,需要用String类型的变量作接收。
invoke (Object obj, Object... args) :(invoke本身就是调用的意思)
说明(重要):
①该方法较特别的一点在于: 可作接收可不作接收,其取决于所调用函数的返回值类型。若返回值类型是void类型,则不作接收,若返回值类型不是void,则需要作接收。
②该方法默认返回类型是Object,因此,在作接收时,同样可直接向下转型。
③invoke() 方法所需的形参有两类,首先, 需要传入一个你所分析类的对象,这个对象可以由构造器中的方法newlnstance() 来创建,其次, 再直接传入方法所需的形参。如果是无参方法,就只传入所分析类的对象。
eg1:Method method1 = clazz.getMethod("function1"); method1.invoke(student2);
eg2:Method method3 = clazz.getMethod("function3", int.class);
int i = (int) method3.invoke(student2, 11);
④invoke() 复杂的地方在于,它既需要考虑函数的返回值类型(决定是否接收),又需要考虑函数是否带参(决定是否传入形参)。把握好这两点就不易出错了。
4.代码演示(不理解多看注释):
老规矩,我们先在method包下创建一个学生类,用来模拟我们要分析的类。
Student2类的代码如下:
packageknowledge.reflect.method; publicclassStudent2 { //成员变量(private)privateintage; //(公共的)空参构造和带参构造publicStudent2() {} publicStudent2(intage) {this.age=age;} //getXXX和setXXX方法publicvoidsetAge(intage) {this.age=age;} publicintgetAge() {returnage;} //公共的空参方法publicvoidfunction1() { System.out.println("👴是公有的空参方法!"); } //公共的带参方法publicvoidfunction2(inta) { System.out.println("👴是公有的带参方法!您输入的值是:"+a); } //私有的带参方法privateintfunction3(intc, intd) { System.out.println("👴是私有的带参方法!"); returnc*d; } // 重新toString方法,用来打印对象的各个属性值publicStringtoString() { return"Student2{"+"age='"+age+'\''+'}'; } }
好的,接下来我们用代码来演示一下获取Method对象的三种方式以及Method类的常用方法。
packageknowledge.reflect.method; importjava.lang.reflect.Constructor; importjava.lang.reflect.Method;//别忘了导包,虽然IDEA是自动导包的。/***需求: 通过反射获取Student1类中的成员方法并调用** 方法PS:* public void setAccessible(boolean flag) //是否开启暴力反射(true:开启)*/publicclassGetMethod { publicstaticvoidmain(String[] args) throwsException { //老规矩,抛出异常的顶层父类Exception//1.首先获取Student2类的字节码文件对象。Classzz=Class.forName("knowledge.reflect.method.Student2"); //2.通过获取的字节码文件对象来获取构造器对象,并通过newInstance()方法创建Student2类的对象。//注意看两个构造情况下输出结果的不同。//空参构造Constructorcon=zz.getConstructor(); Student2st= (Student2) con.newInstance(); System.out.println("看看重写了toString方法后打印出的Student2类对象st长什么样子:"+st); //带参构造Constructorcon2=zz.getConstructor(int.class); Student2st2= (Student2) con2.newInstance(19); System.out.println("看看重写了toString方法后打印出的Student2类对象st2长什么样子:"+st2); System.out.println("``````````````````````"); /*①注意我们输出利用空参构造创建的Student2对象st时,age = 0,这是因为我们没有给age赋初值,而int类型的默认值就是0②而当我们输出利用带参构造创建的Student2对象st2时,age = 19,这是因为我们在带参构造中传入了参数19,给age赋了初值为19。*///3.通过获取的字节码文件对象来获取成员方法对象(三种方式),并通过invoke()调用此方法//方式一:getMethod (String name, Class <?>... parameterTypes)://调用公共的空参方法Methodmethod1=zz.getMethod("function1"); /*因为调用的是公有的无参方法,所以此处只需传入字符串形式的方法名*/System.out.println("打印一下方法对象method1:"+method1); System.out.println("只打印 method1对象的方法名:"+method1.getName()); System.out.println("``````````````````````"); /**调用invoke() 方法时,接不接收取决于函数的返回值类型!!!*/method1.invoke(st); /*因为method1是公有的空参方法对象,而Student2类中,公有的空参方法function1() 返回值类型是void,因此不需要作接收。又因为是空参方法,所以也不需要传入参数,只需传入一个Student2类的对象就可以了所以此处直接调用invoke() 方法,且只传入了Student2类的对象,而没有作接收。*/System.out.println("``````````````````````"); //调用公共的带参方法Methodmethod2=zz.getMethod("function2", int.class); /*因为调用的是公有的带参方法,所以此处除了要传入字符串形式的方法名,还需要传入该带参方法的形参对应的字节码文件对象。*/method2.invoke(st, 5); /*因为method2是公有的带参方法对象,而Student2类中,公有的带参方法function2() 返回值类型是void,因此也不需要作接收。又因为是带参方法,所以不仅需要传入一个Student2类型的对象,还需传入对应的参数。所以此处直接调用invoke() 方法,且同时传入了Student2类型的对象和该方法对应的参数,而没有作接收。*/System.out.println("``````````````````````"); //方式二:getDeclaredMethod (String name, Class <?> ...)://调用私有的带参方法Methodmethod3=zz.getDeclaredMethod("function3", int.class, int.class); /** 此处私有不让调用,开启暴力反射!! */method3.setAccessible(true); inti= (int) method3.invoke(st, 3,9); System.out.println("您输入的两个数的乘积为:"+i); System.out.println("``````````````````````"); //方式三:获取Student1类的所有的成员方法(不含私有),用Method[] 作接收。Method[] methods=zz.getMethods(); for (Methodmd : methods) { System.out.println(md); } System.out.println("``````````````````````"); /*因为Student2类有继承自Object类的方法,因此这里遍历的方法会非常多。巨多。*///4.试着来获取Student2类中的setAge()方法,来给Student对象设置值。Methodmethod4=zz.getMethod("setAge", int.class); method4.invoke(st, 20); System.out.println("通过setAge设置值后,打印出st对象,注意与之前的st对象作比较:"+st); } } /** 总结:* invoke() 复杂的地方在于,它既需要考虑函数的返回值类型(决定是否接收),* 有需要考虑函数是否带参(决定是否传入形参)。把握好这两点就不易出错了。* */
5.输出结果及分析:
输出结果:(太长了只能分两块)
分析:
I.首先我们看到,当我们用空参构造创建学生对象时,输出的学生对象的属性值age默认为0. 而当我们用带参构造来创建学生对象时,输出的学生对象的属性值age是我们传入的形参值19.
II.其次我们看到,打印出的方法对象,前缀修饰符+ 返回值类型,后缀方法名。不禁然然我们想起打印出的构造器对象,当然构造器对象是肯定没有返回值类型的。
III. 然后分别是公有空参方法method1.invoke(),公有带参方法method2.invoke() 和私有带参方法method3.invoke()的实现。
IV. 由于Student2类中有继承自Object类的非私有方法,所以遍历Method数组时,你会看到好多好多的方法,巨多。
V.通过setAge修改了之前 通过空参构造创建的对象st的属性值, 成功!
五.获取成员变量(Field)
1.前言:
你能挺到这里,可见你的头铁(优秀),其实把Constructor解决后,后头这俩都是小菜。
2.Field对象:
Field对象又称域对象,指类的属性(成员变量)。
3.三种方式获取Field对象:
方式一:
getField (String name):
说明:
①该方法返回一个Field对象,用Field类型作接收。
②该方法仅需传入一个字符串类型的变量,即你要修改的属性的名字(属性名)
③仅限于公共属性
方式二:
getDeclaredField (String name):
说明:
①可用于获取私有属性。
方式三:
getDeclaredFields ()
说明:
①该方法可以返回此类所有(包含私有) 属性的数组。用Field类型的数组作接收,即Field[] fields = .........;
4.Field类的常用方法:
set (Object obj, Object value):
说明:
①该方法返回值为void类型,即不需要作接收。
②该方法可以设置obj对象的指定属性值为value。obj对象即是你用构造器中newInstance() 方法所创建的对象,指定属性即为创建field对象时传入属性名的那个属性。
setAccessible (boolean flag):
说明:
①是不是很眼熟? 没错,这就是访问私有构造器,私有方法,私有成员前必须进行的工作:开启暴力反射(设置为true)。
②该方法可以将Field对象对应的属性的可访问性设置为指定布尔值,即一个属性能不能访问你来说了算!
5.代码演示:
老规矩,我们先在field包下创建一个演示类Student3,用来模拟我们要分析的类。
编辑
Student3学生类代码如下:
packageknowledge.reflect.Field; publicclassStudent3 { //公共的属性publicStringname; //私有的属性privateintage; //重写toString方法,用来打印学生对象属性值(可用快捷键Alt + Insert,选择toString())publicStringtoString() { return"Student3{"+"name='"+name+'\''+", age="+age+'}'; } }
正片开始,GetField代码演示:
packageknowledge.reflect.Field; importjava.lang.reflect.Field; //别忘了导包importjava.lang.reflect.Constructor; /*** 需求: 通过反射获取成员变量并使用*/publicclassGetField { publicstaticvoidmain(String[] args) throwsException{ //1.首先获取Student3类的字节码文件对象Classcs=Class.forName("knowledge.reflect.Field.Student3"); //2.通过获取的字节码文件对象来获取构造器对象。Constructorcon=cs.getConstructor(); Student3student1= (Student3) con.newInstance(); /*当你把前两步用习惯之后,可以直接把两步合二为一,达到代码优化的效果这也叫做链式编程,如下代码第20行:*/Student3student2= (Student3) cs.getConstructor().newInstance(); //3.获取Field对象的三种方式://方式一: getField (String name):/*需要传入属性名,String类型*/Fieldfield1=cs.getField("name"); System.out.println("给👴看看name属性的样子:"+field1); //方式二: getDeclaredField (String name):Fieldfield2=cs.getDeclaredField("age"); System.out.println("给👴看看age属性的样子:"+field2); System.out.println("----------------------------"); //方式三: getDeclaredFields ()/*以Field类型的数组作接收,可使用增强for或者迭代器来遍历。*/Field[] fields=cs.getDeclaredFields(); for (Fieldfield : fields) { System.out.println(field); } //4.Field类的常用方法://set (Object obj, Object value):/*在调用set() 方法 修改Student3类的属性之前我们先输出一下学生对象student2,来看看属性的初始值是多少。*/System.out.println("----------------------------"); System.out.println("看看默认的属性值是多少:"+student2); /*结果显示为name='null', age=0然后我们来修改属性值*/field1.set(student2, "Cyan"); /*先修改一下公有属性name看看效果如何*/System.out.println("修改name后学生类的属性值为:"+student2); /*再来修改一下私有属性age看看效果如何*//*假设修改私有属性前,没有开启暴力反射,又会报IllegalAccessException错误*/field2.setAccessible(true);//开启暴力反射。field2.set(student2, 19); System.out.println("修改age后学生类的属性值为:"+student2); } }
6.输出结果及分析:
输出结果:
分析:
I.来看属性的输出结果: 前面是修饰符,name属性是紧接着String类的正名,而int是基本数据类型,最后是它们各自的变量名。而且它们都是Student3这个类的属性。
II.通过结果可以看到我们成功用Field类的set方法修改了Student3类的属性值。
六.完结撒花❀❀❀❀❀
祝贺你成功学完了反射(Class-Constructor-Method-Field一条路),非常感谢你能看到这里,创作不易,所有代码基本都是up手码的,也作了非常详细的注释,觉得不错就给up点个赞吧。感谢阅读!
System.out.println("END---------------------------------------------------------------------------");