您的位置:首页 > 职场人生

黑马程序员--Java基础加强(3)--反射

2013-11-25 17:28 483 查看
------------------
android培训、java培训、期待与您交流! ---------------------
反射不是JDK1.5的新特性,从JDK1.2就有了。Struts,Hibernate,Spring包括JUnit等框架都用到了反射。

一、 反射的基础—Class类

1、什么是Class类?

任何事物都可以看做一个对象,相同的一类对象就抽象成为一类。那么Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。

通俗类比:众多的人用什么类表示:Person。那么众多的Java类用什么类表示呢:Class 。

Class类中的方法getName()获取该类的名字,getMethods()获取该类中的方法,getInterface()获取该类实现的接口,都是所有类共有的属性。

2、Class类的实例对象代表内存中的一份字节码。

什么是字节码?

JVM想要使用某个类,先将这个类的二进制文件加载进内存获得其字节码文件,再使用这个字节码文件生产出一个个该类的对象来。每一个类都在内存中只有一份字节码,而类的字节码就是Class的实例对像。具体到内存,就是一个个存储一份份字节码的存储空间。

3、得到Class 字节码的三种方式

(1)类名.class,如Person.class;

(2)类的对象名.getClass();如 Person p = new Person(); p.getClass();

(3)Class.forName("具体的类名如:java.lang.String");

注:第三种方式用的较多,因为可以在类还未加载到内存中时,源程序中就可以使用,不需要事先知道该类的名字。可以将forname的参数作为一个未知字符串,等程序运行时再临时传进去,比较方便。

4、九个预定义的Class实例对象

八个基本数据类型的字节码文件(boolean,byte,char,short,int,long,float,double) + void对应的字节码文件。

注意:八个基本数据类型对应的类的字节码,并不是预定义的Class实例对象。

5、Class类的isPrimitive()方法 :是不是原始类型。

例如String.class.isPrimitive()返回false,int.class.isPrimitive()返回true, Integer.TYPE.isPrimitive()返回true, int[].class.isPrimitive()返回false,int[].class.isArray返回true。注意查看API。

二、反射及应用

1、反射就是把java类中的各个成分映射成相应的java类。

就是说,java类中的每一个成分,都能解析成为一个相应的类,比如,变量能解析成Field类,方法能解析成Method类,构造函数能解析成Constructor类。而java类中的每一个成分都能用相应的类的对象表示。Java类的class文件可以通过getField方法获取该类中某个变量的Field对象,然后加以操作。

2、构造函数的反射应用(Constructor)

(1)Constructor类代表某个类中的一个构造方法。

(2)得到某个类所有的构造方法,例如:Constructor[] constructors = Class.forName("java.lang.String").getConstructors();(注意构造函数必须是public的才能这样获得)

(3)得到某类某一个构造方法:例如:Constructor constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);//第二个参数表示想要获得的构造方法的参数类型。有什么样的参数类型,就传入什么类型的class文件。

(4)获得构造方法后,有很多用处,比如创建新的实例对象:

Constructor constructor = String.class.getConstructor(StringBuffer.class);

String str2 = (String)constructor.newInstance(new StringBuffer("abc"));//如果使用带参数的构造函数创建实例对象,前后参数要一致,前面用的是该参数类型类的class文件,后面用的是该参数类型类的实例。注意这里为什么要(String)强制转型?因为Constructor本来是有泛型的,这里没加,它不知道生成的实例到底是什么类型,所以需要强转一下。

(5)另外一种创建实例的方法Class的newInstance()方法

用该方法创建String对象:String obj = (String)String.class.newInstance();

该方法内部先得到默认的构造方法,然后用该默认构造方法创建实例对象。用到了缓存机制来保存默认构造方法的实例对象。

3、成员变量的反射(Field)

(1)Field类代表某个类中的一个成员变量。

(2)获得类的某个成员变量:Field fName = Person.class.getField(“name”);//获取Person类的成员变量name。

得到的Field对象是对应到类上面的成员变量,代表的是成员变量的定义,而不是具体的值,但是可以通过这个定义,获得某个对象的该变量的值。

举例如下:

ReflectPoint po = new ReflectPoint(3,5);
Field fieldY = po.getClass().getField("y");//fieldY的值是多少,是5吗?不是。FieldY不是对象身上的变量,而是类上的代表该变量的字节码,需要指定某个对象才能得到对应的值:
System.out.println(fieldY.get(po));//将打印出po对象的y变量的值5。


(3)暴力反射

如果成员变量是私有的,那么getField方法将获取不到该变量,这时需要方法getDeclaredField方法获取,但是获取了该变量也不能取出指定对象对应的变量值,这时需要设置标志.setAccessible(true);这样就能获得对象的变量值了。这个过程是强取,即暴力反射。

