Java 基础篇专栏 – 反射

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

使用反射获取程序运行时的对象和类的真实信息。

获取 Class 对象

每个类被加载之后,系统会为该类生成一个对应的 Class 对象,通过该 Class 对象可以访问到 JVM 中的这个类。

  • 使用 Class 类的 forName(String clazzName) 静态方法。字符串参数的值是某个类的全限定类名,必须包含完整的包名

  • 调用某个类的 class 属性

  • 调用某个对象的 getClass() 方法。该方法是 java.lang.Object 类中的一个方法,所有的 Java 对象都可以调用,返回该对象所属类对应的 Class 对象

获取 Class 对象中信息

Class 类提供了大量的实例方法来获取该 class 对象所对应的类的详细信息。更多请参考 API。

 1import java.lang.reflect.*;
2import java.lang.annotation.*;
3
4public class ClassTest {
5    private ClassTest() {
6    }
7
8    public ClassTest(String name) {
9        System.out.println("执行有参数的构造器");
10    }
11
12    public void info() {
13        System.out.println("执行无参数的info方法");
14    }
15
16    public void info(String str) {
17        System.out.println("执行有参数的info方法" + ",其 str 参数值: " + str);
18    }
19
20    class Inner {
21    }
22
23    public static void main(String[] args) throws Exception {
24        Class<ClassTest> clazz = ClassTest.class;
25
26        // 获取 clazz 对象所对应类的全部构造器
27        Constructor<?>[] ctros = clazz.getDeclaredConstructors();
28        System.out.println("ClassTest 的全部构造器如下: ");
29        for (Constructor c : ctros) {
30            System.out.println(c);
31        }
32
33        // 获取 clazz 对象所对应类的全部 public 构造器
34        Constructor<?>[] publicCtors = clazz.getConstructors();
35        System.out.println("ClassTest的全部public构造器如下:");
36        for (Constructor c : publicCtors) {
37            System.out.println(c);
38        }
39
40        // 获取 clazz 对象所对应类的全部 public 方法
41        Method[] mtds = clazz.getMethods();
42        System.out.println("ClassTest 的全部 public 方法如下: ");
43        for (Method md : mtds) {
44            System.out.println(md);
45        }
46
47        // 获取 clazz 对象所对应类的指定方法
48        System.out.println("ClassTest 里带一个字符串参数的 info 方法为:" + clazz.getMethod("info", String.class));
49
50        // 获取 clazz 对象所对应类的全部注解
51        Annotation[] anns = clazz.getAnnotations();
52        System.out.println("ClassTest 的全部 Annotation 如下: ");
53        for (Annotation an : anns) {
54            System.out.println(an);
55        }
56
57        // 获取 clazz 对象所对应类的全部内部类
58        Class<?>[] inners = clazz.getDeclaredClasses();
59        System.out.println("ClassTest 的全部内部类如下: ");
60        for (Class c : inners) {
61            System.out.println(c);
62        }
63
64        // 使用 Class.forName() 方法加载 ClassTest 的 Inner 内部类
65        Class inClazz = Class.forName("ClassTest$Inner");
66
67        // 访问该类所在的外部类
68        System.out.println("inClazz 对应类的外部类为: " + inClazz.getDeclaringClass());
69
70        System.out.println("ClassTest 的包为:" + clazz.getPackage());
71        System.out.println("ClassTest 的父类为:" + clazz.getSuperclass());
72
73    }
74}

应用

Class 对象可以获得对应类的方法(由 Method 表示)、构造器(由 Constructor 表示)、成员变量(由 Field 对象表示),且这个三个类都实现了 java.lang.reflect.Member 接口。程序可以通过 Method 对象来执行对应的方法,通过 Constructor 对象来调用对应的构造器创建实例,通过 Field 对象直接访问并修改对象的成员变量值。

创建对象

  • 使用 Class 对象的 newInstance() 方法来创建 Class 对象对应类的实例。要求该 Class 对象的对应类有默认构造器

  • 先使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance() 方法来创建该 Class 对象对应类的实例。这种方式可以选择使用指定的构造器来创建实例

方式一

实现了一个简单的对象池,该对象池会根据配置文件读取 key-value 对,然后创建这些对象并放入 HashMap 中

 1import java.io.FileInputStream;
2import java.io.IOException;
3import java.util.HashMap;
4import java.util.Map;
5import java.util.Properties;
6
7public class ObjectPoolFactory {
8    private Map<String, Object> objectPool = new HashMap<>();
9
10    private Object createObject(String clazzName) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
11        Class<?> clazz = Class.forName(clazzName);
12        // 使用 Class 对象对应的类的默认构造器
13        return clazz.newInstance();
14    }
15
16    public void initPool(String fileName) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
17        try (
18                FileInputStream fis = new FileInputStream(fileName)
19        ) {
20            Properties props = new Properties();
21            props.load(fis);
22            for ( String name: props.stringPropertyNames()) {
23                objectPool.put(name, createObject(props.getProperty(name)));
24            }
25        } catch (IOException ex) {
26            System.out.println("读取" + fileName + "异常");
27        }
28    }
29
30    public Object getObject(String name) {
31        return objectPool.get(name);
32    }
33
34    public static void main(String[] args) throws Exception{
35        ObjectPoolFactory pf = new ObjectPoolFactory();
36        pf.initPool("obj.txt");
37        System.out.println(pf.getObject("a"));
38        System.out.println(pf.getObject("b"));
39    }
40}
41/*
42obj.txt 内容:
43
44a=java.util.Date
45b=javax.swing.JFrame
46*/

方式二
 1import java.lang.reflect.Constructor;
