Java反射学习

类的加载

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。

  • 加载:将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
  • 连接
    • 验证:是否有正确的内部结构,并和其他类协调一致
    • 准备:负责为类的静态成员分配内存,并设置默认初始化值
    • 解析:将类的二进制数据中的符号引用替换为直接引用
  • 初始化:执行类构造器<clinit>()方法的过程。

类初始化时机

  • 创建类的实例
  • 访问类的静态变量,或者为静态变量赋值
  • 调用类的静态方法
  • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
  • 初始化某个类的子类
  • 直接使用java.exe命令来运行某个主类

类加载器

负责将.class文件加载到内存中,并为之生成对应的Class对象

类加载器的组成

  • Bootstrap ClassLoader 根类加载器
    • 也被称为引导类加载器,负责Java核心类的加载,例如System.String
  • Extension ClassLoader 扩展类加载器
    • 负责JRE的扩展目录中jar包的加载
  • System ClassLoader 系统类加载器
    • 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径

反射

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性。

要想解剖一个类,必须先要获取到该类的字节码文件对象,而解剖使用的就是Class类中的方法,所有先要获取到每一个字节码文件对应的Class类型的对象。

为什么需要反射?

反射赋予了JVM动态编译的能力,动态编译可以最大限度的体现Java的灵活性,否则类的元信息只能通过静态编译的形式实现,而不能动态编译,也就是说在编译以后,程序在运行时的行为就是固定的了,如果要在运行时改变程序的行为,就需要动态编译,因此在Java中就需要反射来实现。

反射举例

首先实现一个Person类,在Person类中有成员变量,构造函数,和成员方法。

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
package refTest;

