4.1、什么是反射
在程序的运行过程中,通过字节码文件动态的获取类中的成员信息(构造器、方法、字段),这种就叫做反射。目的是为了通过程序自动获取构造器来创建对象、获取方法并调用。
4.2、字节码对象
Java 代码会经历三个阶段:
我们可以通过多个实物,发现他们的共性,来抽象成一个类,类就是对象的模板,而一个个的实体就是对象
字节码也是真实存在的文件,每一个字节码都是一个实例,而 JVM 要来存放这些字节码就需要抽象成模板,再通过模板来创建对象,存放每份字节码的信息。当要使用某份字节码时(比方说创建 Person 对象),就从 JVM 中调出存了 Person.class 内容的 Class 对象,然后再去实例化 Person 对象。3.
JDK 中定义好了 Class 类:java.lang.Class,该类中有大量 gte 开头的方法,表示可以使用字节码对象来获取信息,所以我们当我们拿到了字节码对象就可以直接操作当前字节码中的构造器、方法、字段等等。
4.3、获取字节码对象
通过 API,我们可以得知 Class 没有公共的构造器,原因是因为 Class 对象在加载类时由 Java 虚拟机自动构建。
方式一
通过 Class 类的forName()方法来获取字节码对象**(常用)**,多用于配置文件,将类名定义在配置文件中。读取文件,加载类以及各种流行框架中。
Class.forName(String className); //通过类的全限定名来获取字节码对象,全限定类名是包名+类型Class.forName("java.lang.String"); //JVM中存在则返回,不存在就抛出异常
复制代码
方拾二
通过对象的 getClass()方法来获取字节码对象,多用于对象的获取字节码的方式。
new User().getClass(); //通过父类Object中的getClass方法
复制代码
方式三
通过类型(基本类型)的 class 字段来获取字节码对象,多用于参数的传递。
总结
以上的三种方式第一种是使用最多的,在各种框架中都有使用。
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的 Class 对象都是同一个。
@Testpublic void testGetClass() throws Exception { // 1 通过类的全限定名 Class.forName(); Class clz1 = Class.forName("cn.linstudy.domain.Person"); // 2 通过对象的getClass() 方法 Person p = new Person(); Class clz2 = p.getClass(); // 3 通过class 字段去获取 Class clz3 = Person.class; // 字节码只会加载一次,所有不管用的哪种方式去获取字节码,都是同一个 System.out.println(clz1 == clz2); //true System.out.println(clz2 == clz3); //true System.out.println(clz1 == clz3); //true // int 类型和int数据类型不是同一个 System.out.println(int.class); System.out.println(int[].class);}
复制代码
4.4、获取构造器
使用反射的目的无外乎是使用程序动态操作类的成员,比如说方法,而且操作方法首先得有对象,而对象是通过构造器来创建的,所以必须先获取构造器。
4.4.1、获取所有构造器
public Constructor<?>[] getConstructors();:获取所有 public 修饰的构造器。
public Constructor<?>[] getDeclaredConstructors();:获取所有的构造器(包括非 public)
4.4.2、获取指定的构造器
public Constructor getConstructor(Class... parameterTypes);
public Constructor getDeclaredConstructor(Class...parameterTypes)
parameterTypes : 参数的类型(构造方法的参数列表的类型).
结论
带着 s 表示获取多个.带着 Declared 表示忽略权限,包括私有的也可以获取到。
4.4.3、获取构造器练习
@Testpublic void testGetConstructor() throws NoSuchMethodException { // 获取字节码对象 Class clz = Person.class; // 1 获取所有 public 构造器 Constructor[] cons1 = clz.getConstructors(); for(Constructor con : cons1){ System.out.println(con); } System.out.println("--------"); // 2 获取所有构造器,包括 private Constructor[] cons2 = clz.getDeclaredConstructors(); for(Constructor con : cons2){ System.out.println(con); } // 3 获取无参构造器 Constructor con1 = clz.getConstructor(); System.out.println(con1); // 4 获取带参构造器 Constructor con2 = clz.getConstructor(Long.class, String.class); System.out.println(con2); // 5 获取指定 private 构造器 Constructor con3 = clz.getDeclaredConstructor(String.class); System.out.println(con3);}
复制代码
常见错误
参数不匹配,报错.找不到指定的构造器
4.4.4、调用构造器创建对象
public Object newInstance(Object... initargs);// initargs: 调用该构造器传递的实际参数.参数列表一定要匹配(类型,个数,顺序).
复制代码
@Testpublic void testCreateObject() throws Exception { // 获取字节码对象 Class clz = Class.forName("cn.linstudy.domain.Person"); // 获取带参数构造器,参数为参数类型 Constructor con1 = clz.getConstructor(Long.class, String.class); //调用构造器 Object obj = con1.newInstance(1L, "小狼"); System.out.println(obj); // 获取带有参数的 private 构造器 Constructor con2 = clz.getDeclaredConstructor(String.class); // 调用私有构造器,必须先设置为可访问 con2.setAccessible(true); Object obj2 = con2.newInstance("小码"); System.out.println(obj2);}
复制代码
注意: 不能直接访问没有权限(非 public)的成员,如果想要使用反射去操作非 public 的成员.必须设置一个可以访问的标记.
我们尝试私有化构造器来创建对象,结果被告知权限不够
解决办法如下:
public void setAccessible(boolean flag): 传递一个 true,表示可以访问,表示不管权限.
从 API 中我们可以发现,Constructor,Field,Method是 AccessibleObject 的子类,因为这三种成员都是可以被访问 private 修饰符修饰的。
package com.test.reflect;
import java.lang.reflect.Constructor;
/** * @author Xiao_Lin * @date 2020/12/28 20:17 */public class TestReflect {
public static void main(String[] args) throws Exception { Class<?> student = Class.forName("com.test.reflect.Student"); System.out.println(student); Constructor<?> constructor = student.getDeclaredConstructor(String.class); constructor.setAccessible(true); Object zs = constructor.newInstance("张三"); System.out.println(zs);
}}
复制代码
只要看到传入全限定名,基本上都是要使用反射,通过全限定名来获取字节码对象. 只要看到无指定构造器但是能创建对象,基本上都是要通过字节码对象的 newInstance 去创建对象.
4.5、获取方法
4.5.1、获取所有方法
public Method[] getMethods();: 可以获取到所有的公共的方法,包括继承的。
public Method[] getDeclaredMethods();:获取到本类中所有的方法,包括非 public 的,不包括继承的。
4.5.2、获取指定的方法
public Method getMethod(String name, Class<?>... parameterTypes);
public Method getDeclaredMethod(String name, Class<?>... parameterTypes):
name: 方法名,parameterTypes: 当前方法的参数列表的类型
注意,要找到某一个指定的方法,必须要使用方法签名才能定位到,而方法签名=方法名+参数列表,经验和获取构造器的经验一样,带着 s 表示获取多个,带着 declared 表示忽略访问权限。
4.5.3、获取方法的练习
@Testpublic void testGetMethod() throws Exception { /** 1 获取所有 public 方法,包括父类的 2 获取所有方法,包括 private 不包括父类的 3 获取指定参数的public 的方法,包括父类的 4 获取指定参数的private 方法,不包括父类的 **/ // 1 获取字节码对象 Class clz = Class.forName("cn.linstudy.domain.Person"); // 2 获取构造器来创建对象 // 3 获取方法 //1 获取所有 public 方法,包括父类的 Method[] methods = clz.getMethods(); for(Method m : methods){ System.out.println(m); } System.out.println("---------"); //2 获取所有方法,包括 private 不包括父类的 Method[] methods2 = clz.getDeclaredMethods(); for(Method m : methods2){ System.out.println(m); } System.out.println("---------"); //3 获取指定参数的 public 的方法,包括父类的 Method sayHelloMethod = clz.getMethod("sayHello", String.class); System.out.println(sayHelloMethod); //4 获取指定参数的private 方法,不包括父类的 Method doWorkMethod = clz.getDeclaredMethod("doWork", String.class); System.out.println(doWorkMethod);}
复制代码
4.6、调用方法
public Object invoke(Object obj, Object... args);:
obj: 表示调用该方法要作用到那个对象上..
args:调用方法的实际参数方法的返回值表示,调用该方法是否有返回值,如果有就返回,如果没有返回 null。
传统的调用方法
Student t = new Student(1, "张三"); t.sleep(5);// 张三,睡5个小时。
复制代码
使用反射创建对象调用方法
Method m = clz.getMethod(“sleep”, int.class);// 找到sleep方法。m.invoke(obj, 5);// 睡,5个小时。
复制代码
public class Person { private Long id; private String name; public Person() { } public Person(Long id, String name) { this.id = id; this.name = name; } private Person(String name) { this.name = name; } public void sayHello(){ System.out.println("hello"); } public String sayHello(String name){ System.out.println(name + ": hello"); return "您好"; } public static void sayHello(String name,Long id){ System.out.println("调用静态方法"); } private void doWork(){ System.out.println("doWork"); } private void doWork(String name){ System.out.println(name + ": doWork"); } // getter方法 setter 方法 public String toString() { return "Person{" + "id=" + id + ", name='" + name + '\'' + '}'; }}
复制代码
@Testpublic void testGetMethod() throws Exception { // 1 获取字节码对象 Class clz = Class.forName("com.reflect.Person"); // 2 获取构造器来创建对象 Object obj = clz.newInstance(); // 使用公共的无参数的构造器 // 3 获取方法 //1 获取所有 public 方法,包括父类的 Method[] methods = clz.getMethods(); for(Method m : methods){ System.out.println(m); } System.out.println("---------"); //2 获取所有方法,包括 private 不包括父类的 Method[] methods2 = clz.getDeclaredMethods(); for(Method m : methods2){ System.out.println(m); } System.out.println("---------"); //3 获取指定参数的 public 的方法,包括父类的 Method sayHelloMethod = clz.getMethod("sayHello", String.class); System.out.println(sayHelloMethod); // 调用方法 Object val1 = sayHelloMethod.invoke(obj, "张三");
System.out.println("值1:" + val1); //4 获取指定参数的private 方法,不包括父类的 Method doWorkMethod = clz.getDeclaredMethod("doWork", String.class); System.out.println(doWorkMethod); // 设置可访问 doWorkMethod.setAccessible(true); // 调用私有的方法 doWorkMethod.invoke(obj,"李四"); // 调用静态方法 Method staticSayHelloMethod = clz.getDeclaredMethod("sayHello", String.class, Long.class); // 不需要对象去调用,但是参数必须加上null,不然会把后面的参数作为调用方法的对象了. staticSayHelloMethod.invoke(null,"小明",1L);}
复制代码
注意:
方法也是可以被访问私有修饰符修饰的,所以,如果要访问非 public 修饰的方法,需要在访问之前设置可访问 method.setAccessible(true);。
如果调用的是静态方法,是不需要对象的,所以此时在 invoke 方法的第一个参数,对象直接传递一个 null 即可。
4.7、获取字段
4.7.1、获取单个字段
public Field getField(String name);:
public Field getDeclaredField(String name);
name 要获取的字段的名称
4.7.2、获取所有字段
public Field[];
getFields() ;
public Field[] getDeclaredFields();
@Testpublic void testField() throws Exception { // 1 获取字节码对象 Class clz = Person.class; Object obj = clz.newInstance(); // 2 获取字段 Field[] fs = clz.getFields(); for(Field f: fs){ System.out.println(f); } Field[] fs2 = clz.getDeclaredFields(); for(Field f: fs2){ System.out.println(f); } // 获取单个字段 Field nameField = clz.getDeclaredField("name"); System.out.println(nameField);}
复制代码
4.8、操作字段
get(Object obj);
set(Object obj,Object value);
// 设置私有字段可访问nameField.setAccessible(true);// 操作name字段// 设置那么字段的数据nameField.set(obj,"小狼");// 获取name字段的数据Object nameValue = nameField.get(obj);System.out.println(nameValue);
复制代码
评论