Java系列之反射

躬行之

共 11409字,需浏览 23分钟

 ·

2020-10-19 09:15

1fa84183c75d2269318ee815835ddabe.webp最近知识比较零碎,不适合整理成文,来一篇以前的关于反射的学习笔记,主要内容如下:

  1. 反射机制

  2. 反射获取类的信息

  3. 反射操作类的信息

  4. 反射获取泛型

  5. 反射获取注解信息

反射机制

Java 的反射机制是指在运行状态下,对于任意一个类,都能够知道这个类的所有属性和方法,反射是一种可在代码运行时动态获取类的信息的一种机制,可通过反射获取在编译期不可能获取到的类的信息,当一个任意类被类加载器(ClassLoader)首次加载之后会自动生成一个该类对应的 Class 对象,这个 Class 对象保存了对应类的所有信息。这种当一个任意类被类加载器加载之后,动态获取 Class 对象的信息以及动态操作 Class 对象的属性和方法的功能称之为 Java 的反射机制。Java 反射机制的关键是获取某个任意类的 Class 对象,下面的内容就是如何通过这个 Class 对象获取和动态操作类的相关信息,为了便于后文中对反射的使用,这里先创建一个 User 类供下文中使用,具体如下:
 1package com.manu.reflection.bean;
2
3/**
4 * 反射测试类
5 */

6public class User {
7
8    private int id;
9    private String name;
10    private String password;
11
12    public User() {
13        super();
14    }
15    public User(int id, String name, String password) {
16        super();
17        this.id = id;
18        this.name = name;
19        this.password = password;
20    }
21    public int getId() {
22        return id;
23    }
24    public void setId(int id) {
25        this.id = id;
26    }
27    public String getName() {
28        return name;
29    }
30    public void setName(String name) {
31        this.name = name;
32    }
33    public String getPassword() {
34        return password;
35    }
36    public void setPassword(String password) {
37        this.password = password;
38    }
39    @Override
40    public String toString() {
41        return "User [id=" + id + ", name=" + name + ", password=" + password + "]";
42    }
43
44}

反射获取类的信息

这里总结一下如何获取类的基本信息,如类的构造方法、属性、方法等,可通过某个类对应的 Class 对象对应的 getter 方法获取某个类的名称、构造方法、属性、方法等,下面以获取某个类的构造方法为例说明获取方式不同:
  • getConstructors:表示获取某个类中修饰符为 public 的所有构造方法,如 getFields()、getMethods() 还包括从父类继承来的 public 修饰的属性和方法。

  • getDeclaredConstructors:表示获取某个类中所有声明的构造方法,只限定于本类中。

此外,还可获取指定的构造方法、属性、方法等,下面的代码主要以获取已定义(Declared)的构造方法、属性、方法为例,参考如下:
 1/**
2 * 反射获取类的信息
3 */

