Java 反射 Reflect

Class 类

面向对象的世界里,万事万物都是对象,类也是对象。在 Java 中,除 静态成员 和 普通数据类型 不是对象,其他皆为对象。
其中定义的各种类,是 java.lang.Class 类的实例对象

类对象的表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Test {
public static void main(String[] args) {
// 创建一个 Foo 类的实例对象 foo1
Foo foo1 = new Foo(); // foo1 是 Foo 类的实例对象
// 万事万物皆是对象, Foo 类本身也是对象,它是 Class 类的实例对象
// 所有的类都是 Class 类的实例对象, 这个实例对象有三种表示方式

// 方式一, 通过类的静态成员变量 class,每个类都包含这个变量
Class c1 = Foo.class;

// 方式二, 通过类的实例对象的 getClass 方法
Class c2 = foo1.getClass();

// 方式三, 通过类的全称(包含包名)获取
Class c3 = Class.forName("com.test.Foo");

// c1,c2,c3 表示了 Foo 类的类类型 (class type), 且一个类只可能是 Class 类的一个类类型,
// 即是 一个类的类类型 是唯一的, 所以 以上三者是相等的
bool b1 = c1 == c2; // true
bool b2 = c2 == c3; // true

// 通过类的类类型,就可以去创建该类的实例对象,
// 需要注意,该类需要定义一个无参构造函数,且生成的实例对象需要强制类型转换
Foo foo2 = (Foo)c3.newInstance()


}
}

class Foo {}

动态加载类与静态加载类

静态加载类: 编译时刻加载的类就是静态加载类,new 类名 出来的就是静态加载类,编译时刻就需要加载可能使用的所有的类。
动态加载类: 运行时刻加载的类就是动态加载类,看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 定义借口,同意标准
interface Animalale {
public void run();
}

// 定义类, 实现统一标准
class Dog implements Animalale {
public void run() {
System.out.println("dog running.");
}
}

public class Test {
public static void main(String[] args) {
try {
// 动态加载类, 运行时刻加载
Class c1 = Class.forName[args[0]]
// 通过类的类类型,去创建实例对象,
Animalale animal = (Animalale) c1.newInstance();
animal.run();
}
catch (Exception e) {
e.printStackTrace()
}
}
}

// 命令行输入
java test Dog
// 输出
dog running.

上面的代码编译后,如有新的动物加入,只需要新写一个类实现 Animal 接口,编译这个新的类即可,原来的代码不需要改动。开发过程中,功能性的类都可以通过这种动态加载方式实现。

数据类型的类类型

1
2
3
4
5
6
7
//  void 关键字 和 基本数据类型 都有类类型
Class c1 = int.class;
Class c2 = String.class;
Class c3 = double.class; // double 数据类型的类类型
Class c4 = Double.class; // Double 类的类类型
Class c5 = void.class;
// 注意此处的 c3 != c4,

Class 类的常用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

/**
* 获取类的成员方法信息
* @param obj 类的实例对象
*/
public static void printClassMethodMessage(Object obj){
// 获取类信息,首先需要获得类类型
Class c = obj.getClass();// 传递的是哪个子类的实例对象,获取的就是该子类的类类型
// 获取类名(包含包名)
System.out.println("类名称:"+c.getName());
// 获取类名(不包含包名)
// System.out.println("类名称:"+c.getSimpleName());
/*
* Method 类,所有成员方法都是 Method 类的实例对象
* getMethods() // 获取所有 public 类型的成员方法,包括父类继承过来的方法
* getDeclaredMethods() // 获取该类所有自己的声明的成员方法,包括public, private等,不论访问权限
*/
Method[] ms = c.getMethods();
for(int i = 0; i < ms.length;i++){
// 获取返回值类型的类类型
Class returnType = ms[i].getReturnType();
System.out.print(returnType.getName()+" ");
// 获取方法名称
System.out.print(ms[i].getName()+"(");
// 获取参数类型的类类型 数组
Class[] paramTypes = ms[i].getParameterTypes();
for (Class class1 : paramTypes) {
System.out.print(class1.getName()+",");
}
System.out.println(")");
}
}

