声明:本文仅仅是总结学习笔记,使用了以下2篇文章
1. Java系列笔记JavaRTTI和反射机制
(http://wenku.baidu.com/link?url=9vDbJfeIXeDuBNcGad4jLWZ3otRJO6Nb3IIfIWC5_9IzaikRL5QOMmZFZz_jXf_NW-fRcApy58OHrzxSE1AovyqJl7pBWvlYCzaV3mC1SdG)
2. 张孝祥_Java_基础加强_高新技术笔记部分内容
一. 前言
并不是所有的 Class 都能在编译时明确,因此在某些情况下需要在运行时再发现和确定类型信息(比如:基于构建编程,),这就是 RTTI(Runtime TypeInformation,运行时类型信息)。
运行时类型识别(RTTI,Run-Time Type Identification)是Java中非常有用的机制,在Java运行时,RTTI维护类的相关信息。多态(polymorphism)是基于RTTI实现的。RTTI的功能主要是由Class类实现的。
在 java 中,有两种 RTTI 的方式,一种是传统的,即 假设在编译时已经知道了所有的类型;还有一种,是利用 反射机制,在运行时再尝试确定类型信息。
本文主要讲反射方式实现的RTTI,建议在阅读本文之前,先了解类的加载机制
在本文中,将共同使用下面的红苹果类 RedApple,该类中定义了公有、私有方法,变量,构造方法,父类、父接口等:
package com.xmm.trri;
/**
* 接口:水果
*/
public interfaceIFruit {
/**
* 方法:成长
* @param sun 太阳
* @return 成长信息
* @throws Exception
*/
String grow(String sun)throws Exception;
}
/**
* 抽象类:苹果
* 继承:水果
*/
public abstract class AbstractApple implements IFruit {
/**
* 方法:成长
* @param sun太阳
* @return 成长信息
* @throws Exception
*/
@Override
publicString grow(String sun)throwsException {
System.out.println(sun +"正在朝阳这苹果树呢!");
return"";
}
}
/**
* 类:红苹果
* 继承:苹果
*/
public classRedApple extendsAbstractApple {
//名字
privateStringname;
//颜色
publicStringcolor;
//个数
protected int size;
//单个售价
public static final int price= 10;
/**
* 无参构造函数
* 一定声明为public类型,否则getConstructors无法得到
*/
publicRedApple() {
super();
System.out.println("初始化[RedApple]无参构造函数");
setName("红富士");
color= "红色";
size= 5;
}
/**
* 有参构造函数
* @param name 名字
* @param color颜色
* @param size 个数
*/
publicRedApple(String name, String color,int size) {
this.setName(name);
this.name= name;
this.color= color;
this.size= size;
}
/**
* 获得:红苹果名称
*/
publicString getName() {
returnname;
}
/**
* 设置:红苹果名称
*/
public void setName(String name) {
this.name= name;
}
/**
* 方法:成长
* @param sun太阳
* @return 成长信息
* @throws Exception
*/
@Override
publicString grow(String sun)throwsException {
return super.grow(sun);
}
/**
* 构建消息
* @param sun太阳
* @return 成长信息
*/
privateString buildMessage(String sun){
return"红富士苹果正在享受"+ sun +"呢!";
}
}
二. 传统的 RTTI
传统的 RTTI严格的说,反射也是一种形式的 RTTI,不过,一般的文档资料中把 RTTI 和反射分开,因为一般的,大家认为 RTTI 指的是传统的 RTTI,通过继承和多态来实现,在运行时通过调用超类的方法来实现具体的功能(超类会自动实例化为子类,或使用 instance of)。
传统的 RTTI 有 3 种实现方式:
1. 向上转型或向下转型(upcasting and downcasting) ,在 java 中,向下转型(父类转成子类)需要强制类型转换
2. Class 对象(用了 Class 对象,不代表就是反射,如果只是用 Class 对象 cast 成指定的类,那就还是传统的 RTTI)
3. instanceof 或isInstance()
传统的 RTTI 与反射最主要的区别,在于 RTTI 在编译期需要.class 文件,而反射不需要。传统的 RTTI 使用转型或 Instance 形式实现,但都需要指定要转型的类型,比如:
public voidrtti(Object obj) throwsClassNotFoundException {
//类型转换
IFruitredApple = (RedApple)obj;
//instanceof判断
if (objinstanceof RedApple){}
}
注意其中的 obj 虽然是被转型了,但在编译期,就需要知道要转成的类型RedApple,也就是需要 RedApple的.class 文件。相对的,反射完全在运行时在通过 Class 类来确定类型,不需要提前加载RedApple的.class 文件
三. 反射
摘要
Reflection 是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。
一句简单的话:反射就是把Java类中的各种成分映射成相应的java类。
那到底什么是反射(Reflection)呢?反射有时候也被称为内省(Introspection),事实上,反射,就是一种内省的方式,Java 不允许在运行时改变程序结构或类型变量的结构,但它允许在运行时去探知、加载、调用在编译期完全未知的 class,可以在运行时加载该 class,生成实例对象(instance object),调用 method,或对 field 赋值。这种类似于“看透”了 class 的特性被称为反射(Reflection),我们可以将反射直接理解为:可以看到自己在水中的倒影,这种操作与直接操作源代码效果相同,但灵活性高得多。
关于 Java 的反射 API,没必要去记忆,可以在任何 JDK API 中查询即可:
Class 类:http://www.ostools.net/uploads/apidocs/jdk-zh/java/lang/Class.html
reflect 包:http://www.ostools.net/uploads/apidocs/jdk-zh/java/lang/reflect/package-summary.html
四. 反射的实现方式
package com.xmm.trri;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
public classTestApple {
public static voidmain(String[] args) {
try {
//获得类对象
Class> clazz = Class.forName("com.xmm.trri.RedApple");
printInfo("获得类对象",clazz);
/*--获得类对象: class com.xmm.trri.RedApple--*/
// 获得超类
Class> superClass =clazz.getSuperclass();
printInfo("获得超类",superClass);
/*--获得超类: classcom.xmm.trri.AbstractApple--*/
// 获得所有父接口
Class>[] interfaces =clazz.getInterfaces();
printInfo("获得所有父接口",Arrays.toString(interfaces));
/*--获得所有父接口: []--???--*/
// 实例化
RedApple redApple = (RedApple)clazz.newInstance();
printInfo("实例化",redApple);
/*--实例化: com.xmm.trri.RedApple@961dff--*/
// 获得访问属性为 public的构造方法
Constructor>[] constructors =clazz.getConstructors();
printInfo("获得构造方法",Arrays.toString(constructors));
/*--获得构造方法: [public com.xmm.trri.RedApple(),
* public com.xmm.trri.RedApple(java.lang.String,java.lang.String,int)]--*/
// 获得指定参数的构造方法
Constructor> constructor =clazz.getDeclaredConstructor(
String.class, String.class,int.class);
printInfo("获得指定构造方法",constructor);
/*--获得指定构造方法: publiccom.xmm.trri.RedApple(java.lang.String,java.lang.String,int)--*/
// 获得方法,getMethod只能获得 public 方法,包括父类和接口继承的方法
Method method = clazz.getMethod("grow", String.class);
printInfo("获得公有方法",method);
/*--获得公有方法: public java.lang.Stringcom.xmm.trri.RedApple.grow(java.lang.String)
* throws java.lang.Exception--*/
// 调用方法
method.invoke(redApple, "中午太阳");
/*--中午太阳正在照耀这苹果树呢!--*/
// 获得修饰符,包括 private/public/protect,static
String modifier = Modifier.toString(method.getModifiers());
printInfo("获得方法修饰符",modifier);
/*--获得方法修饰符: public--*/
// 获得参数类型
Class>[] paramTypes =method.getParameterTypes();
printInfo("获得方法参数类型",Arrays.toString(paramTypes));
/*--获得方法参数类型: [class java.lang.String]--*/
// 获得返回值类型
Class> returnType =method.getReturnType();
printInfo("获得方法返回值类型",returnType);
/*--获得方法返回值类型: class java.lang.String--*/
// 获得异常类型
Class>[] excepTypes =method.getExceptionTypes();
printInfo("获得方法异常类型",Arrays.toString(excepTypes));
/*--获得方法异常类型: [classjava.lang.Exception]--*/
// 调用私有方法,getDeclaredMethod获得类自身的方法,
// 包括 public,protect,private方法
Method method2 =clazz.getDeclaredMethod("buildMessage",String.class);
// 暴力反射
method2.setAccessible(true);
String result =(String)method2.invoke(redApple, "中午太阳");
printInfo("获得私有方法",result);
/*--获得私有方法:红富士苹果正在享受中午太阳呢!--*/
// 获得全部字段
Field[] fields = clazz.getFields();
printInfo("获得类全部字段",Arrays.toString(fields));
/*--
* 获得类全部字段:[public java.lang.String com.xmm.trri.RedApple.color,
[public static final intcom.xmm.trri.RedApple.price]
* --*/
// 获得类自身定义的指定字段
Field field = clazz.getDeclaredField("name");
printInfo("获得自身指定的名称字段",field);
/*--获得自身指定的名称字段: private java.lang.String com.xmm.trri.RedApple.name--*/
// 获得类及其父类,父接口定义的 public字段
Field field2 = clazz.getField("color");
printInfo("获得公有的指定名称字段",field2);
/*--获得公有的指定名称字段: public java.lang.Stringcom.xmm.trri.RedApple.color--*/
// 获得字段权限修饰符,包括private/public/protect,static,final
String fieldModifier = Modifier.toString(field2.getModifiers());
printInfo("获得字段权限修饰符",fieldModifier);
/*--获得字段权限修饰符: public--*/
// 操作数组
int[] exampleArray = { 1, 2, 3, 4, 5 };
// 获得数组类型
Class> componentType =exampleArray.getClass().getComponentType();
printInfo("数组类型",componentType.getName());
/*--数组类型:int--*/
// 获得长度
printInfo("数组长度",
Array.getLength(exampleArray));
/*--数组长度: 5--*/
// 获得指定元素
printInfo("获得数组元素",
Array.get(exampleArray, 2));
/*--获得数组元素: 3--*/
// 修改指定元素
Array.set(exampleArray, 2, 6);
printInfo("修改数组元素",Arrays.toString(exampleArray));
/*--修改数组元素: [1, 2, 6, 4, 5]--*/
// 获得当前的类加载器
printInfo("获得当前类加载器",
redApple.getClass().getClassLoader().getClass().getName());
/*--获得当前类加载器:sun.misc.Launcher$AppClassLoader--*/
} catch (ClassNotFoundException e) {
e.printStackTrace();
}catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
public static voidprintInfo(String info, Object obj) {
if (obj.getClass().isArray()) {
System.out.println(info +": ");
int length = Array.getLength(obj);
System.out.println("Array Size: " + length);
for (int i = 0; i < length; i++) {
System.out.print("Array[" + i +"]: " + Array.get(obj, i)
+ ", ");
}
if (length != 0)
System.out.println();
}
System.out.println(info +": " + obj.toString());
}
}
// Modifier 类提供了static
方法和常量,对类和成员访问修饰符进行解码
// Array
类提供了动态创建和访问 Java数组的方法
通过上面的代码,可以清晰的理解如何“在水中看到自己”,不过需要注意的有几点:
1. 在 java 的反射机制中,getDeclaredMethod 得到的是全部方法,getMethod 得到的是公有方法;
2. 反射机制的 setAccessible 可能会破坏封装性,可以任意访问私有方法和私有变量;
3. setAccessible 并不是将 private 改为 public,事实上,public 方法的accessible属性也是 false 的,setAccessible 只是取消了安全访问控制检查,所以通过设置 setAccessible,可以跳过访问控制检查,执行的效率也比较高。参考:
http://blog.csdn.net/devilkin64/article/details/7766792
五. 反射的性能
反射机制给予 Java 开发很大的灵活性,但反射机制本身也有缺点,代表性的缺陷就是反射的性能,一般来说,通过反射调用方法的效率比直接调用的效率要至少慢一倍以上。关于性能的问题,可以参考这篇博客
http://blog.csdn.net/l_serein/article/details/6219897
六. 反射与设计模式
反射的一个很重要的作用,就是在设计模式中的应用,包括在工厂模式和代理模式中的应用。关于这一方面,我会在后续的文章中介绍,有兴趣的朋友也先可以参考这篇文章
http://www.cnblogs.com/rollenholt/archive/2011/09/02/2163758.html中关于动态代理模式实现方法的文章。
七. 其他
1. 将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"。
privateStringa = "a-b";
protectedStringb = "b-b";
publicStringc = "c-b";
publicString toString(){
return"a:" + a + "--b:" + b + "--c:" + c;
}
public static voidmain(String[] args)throwsInstantiationException, IllegalAccessException, ClassNotFoundException,IllegalArgumentException, SecurityException, InvocationTargetException,NoSuchMethodException {
//1. 获得Class对象
Class clazz = Class.forName("com.ldcms.hotel.action.Test");
//2. new一个对象
Test test = (Test) clazz.newInstance();
//3. 获得对象的所有字段
Field[] fields =clazz.getDeclaredFields();
//4. 遍历字段集
for(Field field : fields) {
//5.去除安全安全检查
field.setAccessible(true);
//6.获得字段的值
String value = (String)field.get(test);
//7.替换值
if(value.contains("b")){
value.replace("b","a");
//8.重新设置值
field.set(test, value.replace("b","a"));
}
}
//9. 打印输出
System.out.println(clazz.getDeclaredMethod("toString",null).invoke(test,null));
//结果:a: a-a--b: a-a--c:c-a
}
2. Method类代表某个类中的一个成员方法得到类中的某一个方法:
例子: Method charAt =Class.forName("java.lang.String").getMethod("charAt",int.class);
调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1));
如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法!
jdk1.4和jdk1.5的invoke方法的区别:
Jdk1.5:public Objectinvoke(Object obj,Object... args)
Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。
3. 用反射方法执行某个类中的main方法
目标:
写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。用普通方式调完后,大家要明白为什么要用反射方式去调啊?
问题:
启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。
解决办法:
mainMethod.invoke(null,newObject[]{new String[]{"xxx"}});
mainMethod.invoke(null,(Object)newString[]{"xxx"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了