4private static void getReflectClassInfo() {
5    try {
6        //获取某个类的Class对象
7        String name = "com.manu.reflection.bean.User";
8        Class clazz = Class.forName(name);
9
10        //反射获取类的名称
11        System.out.println("----------反射获取类的名称----------");
12        String n1 = clazz.getName();//完整路径:包名+类名 (com.manu.reflection.bean.User)
13        String n2 = clazz.getSimpleName();//类名(User)
14        System.out.println("获取类的名称n1:"+n1);
15        System.out.println("获取类的名称n2:"+n2);
16
17        //反射获取类的构造方法
18        System.out.println("----------反射获取类的构造方法----------");
19        Constructor c1 = clazz.getDeclaredConstructor(null);
20        System.out.println("获取无参构造方法:"+c1);
21        Constructor c2 = clazz.getDeclaredConstructor(int.class,String.class,String.class);
22        System.out.println("获取有参构造方法:"+c2);
23        Constructor[] constructors = clazz.getDeclaredConstructors();
24        for(Constructor c: constructors) {
25            System.out.println("获取所有的构造方法:"+c);
26        }
27
28        //反射获取类的属性
29        System.out.println("----------反射获取类的属性----------");
30        Field f1 = clazz.getDeclaredField("name");
31        System.out.println("获取名称为name的属性:"+f1);
32        Field[] fields = clazz.getDeclaredFields();
33        for(Field f : fields) {
34            System.out.println("获取所有的属性:"+f);
35        }
36
37        //反射获取类的方法
38        System.out.println("----------反射获取类的方法----------");
39        Method m1 = clazz.getDeclaredMethod("getName"null);//获取无参方法
40        Method m2 = clazz.getDeclaredMethod("setName", String.class);//获取有参方法
41        System.out.println("获取方法名为getName的方法m1:"+m1);
42        System.out.println("获取方法名为setName的方法m2:"+m2);
43        Method[] mathods = clazz.getDeclaredMethods();
44        for(Method m: mathods) {
45            System.out.println("获取所有方法:"+m);
46        }
47
48    } catch (Exception e) {
49        e.printStackTrace();
50    }
51}
通过反射获取某个类的相关信息主要如上,来上述代码的执行结果如下:
 1----------反射获取类的名称----------
2获取类的名称n1:com.manu.reflection.bean.User
3获取类的名称n2:User
4----------反射获取类的构造方法----------
5获取无参构造方法:public com.manu.reflection.bean.User()
6获取有参构造方法:public com.manu.reflection.bean.User(int,java.lang.String,java.lang.String)
7获取所有的构造方法:public com.manu.reflection.bean.User()
8获取所有的构造方法:public com.manu.reflection.bean.User(int,java.lang.String,java.lang.String)
9----------反射获取类的属性----------
10获取名称为name的属性:private java.lang.String com.manu.reflection.bean.User.name
11获取所有的属性:private int com.manu.reflection.bean.User.id
12获取所有的属性:private java.lang.String com.manu.reflection.bean.User.name
13获取所有的属性:private java.lang.String com.manu.reflection.bean.User.password
14----------反射获取类的方法----------
15获取方法名为getName的方法m1:public java.lang.String com.manu.reflection.bean.User.getName()
16获取方法名为setName的方法m2:public void com.manu.reflection.bean.User.setName(java.lang.String)
17获取所有方法:public java.lang.String com.manu.reflection.bean.User.toString()
18获取所有方法:public java.lang.String com.manu.reflection.bean.User.getName()
19获取所有方法:public int com.manu.reflection.bean.User.getId()
20获取所有方法:public void com.manu.reflection.bean.User.setName(java.lang.String)
21获取所有方法:public java.lang.String com.manu.reflection.bean.User.getPassword()
22获取所有方法:public void com.manu.reflection.bean.User.setId(int)
23获取所有方法:public void com.manu.reflection.bean.User.setPassword(java.lang.String)

反射操作类的信息

通过 Java 的反射机制可以获取的某个类的构造方法、属性以及方法,然后就可以对该类进行相关操作了,下面是将通过 Java 的反射机制构建该类的对象、操作该类对象的属性以及调用该类对象的方法,具体参考如下:
 1/**
2 * 反射操作类的信息
3 */