(4)、构造函数和成员变量反射的代码示例:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class MyReflectTest {
public static void main(String[] args) throws Exception{

Constructor[] pConstructors = Person.class.getConstructors();//获取Person类中的所有构造函数
Constructor pConstructor1 = Person.class.getConstructor(null);//获取Person类的无参构造函数
Constructor pConstructor2 = Person.class.getConstructor(String.class,int.class,String.class);//获取Person类含有三个参数的构造函数
for(Constructor con:pConstructors){//增强for循环,打印出获得的每个构造函数
System.out.println(con);
}

//使用反射所得的构造函数创建Person实例对象
Person p1 = (Person)pConstructor1.newInstance(null);
Person p2 = (Person)pConstructor2.newInstance(new String("zhangsan"),34,new String("beijing"));
System.out.println(p1);
System.out.println(p2);
//取出特定对象的变量值name
Field pFieldName = Person.class.getField("name");
System.out.println(pFieldName.get(p2));

//以下演示暴力反射,取出私有的age属性值
Field pFieldAge = Person.class.getDeclaredField("age");
pFieldAge.setAccessible(true);
System.out.println(pFieldAge.get(p2));

//成员变量反射应用练习:将成员变量所有字符串中的‘b’变成‘a’
Person p3 = new Person("qibaishi",75,"beijing");
Field[] fields = p3.getClass().getFields();//先将p3中的所有成员变量取出
for(Field field:fields){//遍历所有变量
if(field.getType()==String.class){//判断是不是String类型
String oldStr = (String)field.get(p3);//如果是String类型,取出,并替换
String newStr = oldStr.replace('b', 'a');
field.set(p3, newStr);//将替换后的String反馈给p3
}
}
System.out.println(p3);//打印p3看结果
}
}


4、成员方法的反射(Method)

(1)Method类代表类中的成员方法。

(2)如何获取成员方法并调用?先得到某个方法的Method对象,然后再针对调用该方法的对象调用该方法。

例子:获得String类中的charAt方法并调用。

String str1 = "abc";
Method methodCA = String.class.getMethod("chartAt",int.class);//两个变量分别为该方法的名字和该方法的参数列表
char c = methodCA.invkoe(str1,1);//invoke是调用的意思,两个参数分别表示调用该方法的对象和传入该方法的参数。如果第一个表示对象的参数为null,则说明该方法是静态的。


5、数组的反射应用

(1)具有相同的元素类型和相似维度的数组反射出来的class都是同一个字节码文件。

(2)数组与Object类的关系。

数组可以看做一个Object对象,但是基本数据类型int不可以看成一个Object对象。

int一维数组、二维数组和String数组的不同。

举例说明:

int[] a1 = new int[]{1,2,3};
int[][] a2 = new int[][]{{3,4,6}{7,8,9}{11,3,44}};
String[] str = new String[]{“ghjhtg”,”dssd”,”dfg”};
Object  o1 = a1;//对,一个int一维数组可以作为一个Object子类对象
Object  o2 = a2;//对,一个int二维数组可以作为一个Object子类对象
Object  o3 =str;//对,一个String数组可以作为一个Object子类对象

Object[] or1 = a1;//错I
Object[] or2 =a2;//对II
Object[] or3 =str;//对III


注意,Object数组中存储的应当是Object对象。

II对是因为, int二维数组赋值给Object[]数组,相当于object数组中每个元素都是一个int一维数组,每个int[]相当于一个Object对象,这是可以的。

III对事因为,String数组赋值给Object[]数组,相当于Object数组中每个元素是一个String字符串,每个String可以看成一个Object对象,所以这也是可以的。

I错是因为,int一维数组赋值给Object[]数组,相当于Object数组中每个元素存储的是一个int值,而int值并不是Object的子类对象,所以不对。

由以上区分,引申出,Arrays工具类中的asList方法处理int[]和String[]时的差异。如果传入的是String数组,将会将数组中的字符串元素打印出来,如果传入的是int型一维数组,则打印的还是该数组的哈希地址值,因为它会把int[]看成一个整体作为Object对象传入。

(3)位于java.lang.reflect包中的Array工具类用于完成对数组的反射操作。

举例:用于打印数组中的元素:

public static void printObject(Object obj){
Class class = obj.getClass();
if(class.isArray){
int len = Array.getLength(obj);//getLength(obj)用于获得数组的长度
for(int i=0;i<len;i++){//如果是数组就用Array工具操作
System.out.println(Array.get(obj,i));//get(obj,i)获取obj数组中脚标为i的元素
}
}else{ System.out.println(obj);}
}


(4)如何得到数组中元素的类型?

答案是无法获得,但是我们可以获得某个元素的类型,因为有可能数组中元素类型并不一定一样,

比如,

Object obj = new Object[]{"a",1};
obj.getClass().getName();//可以通过这种方式获得0元素的类型。


6、反射的作用

(1)反射的作用实现框架功能.

好处一,利用反射,对象参数可以在调用时才传入,不需要必须事先写好。调用者类先写好,被调用类只要调用类运行前写好就行。

例如,Struts框架,框架先写好,自己的代码后写好

(2)反射使用举例:调用运行时接收的参数类main方法:

注意运行时配置要传入的类参数,如:

import java.lang.reflect.Method;

public class ReflectApply {
public static void main(String[] args) throws Exception{

String className = args[0];//将传入的参数类名接收到className中
Class clazz = Class.forName(className);//将接收的类名获得其class文件
Method mainMethod  = clazz.getMethod("main", String[].class);//获得传入类的main方法
mainMethod.invoke(null, (Object)new String[]{"aaa","bbb","ccc"});//为这个类的main方法传入参数new String[]{"aaa","bbb","ccc"}
//注意参数String数组要封装成Object,否则会被JVM自动拆包,把每一个String元素都作为参数,这样会报参数类型不符错误。
}
}


别忘了Person类中加上主函数:

public static void main(String[] args){
for(String arg:args){//遍历主函数传入的参数,并打印
System.out.println(arg);
}
}


(3)另外,反射还可以从配置文件中读取信息,以达到用户使用,比如配置文件,键为String,值为类名,这样利用Properties根据键值获得类名,利用Class.forName()获得类的class文件,就可以进行相关操作。这样有时就可以只修改配置文件就能达到目的,功能更强大。

配置文件一般写上绝对路径,其路径最好是能让客户自己设置,然后用get方法获取的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