今天在慕课网上看了一些有关Java反射的视频,感觉收获不小。反射(Reflection)能够让运行于JVM中的程序检测和修改运行时的行为。自从学Java以来就一直对反射这块内容不是很理解,今天就觉得自己突然开窍了,好像理解了些什么,于是就趁热打铁记录下来吧。
一切皆是对象
要学习面向对象编程,首先要理解什么是对象,面向对象更是一种思想,而不仅仅是一种技术。在Java中,对象是某个类的实例,那么类本身是不是一个对象呢?根据一切皆是对象的思想,当然,类也是对象。Java中任何一个类都是java.lang.Class类的实例对象。如
String hello = new String("hello");
其中,hello是一个String对象的引用,而String这个类则是java.lang.Class类的一个实例对象。类中的方法实际上也是对象,类中每一个方法都是Method类的实例对象。
java.lang.Class
下图是java.lang.Class类源码中构造方法部分的截图。可以看到该类的构造方法是私有方法,注释中指出只有Java虚拟机才能创建这个类的实例。
通过下面的代码来了解一下Class类:
Class s1 = String.class;
String hello = new String("hello");
Class s2 = hello.getClass();
System.out.println(s1 == s2);//true
- 任何一个类都有一个隐含的静态成员变量class,可以通过这个成员变量取得该类的类类型
- 也可以通过任何一个对象的getClass()方法,取得该对象所属类的类类型
- s1 == s2为true,因为s1和s2都是Class类的对象(String的类类型)
Class类中有一个静态方法forName(“ClassFullName”),Class.forName(“ClassFullName”)不仅表示了类的类类型,还代表了动态加载该类。Java类在被使用之前需要先被类加载器加载,在编译时加载类属于静态加载,在运行时加载类便属于动态加载。通过new创建类的实例时,类是静态加载的,在编译的时候就需要加载所有可能用到的类。关于Java类的静态加载详情请见我的这篇博客深入理解Java类加载原理。而动态加载类是在运行时刻进行类的加载,如果类没有找到则会抛出异常。
总的来说,一个类的类类型,即Class类的一个实例对象,包含了这个类的信息(好比String.class包含了String类的信息),也可以通过这个类的类类型,对这个类进行操作,比如创建一个该类的对象,调用某个方法等等。
java.lang.reflect.Method
上面我们简单介绍了java.lang.Class类的一些信息,接下来我们继续了解下java.lang.reflect.Method类,下图是该类源码中构造方法部分的截图。
我们已经说明,方法也是对象:
1. 一个成员方法就是一个Method对象。
2. java.lang.Class类中的getMethods()方法可以获取所有的访问控制为public的方法,包括从父类继承而来的。
3. java.lang.Class类中的getDeclaredMethods()方法可以获取该类自己声明的所有方法。
4. 也可以通过java.lang.Class类中的getMethod(“functionName”,classType…)即方法名+参数类型列表的形式唯一取得一个方法。
下面是一个实例代码
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Student {
public String printName(String familyName,String lastName){
System.out.println("familyName: " + familyName + " lastName: " + lastName);
return "Hello "+lastName;
}
public static void main(String[] args) {
Student student = new Student();
// 取得student对象的类类型
Class studentClass = student.getClass();
try {
// 通过方法名和参数类类型列表取得方法对象
// 其中pringName是方法名,Class数组中为参数类类型列表
Method method = studentClass.getMethod("printName", new Class[]{String.class,String.class});
// 调用这个方法,效果与调用student.printName("Wang","Colin")相同
// object为方法的返回值。如果方法的返回类型为void,则object = null
Object object = method.invoke(student, new Object[]{"Wang","Colin"});
System.out.println(object);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
总之,Method对象包含了一个方法的信息,可以通过这个对象,对方法进行操作。
Java中关于集合泛型的本质
编译后的集合泛型是去泛型化的,编译前集合泛型的作用是防止向一个集合中放入不同类型的对象,否则将不能通过编译。但是编译之后,通过反射动态的向一个集合中放入不同类型的对象就没有问题,说明在编译之后
ArrayList<String> al = new ArrayList<String>();
ArrayList al = new ArrayList();
这两条语句是一样的。反射的操作都是编译之后的操作,发生在运行时刻,所以通过反射可以绕过编译。另一种方式是把编译之后的Java字节码用jdk中提供的工具反编译成Java代码,就会发现编译后的集合泛型都是去泛型化的。
以上。