黑马程序员----java中强大的功能-反射

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

一、反射

1.反射的基础-Class类

java的反射功能始于jdk1.2,其基础是一个特殊的类-Class。

java程序中用到的各个java类的共性就是它们都是类,Class就是描述所有类的类,这是面向对象编程的思想的体现,类也是对象,抽取它们的特性,便得到了Class类。

Class的实例对象是什么呢?
举个例子:Person类代表人,其实例对象就是一个个具体的人,如张三、李四。

Class代表java中的类,它的对象对应各个类在内存中的字节码,例如Person类的字节码Person.class,ArrayList类的字节码Array.class等等。类会被类加载器加载到内存中,占用一片内存空间,里面存放的就是类的字节码,不同类的字节码不同,一个字节码可以用一个Class对象来表示。所以Class的实例对象就是一个类的字节码,一个类只有一个字节码,所以Class的对应每个类的实例对象只有一个。

得到字节码的方式有三种:

(1)类名.class  例如:System.class

(2)对象.getClass()  例如:new Data().getClass()

(3)Class.forName()  接收一个类名,返回该类的字节码,如果该字节码曾经被加载到虚拟机,则直接拿来用,如果没有加载过,则从类加载器中加载。

例如:Class.forName("java.util.Data");

字节码对象分类:

(1).九个预定义Class实例对象

包括八个基本数据类型(byte,short,int,long,double,float,char,boolean),例如int.class等效于Integer.TYPE,TYPE就是包装了基本类型的字节码,另外还有关键字void的字节码void.class,这九个是预定义的Class示例对象。
因为它们本身不是对象,所以单独预定义它们的字节码文件。
代码示例:

<span style="font-family:Microsoft YaHei;">public class ReflectTest 
{
	public static void main(String[] args) throws Exception
	{
	String str1 = "abc";
	
	Class cls1 = str1.getClass();
	Class cls2 = String.class;
	Class cls3 = Class.forName("java.lang.String");
	
	System.out.println(cls1==cls2);
	System.out.println(cls1==cls3);
	}
}</span>


 

结果为两个true,说明这三种方法获取的字节码都是同一个。

Class中的isPrimitive()方法可用来判断当前对象是否是基本数据类型的字节码对象,例如:

int.class.isPrimitive();

结果是true
注意:
int.class和Integer.class是不同的,一个是预定义的字节码对象,一个是包装类的字节码文件对象,不需要预定义,代表的对象是不同的。

(2).数组类型的Class实例对象

数组也是对象,因此也有对应的字节码文件,例如:

int[].class

Class中的isArray()方法可用来判断当前对象是否是数组的字节码对象,例如

int[].class.isArray();

结果是true

总之,只要在源程序中出现的类型,都有各自的Class实例对象。

 

2.了解了Class类,便有了反射的基础。

反射就是把java类中的各种成分映射成相应的java类。例如,一个java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、package等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通多调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。

二、构造方法的反射应用

若我们想要得到一个类中的构造方法,可以通过反射来实现。把构造方法作为一个个对象来看待,抽取它们的共性,就得到了Constructor类。

具体应用有:

1.得到某个类所有的构造方法:

Constructor[] constructors = Class.forName("java.lang.String").getConstructors();

2.得到某一个构造方法:

Constructor[] constructors = Class.forName("java.lang.String").getConstructors(StringBuffer.class);

注意:得到参数为StringBuffer的构造方法,应该传入tringBuffer的字节码对象

3.创建实例对象:

(1)通常方式:

String str = new String(new StringBuffer("abc"));

(2)反射方式:

String str = (String)constructor.newInstance(new StringBuffer("abc"));

对象constructor用newInstance传入StringBuffer参数,产生一个新String实例,但是为了便于理解该实例是String类型,所有要(String)强制转换。

传入参数必须与该构造方法的参数相匹配,否则会报非法参数异常。

4.Class.newInstance()方法:

例子:String obj=(String)Class.forName("java.lang.String").newInstance();

这里并没有显露出使用了构造方法的反射,其实该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。所以如果正好用到默认构造方法,用这个语句比较方便。

该方法内部的具体代码是怎样书写的呢?用到了缓存机制来保存默认构造方法的实例对象。

三、成员变量的反射

成员变量的反射基于Field类。
Field类代表某个类中的一个成员变量,只对应只对应字节码的变量,而不代表具体对象的变量,若要取得某个对象对应的变量,应将具体对象传入。

代码示例:

<span style="font-family:Microsoft YaHei;">public class ReflectPoint//自定义类
{
	private int x;
	public int y
	
	public ReflectPoint(int x,int y)
	{ 
		super();
		this.x=x;
		this.y=y;
	}
}

class ReflectDemo
{
	ReflectPoint pt1=new ReflectPoint(3,5);
	//用getField方法无法得到私有的变量,可以用getDeclareField得到,该方法即可以得到私有变量,也可以得到非私有变量
	Field fieldY=pt1.getclass().getField("y");
	System.out.println(fieldY.get(pt1));

	//getDeclareField方法虽然可以得到私有变量,却无法访问
	Field fieldX=pt1.gerClass().getDeclareField("x");
	
