前言
有了之前的注解内容,要搭配 Java 反射就能给注解注入灵魂了。
Java Reflection——反射
基础概念
- Reflecttion(反射)是 Java 被视为动态语言的关键,反射机制允许程序在执行期借助于 Reflection API 取地任何类的内部信息,并且直接操作任意对象的内部属性及方法。
- 加载完类之后,在堆内存的方法中就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看类的结构。这个对象就像是一面镜子,透过这个镜子看到类的结构,所以 Reflection 我们形象的成为反射。
正常方式:引入需要的"包类"名称
-> 通过 new 实例化
-> 取得实例化对象
反射方式:实例化对象
-> getClass()方法
-> 得到完整的"包类"名称
反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
- 等等
优点和缺点
优点
- 可以实现动态创建对象和编译,体现出很大的灵活性
缺点
- 对性能有影响。使用反射基本上是一种解释操作,我们可以告诉 JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同操作。
反射相关的主要 API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
Class 类
在 Object 类中定义了 getClass()方法,此方法被所有子类继承
1 | public final Class getClass() |
getClass 方法的返回值是一个 Class 类,此类是 Java 反射的源头,实际上所谓的反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。
对象照镜子可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
- Class 本身也是一个类
- Class 对象只能由系统建立对象
- 一个加载的类在 JVM 中只会有一个 Class 实例
- 一个 Class 对象对应的是一个加载到 JVM 中的一个.class 文件
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过 Class 可以完整地得到一个类中的所有被加载的结构
- Class 类是 Reflection 的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class 对象
看个实际例子:
1 | package com.yubulang.pojo.demo01; |
2 | |
3 | // 什么叫反射 |
4 | public class Demo01 { |
5 | public static void main(String[] args) throws ClassNotFoundException { |
6 | // 通过反射获取类的Class对象 |
7 | Class c1 = Class.forName("com.yubulang.pojo.demo01.User"); |
8 | Class c2 = Class.forName("com.yubulang.pojo.demo01.User"); |
9 | Class c3 = Class.forName("com.yubulang.pojo.demo01.User"); |
10 | Class c4 = Class.forName("com.yubulang.pojo.demo01.User"); |
11 | |
12 | // 一个类在内存中只有一个Class对象,所以hashCode都相同 |
13 | // 一个类被加载后,类整个结构都会被封装在Class对象中。 |
14 | System.out.println(c1.hashCode()); |
15 | System.out.println(c2.hashCode()); |
16 | System.out.println(c3.hashCode()); |
17 | System.out.println(c4.hashCode()); |
18 | } |
19 | } |
20 | |
21 | // 实体类: |
22 | class User { |
23 | private String name; |
24 | private int id; |
25 | private int age; |
26 | |
27 | public User() { |
28 | } |
29 | |
30 | public User(String name, int id, int age) { |
31 | this.name = name; |
32 | this.id = id; |
33 | this.age = age; |
34 | } |
35 | |
36 | public String getName() { |
37 | return name; |
38 | } |
39 | |
40 | public void setName(String name) { |
41 | this.name = name; |
42 | } |
43 | |
44 | public int getId() { |
45 | return id; |
46 | } |
47 | |
48 | public void setId(int id) { |
49 | this.id = id; |
50 | } |
51 | |
52 | public int getAge() { |
53 | return age; |
54 | } |
55 | |
56 | public void setAge(int age) { |
57 | this.age = age; |
58 | } |
59 | |
60 | |
61 | public String toString() { |
62 | return "User{" + |
63 | "name='" + name + '\'' + |
64 | ", id=" + id + |
65 | ", age=" + age + |
66 | '}'; |
67 | } |
68 | } |
常用方法
方法名 | 功能说明 |
---|---|
static Class forName(String name) | 返回指定类名 name 的 Class 对象 |
Object newInstance() | 调用默认构造函数,返回 Class 对象的一个实例 |
String getName() | 返回此 Class 对象所表示的实体(类、接口、数组类或 void)的名称。 |
Class getSuperClass() | 返回当前 Class 对象的父类 Class 对象 |
Class[] getInterfaces() | 获取当前 Class 对象的接口 |
ClassLoader getClassLoader() | 返回该类的加载器 |
Constructor[] getConstructors() | 返回一个包含某些 Constructor 对象的数组 |
Method getMethod(String name, Class..T) | 返回 Mthod 对象,此对象的形参型为 paramType |
Field[] getDeclaredFields() | 返回 Field 对象的一个数组 |
获取 Class 类的实例
- 若已知具体的类,通过类的 class 属性获取,该方法最为安全可靠,程序性能最高
1
Class person = Person.class;
- 已知某个类的实例,调用该实例的 getClass()方法获取 Class 对象
1
class Person {}
2
Person person = new Person();
3
Class person = person.getClass();
- 已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法 forName()获取,可能抛出
ClassNotFoundException
综合实例代码:1
Class person = Class.forName("demo01.Person");
1 | package com.yubulang.pojo.demo02; |
2 | |
3 | // 测试class类的创建方式有哪些 |
4 | public class Demo02 { |
5 | public static void main(String[] args) throws ClassNotFoundException { |
6 | Person person = new Student(); |
7 | System.out.println("这个人是:" + person.getName()); |
8 | |
9 | // 方式一:通过对象获得 |
10 | Class<? extends Person> c1 = person.getClass(); |
11 | System.out.println(c1.hashCode()); |
12 | |
13 | // 方式二:通过forName获得 |
14 | Class<?> c2 = Class.forName("com.yubulang.pojo.demo02.Student"); |
15 | System.out.println(c2.hashCode()); |
16 | |
17 | // 方式三:通过类名.class |
18 | Class<Student> c3 = Student.class; |
19 | System.out.println(c3.hashCode()); |
20 | |
21 | // 方式四:基本内置类型的包装类都有一个Type属性 |
22 | Class<Integer> c4 = Integer.TYPE; |
23 | System.out.println(c4); |
24 | |
25 | // 获得父类类型 |
26 | Class<?> c5 = c1.getSuperclass(); |
27 | System.out.println(c5); |
28 | } |
29 | } |
30 | |
31 | class Person { |
32 | private String name; |
33 | |
34 | public Person() { |
35 | } |
36 | |
37 | public Person(String name) { |
38 | this.name = name; |
39 | } |
40 | |
41 | public String getName() { |
42 | return name; |
43 | } |
44 | |
45 | public void setName(String name) { |
46 | this.name = name; |
47 | } |
48 | |
49 | |
50 | public String toString() { |
51 | return "Person{" + |
52 | "name='" + name + '\'' + |
53 | '}'; |
54 | } |
55 | } |
56 | |
57 | class Student extends Person { |
58 | public Student() { |
59 | this.setName("学生"); |
60 | } |
61 | } |
62 | |
63 | class Teacher extends Person { |
64 | public Teacher() { |
65 | this.setName("老师"); |
66 | } |
67 | } |
哪些类型可以有 Class 对象
- class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- interface:接口
- []:数组
- enum:枚举
- annotation:注解@interface
- primitive type:基本数据类型
- void
上代码验证下:
1 | package com.yubulang.pojo.demo03; |
2 | |
3 | import java.lang.annotation.ElementType; |
4 | |
5 | public class Demo { |
6 | public static void main(String[] args) { |
7 | // 所有类型的Class |
8 | |
9 | Class<Object> c1 = Object.class; // 类 |
10 | Class<Comparable> c2 = Comparable.class; // 接口 |
11 | Class<String[]> c3 = String[].class; // 一维数组 |
12 | Class<int[][]> c4 = int[][].class; // 二位数组 |
13 | Class<Override> c5 = Override.class; // 注解 |
14 | Class<ElementType> c6 = ElementType.class; // 枚举 |
15 | Class<Integer> c7 = Integer.class; // 基本类型 |
16 | Class<Void> c8 = void.class; // void |
17 | Class<Class> c9 = Class.class; // Class |
18 | |
19 | System.out.println(c1); |
20 | System.out.println(c2); |
21 | System.out.println(c3); |
22 | System.out.println(c4); |
23 | System.out.println(c5); |
24 | System.out.println(c6); |
25 | System.out.println(c7); |
26 | System.out.println(c8); |
27 | System.out.println(c9); |
28 | |
29 | // 只要元素类型与维度一样,就是同一个Class |
30 | int[] a = new int[10]; |
31 | int[] b = new int[100]; |
32 | System.out.println(a.getClass().hashCode()); |
33 | System.out.println(b.getClass().hashCode()); |
34 | } |
35 | } |
Java 内存分析
- 栈内存
- 存放基本变量类型(会包含这个基本类型的具体数值)
- 引用对象的变量(会存放这个引用在堆里面的具体地址)
- 堆内存
- 存放 new 的对象和数组
- 可以被所有的线程共享,不会存放别的对象引用
- 方法区
- 可以被所有的线程共享
- 包含了所有 class 和 static 变量
了解类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对类进行初始化
- 类加载(Load)
- 将类的 class 文件读入内存,并为之创建一个
java.lang.Class
对象。此过程由类加载器完成
- 将类的 class 文件读入内存,并为之创建一个
- 类的连接(Link)
- 将类的二进制数据合并到 JRE 中
- 类的初始化(Initialize)
- JVM 负责对类进行初始化
类的加载与 ClassLoader 的理解
- 加载:将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的
java.lang.Class
对象。 - 链接:将 Java 类的二进制代码合并到 JVM 的运行状态之中的过程。
- 验证:确保加载的类信息符合 JVM 规范,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 初始化:
- 执行类构造器<cIinit>()方法的过程,类构造器<cIinit>()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的<cIinit>()方法在多线程环境中被正确加锁和同步。
代码演示如下
1 | package com.yubulang.pojo.demo04; |
2 | |
3 | public class Demo { |
4 | public static void main(String[] args) { |
5 | /* |
6 | 1. 加载到内存,会产生一个类对应Class对象 |
7 | 2. 链接,链接结束后 m = 0 |
8 | 3. 初始化 |
9 | 方法区会有类的白能量初始化信息,而赋值的动作会在下面完成 |
10 | <cInit>() { |
11 | // 编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生 |
12 | System.out.println("A类静态代码块初始化"); |
13 | m = 300; |
14 | m = 100; |
15 | } |
16 | */ |
17 | A a = new A(); |
18 | System.out.println(A.m); |
19 | } |
20 | } |
21 | |
22 | class A { |
23 | static { |
24 | System.out.println("A类静态代码块初始化"); |
25 | m = 300; |
26 | } |
27 | |
28 | static int m = 100; |
29 | |
30 | public A() { |
31 | System.out.println("A类的无参构造器初始化"); |
32 | } |
33 | } |
什么时候会发生类初始化?
类的主动引用(一定会发生类的初始化)
- 当虚拟机启动,先初始化 main 方法所在类
- new 一个类的对象
- 调用类的静态成员(除了 final 常量)和静态方法
- 使用 java.lang。reflect 包的方法和对类进行反射调用
- 初始化一个类,如果其父类没有被初始化,则先初始化它的父类
类的被动引用(不发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
上面话对应的例子:
1 | package com.yubulang.pojo.demo05; |
2 | |
3 | public class Demo { |
4 | static { |
5 | System.out.println("Main所在类被加载"); |
6 | } |
7 | |
8 | public static void main(String[] args) throws ClassNotFoundException { |
9 | // 类的主动引用(一定会发生类的初始化) |
10 | // 1. 主动引用,初始化一个类,如果其父类没有被初始化,则先会初始化其父类 |
11 | /* |
12 | 输出: |
13 | Main所在类被加载 |
14 | 父类被加载 |
15 | 子类被加载 |
16 | */ |
17 | // Son son = new Son(); |
18 | |
19 | // 2. 反射也会产生主动给引用 |
20 | /* |
21 | 输出: |
22 | Main所在类被加载 |
23 | 父类被加载 |
24 | 子类被加载 |
25 | */ |
26 | // Class.forName("com.yubulang.pojo.demo05.Son"); |
27 | |
28 | // 3. 调用类的静态成员(除了final常量)和静态方法 |
29 | /* |
30 | 输出: |
31 | Main所在类被加载 |
32 | 父类被加载 |
33 | 子类被加载 |
34 | 100 |
35 | */ |
36 | // System.out.println(Son.m); |
37 | |
38 | /* |
39 | 输出: |
40 | Main所在类被加载 |
41 | 父类被加载 |
42 | 子类被加载 |
43 | Son出来秀了 |
44 | */ |
45 | // Son.show(); |
46 | |
47 | |
48 | // ==================================================== |
49 | |
50 | // 类的被动引用(不会发生类的初始化) |
51 | // 1. 当通过子类引用父类的静态变量,不会导致子类初始化 |
52 | /* |
53 | 输出: |
54 | Main所在类被加载 |
55 | 父类被加载 |
56 | 2 |
57 | */ |
58 | // System.out.println(Son.b); |
59 | |
60 | // 2. 通过数组定义类引用,不会触发此类的初始化 |
61 | // 只有main类被加载 |
62 | // Son[] array = new Son[5]; |
63 | |
64 | // 3. 引用常量不会触发此类的初始化 |
65 | // 只有main类被加载 |
66 | // System.out.println(Son.M); |
67 | } |
68 | } |
69 | |
70 | class Father { |
71 | static int b = 2; |
72 | |
73 | static { |
74 | System.out.println("父类被加载"); |
75 | } |
76 | } |
77 | |
78 | class Son extends Father { |
79 | static { |
80 | System.out.println("子类被加载"); |
81 | m = 300; |
82 | } |
83 | |
84 | static int m = 100; |
85 | static final int M = 100; |
86 | |
87 | public static void show() { |
88 | System.out.println("Son出来秀了"); |
89 | } |
90 | } |
类加载器的作用
- 类加载的作用:将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的
java.lang.Class
对象,作为方法区中类数据的访问入口。 - 类缓存:标准的 JavaSE 类加载器可以按照要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间,不过 JVM 垃圾回收机制可以回收这些 Class 对象。
类加载器的作用
类加载器作用是用来把类(class)装载进内存。JVM 规范定义了如下类型的类的加载器。
- 引导类加载器:用 C++编写,是 JVM 自带的类加载器,负责 Java 平台核心库,用来装载核心类库。该装载类无法直接获取
- 扩展加载器:负责 jre/lib/ext 目录下 jar 包或 -D java.ext.dirs 指定目录下的 jar 包装入工作库
- 系统类加载器:负责 java –classpath 或 -D java.class.path 所指的目录下的类与 jar 包装入工作,是常用的加载器。
我们来获取下类加载器:
1 | package com.yubulang.pojo.demo06; |
2 | |
3 | public class Demo { |
4 | public static void main(String[] args) throws ClassNotFoundException { |
5 | // 获取系统类的加载器 |
6 | ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); |
7 | System.out.println(systemClassLoader); |
8 | |
9 | // 获取系统类加载器的父类加载器 -> 扩展加载器 |
10 | ClassLoader parent = systemClassLoader.getParent(); |
11 | System.out.println(parent); |
12 | |
13 | // 获取扩展类加载器的父类加载器 -> 根加载器(C++) |
14 | ClassLoader root = parent.getParent(); |
15 | System.out.println(root); |
16 | |
17 | // 测试当前类是哪个加载器加载的 |
18 | ClassLoader classLoader = Class.forName("com.yubulang.pojo.demo06.Demo").getClassLoader(); |
19 | System.out.println(classLoader); |
20 | |
21 | // 测试JDK的类,结果是根加载器(C++)那层的 |
22 | classLoader = Class.forName("java.lang.Object").getClassLoader(); |
23 | System.out.println(classLoader); |
24 | |
25 | // 如何获取系统类加载器可以加载的路径 |
26 | String classPath = System.getProperty("java.class.path"); |
27 | System.out.println(classPath); |
28 | |
29 | // 双清委派机制 |
30 | // 假设你自定义了一个 java.lang.String包, |
31 | // 加载完成后Java会检测包的正确性, |
32 | // 会用自身root加载的java.lang.String包, |
33 | // 覆盖你写的包,以保证包的正确性 |
34 | } |
35 | } |
创建运行类的对象
###获取类运行时类的完整结构
通过反射获取运行时类的完整结构,我们可以获取类的 Field、Method、Constructor、Superclass、Interface、Annotation。
- 实现的全部接口
- 所继承的父类
- 全部的构造器
- 全部的方法
- 全部的字段
- 注解
上例子
1 | package com.yubulang.pojo.demo07; |
2 | |
3 | |
4 | import java.lang.reflect.Constructor; |
5 | import java.lang.reflect.Field; |
6 | import java.lang.reflect.Method; |
7 | |
8 | public class Demo { |
9 | public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { |
10 | Class<?> c1 = Class.forName("com.yubulang.pojo.demo07.User"); |
11 | |
12 | // 获得类的名字 |
13 | System.out.println("=== 类的名字 ==="); |
14 | System.out.println(c1.getName()); // 获取 包和类的名字完整信息 |
15 | // 获取类的简单名字 |
16 | System.out.println(c1.getSimpleName()); // 获取 类的名字信息 |
17 | |
18 | // 获取类的属性 |
19 | System.out.println("=== 类的 public 属性 ===="); |
20 | Field[] fields = c1.getFields(); // 获取类的 public 属性 |
21 | for (Field field : fields) { |
22 | System.out.println(field); |
23 | } |
24 | |
25 | System.out.println("=== 类的所有属性 ===="); |
26 | Field[] declaredFields = c1.getDeclaredFields(); // 获取类的所有属性 |
27 | for (Field declaredField : declaredFields) { |
28 | System.out.println(declaredField); |
29 | } |
30 | |
31 | // 获得指定属性的值 |
32 | System.out.println("=== 获得指定属性的值 ==="); |
33 | Field name = c1.getDeclaredField("name"); |
34 | System.out.println(name); |
35 | |
36 | // 获取所有的方法 |
37 | System.out.println("=== 获取所有的方法 ==="); |
38 | Method[] methods = c1.getMethods(); // 获取本类及其父类的全部public方法 |
39 | for (Method method : methods) { |
40 | System.out.println("getMethods:" + method); |
41 | } |
42 | |
43 | Method[] declaredMethods = c1.getDeclaredMethods(); // 获取本类的全部方法(包含私有方法) |
44 | for (Method declaredMethod : declaredMethods) { |
45 | System.out.println("getDeclaredMethods:" + declaredMethod); |
46 | } |
47 | |
48 | // 获得指定方法 |
49 | System.out.println("=== 获得指定方法 ==="); |
50 | Method getName = c1.getMethod("getName", null); |
51 | System.out.println(getName); |
52 | Method setName = c1.getMethod("setName", String.class); |
53 | System.out.println(setName); |
54 | |
55 | // 获取所有构造器 |
56 | System.out.println("=== 获取所有public构造器 ==="); |
57 | Constructor<?>[] constructors = c1.getConstructors(); |
58 | for (Constructor<?> constructor : constructors) { |
59 | System.out.println(constructor); |
60 | } |
61 | |
62 | System.out.println("=== 获取所有构造器 ==="); |
63 | Constructor<?>[] declaredConstructors = c1.getDeclaredConstructors(); |
64 | for (Constructor<?> constructor : declaredConstructors) { |
65 | System.out.println(constructor); |
66 | } |
67 | |
68 | // 获取指定构造器 |
69 | System.out.println("=== 获取指定构造器 ==="); |
70 | Constructor<?> constructor = c1.getConstructor(null); |
71 | System.out.println(constructor); |
72 | constructor = c1.getConstructor(String.class, int.class, int.class); |
73 | System.out.println(constructor); |
74 | } |
75 | } |
76 | |
77 | class User { |
78 | private String name; |
79 | private int id; |
80 | private int age; |
81 | |
82 | public User() { |
83 | } |
84 | |
85 | public User(String name, int id, int age) { |
86 | this.name = name; |
87 | this.id = id; |
88 | this.age = age; |
89 | } |
90 | |
91 | public String getName() { |
92 | return name; |
93 | } |
94 | |
95 | public void setName(String name) { |
96 | this.name = name; |
97 | } |
98 | |
99 | public int getId() { |
100 | return id; |
101 | } |
102 | |
103 | public void setId(int id) { |
104 | this.id = id; |
105 | } |
106 | |
107 | public int getAge() { |
108 | return age; |
109 | } |
110 | |
111 | public void setAge(int age) { |
112 | this.age = age; |
113 | } |
114 | |
115 | |
116 | public String toString() { |
117 | return "User{" + |
118 | "name='" + name + '\'' + |
119 | ", id=" + id + |
120 | ", age=" + age + |
121 | '}'; |
122 | } |
123 | } |
小结
- 在实际的操作中,取得类的信息的操作代码,并不会经常开发
- 一定要熟悉 java.lang.reflect 包的作用,反射机制。
- 如何获取属性、方法、构造器的名称,修饰符等。
有了 Class 对象,能做什么?
创建类的对象:调用 Class 对象的 newInstance()方法
- 类必须由一个无参数的构造器
- 类的构造器的访问权限需要足够
没有无参构造器时,需要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。
步骤:
- 通过 Class 类 getDeclaredConstrutor(Class … parameterTypes)取得本类的指定形参类型构造器
- 想构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数
- 通过 Constructor 实例化对象
调用指定的方法
通过反射,调用类中的方法,通过 Method 类完成。
- 通过 Class 类的 getMethod(String name, Class…parameterTypes)方法取得一个 Method 对象,并设置此方法操作时所需要的参数类型。
- 之后使用 Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的 obj 对象的参数信息
1 | Object invoke(Object obj, Object... args) |
- Object 对应原方法的返回值,若原方法无返回值,此时返回 null
- 若原方法若为静态方法,此时形参 Object obj 可为 null
- 若原方法形参列表为空,则 Object[] args 为 null
- 若原方法声明为 private,则需要在调用此 invoke()方法前,显式调用方法对象的 setAccessible(true)方法,将可以访问 private 方法。
show the code:
1 | package com.yubulang.pojo.demo08; |
2 | |
3 | import java.lang.reflect.Constructor; |
4 | import java.lang.reflect.Field; |
5 | import java.lang.reflect.InvocationTargetException; |
6 | import java.lang.reflect.Method; |
7 | |
8 | public class Demo { |
9 | public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException { |
10 | // 获取一个Class对象 |
11 | Class<?> c1 = Class.forName("com.yubulang.pojo.demo08.User"); |
12 | |
13 | // 构建一个对象 |
14 | User user = (User) c1.newInstance(); |
15 | |
16 | // 通过构造器创建对象(有参的) |
17 | Constructor<?> declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class); |
18 | User user2 = (User) declaredConstructor.newInstance("鱼不浪", 1, 18); |
19 | System.out.println(user2); |
20 | |
21 | // 通过反射调用方法 |
22 | // invoke(对象, 对象方法的参数) |
23 | User user3 = (User) c1.newInstance(); |
24 | Method setName = c1.getMethod("setName", String.class); |
25 | setName.invoke(user3, "鱼不浪"); |
26 | |
27 | Method getName = c1.getMethod("getName", null); |
28 | System.out.println(getName.invoke(user3, null)); |
29 | |
30 | // 通过反射操作属性 |
31 | User user4 = (User) c1.newInstance(); |
32 | Field name = c1.getDeclaredField("name"); |
33 | // 不能直接操作私有属性,我们要关闭程序安全检测,属性或者方法的setAccessible |
34 | name.setAccessible(true); // 因为是私有属性,所以要把访问类型改成可以访问 |
35 | name.set(user4, "鱼不浪"); |
36 | System.out.println(name.get(user4)); |
37 | } |
38 | } |
39 | |
40 | class User { |
41 | private String name; |
42 | private int id; |
43 | private int age; |
44 | |
45 | public User() { |
46 | } |
47 | |
48 | public User(String name, int id, int age) { |
49 | this.name = name; |
50 | this.id = id; |
51 | this.age = age; |
52 | } |
53 | |
54 | public String getName() { |
55 | return name; |
56 | } |
57 | |
58 | public void setName(String name) { |
59 | this.name = name; |
60 | } |
61 | |
62 | public int getId() { |
63 | return id; |
64 | } |
65 | |
66 | public void setId(int id) { |
67 | this.id = id; |
68 | } |
69 | |
70 | public int getAge() { |
71 | return age; |
72 | } |
73 | |
74 | public void setAge(int age) { |
75 | this.age = age; |
76 | } |
77 | |
78 | |
79 | public String toString() { |
80 | return "User{" + |
81 | "name='" + name + '\'' + |
82 | ", id=" + id + |
83 | ", age=" + age + |
84 | '}'; |
85 | } |
86 | } |
setAccessible
- Method 和 Field、Construtor 对象都有 setAccessible() 方法
- setAccessible 作用时启动和禁用访问安全检查的开关
- 参数值为 true 则表示反射的对象在使用时应该取消 Java 语法访问检查
- 提高反射的效率。如果代码中必须用反射,该句代码需要频繁的被调用,那么请设置为 true
- 使得原本无法访问的私有成员也可以访问
- 参数值为 false 则表示反射的对象应该实施 Java 语言访问检查
测试代码上:
1 | package com.yubulang.pojo.demo09; |
2 | |
3 | import java.lang.reflect.InvocationTargetException; |
4 | import java.lang.reflect.Method; |
5 | |
6 | public class Demo { |
7 | // 普通方式调用 |
8 | public static void test01() { |
9 | User user = new User(); |
10 | long startTime = System.currentTimeMillis(); |
11 | for (int i = 0; i < 1000000000; i++) { |
12 | user.getName(); |
13 | } |
14 | long endTime = System.currentTimeMillis(); |
15 | |
16 | System.out.println("普通方式调用10亿次耗时:" + (endTime - startTime) + "ms"); |
17 | } |
18 | |
19 | // 反射方式调用 |
20 | public static void test02() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { |
21 | Class<?> c1 = Class.forName("com.yubulang.pojo.demo09.User"); |
22 | User user = (User) c1.newInstance(); |
23 | Method getName = c1.getMethod("getName"); |
24 | long startTime = System.currentTimeMillis(); |
25 | for (int i = 0; i < 1000000000; i++) { |
26 | getName.invoke(user, null); |
27 | } |
28 | long endTime = System.currentTimeMillis(); |
29 | |
30 | System.out.println("反射方式没关闭检测调用10亿次耗时:" + (endTime - startTime) + "ms"); |
31 | } |
32 | |
33 | // 反射方式调用 关闭检测 |
34 | public static void test03() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { |
35 | Class<?> c1 = Class.forName("com.yubulang.pojo.demo09.User"); |
36 | User user = (User) c1.newInstance(); |
37 | Method getName = c1.getMethod("getName"); |
38 | getName.setAccessible(true); |
39 | long startTime = System.currentTimeMillis(); |
40 | for (int i = 0; i < 1000000000; i++) { |
41 | getName.invoke(user, null); |
42 | } |
43 | long endTime = System.currentTimeMillis(); |
44 | |
45 | System.out.println("反射方式关闭检测调用10亿次耗时:" + (endTime - startTime) + "ms"); |
46 | } |
47 | |
48 | public static void main(String[] args) { |
49 | test01(); |
50 | |
51 | try { |
52 | test02(); |
53 | test03(); |
54 | } catch (Exception ignored) { |
55 | System.out.println(ignored.getMessage()); |
56 | } |
57 | } |
58 | } |
59 | |
60 | class User { |
61 | private String name; |
62 | private int id; |
63 | private int age; |
64 | |
65 | public User() { |
66 | } |
67 | |
68 | public User(String name, int id, int age) { |
69 | this.name = name; |
70 | this.id = id; |
71 | this.age = age; |
72 | } |
73 | |
74 | public String getName() { |
75 | return name; |
76 | } |
77 | |
78 | public void setName(String name) { |
79 | this.name = name; |
80 | } |
81 | |
82 | public int getId() { |
83 | return id; |
84 | } |
85 | |
86 | public void setId(int id) { |
87 | this.id = id; |
88 | } |
89 | |
90 | public int getAge() { |
91 | return age; |
92 | } |
93 | |
94 | public void setAge(int age) { |
95 | this.age = age; |
96 | } |
97 | |
98 | |
99 | public String toString() { |
100 | return "User{" + |
101 | "name='" + name + '\'' + |
102 | ", id=" + id + |
103 | ", age=" + age + |
104 | '}'; |
105 | } |
106 | } |
反射操作泛型
- Java 采用泛型擦除的机制来引入泛型,Java 中的泛型仅仅时给编译器 javac 使用的,确保数据的安全性和免去强制类型转换问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除
- 为了通过反射操作这些类型,Java 新增了 ParameterizedType,GenericArrayType,TypeVariable 和 WildcardType 几种类型来代表不能被统一的 Class 类中的类型但是又和原始类型齐名的类型。
- ParameterizedType:表示一种参数化类型,比如 Collection<String>
- GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
- TypeVariable:是各种类型变量的公共父接口
- WildcardType:代表一种通配符类型表达式
上代码:
1 | package com.yubulang.pojo.demo10; |
2 | |
3 | import java.lang.reflect.Method; |
4 | import java.lang.reflect.ParameterizedType; |
5 | import java.lang.reflect.Type; |
6 | import java.util.List; |
7 | import java.util.Map; |
8 | |
9 | public class Demo { |
10 | // 通过反射获取泛型 |
11 | public void test01(Map<String, User> map, List<User> list) { |
12 | System.out.println("test01"); |
13 | } |
14 | |
15 | public Map<String, User> test02() { |
16 | System.out.println("test02"); |
17 | return null; |
18 | } |
19 | |
20 | public static void main(String[] args) throws NoSuchMethodException { |
21 | Method method = Demo.class.getMethod("test01", Map.class, List.class); |
22 | |
23 | // 获取参数的泛型类型 |
24 | Type[] genericParameterTypes = method.getGenericParameterTypes(); |
25 | for (Type genericParameterType : genericParameterTypes) { |
26 | System.out.println(genericParameterType); |
27 | |
28 | // 检测参数是不是结构化参数类型 |
29 | if (genericParameterType instanceof ParameterizedType) { |
30 | |
31 | // 获取泛型类型中的真实类型 |
32 | Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments(); |
33 | for (Type actualTypeArgument : actualTypeArguments) { |
34 | System.out.println(actualTypeArgument); |
35 | } |
36 | } |
37 | } |
38 | |
39 | System.out.println("=================================="); |
40 | |
41 | Method method02 = Demo.class.getMethod("test02", null); |
42 | // 获取函数的返回值泛型类型 |
43 | Type genericReturnType = method02.getGenericReturnType(); |
44 | // 检测参数是不是结构化参数类型 |
45 | if (genericReturnType instanceof ParameterizedType) { |
46 | System.out.println(genericReturnType); |
47 | |
48 | // 获取泛型类型中的真实类型 |
49 | Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments(); |
50 | for (Type actualTypeArgument : actualTypeArguments) { |
51 | System.out.println(actualTypeArgument); |
52 | } |
53 | } |
54 | } |
55 | } |
56 | |
57 | class User { |
58 | private String name; |
59 | private int id; |
60 | private int age; |
61 | |
62 | public User() { |
63 | } |
64 | |
65 | public User(String name, int id, int age) { |
66 | this.name = name; |
67 | this.id = id; |
68 | this.age = age; |
69 | } |
70 | |
71 | public String getName() { |
72 | return name; |
73 | } |
74 | |
75 | public void setName(String name) { |
76 | this.name = name; |
77 | } |
78 | |
79 | public int getId() { |
80 | return id; |
81 | } |
82 | |
83 | public void setId(int id) { |
84 | this.id = id; |
85 | } |
86 | |
87 | public int getAge() { |
88 | return age; |
89 | } |
90 | |
91 | public void setAge(int age) { |
92 | this.age = age; |
93 | } |
94 | |
95 | |
96 | public String toString() { |
97 | return "User{" + |
98 | "name='" + name + '\'' + |
99 | ", id=" + id + |
100 | ", age=" + age + |
101 | '}'; |
102 | } |
103 | } |
反射操作注解
- getAnnotations
- getAnnotation
练习注解:
1 | package com.yubulang.pojo.demo11; |
2 | |
3 | import java.lang.annotation.*; |
4 | import java.lang.reflect.Field; |
5 | |
6 | // 练习反射操作注解 |
7 | public class Demo { |
8 | public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { |
9 | Class<?> c1 = Class.forName("com.yubulang.pojo.demo11.Student"); |
10 | |
11 | // 通过反射获得注解 |
12 | Annotation[] annotations = c1.getAnnotations(); |
13 | for (Annotation annotation : annotations) { |
14 | System.out.println(annotation); |
15 | } |
16 | |
17 | // 获得注解的value的值 |
18 | TableMapper tableMapper = (TableMapper) c1.getAnnotation(TableMapper.class); |
19 | System.out.println(tableMapper.value()); |
20 | |
21 | // 获取类指定的注解 |
22 | Field name = c1.getDeclaredField("name"); |
23 | FieldMapper nameFieldAnnotation = name.getAnnotation(FieldMapper.class); |
24 | System.out.println(nameFieldAnnotation.columnName()); |
25 | System.out.println(nameFieldAnnotation.type()); |
26 | System.out.println(nameFieldAnnotation.length()); |
27 | } |
28 | } |
29 | |
30 | // 类名的注解 |
31 | (ElementType.TYPE) |
32 | (RetentionPolicy.RUNTIME) |
33 | TableMapper { |
34 | String value(); |
35 | } |
36 | |
37 | // 属性的注解 |
38 | (ElementType.FIELD) |
39 | (RetentionPolicy.RUNTIME) |
40 | FieldMapper { |
41 | String columnName(); |
42 | |
43 | String type(); |
44 | |
45 | int length(); |
46 | } |
47 | |
48 | "db_student") ( |
49 | class Student { |
50 | "db_id", type = "int", length = 10) (columnName = |
51 | private int id; |
52 | |
53 | "db_age", type = "int", length = 10) (columnName = |
54 | private int age; |
55 | |
56 | "db_name", type = "varchar", length = 64) (columnName = |
57 | private String name; |
58 | |
59 | public Student() { |
60 | } |
61 | |
62 | public Student(int id, int age, String name) { |
63 | this.id = id; |
64 | this.age = age; |
65 | this.name = name; |
66 | } |
67 | |
68 | public int getId() { |
69 | return id; |
70 | } |
71 | |
72 | public void setId(int id) { |
73 | this.id = id; |
74 | } |
75 | |
76 | public int getAge() { |
77 | return age; |
78 | } |
79 | |
80 | public void setAge(int age) { |
81 | this.age = age; |
82 | } |
83 | |
84 | public String getName() { |
85 | return name; |
86 | } |
87 | |
88 | public void setName(String name) { |
89 | this.name = name; |
90 | } |
91 | |
92 | |
93 | public String toString() { |
94 | return "Student{" + |
95 | "id=" + id + |
96 | ", age=" + age + |
97 | ", name='" + name + '\'' + |
98 | '}'; |
99 | } |
100 | } |
总结
反射和注解提供了给我们更多的操作空间,掌握它可以写出很多优雅的代码。Enjoy it!