Java反射入门
1.概述
反射就是在运行状态,对于任意一个类,都可以直达这个类的属性和方法;对于任何一个对象,都可以调用它的任意一个方法和属性。而对于官方的解释是,反射允许对封装类的字段、方法以及构造函数进行编程访问。
2.反射的使用方式
反射的应用场景主要是先获取class对象,然后根据需求获取构造函数、成员变量和成员方法的一个或多个,并对其进行相应的操作。
2.1获取class对象的三种方式
①Class.forName("类的路径“): 当知道该类的全路径名时,可以使用该方法获取 Class 类对象。
②类名.class。只适合在编译前就知道操作的 Class,通常在作为参数的场景中使用。
③对象名.getClass()。只适合已有这个对象时使用。
2.2获取构造函数
1)提供的方法
方法 | 说明 |
Constructor<?>[] getConstructors() |
获取对象的所有公共构造方法 |
Constructor<?>[] getDeclaredConstructors() | 获取对象的所有构造方法 |
Constructor<T> getConstructor(Class<?>... parameterTypes) | 根据参数类型 获取对象的单个公共构造方法 |
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) | 根据参数类型 获取对象的单个构造方法 |
T newInstance(Object ... initargs) | 根据指定的构造方法创建对象 |
2)方法实战
首先创建一个对象,添加构造方法,使用不同的权限修饰符
package com.zxh.demo.entity; public class User { public String name; private String pwd; private Integer age; public User() { } public User(String name) { this.name = name; } private User(String name, Integer age) { this.name = name; this.age = age; } public User(String name, String pwd, Integer age) { this.name = name; this.pwd = pwd; this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", pwd='" + pwd + '\'' + ", age=" + age + '}'; } }
获取构造方法
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { //利用反射获取类 Class<?> aClass = Class.forName("com.zxh.demo.entity.User"); //获取所有公共的构造方法 Constructor<?>[] constructors = aClass.getConstructors(); System.out.println("User类所有公共的构造方法"); Arrays.stream(constructors).forEach(System.out::println); //获取所有的构造方法 Constructor<?>[] constructors2 = aClass.getDeclaredConstructors(); System.out.println("User类所有的构造方法"); Arrays.stream(constructors2).forEach(System.out::println); //根据参数获取构造方法 Constructor<?> con1 = aClass.getConstructor(String.class); System.out.println("根据参数获取构造方法1:" + con1); Constructor<?> con2 = aClass.getDeclaredConstructor(String.class, Integer.class); System.out.println("根据参数获取构造方法2:" + con2); Constructor<?> con3 = aClass.getConstructor(String.class, Integer.class); System.out.println("根据参数获取构造方法3:" + con3); }
在上述代码中,其实就用到了获取class对象的两种方式。在根据参数获取构造方法时,传入了参数类型,这个参数需要和构造方法中的参数保持一致。打印结果如下
可用看出每个构造方法都标识了权限修饰符和参数类型。getDeclaredConstructors()和getConstructors()的区别在于,前者可获取对象的所有构造方法,而后者只能获取到公共的,通过权限修饰符可用明显的看出来。同理,根据参数获取构造方法也是这样区分的。另外,在根据参数调用公共获取构造方法的方法时抛出了异常,说是没有找到这个构造方法,原因是这个方法是私有的,使用公有的方法自然获取不到。
除此之外,还有很多的方法,这里就列举几个进行说明:
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //利用反射获取类 Class<?> aClass = Class.forName("com.zxh.demo.entity.User"); Constructor<?> con2 = aClass.getDeclaredConstructor(String.class, Integer.class); System.out.println("根据参数获取构造方法2:" + con2); int modifiers = con2.getModifiers(); System.out.println("构造方法权限修饰符:" + modifiers);//返回是的数字,在jdk api文档中有对此常量的说明 Parameter[] parameters = con2.getParameters(); System.out.println("获取构造方法所有的参数信息"); Arrays.stream(parameters).forEach(System.out::println); //暴力反射:临时取消权限校验 con2.setAccessible(true); //根据构造方法创建对象 Object newInstance = con2.newInstance("张三", 23); System.out.println(newInstance); }
在创建对象时,由于此时获取的构造方法是私有的,也就是说正常情况下是不能使用此构造方法创建对象。虽然通过反射的机制获取到了此构造函数,但无法使用此方法。那么此时就需要使用setAccessilbe()设置值为true来临时取消权限访问校验,对于成员方法也可以这样处理。打印结果如下
在创建对象时,idea的提示就用到了反射(后面包括方法和属性的调用),只能使用公有的构造方法创建对象
2.3获取成员变量
获取成员变量(属性)和上述获取构造方法的方式是类似的,其方法如下
方法 | 说明 |
Field[] getFields() |
获取对象的所有公共属性 |
Field[] getDeclaredFields() | 获取对象的所有属性 |
Field getField(String name) | 根据属性名称 获取对象的单个属性 |
Field getDeclaredField(String name) | 根据属性名称 获取对象的单个属性 |
void set(Object obj, Object value) | 给对象的属性设置值 |
Object get(Object obj) | 获取属性的值 |
首先需要给对象添加get和set方法(设置不同的权限修饰符)
public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } public Integer getAge() { return age; } private void setAge(Integer age) { this.age = age; }
获取属性的基本信息
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { //利用反射获取类 Class<?> aClass = Class.forName("com.zxh.demo.entity.User"); Field[] fields = aClass.getFields(); System.out.println("获取所有公共的属性"); Arrays.stream(fields).forEach(System.out::println); Field[] fields2 = aClass.getDeclaredFields(); System.out.println("获取所有的属性"); Arrays.stream(fields2).forEach(System.out::println); Field field = aClass.getField("name"); System.out.println("根据属性名获取属性1:" + field); Field field2 = aClass.getDeclaredField("age"); System.out.println("根据属性名获取属性2:" + field2); Field field3 = aClass.getField("age"); System.out.println("根据属性名获取属性3:" + field3); }
打印结果
可以看出,属性都包含权限修饰符、属性类型和属性名称。当使用公有的方式去获取私有的属性时也会抛异常。
除此之外,还可以给属性设置值和获取值:
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { //利用反射获取类 Class<?> aClass = Class.forName("com.zxh.demo.entity.User"); Field field2 = aClass.getDeclaredField("age"); System.out.println("根据属性名获取属性2:" + field2); field2.setAccessible(true);//私有,必须设置为true User user = new User(); field2.set(user, 20); Object o = field2.get(user); System.out.println(o); System.out.println(user); }
打印结果如下
可以看到,上述是给用户对象的age设置值,也可以来获取对象的这个属性,前提是把对象作为参数进行传递。
2.4获取成员方法
获取成员方法和上述获取成员方法的方式是类似的,其方法如下
方法 | 说明 |
Method[] getMethods() |
获取对象的所有公共方法。会一并获取到父类的公有方法 |
Method[] getDeclaredMethods() | 获取对象的所有方法,不会获取父类的方法 |
Method getMethod(String name, Class<?>... parameterTypes) | 根据方法名和形参类型 获取单个方法 |
Method getDeclaredMethod(String name, Class<?>... parameterTypes) | 根据方法名和形参类型 获取单个方法 |
Object invoke(Object obj, Object... args) | 运行方法。参数一:调用给方法的对象。参数二:调用该方法的参数,没有就不写 |
首先需要给对象添加两个方法用于演示(设置不同的权限修饰符)
public void say(String title) { System.out.println("我在讲话,主题是:" + title); } protected void running(String addr) { System.out.println("我正在" + addr + "跑步"); }
获取方法并执行
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { //利用反射获取类 Class<?> aClass = Class.forName("com.zxh.demo.entity.User"); Method[] methods = aClass.getMethods(); System.out.println("获取所有公共的方法"); Arrays.stream(methods).forEach(System.out::println); Method[] methods2 = aClass.getDeclaredMethods(); System.out.println("获取所有的方法"); Arrays.stream(methods2).forEach(System.out::println); Method method = aClass.getMethod("say", String.class); System.out.println("获取方法1:" + method); User user = new User(); method.invoke(user, "学习反射"); Method method2 = aClass.getDeclaredMethod("running", String.class); System.out.println("获取方法2:" + method2); method2.setAccessible(true); method2.invoke(user, "公园"); Method method3 = aClass.getMethod("running", String.class); System.out.println("获取方法3:" + method3); }
执行结果,方法过多,只截取部分
获取单个方法时,不仅要指定方法名还要传入形参类型,为的是解决方法重载问题。
通过上述几个小节的介绍,那么在使用反射时,无论是获取成员变量还是成员方法,通常都会使用获取所有的方式,并不会只获取公有的,其优势很明显。
2.5实战演练
1)获取对象的所有属性名和值
2)根据传入的参数动态创建对象并调用方法
以上两个都是简单的用法,实际应用场景会复杂的多。