JAVA——反射(Reflect)

本文深入解析了反射机制的概念及其应用场景,对比了静态加载与动态加载的区别,并详细介绍了如何通过反射获取类的方法、成员变量及构造函数等信息。

什么是反射:

In computer science, reflection is the ability of a computer program to examine, introspect, and modify its own structure and behavior at runtime.(From wikipedia)

也就是说,反射是计算机程序在运行时刻能够获取自身信息的能力。


为什么要用反射呢?

首先我们看一个简单的反射的例子:

public class MyClass {
	public void print() {
		System.out.println("print方法被调用了");
	}
}
import java.lang.reflect.Method;

public class Main {
	public static void main(String args[]) {
		try {
			Class c = Class.forName("MyClass");
			Method m = c.getMethod("print");
			m.invoke(c.newInstance());
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
}
输出结果:

print方法被调用了


为什么调用这个方法要绕来绕去的呢?直接创建实例然后调用它的方法不好吗??

这就涉及到静态加载和动态加载了。


静态加载:

public class Main {
	public static void main(String args[]) {
		if (args.equals("MyClass")) {
			MyClass mc = new MyClass();
			mc.print();
		}
		if (args.equals("MyClass2")) {
			MyClass2 mc2 = new MyClass2();
			mc2.print();
		}
	}
}


在这个代码中,就是静态加载类。通过new关键字来静态加载。

很显然,在上面这个代码中,这是通不过编译的,因为我们根本就没有MyClass2这个类,mc2也没有print方法。

设想一下,假设Main这个程序是一个窗口程序,上面有若干个按钮,点击不同的按钮就会开启不同的程序。

那么,在静态加载中,就需要每个类都是写好的,是存在的,并且当我们想要增加程序的时候,就需要对Main来重新编译,这显然是不好的。

因为我整个程序不能因为其中一个程序不能启动或者不存在,其他程序也跟着不能打开,而增加程序的时候也不应该每次都将整个程序都编译一边,如果程序每次更新,都需要将整个程序都重新安装一次,那么用户体验一定是很差的。


动态加载:

public class Main {
	public static void main(String args[]) {
		try {
			Class c = Class.forName("args");
			MyClass mc = (MyClass)c.newInstance();
			mc.print();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
而动态加载则不同,避开了编译,到了运行时类不存在才会报错,这显然就是符合我们的理念的。

但是这又存在一个问题,在上面这个例子中,还是强制类型转换,转换成了MyClass类,那不是和静态加载没区别,如果我希望启动MyClass2呢?还不是得重新写个代码强制类型转换成MyClass2,再调用吗?

这个时候就到多态大展身手的时候了。

public interface IPrinciple {
	public void print();
}
而MyClass和MyClass2只需要简单的实现IPrinciple接口,就可以在Main函数中这样使用了:

public class Main {
	public static void main(String args[]) {
		try {
			Class c = Class.forName("args");
			IPrinciple mc = (IPrinciple)c.newInstance();
			mc.print();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
这样一来,只要实现了同样的接口,Main想调用哪个就调用哪个,并且不会因为某些类不存在而导致存在的类不能被调用的情况。更重要的是,在我们增加功能的时候,Main是不需要进行重新编译的,只需要编译新增加的功能,整个程序就更新了。


谈了这么多,那到底怎么使用反射呢?

首先第一步:获取类类型

获取类类型的方法有三种:

1.Class c = MyClass.class; // 通过类.class获取类类型

2.Class c = mc.getClass(); // 通过类的实例的getClass方法获取类类型

3.Class c = Class.forName("包的全名"); // 通过Class的forName方法获取类类型,需要所在包的全名

那么问题又来了,什么是类类型(class type)呢?

Class类是所有类(注意是对象)的共有信息的抽象,比如该类实现的接口、对应的加载器、类名等等。而类类型就是当前这个类,比如MyClass的Class类的对象,就是MyClass.class是Class类的对象。这个对象在类被加载后由JVM自动构造。也是由JVM管理的,Class类是没有公共的构造方法的,是私有的native的。

这样说还是不是很能让人明白,简单来说,类类型就是一个对象,这个对象里存放着类的所有信息,例如类的方法,类的成员变量。就好像是一个设计图纸,通过图纸你可以看到这个类的所有东西和结构,也可以通过这个图纸得到实例对象。

所以说,要使用反射,就必须先获取类类型。


注意,以下获得的都是类类型,例如获得Method.class

获取类的方法信息:

Method getMethod(String name, Class[] params) // 通过方法名name和形参列表params,是变长参数,获得类的public方法,不存在的话返回null,包括继承来的方法

Method[] getMethods() // 获得所有的public方法,包括继承来的方法

Method getDeclaredMethod(String name, Class[] params)// 获得类中定义的所有方法,不受访问权限的限制,但不包括继承来的方法。

Method[] getDeclaredMethods() // 获得类中定义的所有方法


获取类的成员变量信息:

Field getField(String name) // 根据变量名得到相应的public变量

Field[] getFields() // 获得类中所有的public变量

Field getDeclaredField(String name) // 根据变量名得到所有在类中定义的变量

Field[] getDeclaredFields() // 获得类中所有定义的变量


获取类的构造函数信息:

Constructor getConstructor(Class[] params)  // 根据形参列表获得对应的public构造函数

Constructor[] getConstructors() // 获得所有public构造函数

Constructor getDeclaredConstructor(Class[] params)// 根据形参列表获得对应的所有构造函数

Constructor[] getDeclaredConstructors() // 获得所有构造函数


这里就简单的说列举一些API,还有很多API这里就不一一列出了。可以参考javadoc文档。

不过不管怎么样,都需要首先获取类类型。


最后,谈一下方法反射的基本操作:

1.获取某个方法的类类型

Method m = c.getDeclaredMethod("方法名",可变参数列表(参数类型.class))

2.方法的反射操作,调用invoke方法。

m.invoke(对象,参数列表)

如果没有形参,则不用填写,或者空Object数组。如果没有返回值,则返回null,否则返回Object对象,需要进行强制类型转换。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值