在面向对象设计思想中,使用类这一概念表述一类具有相同属性的对象;而这些属性值具体是什么,由该类的每个实例化对象来确定,每个对象可以有不同的属性值。反之,这些是否属于同一类事务,java使用Class来描述类的访问属性、包名、字段名。学习反射,首先就要了解这个首字母大写的-Class
一、Class—字节码
在源程序中,实例化某个类时,先将类编译成.class形成二进制代码,然后把二进制代码加载到java内存中,用于创建对象。这个二进制代码就是Class类的字节码(Byte-code)。当使用到多个类时,内存中就会加载多个不同的字节码,占用内存空间。字节码跟类是对应的,不同字节码在jvm中的内容以类的类型区分。
有了对象实例,通过obj.getClass()方法也可以获得这个对象在内存中的字节码,得到字节码方可得到这个对象所对应的类。反之,知道了这个类也可以获得实例对象。
二、一个New并不能解决问题
那么问题来了,有些人可能会说,干嘛那么麻烦都个大圈,我直接new 一个类,不就可以轻松创建这个实例吗?
这就涉及到java对象创建的两个编译方式:静态编译和动态编译
静态编译:在编译时确定对象,绑定对象即可通过编译。
动态编译:运行时确定类型,绑定对象。
静态编译使用new关键字就可以获取创建对象属性方法,但在动态编译时就行不通了。反射正是实现了动态创建对象和编译。通过反射,传入类名,形成字节码,加载到jvm中创建实例,然后获取实例信息,最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。
简言之,在程序还不知道要调用哪些类,创建哪些实例时,代码是无法将某个具体的类以new的方式实例化的。正如你还没结婚,没办法先给自己未来的小孩买各种女式婴儿用品,万一他是个男孩儿呢?使用反射的目的即在于:传入一个无论是什么类,通过反射都可一获取到这个类对应对象的属性、方法等信息。
联想一下Spring 等框架,框架设计者并不知道我们会创建哪些类,那为什么把bean网配置文件里一放,class属性一写,它就可以直接使用这个类的可用属性和公共方法了呢。这就是典型的反射应用。
三、反射具体怎么用?
通过反射,只要知道类名,就可以获取该类方法、属性、构造方法等信息,然后通过java API对这些方法进行操作,对属性赋值等。下面介绍几种常用的利用反射机制实现获取类信息类型;
构造方法(Constructor)
1)、获取类的构造方法 Class.forName(“java.lang.String”).getConstructors();//得到String类的所有构造方法
Class.forName(“java.lang.String”).getConstructor(“StringBuffer.class”);//得到某一个类的构造方法
2)、使用构造方法创建对象实例:
通常:String str=new String(new StringBuffer(“aaa”))
反射:Constructor c=Class.forName("java.lang.String").getDeclaredConstructor(StringBuffer.class);//获取StringBuffer类的方法构造器
String s=(String)c.newInstance(new StringBuffer("adf"));//创建名为adf的StringBuffer对象实例,强转成String类型输出
System.out.println(s.toString()); //adf
获取变量(Field)
例如创建一个坐标类Point,有xy两个成员变量。
1)、使用反射获取类的成员变量
System.out.println(Class.forName("java.lang.reflect.Array").getDeclaredFields()); //获取所有声明过的成员变量
System.out.println(Class.forName("java.lang.reflect.Array").getFields());//无法获取private 私有成员变量
2)、通过set、get方法对该对象属性进行数据操作
Field day=Class.forName("it.webservice.mobile.WeekDay").getField("day");//noSuchFieldEcxeption
day.setAccessible(true);
System.out.println(day.get(day));
使用反射获取变量在框架中使用频繁,以Spring为例,在Spring配置文件中写入类的包名.类名,添加property属性,程序启动时自动扫描配置文件,把类实例化,同时反射获取property属性信息就知道这个类中声明了多少个字段,再通过javaBean自省get/set的方式为对象取值赋值。使用非常广泛。
获取方法(Method)
1)、获取方法
Class.forName("java.lang.reflect.Array").getMethods();//返回一个method[]数组
2)、可以使用invoke方法调用该方法
Method[] methods=Class.forName("java.lang.reflect.Array").getMethods();
3)、某个类main方法的获取与调用
System.out.println(Class.forName("it.webservice.mobile.WeekDay").getMethod("main", String[].class));//方法名,方法传入参数类型
Class.forName("it.webservice.mobile.WeekDay").getMethod("main", String[].class).invoke(null, (Object)new String[]{"jdkaj"});
注意在获取类的main方法时,main方法的参数类型为String集合,在调用main方法时,注意第一个参数为null表示main的静态方法属性;第二个参数传入一个string数组,并强转成一个Object对象类型。因为如果传入普通数组,使用反射会把数组打散成若干个参数,所以转成一个Object对象传入invoke使得main传入正确参数。对于数组的反射应用会在下一篇博文中谈到。
四、反射在框架中的具体应用
在具体框架中一般都会结合配置文件的使用,把可变的传入类类型以配置文件配置的形式进行配置。简单的代码实现流程如下:
public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
InputStream input=new FileInputStream("config.properties");//加载config配置文件
Properties prop=new Properties();
prop.load(input);
input.close();
String className=prop.getProperty("className");//获取配置文件中className值:java.util.ArrayList
Collection cl=(Collection)Class.forName(className).newInstance();//实例化ArrayList
System.out.println(cl.toString());
}
通过查看Spring mvc 框架的部分源码,很容易发现这种加载配置文件-利用反射将类实例化的影子。例如在Spring MVC核心过滤器 dispatcher中,通过三级继承,父类实现ApplicationContextAware接口,而这个接口仅提供了一个方法
public interface ApplicationContextAware extends Aware {
/**
* Set the ApplicationContext that this object runs in.
* Normally this call will be used to initialize the object.
* Invoked after population of normal bean properties but before an init callback such
* as {@link org.springframework.beans.factory.InitializingBean#afterPropertiesSet()}
* or a custom init-method. Invoked after {@link ResourceLoaderAware#setResourceLoader},
* {@link ApplicationEventPublisherAware#setApplicationEventPublisher} and
* {@link MessageSourceAware}, if applicable.
* @param applicationContext the ApplicationContext object to be used by this object
* @throws ApplicationContextException in case of context initialization errors
* @throws BeansException if thrown by application context methods
* @see org.springframework.beans.factory.BeanInitializationException
*/
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
从这个方法漫长的注释不难看出,这个接口主要用于提供配置文件初始化时,调用初始化对象时使用的,也就是说,传入applicationContext参数,底层肯定跟上面代码的逻辑八九不离十,加载配置,同时利用反射初始化配置中的normal Bean 和properties。
一通百通,搞懂这些原理,可能确实在平时的编程中会相对比较少用到反射、委托等,但做平台、做框架的毋庸置疑,百分之百会使用到这些技术提高灵活扩展性。因为目前大多都是在用框架,毕竟金字塔顶端的也只是那么少部分。但如果遇到系统瓶颈了,考虑使用这些核心技术来解决问题的那一天就不远了。视野决定高度。
下一篇会介绍数组的反射应用,另外侧重介绍ArrayList 和HashSet在存储和使用上的区别与联系。