4private static void setReflectClassInfo() {
5    try {
6        //获取某个类的Class对象
7        String name = "com.manu.reflection.bean.User";
8        Class clazz = Class.forName(name);
9
10        //反射操作类的构造方法
11        System.out.println("----------反射操作类的构造方法----------");
12        Constructor c1 = clazz.getDeclaredConstructor(null);//获取无参构造方法
13        Constructor c2 = clazz.getDeclaredConstructor(int.class,String.class,String.class);//获取带参构造方法
14        User u1 = c1.newInstance();
15        User u2 = c2.newInstance(1000,"jzman-blog","111111");
16        System.out.println("u1:"+u1);
17        System.out.println("u2:"+u2);
18
19        //反射操作类的属性
20        System.out.println("----------反射操作类的属性----------");
21        User u3 = c1.newInstance();
22        Field field = clazz.getDeclaredField("name");
23        field.setAccessible(true);//设置该属性不需要安全检查,可直接放访问
24        field.set(u3, "jzman");//反射设置User对象的name属性值
25        System.out.println("u3:"+u3);
26        System.out.println("获取User对象u3的name属性值:"+field.get(u3));
27
28        //反射操作类的方法
29        System.out.println("----------反射操作类的方法----------");
30        User u4 = c1.newInstance();
31        Method method = clazz.getDeclaredMethod("setPassword", String.class);
32        method.invoke(u4, "222222");//设置User对象u4的password属性,等同于u4.setPassword("222222);
33        System.out.println("u4:"+u4);
34
35    } catch (Exception e) {
36        e.printStackTrace();
37    }
38}
上述代码的执行结果如下:
1----------反射操作类的构造方法----------
2u1:User [id=0, name=null, password=null]
3u2:User [id=1000, name=jzman-blog, password=111111]
4----------反射操作类的属性----------
5u3:User [id=0, name=jzman, password=null]
6获取User对象u3的name属性值:jzman
7----------反射操作类的方法----------
8u4:User [id=0, name=null, password=222222]
实际开发中肯定会遇到某个组件中需要某个属性,但是该属性又是私有的,这时候就可以使用 Java 的反射机制了。

反射操作泛型

Java 采用泛型擦除的机制,Java 中的泛型仅仅是给编译器 javac 使用的,这样可确保数据的安全性的免去强制类型转换的麻烦,当编译完成之后,所有和泛型相关的类型将会被全部擦除,为了能够使用反射操作这些类型,新增了四种类型 GenericArrayType、ParameterizedType、TypeVariable 和 WildcardType 来表示不能被归一到 Class 类中的类型但又是和原始类型齐名的类型,也就是说正常的能够获取对应的 Class 对象的 Type 还是 Class 对象,如基本数据类型。反射操作泛型就要涉及到 Java 中的 Type, 在 Java 中 Type 表示所有类型的公共接口,这些类型包括原始类型、参数化类型、数组类型、类型变量和基本类型,其声明如下:
1/**
2 * Type 是 Java 语言中所有类型的公共接口
3 * 这些类型包括原始类型、参数化类型、数组类型、类型变量和基本类型
4 */

5public interface Type {
6    default String getTypeName() {
7        return toString();
8    }
9}
Type 有四个直接子接口,具体含义如下:
1GenericArrayType:表示泛型数组类型,如 ArraryList[] listArrary
2ParameterizedType:表示参数化类型(泛型),如 ArrayList list
3TypeVariable:表示类型变量,如T
4WildcardType:表示一个通配符类型,如 ?、? extends Number 或 ? super Integer
下面以 GenericArrayType 和 ParameterizedType  为例来说明如何使用反射来操作泛型类型,具体如下:
 1//用来测试获取泛型的属性