	//设置fieldX为可以访问
	fieldX.setAccessible(true);
	System.out.println(fieldX.get(pt1)); 	 
}</span>

 

练习题:

将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"

先通过反射得到成员变量的反射类对象,再用其getType()方法得到该变量的类型,返回的是字节码对象,判断其是否是String.class,若符合,说明这个变量类型是String,然后用replace方法替换字符即可

<span style="font-family:Microsoft YaHei;">import java.lang.reflect.*;

class ReflectPoint
{
	private int x;
	public int y;
	public String str1="ball";
	public String str2="basketball";
	public String str3="thinkpad";
	
	public ReflectPoint(int x,int y)
	{
		super(); 
		this.x=x;
		this.y=y;
	}
	public String toString()
	{
		return str1+":"+str2+":"+str3;
	}
}
class ReflectDemo
{
	public static void main(String[] args)throws Exception
	{
		ReflectPoint pt1=new ReflectPoint(3,5);
		changeStringValue(pt1);
		System.out.println(pt1);
	}
	private static void changeStringValue(Object obj) throws Exception
	{
		Field[] fields=obj.getClass().getFields();
		for(Field field:fields)
		{
			if(field.getType()==String.class)//这里用等号而不是equals方法,因为字节码文件只有一份
			{
				String oldValue= (String)field.get(obj);
				String newValue=oldValue.replace('b','a');
				field.set(obj,newValue);
			}
		}
	}
}</span>

四、成员方法的反射

Method类代表某个类中的一个成员方法。

得到类中的某一个方法:

Method charAt=Class.forName("java.lang.String").gerMethod("charAt",int.class
传入方法名和参数的字节码,得到String中的charAt方法

调用方法:

通常方式:System.out.println(str.charAt(1));

反射方式:System.out.println(charAt.invoke(str,1));

invoke方法:传入调用该方法的实例和该方法的参数列表。返回该方法的调用结果。

如果传递给Method对象的invoke()方法的一个参数为null,说明该Method对象对应的是一个静态方法。

jdk1.4和jdk1.5的invoke方法的区别:

JDK1.5:public Object invoke(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})形式。对于不是对象的参数,有自动装箱的功能。

练习题:

用反射方式执行某个类中的main方法

为什么要用反射的方法调用?因为有时候不知道该main方法存在于哪个类中,所以无法用类名.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,new Object[]{new String[]{"xxx"}});

mainMethod.invoke(null,(Object)new String[]{"xxx"});

编译器会做特殊处理,编译时不把参数当做数组看待,也就不会数组打散成若干个参数了。

<span style="font-family:Microsoft YaHei;">import java.lang.reflect.*;

class ReflectDemo
{
	public static void main(String[] args)throws Exception
	{
		//方法1:直接调用
		TestArguments.main(new String[]{"111","222","333"});
		
		//方法2:反射
		String startingClassName = args[0];//此处arg[0]为java运行时输入的字符串参数,此处应为"TestArguments"
		Method mainMethod = Class.forName(startingClassName).getMethod("main".String[].class);
		
		mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}});
		
		//下面是错误语句,因为为了兼容jdk1.4,虚拟机会对传入的数组进行拆包动作,导致参数错误
		//mainMethod.invoke(null,new String[]{"111","222","333"});
		//下面这句也是正确语句,因为将数组强调为Object后,不再被当做多个参数看待
		//mainMethod.invoke(null,(Object)new String[]{"111","222","333"});
	} 

class TestArguments
{
	public static void main(String[] args)
	{
		for(String arg:args)
		{
			System.out.println(arg);
		}
	}
} </span>

五、数组的反射

数组的一些性质:

具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。

int a1=new int[3];

int a2=new int[4];

int[][] a3 = new int[2][3];

String [] a4=new String[3];

System.out.println(a1.getClass()==a2.getclass());//结果为true
System.out.println(a1.getClass()==a4.getclass());//结果为false
System.out.println(a1.getClass()==a3.getclass());//结果为false

代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。

System.out.println(a1.getClass().getSuperclass().getName());

System.out.println(a4.getClass().getSuperclass().getName());

结果都是java.lang.Object,说明数组类型的父类都是Object

基本类型的一维数组可以被当作Object类型使用,不能当做Object[]类型使用,非基本类型的以为数组,既可以当做Object类型使用,又可以当做Objec[]类型使用。

Object[] Obj=a1;//错误,a1元素为基本类型

想要得到数组中的元素类型

Object[] a=new Object[]{"a",1};

a[0].getClass().getName();

六、反射的作用——实现框架功能

框架与工具类有区别,类被用户的类调用,而框架则是调用用户提供的类。

框架要解决的一个问题是,写框架的时候,用户还没有提供其自己写的类,框架如何调用到以后会出现的类呢?这个时候无法直接创建新类的实例对象,只能用反射的方式来调用。所以反射在框架上的应用比较多。

反射虽然强大,但是有其弊端,反射会导致代码臃肿,效率低下,性能下降,所以尽量不要在编程中大量使用。

 



更多方法参见API文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值