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

------------------ android培训java培训、期待与您交流! ---------------------

反射不是JDK1.5的新特性,从JDK1.2就有了。StrutsHibernateSpring包括JUnit等框架都用到了反射。

一、 反射的基础—Class

1、什么是Class类?

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

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

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

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

        什么是字节码?

        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实例对象

      八个基本数据类型的字节码文件(booleanbytecharshortintlongfloatdouble) + void对应的字节码文件。

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

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

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

二、反射及应用

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

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

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

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

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)另外一种创建实例的方法ClassnewInstance()方法

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

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

 3成员变量的反射(Field

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

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

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

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方法获取的。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值