java反射机制

Java反射机制的定义

Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

用一句话总结就是反射可以实现在运行时可以知道任意一个类的属性和方法。俗称”超级工具箱“。


反射都用于哪些场景

  1. 用于代码编辑工具中,如Eclipse或者idea,我们在代码编写的时候,是不是经常自动的给我们各种提示呢,这就是用到了反射的原因。

  2. 一些框架的开发,为了程序更加的优雅和便携,就会用到反射机制。比如,spring的bean的自动注入,看如下代码:

1
<bean id="person" class="com.study.beans.User" init-method="initUser">

还有Mybatis 在 Mapper 使用外部类的 Sql 构建查询时,也是用到反射:

1
2
3
4
5
6
7
8
9
10
11
12
@SelectProvider(type = UserSql.class, method = "getListSql")
List<User> getList();

public class UserSql {
public String getListSql() {
String sql = new SQL() {
SELECT("*");
FROM("user");
}.toString();
return sql;
}
}
  1. 数据库连接池。使用反射调用不同数据库驱动
1
2
3
4
5
String url = "jdbc:mysql://127.0.0.1:3306/database";
String username = "root";
String password = "root";
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, username, password);

反射机制的优缺点

为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念

  • 静态编译:在编译时确定类型,绑定对象,即通过。
  • 动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。

优点

可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。

比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。

采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。

缺点

对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。


理解Class类和类类型

想要了解反射首先理解一下Class类,它是反射实现的基础。

类是java.lang.Class类的实例对象,而Class是所有类的类(There is a class named Class)
对于普通的对象,我们一般都会这样创建和表示:

1
Code code = new Code();

上面说了,所有的类都是Class的对象,那么如何表示呢,可不可以通过如下方式呢:

1
Class c = new Class();

但是我们查看Class的源码时,是这样写的:

1
2
3
private  Class(ClassLoader loader) { 
classLoader = loader;
}

可以看到构造器是私有的,只有JVM可以创建Class的对象,因此不可以像普通类一样new一个Class对象,虽然我们不能new一个Class对象,但是却可以通过已有的类得到一个Class对象,共有三种方式,如下:

1
2
3
4
5
6
7
8
9
10
// 这说明任何一个类都有一个隐含的静态成员变量class,
// 这种方式是通过获取类的静态成员变量class得到的
Class c1 = Code.class;

// code1是Code的一个对象,这种方式是通过
// 一个类的对象的getClass()方法获得的
Class c2 = code1.getClass();

// 这种方法是Class类调用forName方法,通过一个类的全量限定名获得
Class c3 = Class.forName("com.jelly.reflect.Code");

这里,c1、c2、c3都是Class的对象,他们是完全一样的,而且有个学名,叫做Code的类类型(class type)。

这里就让人奇怪了,前面不是说Code是Class的对象吗,而c1、c2、c3也是Class的对象,那么Code和c1、c2、c3不就一样了吗?为什么还叫Code什么类类型?

这里不要纠结于它们是否相同,只要理解类类型是干什么的就好了,顾名思义,类类型就是类的类型,也就是描述一个类是什么,都有哪些东西,所以我们可以通过类类型知道一个类的属性和方法,并且可以调用一个类的属性和方法,这就是反射的基础。

举个简单例子代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
//第一种:Class c1 = Code.class; 方式
Class class1=ReflectDemo.class;
System.out.println(class1.getName());

//第二种:Class c2 = code1.getClass(); 方式
ReflectDemo demo2= new ReflectDemo();
Class c2 = demo2.getClass();
System.out.println(c2.getName());

//第三种:Class c3 = Class.forName("com.jelly.reflect.Code"); 方式
Class class3 = Class.forName("com.jelly.reflect.ReflectDemo");
System.out.println(class3.getName());
}
}

// 执行结果:
com.jelly.reflect.ReflectDemo
com.jelly.reflect.ReflectDemo
com.jelly.reflect.ReflectDemo

Java反射相关操作

前面我们知道了怎么获取Class,那么我们可以通过这个Class干什么呢?

3rtmss.jpg

总结如下:

  • 获取成员方法Method
  • 获取成员变量Field
  • 获取构造函数Constructor
  • 获取对象
  • 获取Class类

获取成员方法

单独获取某一个方法是通过Class类的以下方法获得的:

1
2
3
4
5
// 得到该类所有的方法,不包括父类的
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

// 得到该类所有的public方法,包括父类的
public Method getMethod(String name, Class<?>... parameterTypes)

两个参数分别是方法名和方法参数类的类类型列表。

举个栗子

例如类A有如下一个方法:

1
2
3
public void fun(String name,int age) {
System.out.println("我叫"+name+",今年"+age+"岁");
}

现在知道A有一个对象a,那么就可以通过:

1
2
3
4
5
6
7
8
9
10
11
// 生成class
Class c = Class.forName("com.jelly.reflect.Person");

// newInstance可以初始化一个实例
Object o = c.newInstance();

