Java中的反射机制
Posted 王英豪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中的反射机制相关的知识,希望对你有一定的参考价值。
在我的上篇文章模拟JDK动态代理实现中涉及到了反射机制,学无止境,在此系统的学习一下Java中的反射机制。首先给出Java反射机制的定义:
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
从以上的描述中可以看出Java中的动态特性,那么Java属于动态语言吗?一般而言说到动态语言,大致认同的一个定义是:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。
尽管在这样的定义与分类下Java不是动态语言,但是它却有着一个非常突出的动态相关机制,即反射机制。通过Java反射机制可以在运行时才加载class,得到这个类的类类型的相关信息,生成实体对象、或对其成员变量设值、唤起方法。
总上,Java反射机制主要提供了以下功能: 在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。
类的类类型(com.lang.Class类的实例对象)
当Java虚拟机加载一个类时,会隐含的创建描述这个类的Class实例,通常把这个Class类的实例对象叫做这个类的类类型( class type),这样更易理解。下面为一个简单示例:
package com.reflact;
public class Test1 {
public static void main(String args[]){
Car car1 = new Car();
}
}
我们都知道 car1 是 Car类 的一个实例对象,那么 Car 这个类到底是什么呢? 这个 Car 类 本身也是一个对象 。在面向对象的世界里,万事万物皆对象,类也是对象,是Class类的实例对象。为了更易理解,我们把这个对象叫做该类的类类型(class type)。对比 car1 和 Car ,可以理解成car1 对象是按照Car这个类的类型来实例化的,同样Car类也有它自己的 类类型,可以通过以下三种方式获取Car类的类类型(class type):
package com.reflact;
public class Test1 {
public static void main(String args[]){
Car car1 = new Car();
Class c1 = Car.class;
Class c2 = car1.getClass();
try {
Class c3 = Class.forName("com.reflact.Car");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
c1、c2和c3都是Car类的类类型(class type),其中第一种方式和第三种方式通过类来获取类类型,第二种是通过类的实例化对象来获取类类型。那么c1、c2、c3是否相等呢?做以下测试:
package com.reflact;
public class Test1 {
public static void main(String args[]) throws ClassNotFoundException {
Car car1 = new Car();
Class c1 = Car.class;
Class c2 = car1.getClass();
Class c3 = Class.forName("com.reflact.Car");
System.out.println(c1 == c2);
System.out.println(c1 == c3);
}
}
测试结果:
按照类类型的角度很容易理解,c1、c2、c3都代表了Car类的类类型,所以它们是同一个东西,什么东西呢?上面提到类是java.lang.Class类的实例对象,也就是说c1、c2、c3都是Class类的实例对象,而一个类只能是Class类的一个实例化对象,所以c1、c2、c3是Class类的同一个实例对象,所以它们相等。
通过类的类类型创建类的实例对象
在初学Java创建对象的方式时知道有显式创建对象和隐式创建对象两类创建方式,其中显式创建对象有以下四种方式:
- 用new语句创建对象
- 运用反射手段
- 调用对象的clone()方法
- 运用反序列化手段
其中运用反射手段就是通过类的类类型来创建类的实例对象。示例如下:
Class c1 = Car.class;
try {
Car car2 = (Car)c1.newInstance();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
那么通过反射手段来创建类的实例对象相比 new语句创建对象有什么优点呢?比如Class c3 = Class.forName(“com.reflact.Car”);这个语句可以实现类的动态加载,首先我们来了解下类的静态加载和动态加载:
- 类的静态加载:类在编译时就要提供
- 类的动态加载:类在编译时可以不存在
而通过反射手段可以实现类的动态加载,所以反射就有了相当重要的作用,比如某些框架中(struts,spring)使用反射来根据配置文件中的类路径来找到类的位置,然后动态执行你事先实现的方法;再比如在json序列化中,需要使用反射找到你类的所有成员,然后在动态获取这些成员的值,然后生成一个类似于{propertyName:value}的json字符串。
基本数据类型的类类型
在上面涉及到的是Car类的类类型,其实基本数据类型也有自己的类类型。如下所示:
Class a = int.class;
Class b = String.class;
Class c = double.class;
Class d = Double.class;
Class e = void.class;
Class f = Void.class;
System.out.println(a.getName());
System.out.println(b.getName());
System.out.println(c.getName());
System.out.println(d.getName());
System.out.println(e.getName());
System.out.println(f.getName());
输出结果:
到现在已经了解了什么是类的类类型、怎么获得类的类类型、通过类的类类型可以创建类的实例对象等,那么通过类的可以获得哪些信息呢?
通过类类型获取类的方法信息
package com.reflact;
import java.lang.reflect.Method;
public class ClassUtil {
public static void printClassMethodMessage(Object obj){
//通过类的实例对象获取类的类类型
Class c = obj.getClass();
//获取类的名称
System.out.println("类的名称:"+c.getName());
//获取类的所有public方法,包括父类继承而来的
Method[] ms = c.getMethods();
//获取该类自身声明的所有方法
Method[] ms2 = c.getDeclaredMethods();
for(int i=0;i<ms.length;i++){
//获取方法的返回值类型的类类型
Class returnType = ms[i].getReturnType();
System.out.print(returnType.getName()+" ");
//得到方法名称
System.out.print(ms[i].getName()+"(");
//得到方法参数列表的类型的类类型
Class[] paramTypes = ms[i].getParameterTypes();
for(Class param:paramTypes){
System.out.print(param.getSimpleName()+", ");//过滤掉包名的类名
}
System.out.print(")");
System.out.println();
}
}
}
测试结果如下:
通过类类型获取类的成员变量信息
package com.reflact;
import java.lang.reflect.Field;
public class ClassUtil {
public static void printClassFieldMessage(Object obj){
//通过类的实例对象获取类的类类型
Class c = obj.getClass();
//获取类的名称
System.out.println("类的名称:"+c.getName());
//获取所有public成员变量信息
Field[] fs = c.getFields();
//获取该类自身声明的所有成员变量
Field[] fs2 = c.getDeclaredFields();
for(Field f:fs){
//获取成员变量类型的类类型
Class fieldType = f.getType();
//获取成员变量的类型
String typeName = fieldType.getName();
//获取成员变量的名称
String fieldName = f.getName();
System.out.println(typeName+" "+fieldName);
}
}
}
测试类:
package com.reflact;
public class Test1 {
public static void main(String args[]) throws ClassNotFoundException {
Car car1 = new Car();
// ClassUtil.printClassMethodMessage(car1);
ClassUtil.printClassFieldMessage(new Integer(1));
}
}
测试结果:
通过类类型获取类的构造函数信息
package com.reflact;
import java.lang.reflect.Constructor;
public class ClassUtil {
public static void printConMessage(Object obj){
//通过类的实例对象获取类的类类型
Class c = obj.getClass();
//获取类的名称
System.out.println("类的名称:"+c.getName());
//获取所有的public的构造函数
Constructor[] cs = c.getConstructors();
//获取所有构造函数
Constructor[] cs2 = c.getDeclaredConstructors();
for(Constructor con:cs2){
//获取构造函数名称
System.out.print(con.getName()+"(");
//获取构造函数的参数列表类型的类类型
Class[] paramTypes = con.getParameterTypes();
for(Class cls:paramTypes){
System.out.print(cls.getName()+", ");
}
System.out.print(")");
System.out.println();
}
}
}
测试类:
package com.reflact;
public class Test1 {
public static void main(String args[]) throws ClassNotFoundException {
Car car1 = new Car();
// ClassUtil.printClassMethodMessage(car1);
// ClassUtil.printClassFieldMessage(new Integer(1));
ClassUtil.printConMessage(new String("hello"));
}
}
测试结果:
以上示例通过类的类类型获取了类的方法、成员变量以及构造函数信息,通过类的类类型还可以获得除此之外的其他信息,比如类的接口信息、访问权限、包名等等,可查阅文档了解。
通过类类型获取方法后调用方法
package com.reflact;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test1 {
public static void main(String args[]){
//获取类的类类型
A a1 = new A();
Class c = a1.getClass();
//获取方法 名称和参数确定
try {
//参数列表为可变数组 可用以下两种方式表示
Method md = c.getMethod("print", new Class[]{int.class,int.class});
Method md2 = c.getMethod("print", int.class,int.class);
//方法的反射操作,参数列表为可变数组,同样以下两种方式都可表示
Object returnValue = md.invoke(a1, new Object[]{10,20});//无返回值则返回null
Object returnValue2 = md.invoke(a1, 10,20);
Method md3 = c.getMethod("print");
md3.invoke(a1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class A {
public void print(){
System.out.println("null.........");
}
public void print(int a, int b) {
System.out.println(a+b);
}
public void print(String a, String b) {
System.out.println(a.toUpperCase()+","+b.toLowerCase());
}
}
测试结果:
学到这里已经明显和之前动态代理部分的知识关联起来了,Java中的反射机制真的特别重要,最近学的spring中依赖注入和AOP的底层实现都是建立在反射机制上的。对反射机制理解透彻有助于我们去理解那些框架,当然在我认为并框架不重要,学好这些框架底层的实现原理比学会框架的使用重要的多的多,这也能决定一个程序员思考的深度,不至于被淹没在花样百出的框架中。关于Java的反射机制用处十分广泛,比如:
通过反射机制了解集合泛型的本质
首先来看以下简单示例:
package com.reflact;
import java.util.ArrayList;
public class Test2 {
public static void main(String[]args){
ArrayList list1 = new ArrayList();
ArrayList<String> list2 = new ArrayList<String>();
list1.add("hello");//编译通过
list1.add(10);//编译通过
list2.add("hello");//编译通过
list2.add(10);//编译出错
}
}
如果定义了ArrayList的泛型为String,就只能添加Sting类型的对象;如果没有指定泛型,则可添加多种类型。上例中list1没有指定泛型,list2指定了String泛型,那么他们的类类型是否相等呢?
package com.reflact;
import java.util.ArrayList;
public class Test2 {
public static void main(String[]args){
ArrayList list1 = new ArrayList();
ArrayList<String> list2 = new ArrayList<String>();
Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1==c2);
}
}
测试结果:
由于反射操作是在编译操作完成之后进行的,也就是说编译之后集合的泛型是去泛型化的。那指定泛型有什么作用呢?答案就是Java的集合泛型是用来防止错误输入的,只在编译阶段有效。一般来说一个集合内存放的数据类型是相同的,所以用泛型来增强程序的健壮性,减少出错率。那我们能不能绕过编译阶段往规定泛型为String类型的集合里添加int类型呢?答案也是可以的,这就用到了上面的方法反射操作。
测试代码如下:
package com.reflact;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class Test2 {
public static void main(String[]args){
ArrayList<String> list = new ArrayList<String>();
Class c = list.getClass();
try {
Method m = c.getMethod("add", Object.class);
m.invoke(list, 100);
m.invoke(list, 200);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
测试结果:
以上示例通过方法反射操作,成功的将int类型数据放入了泛型规定为String类型的集合中。
以上是关于Java中的反射机制的主要内容,如果未能解决你的问题,请参考以下文章