JAVA反射(基础详细版)
1.反射
1.1 反射的概述
我们首先要知道什么是反射:
反射就是指在运行状态中,对于任意一个类,都能够知道并调用这个类的所有属性和方法。这种动态获取对象信息和动态调用对象方法的功能叫做Java反射。
可能上面的解释各位理解不了,那么我就通俗理解一下就是:
(1).利用反射创建的对象可以无视修饰符而调用里面的类里面的内容;
(2).可以跟配置文件结合使用,把要创建的对象信息和方法写入到配置文件里面;
1.2 学习反射到底是学习什么
反射都是从class字节码文件里面获得的内容
(1).如何获取class字节码文件对象
(2).利用反射如何获取构造方法(创建对象)
(3).利用反射如何获取成员变量(赋值,获取值)
(4).利用反射如何获取成员方法(运行)
我们本次的测试类是User类,它的源码如下:
package com.example.main;
public class User{
private String name;
private String passWord;
public User(){}
private User(String name){
this.name = name;
}
public User(String name, String passWord) {
this.name = name;
this.passWord = passWord;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
//重写
@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", passWord='" + passWord + ''' +
'}';
}
}
1.3 获取class字节码文件对象的三种方式
package com.example.main;
public class Main {
//注意:千万别忘了抛出异常
public static void main(String[] args) throws ClassNotFoundException {
//1.通过调用Class里面静态方法forName()
//注意里面的参数应输入为“全类名”,即全类名=包名+类名
//里面的clazz1就是Student这个类的字节码文件对象
Class<?> clazz1 = Class.forName("com.example.main.User");
//这里使用了泛型是为了增加灵活性和安全性,里面的?可以是任意类型。
//这里源代码的获取阶段就是————先将User这个类加载到内存中之后,再获取User.class产生的字节码文件对象。
//2.通过class属性获取
Class<?> clazz2 = User.class;
//熟悉Java栈堆的应该明白,虽然这里的两个字节码对象在栈里面地址不同,但是由于class文件在硬盘中是唯一的,因此加载到内存里面产生的对象也是唯一的。
System.out.println(clazz2 == clazz1);//true
//3.可以直接通过User类来获取字节码文件对象
User user = new User();
Class<?> clazz3 = user.getClass();
System.out.println(clazz1 == clazz3);//true
System.out.println(clazz2 == clazz3);//true
}
}
说了半天大家可能还不知道什么是字节码文件和字节码文件对象,字节码文件就是通过Java文件(我们写的Java代码)编译以后的class文件(这个文件在硬盘上是可以找到的)
;而字节码文件对象则是class文件(即字节码文件)在加载到内存中之后,JVM会自动创建好它的对象,而且这个对象里面至少包含了构造函数、成员变量和成员方法。
我们获取的是什么?字节码文件对象,它在内存(不是硬盘)中是唯一的。
1.4 获取构造方法
package com.example.main;
import java.lang.reflect.Constructor;
public class Main {
//注意:千万别忘了抛出异常
public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException {
//第一步肯定还是要先获得字节码文件对象
Class<?> clazz = Class.forName("com.example.main.User");
//1.获取所有的(public)构造方法,这里的数据类型必须用Constructor。
//这里的Constructor里面的泛型也是为了增强安全性和灵活性
Constructor<?>[] constructors1 = clazz.getConstructors();
for (Constructor<?> c: constructors1) {
System.out.println(c);
}
System.out.println("-----------------------------------");
//2.获取所有的构造方法(包括private方法)。
Constructor<?>[] constructors2 = clazz.getDeclaredConstructors();
for (Constructor<?> c: constructors2) {
System.out.println(c);
}
System.out.println("------------------------------------");
//3.获取指定的空参构造
Constructor<?> constructor3 = clazz.getConstructor();
System.out.println(constructor3);
//观察控制台打印的数据,你会发现什么?
Constructor<?> constructor4 = clazz.getDeclaredConstructor(String.class,String.class);
System.out.println(constructor4);
Constructor<?> constructor5 = clazz.getConstructor();
//之所以不相等是因为每次获得构造方法对象时,都会新New一个对象。
System.out.println(constructor5 == constructor3);//false
}
}
控制台打印的数据如下所示:
1.5 获取构造方法并创建对象
涉及到的方法为:newInstance
package com.example.main;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Main {
//注意:千万别忘了抛出异常
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//1.获取无参构造,并创建对象
//先获取字节码文件对象
Class<?> clazz1 = Class.forName("com.example.main.User");
//获取空参构造方法
Constructor<?> constructor = clazz1.getConstructor();
//利用空参构造方法创建对象
User user = (User) constructor.newInstance();
System.out.println(user.toString());
System.out.println("--------------------------------------");
//2.获取有参构造,并创建对象
//获得字节码文件对象
Class<?> clazz2 = Class.forName("com.example.main.User");
//获取有参构造方法
Constructor<?> constructor1 = clazz2.getDeclaredConstructor(String.class);
//临时修改构造方法的访问权限,即暴力反射
constructor1.setAccessible(true);
//直接创建对象
User user1 = (User) constructor1.newInstance("李金轩");
System.out.println(user1);
}
}
控制台打印的结果如图所示:
1.6 获取成员变量
这个和获取构造方法的相关代码非常相似,直接看代码:
package com.example.main;
import java.lang.reflect.Field;
public class Main {
//注意:千万别忘了抛出异常
public static void main(String[] args) throws ClassNotFoundException,NoSuchFieldException {
//获取字节码文件对象
Class<?> clazz1 = Class.forName("com.example.main.User");
//1.获取所有的public成员变量
Field[] fields = clazz1.getFields();
for (Field f: fields) {
System.out.println(f);//User类我上面已经给了,由于都是私有变量,故这个打印结果肯定为空。
}
//2.获取所有的成员变量(public+private)
Field[] fields1 = clazz1.getDeclaredFields();
for (Field f:fields1) {
System.out.println(f);
}
System.out.println("------------------------------");
/*3.获取指定的成员变量对象
如果获取的变量为私有,或者该变量不存在,则会抛出NoSuchFieldException异常。
Field field = clazz1.getField("xiyan");
System.oyt.println(field);
*/
//4.获取单个成员变量(私有或公共都可以)
Field field = clazz1.getDeclaredField("name");
System.out.println(field);
}
}
控制台打印结果为:
1.7 获取成员变量并修改值和获取值
在这个例子中,我将User(String name)构造函数的修饰符由private
改为了public
,代码示例:
package com.example.main;
import java.lang.reflect.Field;
public class Main {
//注意:千万别忘了抛出异常
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
//先实例化两个对象。
User user1 = new User("xiyan");
User user2 = new User("lijinxuan","21");
//目的:通过获取成员变量来获取值并修改值
//1.获取class对象
Class<?> clazz1 = Class.forName("com.example.main.User");
//2.获取成员变量对象
Field field = clazz1.getDeclaredField("name");
//3.暴力反射
field.setAccessible(true);
//4.设置name的值,此处第一个参数为要修改值的Java对象,第二个为要修改的值
field.set(user1,"Xiyan");
//5.获取指定对象的name的值
String name = (String) field.get(user1);
//直接打印结果可以得到
System.out.println(name);
System.out.println(user1);
System.out.println(user2);
}
}
对于上面的反射修改成员变量的值大家可能有点懵,正如我开始说的,反射是可以在运行状态中来动态调用类的属性或方法的,即先实例化对象之后,我们再用了反射来获取并修改Java对象的成员变量值。
上面的运行结果如下:
1.8 获取成员方法
在这个例子中,我将User类改为了如下所示:
package com.example.main;
public class User{
private String getYongJieName(String name){
if (!name.equals("jinxuan")){
return "情况不对,披坚执锐";
} else {
return "金刚伏魔";
}
}
public String getLOLName(String name){
if (!name.equals("lijinxuan")){
return "德玛西亚";
} else {
return "诺克萨斯";
}
}
protected void getXl(){
System.out.println("八嘎呀路");
}
}
接下来就是主方法执行代码,如下所示:
package com.example.main;
import java.lang.reflect.Method;
public class Main {
//注意:千万别忘了抛出异常
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//1.首先创建字节码文件对象
Class<?> clazz = Class.forName("com.example.main.User");
//2.获取方法
//此处的getMethod()方法不仅获得了自己类内部的public方法,还可以获得父类中的public修饰的方法
Method[] methods = clazz.getMethods();
for (Method m : methods) {
System.out.println(m);
}
System.out.println("=================================");
//可以获得自己类内部的所有方法(私有+公共)
Method[] methods1 = clazz.getDeclaredMethods();
for (Method m: methods1) {
System.out.println(m);
}
System.out.println("=================================");
//获得单个指定的方法(空参)
Method method1 = clazz.getDeclaredMethod("getXl");
System.out.println(method1);
//指定方法(有参)
Method method2 = clazz.getMethod("getLOLName", String.class);
System.out.println(method2);
System.out.println("-----------------------------");
//获取指定的私有方法
Method method = clazz.getDeclaredMethod("getYongJieName", String.class);
System.out.println(method);
}
}
这是运行结果:
1.9 获取成员方法并将其运行
在这里我们需要使用invoke
方法,即Object invoke(Object object,Object...args)
,我们直接看代码吧:
package com.example.main;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main {
//注意:千万别忘了抛出异常
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//1.先获取字节码文件对象
Class<?> clazz = Class.forName("com.example.main.User");
//2.获取类的实例化对象
User user = new User();
//3.获取指定的成员方法
Method method1 = clazz.getDeclaredMethod("getYongJieName", String.class);
Method method2 = clazz.getDeclaredMethod("getXl");
//由于getYongJieName()方法是private修饰,因此虽然获得了成员方法对象method1,但如果想访问成员方法则需要暴力反射。
method1.setAccessible(true);
//4.运行
//invoke方法的第一个参数就是实例化对象,第二个是方法参数。
//如果方法有返回值则应该接收返回值。
//这里我需要说的就是如果method1方法本身就运行时抛出异常,那么Invoke会将其封装为InvocationTargetException异常抛出。
String s = (String) method1.invoke(user,"lijinxuan");
//默认返回的是Object类型
Object user1 = method1.invoke(user,"lijinxuan");
System.out.println(user1);
System.out.println(s);
//没有返回值则不用。
method2.invoke(user);
}
}
运行结果:
转载自:https://juejin.cn/post/7336482843009237007