// 获取方法
Method method = c.getMethod("fun", String.class, int.class);

// 调用方法
method.invoke(o, "jelly", 10);

完整代码如下:

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
public class Person {
private String name;
private int age;
private String msg="hello wrold";

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() {
}

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

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

public void fun(String name,int age) {
System.out.println("我叫"+name+",今年"+age+"岁");
}
}

public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.jelly.reflect.Person");
Object o = c.newInstance();
Method method = c.getMethod("fun", String.class, int.class);
method.invoke(o, "jelly", 10);
} catch (Exception e) {
e.printStackTrace();
}
}
}

执行结果:

1
jelly,今年10

怎样,是不是感觉很厉害,我们只要知道这个类的路径全称就能玩弄它于鼓掌之间。

获取所有成员方法

有时候我们想获取类中所有成员方法的信息,要怎么办。可以通过以下几步来实现:
1.获取所有方法的数组:

1
2
3
4
5
6
Class c = Class.forName("com.jelly.reflect.Person");

// 得到该类所有的方法,不包括父类的
Method[] methods = c.getDeclaredMethods();
// 或者,得到该类所有的public方法,包括父类的
Method[] methods = c.getMethods();

2.然后循环这个数组就得到每个方法了:

完整代码如下:
person类跟上面一样,这里以及后面就不贴出来了,只贴关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.jelly.reflect.Person");
Method[] methods = c.getDeclaredMethods();
for(Method m:methods){
String methodName= m.getName();
System.out.println(methodName);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

//
执行结果:
getName
setName
setAge
fun
fun
getAge

这里如果把c.getDeclaredMethods();改成c.getMethods();执行结果如下,多了很多方法,因为把Object里面的方法也打印出来了,因为Object是所有类的父类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
getName
setName
getAge
setAge
fun
fun
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

获取成员变量

想一想成员变量中都包括什么:成员变量类型+成员变量名

类的成员变量也是一个对象,它是java.lang.reflect.Field的一个对象,所以我们通过java.lang.reflect.Field里面封装的方法来获取这些信息。

单独获取某个成员变量,通过Class类的以下方法实现:

1
2
3
4
5
// 获得该类自身声明的所有变量,不包括其父类的变量
public Field getDeclaredField(String name)

// 获得该类自所有的public成员变量,包括其父类变量
public Field getField(String name)

参数是成员变量的名字。

举个栗子

例如一个类A有如下成员变量:

1
private int n;

如果A有一个对象a,那么就可以这样得到其成员变量:

1
2
Class c = a.getClass();
Field field = c.getDeclaredField("n");

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.jelly.reflect.Person");
//获取成员变量
Field field = c.getDeclaredField("msg"); //因为msg变量是private的,所以不能用getField方法
Object o = c.newInstance();
//设置是否允许访问,因为该变量是private的,
//所以要手动设置允许访问,如果msg是public的就不需要这行了。
field.setAccessible(true);
Object msg = field.get(o);
System.out.println(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}

//
执行结果:
hello wrold

获取所有成员变量

1.获取所有成员变量的数组:

1
Field[] fields = c.getDeclaredFields();

2.遍历变量数组,获得某个成员变量field

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.jelly.reflect.Person");
Field[] fields = c.getDeclaredFields();
for(Field field :fields){
System.out.println(field.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

//
执行结果:
name
age
msg

获取构造函数

最后再想一想构造函数中都包括什么:构造函数参数

同上,类的成构造函数也是一个对象,它是java.lang.reflect.Constructor的一个对象,所以我们通过java.lang.reflect.Constructor里面封装的方法来获取这些信息。

单独获取某个构造函数,通过Class类的以下方法实现:

1
2
3
4
5
//  获得该类所有的构造器,不包括其父类的构造器
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

// 获得该类所以public构造器,包括父类
public Constructor<T> getConstructor(Class<?>... parameterTypes)

这个参数为构造函数参数类的类类型列表。

举个栗子

例如类A有如下一个构造函数:

1
2
3
public A(String a, int b) {
// code body
}

那么就可以通过:

1
Constructor constructor = a.getDeclaredConstructor(String.class, int.class);

来获取这个构造函数。

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.jelly.reflect.Person");
//获取构造函数
Constructor constructor = c.getDeclaredConstructor(String.class);
//设置是否允许访问,因为该构造器是private的,
//所以要手动设置允许访问,如果构造器是public的就不需要这行了。
constructor.setAccessible(true);
constructor.newInstance("jelly");
} catch (Exception e) {
e.printStackTrace();
}
}
}

// 执行结果:
jelly

注意:Class的newInstance方法,只能创建只包含无参数的构造函数的类,如果某类只有带参数的构造函数,那么就要使用另外一种方式:

1
fromClass.getDeclaredConstructor(String.class).newInstance(“jelly”);

获取所有的构造函数

1.获取该类的所有构造函数,放在一个数组中:

1
Constructor[] constructors = c.getDeclaredConstructors();