public class Person {
private String name;
int age;
public String address;

public Person(){

}

Person (String name,int age) {
this.name = name;
this.age = age;
}

public Person(String name,int age,String address) {
this.name = name;
this.age = age;
this.address = address;
}

public void show() {
System.out.println("show");
}

private Person(String name) {
this.name = name;
}

public void method(String s) {
System.out.println("method" + s);
}

public String getString(String s,int i) {
return s + "---" + i;
}

private void function() {
System.out.println("function");
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}

通过反射获取到字节码文件对象的三种方法

1
2
Person p = new Person();
Class c1 = p.getClass();
1
Class c2 = Person.class()
1
Class c3 = Class.forName("refTest.Person");

通过反射获取公共有参构造方法并实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package refTest;
import com.sun.tools.internal.jxc.ap.Const;
import java.lang.reflect.Constructor;

public class ReflectDemo {

public static void main(String[] args) throws Exception {
//获取字节码文件对象
Class c = Class.forName("refTest.Person");
Constructor con = c.getConstructor(String.class,int.class,String.class);
Object obj = con.newInstance("elssm",22,"西安");
System.out.println(obj);
}
}

通过反射获取私有构造方法并实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package refTest;
import com.sun.tools.internal.jxc.ap.Const;
import java.lang.reflect.Constructor;

public class ReflectDemo {

public static void main(String[] args) throws Exception {
//获取字节码文件对象
Class c = Class.forName("refTest.Person");
//获取私有的有参构造方法对象
Constructor con = c.getDeclaredConstructor(String.class);

con.setAccessible(true);
Object obj = con.newInstance("elssm");
System.out.println(obj);
}
}

通过反射获取所有的成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package refTest1;
import java.lang.reflect.Field;

public class ReflectDemo {

public static void main(String[] args) throws Exception {
//获取字节码文件对象
Class c = Class.forName("refTest.Person");

//获取所有的成员变量
Field[] fields = c.getDeclaredFields();

for (Field field:fields) {
System.out.println(field);
}
}
}

通过反射获取成员变量类型并使用

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
package refTest1;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class ReflectDemo {

public static void main(String[] args) throws Exception {
//获取字节码文件对象
Class c = Class.forName("refTest.Person");

//通过无参构造方法创建对象
Constructor con = c.getConstructor();
Object obj = con.newInstance();
System.out.println(obj);

//获取单个成员变量
Field addressField = c.getField("address");
addressField.set(obj,"西安");
System.out.println(obj);

//获取name并对其赋值
Field nameField = c.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(obj,"elssm");
System.out.println(obj);

//获取age并对其赋值
Field ageField = c.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(obj,22);
System.out.println(obj);
}
}

通过反射获取所有的成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package refTest2;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class ReflectDemo {
public static void main(String[] argv) throws Exception {
Class c = Class.forName("refTest.Person");

// Method[] methods = c.getMethods(); //获取自己的包括父亲的公共方法
Method[] methods = c.getDeclaredMethods(); //获取自己的所有的方法
for (Method method:methods){
System.out.println(method);
}
}
}

通过反射获取单个成员方法并使用

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
package refTest2;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class ReflectDemo {
public static void main(String[] argv) throws Exception {
Class c = Class.forName("refTest.Person");


Constructor con = c.getConstructor();
Object obj = con.newInstance();
//获取单个方法并使用
//public Method getMethod(String name,Class<?>... parameterTypes)
//第一个参数表示的方法名,第二个参数表示的是方法的参数的class类型
Method method = c.getMethod("show");
//public Object invoke(Object obj,Object... args)
//返回值是Object接收,第一个参数表示对象是谁,第二个参数表示调用该方法的实际参数
method.invoke(obj); //本质是调用obj对象的method方法

System.out.println("------------");
Method method1 = c.getMethod("method", String.class);
method1.invoke(obj,"test");

System.out.println("------------");
Method method2 = c.getMethod("getString", String.class, int.class);
Object objString = method2.invoke(obj,"elssm",22);
System.out.println(objString);

System.out.println("------------");
Method method3 = c.getDeclaredMethod("function");
method3.setAccessible(true);
method3.invoke(obj);
}
}

几个反射的例子

通过反射运行配置文件内容

代码结构如下图

1

class.txt中写的是配置文件

1
2
className = test.Teacher
methodName = love

Student

1
2
3
4
5
6
7
package test;

public class Student {
public void love(){
System.out.println("爱生活,爱学习");
}
}

Teacher

1
2
3
4
5
6
7
package test;

public class Teacher {
public void love(){
System.out.println("爱生活,爱教学");
}
}

Worker

1
2
3
4
5
6
7
package test;

public class Worker {
public void love(){
System.out.println("爱生活,爱工作");
}
}

Test类实现

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
package test;

import java.io.FileReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Properties;

public class Test {
public static void main(String[] args) throws Exception {
//加载键值对数据
Properties prop = new Properties();
FileReader fr = new FileReader("./src/test/class.txt");
prop.load(fr);
fr.close();

//获取数据
String className = prop.getProperty("className");
String methodName = prop.getProperty("methodName");

//反射
Class c = Class.forName(className);

Constructor con = c.getConstructor();
Object obj = con.newInstance();

//调用方法
Method m = c.getMethod(methodName);
m.invoke(obj);
}
}
通过反射越过泛型检查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class ArrayListDemo {
public static void main(String[] args) throws Exception{
//创建集合对象
ArrayList<Integer> array = new ArrayList<Integer>();

Class c = array.getClass(); //集合ArrayList的class对象
Method m = c.getMethod("add",Object.class);
m.invoke(array,"hello"); //调用array的add方法,传入的值是hello
m.invoke(array,"world");

System.out.println(array);
}
}
通过反射写一个通用的设置某个对象的某个属性为指定的值

首先写一个实现类Tool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package test;

import java.lang.reflect.Field;

public class Tool {
public void setProperty(Object obj,String propertyName,Object value) throws Exception {
//根据对象获取字节码文件对象
Class c = obj.getClass();
//获取该对象的propertyName成员变量
Field field = c.getDeclaredField(propertyName);
//取消访问检查
field.setAccessible(true);
//给对象的成员变量赋值为指定的值
field.set(obj,value);
}
}

测试类ToolDemo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package test;

public class ToolDemo {
public static void main(String[] args) throws Exception {
Person p = new Person();
Tool t = new Tool();
t.setProperty(p,"name","elssm");
t.setProperty(p,"age",22);

System.out.println(p);
}
}

class Person {
private String name;
public int age;

@Override
public String toString() {
return name + "----" + age;
}
}