2private String[] array;
3private List[] listArray;
4//用来测试获取泛型的方法
5private String testGenericType(Map map, String[] array, int name,User user) {
6    System.out.println("testGenericType");
7    return null;
8}
9
10//通过反射获取泛型
11private static void refectGenericType() {
12    try {       
13        System.out.println("---------GenericArrayType---------");
14
15        //获取泛型数组(GenericArrayType)
16        Field field = ReflectTest02.class.getDeclaredField("listArray");//获取属性listArray
17        GenericArrayType type = (GenericArrayType) field.getGenericType();
18        System.out.println("获取泛型数组:"+type);
19
20        System.out.println("---------ParameterizedType---------");
21
22        //获取参数化类型(泛型)(ParameterizedType)
23        Method method = ReflectTest02.class.getDeclaredMethod("testGenericType", Map.class,String[].class,int.class,User.class);
24        Type[] types = method.getGenericParameterTypes();//获得方法参数类型
25        for(Type type1: types) {
26            System.out.println("方法参数类型:"+type1);
27            if(type1 instanceof ParameterizedType) {
28                System.out.println("ParameterizedType:"+type1);
29            }
30        }
31
32    } catch (Exception e) {
33        e.printStackTrace();
34    }
35}
上述代码中方法 refectGenericType 的执行结果如下:
1---------GenericArrayType---------
2获取泛型数组:java.util.List[]
3---------ParameterizedType---------
4方法参数类型:java.util.Map
5ParameterizedType:java.util.Map
6方法参数类型:class [Ljava.lang.String;
7方法参数类型:int
8方法参数类型:class com.manu.reflection.bean.User
9

可参照代码查看对应的输出结果。

反射获取注解信息

通过反射还可以获取注解信息,记如果对注解比较陌生可以参考之前分享的一篇文章 Java 系列之注解,这里简单模仿数据库表字段与 Java 对象的属性是如何通过注解信息一一对应的,为什么我们使用一些数据库框架的时候,通过一些表注解、字段注解就能够创建表,这里就涉及到使用反射来获取注解信息来生成 SQL,进而生成对应的表。创建两个注解分别作为表注解和字段注解,参考如下:
 1//表注解
2@Target(ElementType.TYPE)
3@Retention(RetentionPolicy.RUNTIME)
4public @interface TableAnnotation {
5    String value();
6}
7
8//字段注解
9@Target(ElementType.FIELD)
10@Retention(RetentionPolicy.RUNTIME)
11public @interface FieldAnnotation {
12    String column();
13    int length();
14    String type();
15}
然后,创建一个类并使用该注解,参考如下:
 1/**
2 * 反射读取注解信息测试Bean
3 * @author jzman
4 */

5
6@TableAnnotation(value = "student_table")
7public class Student {
8    @FieldAnnotation(column = "uid", length = 20, type = "int")
9    private int sId;
10    @FieldAnnotation(column = "name", length = 10, type = "varchar")
11    private String sName;
12    @FieldAnnotation(column = "uiaged", length = 3, type = "varchar")
13    private int sAge;
14
15    //setter、getter方法
16    //...
17}
最后,获取注解信息,参考如下:
 1/**
2 * 反射获取注解信息
3 * @author jzman
4 */

5public class ReflectTest03 {
6    public static void main(String[] args) {
7        try {
8            Class clazz = Class.forName("com.manu.reflection.bean.Student");
9
10            //反射获取类的注解信息
11            TableAnnotation tableAnnotation = (TableAnnotation) clazz.getAnnotation(TableAnnotation.class);
12            System.out.println("反射获取类的注解信息:"+tableAnnotation);
13
14            //反射获取属性的注解信息
15            Field field = clazz.getDeclaredField("sName");
16            FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
17            System.out.println("反射获取属性的注解信息:"+fieldAnnotation);
18
19            //获取其他注解信息使用方式类似
20            //...
21        } catch (Exception e) {
22            e.printStackTrace();
23        }
24    }
25}
上述代码的执行结果如下:
1反射获取类的注解信息:@com.manu.reflection.TableAnnotation(value=student_table)
2反射获取属性的注解信息:@com.manu.reflection.FieldAnnotation(column=name, length=10, type=varchar)
显然,通过反射获取到了相应的注解信息,这些注解信息标注了该类对应数据库表的一些关键信息,然后就可以生成对应的 SQL 语句,这样就不难理解数据库表字段与Java对象属性的映射关系了。使用反射获取私有属性或私有方法是必须设置 setAccessible 为 true 才能跳过 Java 安全检查,从而获取私有的属性、方法等,同时设置 setAccessible 为 true 在一定程度上可以提高反射的运行速度。推荐阅读:
浏览 22
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报