2.遍历构造函数数组,获得某个构造函数constructor:

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ReflectDemo {
public static void main(String[] args){
Constructor[] constructors = c.getDeclaredConstructors();
for(Constructor constructor:constructors){
System.out.println(constructor);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

//执行结果:
public com.jelly.reflect.Person()
public com.jelly.reflect.Person(java.lang.String)

通过反射了解集合泛型的本质

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* 集合泛型的本质
*/
public class GenericEssence {
public static void main(String[] args) {

// 没有泛型
List list1 = new ArrayList();

// 有泛型
List<String> list2 = new ArrayList<String>();


/*
* 1.首先观察正常添加元素方式,在编译器检查泛型
* 这个时候如果list2添加int类型会报错
*/
list2.add("hello");
// 报错!list2有泛型限制,只能添加String,添加int报错
// list2.add(20);

// 此时list2长度为 1
System.out.println("list2的长度是:" + list2.size());


/*
* 2.然后通过反射添加元素方式,在运行期动态加载类,首先得到list1和list2的类类型相同,
* 然后再通过方法反射绕过编译器来调用add方法,看能否插入int型的元素
*/
Class c1 = list1.getClass();
Class c2 = list2.getClass();

// 结果:true,说明类类型完全相同
System.out.println(c1 == c2);

// 验证:我们可以通过方法的反射来给list2添加元素,这样可以绕过编译检查
try {
// 通过方法反射得到add方法
Method m = c2.getMethod("add", Object.class);

//给list2添加一个int型的,上面显示在编译器是会报错的
m.invoke(list2, 20);

//结果:2,说明list2长度增加了,并没有泛型检查
System.out.println("list2的长度是:" + list2.size());
} catch (Exception e) {
e.printStackTrace();
}
}
}

// 执行结果:
list2的长度是:1
true
list2的长度是:2

综上可以看出,在编译器的时候,泛型会限制集合内元素类型保持一致,但是编译器结束进入运行期以后,泛型就不再起作用了,即使是不同类型的元素也可以插入集合。


通过反射取得并修改数组的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
package net.xsoftlab.baike;
import java.lang.reflect.Array;
public class TestReflect {
public static void main(String[] args) throws Exception {
int[] temp = { 1, 2, 3, 4, 5 };
Class<?> demo = temp.getClass().getComponentType();
System.out.println("数组类型: " + demo.getName());
System.out.println("数组长度 " + Array.getLength(temp));
System.out.println("数组的第一个元素: " + Array.get(temp, 0));
Array.set(temp, 0, 100);
System.out.println("修改之后数组第一个元素为: " + Array.get(temp, 0));
}
}

通过反射机制修改数组的大小

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 net.xsoftlab.baike;
import java.lang.reflect.Array;
public class TestReflect {
public static void main(String[] args) throws Exception {
int[] temp = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int[] newTemp = (int[]) arrayInc(temp, 15);
print(newTemp);
String[] atr = { "a", "b", "c" };
String[] str1 = (String[]) arrayInc(atr, 8);
print(str1);
}
// 修改数组大小
public static Object arrayInc(Object obj, int len) {
Class<?> arr = obj.getClass().getComponentType();
Object newArr = Array.newInstance(arr, len);
int co = Array.getLength(obj);
System.arraycopy(obj, 0, newArr, 0, co);
return newArr;
}
// 打印
public static void print(Object obj) {
Class<?> c = obj.getClass();
if (!c.isArray()) {
return;
}
System.out.println("数组长度为: " + Array.getLength(obj));
for (int i = 0; i < Array.getLength(obj); i++) {
System.out.print(Array.get(obj, i) + " ");
}
System.out.println();
}
}

将反射机制应用于工厂模式

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
package net.xsoftlab.baike;
interface fruit {
public abstract void eat();
}
class Apple implements fruit {
public void eat() {
System.out.println("Apple");
}
}
class Orange implements fruit {
public void eat() {
System.out.println("Orange");
}
}
class Factory {
public static fruit getInstance(String ClassName) {
fruit f = null;
try {
f = (fruit) Class.forName(ClassName).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
/**
* 对于普通的工厂模式当我们在添加一个子类的时候,就需要对应的修改工厂类。 当我们添加很多的子类的时候,会很麻烦。
* Java 工厂模式可以参考
* http://baike.xsoftlab.net/view/java-factory-pattern
*
* 现在我们利用反射机制实现工厂模式,可以在不修改工厂类的情况下添加任意多个子类。
*
* 但是有一点仍然很麻烦,就是需要知道完整的包名和类名,这里可以使用properties配置文件来完成。
*
* java 读取 properties 配置文件 的方法可以参考
* http://baike.xsoftlab.net/view/java-read-the-properties-configuration-file
*
* @author xsoftlab.net
*/
public class TestReflect {
public static void main(String[] args) throws Exception {
fruit f = Factory.getInstance("net.xsoftlab.baike.Apple");
if (f != null) {
f.eat();
}
}
}