Java反射机制概述

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

正常方式:引入需要的“包类”的名称 --> 通过new实例化 --> 取得实例化对象
反射方式:实例化对象 --> getClass()方法 --> 得到完整的“包类”名称

动态语言

是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。

静态语言

与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。

Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!

Java反射机制提供的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法(包括私有方法)
  • 在运行时处理注解
  • 生成动态代理

反射相关的主要API

  • java.long.Class:代表一个类
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的构造器

这里将age设置为了public,name设置为了private
并且提供了两个构造器,一个public的,一个private的
同时也提供了两个方法,依旧是一个public的,一个private的

那么在普通的实现方法中,我们不能直接修改Person对象的name,而是需要调用相应的get()或set()方法

同时我们也不能使用私有的构造器来创建对象,对象不能调用private的方法

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
public class Person {
public int age;
private String name;

public int getAge() {
return age;
}

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

public String getName() {
return name;
}

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

public Person() {
}

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

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

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

public void show() {
System.out.println("你好,我是Person类的show方法");
}

private String showNation(String nation) {
System.out.println("我是" + nation + "人");
return nation;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test01() {
//1.创建类的对象
Person person = new Person(18, "张三");
System.out.println(person);
//2.调用对象,调用其内部的属性和方法
person.age = 15;
System.out.println(person);
person.show();
//在Person类的外部,不可以通过Person类的对象调用其内部私有的结构。
//比如:name、showNation以及私有的构造器。
}

下面我们来用反射实现跟上面一样的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test02() throws Exception {
Class<Person> c1 = Person.class;
//1. 通过反射,创建Person类对象
Constructor<Person> cons = c1.getDeclaredConstructor(int.class, String.class);
Person person = cons.newInstance(18, "Tom");
System.out.println(person);
//2. 通过反射,调用对象指定的属性(这里就是在调用一个名为age的属性)
Field age = c1.getDeclaredField("age");
age.set(person, 21);
System.out.println(person);
//3. 通过反射,调用对象指定的方法(这里就是在调用一个名为show的方法)
Method show = c1.getDeclaredMethod("show");
show.invoke(person);
}

下面是用到的Class类中的几个方法

变量和类型 方法 描述
构造器 getConstructor(类<?>… parameterTypes) 返回一个构造器对象,该对象反映此类对象所表示的类的指定公共构造函数。
字段 getDeclaredField(String name) 返回字段对象,该对象反映此类对象表示的类或接口的指定声明字段。
方法 getDeclaredMethod(String name, 类<?>… parameterTypes) 返回方法对象,该对象反映此类对象表示的类或接口的指定声明方法。

下面是Constructor类中用到的方法说明

变量和类型 方法 描述
T newInstance(Object… initargs) 使用此构造器对象表示的构造方法,使用指定的初始化参数创建和初始化构造函数声明类的新实例。

下面是Field类中用到的方法说明

变量和类型 方法 描述
void set(Object obj, Object value) 将指定对象参数上此字段对象表示的字段设置为指定的新值。

下面是Method类中用到的方法说明

变量和类型 方法 描述
Object invoke(Object obj, Object… args) 在具有指定参数的指定对象上调用此方法对象表示的基础方法。

下面我们来用反射调用私有结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test03() throws Exception {
Class<Person> c1 = Person.class;
//调用私有的构造器
Constructor<Person> cons = c1.getDeclaredConstructor(String.class);
cons.setAccessible(true);
Person p1 = cons.newInstance("Wilson");
System.out.println(p1);
//调用私有的属性
Field name = c1.getDeclaredField("name");
name.setAccessible(true);
name.set(p1, "Wendy");
System.out.println(p1);
//调用私有的方法
Method showNation = c1.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
showNation.invoke(p1,"中国");
}

相较于刚刚的代码,不难发现,只多了一行代码

xxx.setAccessible(true);

变量和类型 方法 描述
void setAccessible(boolean flag) 将此反射对象的accessible标志设置为指示的布尔值。

将此反射对象的accessible标志设置为指示的布尔值。 值true表示反射对象在使用时应禁止检查Java语言访问控制。 值false表示反射对象在使用时应强制检查Java语言访问控制,并在类描述中注明变体。

所以设置成true就可以无视private的权限了

理解Class类并获取Class实例

Class类的理解

关于java.lang.Class类的理解

类的加载过程:程序经过Javac.exe命令后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。

换句话说,Class的实例就对应着一个运行时类。

获取Class实例的4种方式

怎么说呢,感觉这部分内容现在学了没啥用,目前已经了解过相关内容,等实际需要用到的时候,再回来看一遍(后面学JDBC,javaWeb和框架的时候),笔记以后再更