2
3public class CreateJFrame {
4    public static void main(String[] args) throws Exception {
5        Class<?> jframeClazz = Class.forName("javax.swing.JFrame");
6        // 选择使用指定的构造器
7        Constructor ctor = jframeClazz.getConstructor(String.class);
8        Object obj = ctor.newInstance("测试窗口");
9        System.out.println(obj);
10    }
11}

调用方法

每个 Method 对象对应一个方法,获得 Method 对象后,就可以通过该 Method 来调用它对应的方法。

Method 包含一个 invoke() 方法,该方法的签名如下:

  • Object invoke(Object obj, Object… args):该方法中的 obj 是执行方法的主调(即类的实例对象),后面的 args 是执行该方法的实参

下面是对之前的对象工厂池进行增强,允许在配置文件中增加配置对象的成员变量值,对象池工厂会读取该对象配置的成员变量值,并利用该对象对应的 setter 方法设置成员变量的值:

 1import java.io.FileInputStream;
2import java.io.IOException;
3import java.lang.reflect.InvocationTargetException;
4import java.lang.reflect.Method;
5import java.util.HashMap;
6import java.util.Map;
7import java.util.Properties;
8
9public class ExtendedObjectPoolFactory {
10    // 定义一个对象池,前面是对象名,后面是实际的对象
11    private Map<String, Object> objectPool = new HashMap<>();
12    private Properties config = new Properties();
13    // 从指定文件中初始化 Properties 对象
14    public void init(String fileName) {
15        try (
16                FileInputStream fis = new FileInputStream(fileName);
17        ) {
18            config.load(fis);
19        } catch (IOException ex) {
20            System.out.println("读取" + fileName + "异常");
21        }
22    }
23    // 定义创建对象的方法
24    private Object createObject(String clazzName) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
25        // 根据字符串来获取对应的 Class 对象
26        Class<?> clazz = Class.forName(clazzName);
27        // 使用 clazz 对应类的默认构造器创建实例
28        return clazz.newInstance();
29    }
30
31    // 初始化对象池
32    public void initPool() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
33        for (String name : config.stringPropertyNames()) {
34            if (!name.contains("%")) {
35                objectPool.put(name, createObject(config.getProperty(name)));
36            }
37        }
38    }
39
40    // 根据属性文件来调用指定对象的 setter 方法
41    public void initProperty() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
42        for (String name : config.stringPropertyNames()) {
43            if (name.contains("%")) {
44                String[] objAndProp = name.split("%");
45                Object target = getObject(objAndProp[0]);
46                String mtdName = "set" + objAndProp[1].substring(1);
47                // 通过 target 的 getClass() 获取它的实现类所对应的 Class 对象
48                Class<?> targetClass = target.getClass();
49                // 获取希望调用的 setter 方法
50                Method mtd = targetClass.getMethod(mtdName, String.class);
51                // 通过 Method 的 invoke 方法执行 setter 方法
52                mtd.invoke(target, config.getProperty(name));
53            }
54        }
55    }
56
57    public Object getObject(String name) {
58        // 从 objectPool 中取出指定 name 对应的对象
59        return objectPool.get(name);
60    }
61
62    public static void main(String[] args) throws Exception {
63        ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();
64        epf.init("extObj.txt");
65        epf.initPool();
66        epf.initProperty();
67        System.out.println(epf.getObject("a"));
68    }
69}
70
71/* extObj.txt 内容
72
73a=java.util.Date
74b=javax.swing.JFrame
75# set the title of a
76a%title=Test Title
77*/

PS:当通过 Method 的 invoke() 方法来调用对应的方法时,Java 会要求程序必须有调用该方法的权限。如果需要调用某个对象的 private 方法,则可以先调用 Method 对象的如下方法:

  • setAccessible(boolean flag):值为 true,表示该 Method 在使用时取消访问权限检查

访问成员变量值

Filed 提供如下两组方法来读取或设置成员变量值:

  • getXxx(Object obj):获取 obj 对象的该成员变量的值。此处的 Xxx 对应 8 中基本类型。如果成员变量的类型是引用类型,则直接使用 get

  • setXxx(Object obj, Xxx val):将 obj 对象的成员变量值设为 val 值。此处的 Xxx 对应 8 中基本类型。如果成员变量的类型是引用类型,则直接使用 set

 1public class Person {
2    private String name;
3    private int age;
4
5    public String toString() {
6        return "Person[name:" + name + ", age:" + age + "]";
7    }
8}
9
10import java.lang.reflect.Field;
11
12public class FieldTest {
13    public static void main(String[] args) throws Exception {
14        Person p = new Person();
15        Class<Person> personClazz = Person.class;
16        Field nameField = personClazz.getDeclaredField("name");
17        nameField.setAccessible(true);
18        nameField.set(p, "crazy");
19        Field ageField = personClazz.getDeclaredField("age");
20        ageField.setAccessible(true);
21        ageField.setInt(p, 30);
22        System.out.println(p);
23    }
24}

泛型在反射中的应用

在反射中使用泛型,反射生成的对象就不需要进行强制类型转换。

 1import java.util.Date;
2
3public class CrazyitObjectFactory {
4    public static <T> getinstance(Class<T> cls) {
5        try {
6            return cls.newInstance();
7        } catch (Exception e) {
8            e.printStackTrace();
9            return null;
10        }
11    }
12
13    public static void main(String[] args) {
14        // 获取实例后无需进行类型转换
15        Date d = CrazyitObjectFactory.getinstance(Date.class);
16    }
17}

Java 基础篇专栏 - 反射

原文始发于微信公众号(SRE工程师):Java 基础篇专栏 - 反射