在Java的世界中,万事万物皆对象。类也不例外,在Java运行的过程中类也是对象。这种设计也是Java反射机制的重要依赖。反射最主要的作用是在Java运行的过程中动态加载类并且可以取得及调用该类的属性及方法。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制 -百度百科
Java程序在运行中时,每一个类都是java.lang.Class类的实例对象,可以把这种对象的类型称为该类的类类型,得到该实例对象之后我们就可以得到相应类的各种信息。
Class类:查看过Class类的源码可知Class类的构造方法是private的,也就是我们不可以对其使用new创建对象,再进一步查看获取对象的方法后可以知道最终是native方法得到该对象,也即是说Class类的实例是在java程序运行时由jvm创建的
反射的作用——动态加载类
下面以一个例子来介绍反射动态加载类的功能,假如该程序有两个功能,每个功能都是一个类,类A,类B分别是这两个功能的类且这两个类之间没有相互调用关系。程序在运行时通过main方法中得到的参数args来确定启动的功能,如果传入字符串”A”则调用A类中的work方法,如果传入字符串”B”则调用B类中的work方法
下面是最常见的一种写法
public class A{
public void work(){
System.out.println("功能A");
}
}
public class B{
public void work(){
System.out.println("功能B");
}
}
public class Main{
public static void main(String[] args){
if(args[0].equals("A")){
A a = new A();
a.work();
}else if(args[0].equals("B")){
B b = new B();
b.work();
}
}
}
这种写法可以实现,但是假如我们说做的程序比较大是多人合作来写的,那么这时候A功能和B功能可能不是同一人所写。那么这时候当我们这个程序需要运行的时候就必须有A和B两个类,但是在实际使用过程中这两个功能并不都要使用,有可能只需要使用其中的一个功能。但是当程序工程中仅有A类或者B类时是无法完成编译的,这也就是编译时加载类和运行时加载类的区别。有时我们仅仅只需要A功能而不需要B功能,那么我们就可以先只有A类,等B功能全部写好,仅仅需要编译B类再放入指定目录下即刻,而不需要将整个工程重新编译。
使用反射来动态加载类的步骤
- 定义一个接口,让所有功能类都实现该接口
- 通过类名来加载类,得到该类的类类型
Class mClass = Class.forName("ClassName");
- 使用newInstance()方法获取到该类的一个对象mClass.newInstance();
- 将得到的对象转成接口类型
InterfaceName objectName = (InterfaceName)mClass.newInstance();
- 通过该类的对象来调用其中的方法
下面就用反射的写法重写上面的实例,要做到能够动态加载还需用到多态性,定义一个接口类C,类A,类B都实现该接口,这样可以将动态加载的类得到的对象转成C类型再调用其中的方法
public interface C{
//定义接口,类A及类B均实现该接口
public void work();
}
public class Main{
public static void main(String[] args) throws ClassNotFoundException ,IllegalAccessException, InstantiationException{
if(args[0].equals("A")){
//通过类名来获取类的类类型
Class mClass = Class.forName("A");
//通过得到的mClass来获取类A的实例,强转成为C类,再调用其中的方法
C a = (C)mClass.newInstance();
a.work();
}else if(args[0].equals("B")){
Class mClass = Class.forName("B");
C b = (C)mClass.newInstance();
b.work();
}
}
}
public class A implements C{
public void work(){
System.out.println("A");
}
}
public class B implements C{
public void work(){
System.out.println("B");
}
}
先对Main.java进行编译,可以发现使用第一种方式时得到的class类文件有Main.class,A.class,B.class,少了任何一个类的源码编译均会报类未找到,而使用反射形式的写法后进行编译只得到了Main.class和C.class,此时如果不对A,B类进行编译就运行会抛出无法找到类的错误
这时我们对A进行编译生成A.class文件后,无需对Main.java重新编译,直接执行java Main A即可加载A类运行
这就是使用反射来动态加载类的简单实例,反射的动态加载类可以用在简单工厂模式之中用于动态扩展工厂类。
动态加载类仅仅是反射的一个用途,通过反射我们还可以动态获取任意类中所有的属性和方法,并且通过反射调用指定的方法,这些都可以让我们的程序具有高扩展性,在程序进行升级的时候仅仅将新的功能类编译成class文件放入指定路径,通过类名即可加载相应的功能而不需要对原有的程序代码做任何的修改和重新编译,更好的遵循程序设计中的开闭原则