反射

这里推荐一篇很好的文章:文章链接

反射:框架设计的灵魂。

框架:半成品软件。可以在框架基础上进行软件开发,简化编码

将类的各个组成部分封装为其他对象,这就是反射机制

Java代码在计算机中经历的三个阶段

具体讲解

反射的优点:

  1. 可以在程序运行过程中操作这些对象
  2. 可以解耦(降低程序的耦合性),提高程序的可扩展性

Class对象

关于Java中的Class对象,我们可以看一篇入门文章,也可以查看jdk1.8api文档

获取Class对象的方式

  • Class.forName(“类名”) 将字节码文件加载进内存,返回Class对象(多用于配置文件,将类名定义在配置文件中。读取文件、加载类)
  • 类.class 通过类的属性class获取Class对象(多用于参数的传递)
  • 对象.getClass() getClass为Object类中定义的方法(多用于去获取具体对象的字节码文件)

例子:首先创建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
package domain;

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

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public Person(String name, int age) {

this.name = name;
this.age = age;
}

public 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
package demo02;

import domain.Person;

public class Demo01 {
// 获取Class对象的三种方式
public static void main(String[] args) throws ClassNotFoundException {
// 1.Class.forName("类名") (静态方法static,属于类)
Class<?> aClass1 = Class.forName("domain.Person");
System.out.println(aClass1); // class domain.Person

// 2.类.class
Class aClass2 = Person.class;
System.out.println(aClass2); // class domain.Person

// 3.对象.getClass()
Person person = new Person();
Class<? extends Person> aClass3 = person.getClass();
System.out.println(aClass3); // class domain.Person

// 同一个字节码文件(*.class文件)在程序运行过程中只会被加载一次。不论通过哪种方式获取的Class对象都是同一个
System.out.println(aClass1==aClass2); // true
System.out.println(aClass3==aClass2); // true
}

}

Class对象的相关方法:

具体可查看查看jdk1.8api文档

获取功能:

1.获取成员变量

  • Filed[] getFileds() 获取所有public修饰的成员变量
  • Filed[] gerFiled(String name) 获取指定名称的public修饰的成员变量
  • Filed[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
  • Filed[] getDeclaredField(String name) 获取指定名称的成员变量,不考虑修饰符

2.获取构造方法

  • Constructor<?>[] getConstructors()

  • Constructor getConstructor(类<?>… parameterTypes)

  • Constructor<?>[] getDeclaredConstructors()

  • Constructor getDeclaredConstructor(类<?>… parameterTypes)

3.获取成员方法:

  • Method[] getMethods()
  • Method getMethod(String name, 类<?>… parameterTypes)
  • Method[] getDeclaredMethods()
  • Method getDeclaredMethod(String name, 类<?>… parameterTypes)

4.获取全类名

  • String getName()

成员变量(Field)的方法:

  • void set(Object obj,Object value) 设置值
  • get(Object obj) 获取值
  • setAccessible(true) 忽略访问权限修饰符的安全检查

构造方法(Constructor)的方法:

  • T newInstance(Object… args) 创建对象(如果使用空参数构造方法创建对象,操作可以简化为:Class对象的newInstance方法)

方法(Method)的方法:

  • Object invoke(Object object,Object… args) 执行方法
  • String getName 获取方法名

成员变量

详细讲解

例子:还是对应上述的Person类,先添加成员变量public int id;,然后我们创建启动类如下:

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

import domain.Person;

import java.lang.reflect.Field;

public class Demo02 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
// 首先要获取Person的Class对象
Class personClass = Person.class;

// Filed[] getDeclaredField(String name),获取指定名称的成员变量,不考虑修饰符
Field id = personClass.getDeclaredField("id");
System.out.println(id); // public int domain.Person.id
// 获取成员变量id(public)的值(get(Object obj) 获取值)
Person person = new Person();
Object idValue = id.get(person);
System.out.println(idValue); // 0
// 设置成员变量id(public)的值(void set(Object obj,Object value) 设置值)
id.set(person,101);
Object newIdValue = id.get(person);
System.out.println("重新设置后id为:"+newIdValue); // 重新设置后id为:101
System.out.println(person); // Person{name='null', age=0, id=101}

// 获取成员变量age(private)的值(get(Object obj) 获取值)
Field age = personClass.getDeclaredField("age");
age.setAccessible(true); // setAccessible(true) 忽略访问权限修饰符的安全检查
Person person2 = new Person("jj", 28);
Object ageValue = age.get(person2);
System.out.println(ageValue); // 28
}
}

构造方法

详细讲解

例子:基于上述Person类,创建启动类如下:

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

import domain.Person;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Demo03 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 首先要获取Person的Class对象
Class personClass = Person.class;

// Constructor<T> getConstructor(类<?>... parameterTypes)
Constructor personClassConstructor = personClass.getConstructor(String.class, int.class);
System.out.println(personClassConstructor); // 这就是一个构造器(public domain.Person(java.lang.String,int))
// 使用构造器创建对象
Object person = personClassConstructor.newInstance("jay", 28);
System.out.println(person); // Person{name='jay', age=28, id=0}
}
}

成员方法

详细讲解

例子:基于上述Person类,我们添加一个study方法()

1
2
3
4
5
6
public void study(){
System.out.println("开始学习");
}
public void study(String name){ // 方法重载
System.out.println("开始学习"+name);
}

然后创建启动类,代码如下:

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

import domain.Person;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Demo04 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 首先要获取Person的Class对象
Class personClass = Person.class;

Person person = new Person();
// 获取指定名称的方法(Method getMethod(String name, 类<?>... parameterTypes) )
Method study_method = personClass.getMethod("study");
// 执行方法
study_method.invoke(person); // 开始学习(方法被成功执行)
// 获取指定名称的方法(Method getMethod(String name, 类<?>... parameterTypes) )
Method study_method2 = personClass.getMethod("study", String.class); // 获取重载的方法
// 执行该方法
study_method2.invoke(person,"java"); // 开始学习java

// 获取方法名
String study_methodName = study_method.getName();
System.out.println(study_methodName); // study
}
}

案例

需求:写一个”框架“,在不能改变该类代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。

实现:

  • 配置文件
  • 反射

步骤:

  1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
  2. 在程序中加载读取配置文件
  3. 使用反射技术来加载类文件进内存
  4. 创建对象
  5. 执行方法

实例:

首先创建配置文件如下:

1
2
className = domain.Person
methodName = study

然后创建ReflectTest.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
31
32
33
34
35
package demo03;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

public class ReflectTest { // 假定框架类
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// 可以创建任意类的对象,并且执行其中任意方法(在不能改变该类代码的前提下)

// 1.加载配置文件pro.properties
Properties properties = new Properties(); // 创建Properties对象
// 加载配置文件,转换成一个集合
ClassLoader classLoader = ReflectTest.class.getClassLoader(); // 获取class目录下的配置文件
InputStream resourceAsStream = classLoader.getResourceAsStream("pro.properties");
properties.load(resourceAsStream);

// 2.获取配置文件中定义的数据
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");

// 3.使用反射加载该类进内存
Class aClass = Class.forName(className);

// 4.创建对象
Object obj = aClass.newInstance();

// 5.执行方法
Method method = aClass.getMethod(methodName); // 获取方法对象
method.invoke(obj); // 执行方法

}
}

这样,我们就可以利用反射,只需要修改配置文件,就可以修改要执行的类以及方法了。