/**
* 获取类的成员变量信息
* @param obj 类的实例对象
*/
public static void printFieldMessage(Object obj) {
Class c = obj.getClass();
/*
* 类的成员变量也是对象, 是 java.lang.reflect.Field的对象
* getFields() // 获取所有 public 类型的成员变量,包括父类继承过来的
* getDeclaredFields() // 获取该类所有自己的声明的成员变量,不论访问权限
*/
//Field[] fs = c.getFields();
Field[] fs = c.getDeclaredFields();
for (Field field : fs) {
// 获取成员变量类型的类类型
Class fieldType = field.getType();
// 获取成员变量类型的名字
String typeName = fieldType.getName();
// 获取成员变量的名字
String fieldName = field.getName();
System.out.println(typeName+" "+fieldName);
}
}

/**
* 获取类的构造函数信息
* @param obj 类的实例对象
*/
public static void printConMessage(Object obj){
Class c = obj.getClass();
/*
* 构造函数也是对象,是java.lang.Constructor的实例对象,它封装的了构造函数的信息
* getConstructors() // 获取所有 public 类型的构造函数
* getDeclaredConstructors() // 获取该类所有自己的声明的构造函数,不论访问权限
*/
//Constructor[] cs = c.getConstructors();
Constructor[] cs = c.getDeclaredConstructors();
for (Constructor constructor : cs) {
// 获取构造函数名字
System.out.print(constructor.getName()+"(");
// 获取构造函数 参数类型的类类型 数组
Class[] paramTypes = constructor.getParameterTypes();
for (Class class1 : paramTypes) {
System.out.print(class1.getName()+",");
}
System.out.println(")");
}
}

方法反射基本操作

通过 Method.invoke(实例对象,[参数列表]) 进行 方法反射操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class MethodDemo1 {
public static void main(String[] args) {
// 获取 print(int,int)方法,先获取类类型
A a1 = new A();
Class c = a1.getClass();
/*
* 获取方法,由方法名和参数列表唯一决定一个方法
* getMethod() // 获取所有 public 类型的成员方法
* getDelcaredMethod() // 获取该类所有自己的声明的成员方法,不论访问权限
*/
try {
// 两种方式获取方法对象,效果一样
//Method m = c.getMethod("print", new Class[]{int.class,int.class});
Method m = c.getMethod("print", int.class,int.class);

// 方法反射操作
// 下面两个参数传递方式,效果一样, o 就是 print 方法的返回值,如没有返回值, o 就是 null
//Object o = m.invoke(a1,new Object[]{10,20});
Object o = m.invoke(a1, 10,20);
上面通过方法反射操作和 通过 类的实例对象 直接调用,效果一样
//a1.print(10, 20);

System.out.println("==================");
// 获取参数为 String 类型的 print(String,String)
//Method m1 = c.getMethod("print", new Class[]{String.class,String.class});
Method m1 = c.getMethod("print",String.class,String.class);
//a1.print("hello", "WORLD");
o = m1.invoke(a1, "hello","WORLD");
System.out.println("===================");
// 获取 无参数的 print(String,String)
// Method m2 = c.getMethod("print", new Class[]{});
Method m2 = c.getMethod("print");
// 两种调用方式,效果一样
// m2.invoke(a1, new Object[]{});
m2.invoke(a1);
} catch (Exception e) {
e.printStackTrace();
}
}
}

class A{
public void print(){
System.out.println("helloworld");
}
public void print(int a,int b){
System.out.println(a+b);
}
public void print(String a,String b){
System.out.println(a.toUpperCase()+","+b.toLowerCase());
}
}

深入理解 Java 泛型

看下面实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static void main(String[] args) {
ArrayList list = new ArrayList();

ArrayList<String> list1 = new ArrayList<String>();
list1.add("hello");
//list1.add(20); // 编译出错, 不能向 String 集合中加入 整型数据
Class c1 = list.getClass();
Class c2 = list1.getClass();
System.out.println(c1 == c2); // 返回结果: true

// 上面说明, 编译之后的集合的泛型是去泛型化的,
// 集合的泛型,目的是防止输入错误,也就是泛型只在编译阶段有效,
// 编译之后就无效了
// 而反射是在 运行时刻加载,通过反射,我们可以绕过泛型的编译限制,如下
try {
Method m = c2.getMethod("add", Object.class);
m.invoke(list1, 20); // 泛型绕过编译,绕过泛型,加入集合
System.out.println(list1.size());
System.out.println(list1);

// 注意此刻 不能 foreach 遍历,遍历报错,类型不对
/*
for (String string : list1) {
System.out.println(string);
}
*/
} catch (Exception e) {
e.printStackTrace();
}
}