---------- android培训、java培训、期待与您交流! ----------
Java从1.2开始就有了反射这个技术,JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。Java领域像Spring、hibernate、struts都依赖于这个技术,把一个对象交给框架,它可以把你的对象通通改掉,如:spring给你注入点东西,struts2把网页提交过来的数据,自动转型封装成action返回给你,hibernate获得你的对象的信息做类与数据库表的映射。
反射的基石Class类
Java类用来描述一类事物的共性,而Class类就是描述了一个Java类的相关信息的,Class 类的实例表示正在运行的 Java 应用程序中的类和接口。Class类描述的信息有,类的名字、类的访问属性、类锁属于的包名、字段名称的列表、方法名称的列表等。在程序的运行期间,一旦我们想生成那个类的一个对象,用于执行程序的Java 虚拟机首先就会检查那个类型的Class 对象是否已经载入。如果没有载入,JVM 就会查找同名的.class 文件,并将其载入,一旦那个类型的Class 对象进入内存,就以它为模版来创建那一类型的所有对象。
以java.lang.String这个类来说明,有两种方式可以获得一个Java类的字节码,就是它对应的Class对象
第一种方法是通过类中一个静态的字段class获得 String.class
第二种方法是通过Java类的基类Object所提供的getClass方法 new String().getClass()
下面的这段代码打印的结果是true,表明每个Java类的字节码在程序中只有一份,这也很容易理解,有一份就够了
public static void main(String[] args) {
//通过上述的第一种方法拿到Class对象
Class c1 = String.class;
//通过上述的第二种方法拿到Class对象
Class c2 = new String().getClass();
//比较着两份字节码是否相同
System.out.println(c1 == c2);
}
Java 基本的 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也有自己的 Class 对象 ,这点Java api中Class类的描述中有提到,可以通过类型+.class获得,如int.class、void.class,这九个对象时Java预定义的Class实例对象。数组类型也有他们对象的Class对象,如:int[].class、long.class等。总的来说,只要源程序中出现的类型,都有各自的Class实例对象。
可以通过Class的静态方法forName获得一个Java对象,它的参数是一个Java类的完整名(包名+类名),如果找不到这个类会抛出ClassNotFound这个异常,
public static void main(String[] args) {
try {
//拿到java.lang.String这个类的字节码
Class strCls = Class.forName("java.lang.String");
//通过这份字节码创建一个String的对象
String str = (String) strCls.newInstance();
//打印String对象str的长度
System.out.println(str.length());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
上面这段代码演示了使用Class.ForName获得一个String对象,程序的打印结果为0,表示str不是一个空的引用,否则调用它的length方法就会抛出空指针异常,因此成功的拿到了一个String对象
Class类的常用方法有
public Annotation[] getAnnotations() 返回它所表示的类或接口上存在的所有注释的数组
public ClassLoader getClassLoader() 返回该类的类加载器
public Constructor<T> getConstructor(Class<?>... parameterTypes) 参数为构造函数的类型的Class,返回一个表示类的构造方法的 Constructor 对象
public Field getDeclaredField(String name) 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。name 参数是一个 String,它指定所需字段的简称
public Method[] getMethods() 返回一个包含某些 Method 对象的数组
public boolean isArray() 判定此 Class 对象是否表示一个数组类
public boolean isInterface() 判定指定的 Class 对象是否表示一个接口类型。
public boolean isPrimitive() 判定指定的 Class 对象是否表示一个基本类型
。。。。。反射技术就是围绕这这些方法进行的。
现在知道了Class类用来是描述Java类的,java类中的,构造方法、包、成员变量、所拥有的方法,Java也都提供了相应的描述对象,分别是Constructor、Package、Field、Method。
构造函数的反射
一个Constructor就代表一个Java类的构造方法,可以使用它来创建一个Java对象,通过代码来介绍下这个类
获得一个对象身上的所有的构造方法,返回的是一个Constructor的数组,打印出这个数组
public static void main(String[] args) {
//获得String类的所有构造方法
Constructor[] constructors = String.class.getConstructors();
for (Constructor c : constructors) {
System.out.println(c);
}
}
打印结果是
public java.lang.String()
public java.lang.String(java.lang.String)
public java.lang.String(char[])
public java.lang.String(char[],int,int)
public java.lang.String(int[],int,int)
public java.lang.String(byte[],int,int,int)
public java.lang.String(byte[],int)
public java.lang.String(java.lang.StringBuilder)
public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],int,int,java.nio.charset.Charset)
public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],java.nio.charset.Charset)
public java.lang.String(byte[],int,int)
public java.lang.String(byte[])
public java.lang.String(java.lang.StringBuffer)
可以看出通过String.class.getConstructors(),这个方法获得了String类的所有构造方法。如果要获得其中的一个构造方法,首先需要解决的问题是怎么样区分这些构造方法,依据是不同的构造方法的参数是不一样,看下Class类中获得指定构造方法的方法public Constructor<T> getConstructor(Class<?>... parameterTypes),它的参数是类型对应的Class,如果要获得无参的那个构造函数,就传递过去一个null值,还是用代码描述
public static void main(String[] args) throws SecurityException, NoSuchMethodException {
//获得String类的无参数的构造方法
Constructor c = String.class.getConstructor(null);
System.out.println(c);
//获得String类的以一个字符数组,构造出String对象的构造方法
Constructor c1 = String.class.getConstructor(char[].class);
System.out.println(c1);
}
打印结果是
public java.lang.String()
public java.lang.String(char[])
分别获得了对应的构造函数。获得构造函数的意义是,通过这个构造方法获得对象
public static void main(String[] args) throws Exception {
//获得String类的以一个字符数组,构造出String对象的构造方法
Constructor c1 = String.class.getConstructor(char[].class);
char[] chars = {'黑','马','程','序','员','训','练','营'};
//调用c1的newInstance方法,把chars传递过去,返回的是Object,把它强制转换为String类型
String str1 = (String)c1.newInstance(chars);
System.out.println(str1);
}
上面程序的打印结果是:黑马程序员训练营
成功的通过构造方法拿到一个String对象,相当于char[] chars = {'黑','马','程','序','员','训','练','营'}; String s = new String(chars);,Class类提供的newInstace方法,其实使用的就是对应类的空的构造方法,为了方便我们编程而提供的此方法。
下面就来说下对Java类各个元素的反射,以下面这个类为例子:
public class Point implements Serializable {
private int x;
private int y;
public Point(int x,int y) {
this.x = x;
this.y = y;
}
public String toString() {
return "x = " + x + "," + "y = " + y;
}
}
成员变量的反射
描述类成员变量的类为Field,它提供有关类或接口的单个字段的信息,以及对它的动态访问权限,它代表的是字节码中的一个成员变量,而不是某个对象的成员变量。反射的字段可能是一个静态字段或者是实例
字段,与获得Constructor类似, Class类也提供了一系列的获得Field的方法。
通过反射拿到 ReflectPoint对象的公共成员变量的值y
public static void main(String[] args) throws Exception {
//new出测试的对象
ReflectPoint p1 = new ReflectPoint(520,909);
//获得ReflectPoint类的成员变量y
Field field = ReflectPoint.class.getField("y");
//得到p1对象,字段y的值
System.out.println(field.get(p1));
}
程序运行结果:909,使用getField这个方法只能拿到访问符是public的成员,面向对象的三大特性之一是封装,在程序中成员变量一般 都是私有的,获得私有成员变量和获得公共的成员变量有区别。
通过反射拿到 ReflectPoint对象的私有成员变量的值x
public static void main(String[] args) throws Exception {
//new出测试的对象
ReflectPoint p1 = new ReflectPoint(520,909);
//获得ReflectPoint类的成员变量y
Field field = ReflectPoint.class.getDeclaredField("x");
//私有成员默认是不可访问的,要设置为可以访问
field.setAccessible(true);
//得到p1对象,字段y的值
System.out.println(field.get(p1));
}
拿到某个私有变量使用的方法getDeclaredField(name),有别于拿到公共对象。私有变量对应的Field,默认是不可见的,使用它之前首先需要通过Field类的setAccessible方法,设置下访问权限为true。这种反射方式也被成为暴利反射,在hibernate中如果把@ID这个注解设置在私有变量上,hibernate就可以获得它的值,使用的就是这个技术。
还可以通过反射把某个对象身上的值改掉
public static void main(String[] args) throws Exception {
//new出测试的对象
ReflectPoint p1 = new ReflectPoint(520,909);
//获得ReflectPoint类的成员变量y
Field field = ReflectPoint.class.getDeclaredField("x");
//私有成员默认是不可访问的,要设置为可以访问
field.setAccessible(true);
//得到p1对象,字段y的值
System.out.println("before: " + field.get(p1));
//把p1对象的x值修改为0
field.set(p1, 0);
System.out.println("after: " + field.get(p1));
}
方法的反射
对方法的反射使用的是Method这个类,Method类代表某个类中的一个成员方法。拿到字节码中的某个方法Method后,可以对具体一个对象调用这个方法,下面比较一下一般的方法调用和反射调用
public static void main(String[] args) throws Exception {
String str = "黑马程序员训练营";
Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
//若要获得str的第二个字符,通常的方式为
System.out.println(str.charAt(2));
//通过反射的方式
System.out.println(charAt.invoke(str, 2));
}
如果传递给Method对象的invoke方法的第一个参数为null,说明这个Method对象对应的是一个静态方法。
数组的反射
在对数组反射钱,首先要了解下关于数组类型有几个基础知识
1、具有相同维数和元素类型的数组属于同一个类型,既具有相同的Class实例对象
2、基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;而非基本数组,既可以当做Object类型使用,又可以当做Object[]类型使用
对于数组的反射,Array这个工具类提供了支持
现有一个数组,通过反射,获得这个数组的长度,并改掉第0个位置的值
public static void main(String[] args) throws Exception {
//初始化一个String数组
String[] strs = {new String("黑"),new String("马")};
//通过反射拿到strs的长度
System.out.println("反射 " + "strs的长度为" + Array.getLength(strs));
//通过反射改掉strs[0]
Array.set(strs, 0, new String("黑黒"));
//遍历strs数组,若打印出黑黒马,则反射成功
for (String s : strs) {
System.out.print(s);
}
}
从上面的程序可以看出,对数组的反射较为简单,使用jdk为我们提供的Array工具类即可
关于反射,大致就有这么多。关于反射大家可以去看张孝祥老师的Java高新技术视频,下载地址
http://www.itcast.cn/channel/video.shtml#java
---------- android培训、java培训、期待与您交流! ----------
详细请查看:http://edu.csdn.net/