反射是java的一大特性,而且是有些框架实现了IoC/DI的原理,本文就来探讨下java中的反射及其优点。
首先是普通的java静态类加载,java静态类是通过new实现的,在编译时刻就要加载所有可能用到的类,这样实际上存在一些缺点的,比如只要有一个类没有找到或者出现重大的问题编译便不会通过,导致其他存在的类也无法使用。另一方面,如果要加载其他的类就要重新进行编译,有的时候是非常不方便的。下面就引入反射机制来动态加载类来解决这个问题。
首先创建一个ClassInterface接口,放在com.classTest包下:
package com.classTest;
public interface ClassInterface {
public void start(); //接口约定start()方法
}
然后分别使用Class1Impl和Class2Impl类去实现这个接口,也放在com.classTest包下:
package com.classTest;
public class Class1Impl implements ClassInterface {
@Override
public void start() {
// TODO Auto-generated method stub
System.out.println("This is Class1Impl"); //实现start()
}
}
package com.classTest;
public class Class2Impl implements ClassInterface {
@Override
public void start() {
// TODO Auto-generated method stub
System.out.println("This is Class2Impl"); //实现start()
}
}
下面先用正常的类加载来说明正常类加载的缺点:
package com.test;
import java.util.Scanner;
import com.classTest.ClassInterface;
public class LoadClassTest {
ClassInterface c1 = new Class1Impl();
ClassInterface c2 = new Class2Impl();
c1.start();
c2.start();
}
上面是正常的new方法生成的对象,然后调用接口里的方法,但是一旦Class1Impl类或者Class2Impl类没有,那有的哪个类也就无法使用,而且想要使用新的ClassInterface接口下的方法还是需要重新写类,重新写测试方法,重新生成测试对象,重新编译...下面介绍利用反射机制动态加载类来解决这个问题:
package com.test;
import java.util.Scanner;
import com.classTest.ClassInterface;
public class LoadClassTest {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException{
//new 创建对象是静态加载类,在编译时刻就需要加载所有可能用到的类
//动态记载类可以解决这个问题
Scanner sc = new Scanner(System.in);
String className = sc.nextLine();
//动态加载类,在运行的时刻进行加载
//使用类类型(class type) 通过Class这个类的forName(className)方法动态创建类对象
Class c = Class.forName(className);
//通过类类型 用newInstance()方法创建该类对象
ClassInterface ci = (ClassInterface) c.newInstance();
ci.start();//动态加载类 调用其方法
}
}
有没有看出这其中的差异,想要加载其他类只需要输入另外的类名即可(这里的类名是包含包的),forName()方法可以通过类名动态创建类类型的对象,然后用类类型的对象的newInstance()方法创建该类类型对象(也就是一个类)的对象(调用newInstance()方法的类类型对象需要进行转化),之后就可以用生成对象调用接口下的方法了。
那这时候可能就会有人问了,要是在我们不知道接口名称为ClassInterface的情况下,我们该如何访问className这个类类型生成的c这个类对象下面的方法呢?我们来实现一下:
Class c = Class.forName("com.classTest.Foo");//创建一个类对象c
Method m1 = c.getMethod("fun1", int.class,int.class);//通过getMethod方法获取c的方法,第一个参数表示想要获取的方法名称fun1,之后的参数列表表示fun1这个函数的所有参数的类类型。
Object o = m1.invoke(foo1,new Object[]{10,20} );//通过invoke方法来调用我们前面通过getMethod()获得的方法,第一个参数表示执行我们所获得方法的对象,第二个参数表示我们所获取方法的参数,这里是两个int类型。如果没有返回值返回null,有返回值则返回具体的返回值,这里将返回结果保存在o对象里。
这样的优点是显而易见的,类无需在编译前全部加载,而且修改接口实现类的时候也不用重新编译。这只是反射的一小部分,但是也是很重要的一部分,也是Spring中IoC/DI的实现基础。(另外告诉大家一个小tip:所谓的泛型数据容器的数据类型(也就是HashMap<String,String>中的<String,String>)不能写入其他数据类型实际上只是存在于编译层面的,是为了防止开发者往里面误写入自己不想写入的东西,然而通过泛型可以跳过编译层面实现往这个泛型数据容器里存入和这个泛型数据容器数据类型不一样的数据,虽然这样之后用foreach遍历就会报错...)