黑马程序员-java反射

---------------------- ASP.Net+Unity开发.Net培训、期待与您交流! ----------------------


一、反射的基础:Class

1、Class概述

    1)Java程序中的各个Java类属于同一类事物,描述这类事物的Java类就是Class(注意与class区分)。Class类描述了类的名字,类的访问属性,类所属于的包名,字段名称 的列表,方法名称的列表,构造方法的列表等。

    2)Class类代表Java类,它的各个实例对象分别对应各个类在内存中的字节码。例如,Person类的字节码,ArrayList类的字节码等。

    3)一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码。不同的类的字节码是不同的,所以它们在内存中的内容是不同的。


2、如何得到各个字节码对应的实例对象(Class类型)

    1)类名.Class。例如,System.Class。

    2)对象.getClass()。例如,new Date().getClass()。

    3)Class.forName("类名")。例如,Class.forName("java.util.Date");。

注意:

    1)Class.forName("类名")的作用:返回字节码。若该字节码已经被类加载器加载进java虚拟机里了,则直接返回该字节码;若该字节码尚未在java虚拟机里,则先通过类加载器加载进虚拟机里,然后再返回该字节码。

    2)反射中多使用第三种方法。因为往往编写程序时,还不知道某个类的名字,只有运行时,别人将类名传进来时才知道。第三种方法可以定义一个变量用于接收别人传来的类名,这样程序比较弹性;而前两种方法均将类名直接写死,程序弹性较低。


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

    1)boolean、byte、char、short、int、long、float、double八大基本类型以及void关键字,这九个均为java预定义Class实例对象。

    2)isPrimitive()方法:判断某个Class实例对象是否是预定义的。

    3)int.class==Integer.TYPE,其他基本类型与其包装类的关系同理。

例1:

package com.cn.itcast;

public class Reflect {
	public static void main(String[] args)throws Exception{
		String str1="abc";
		//获取Class对象的三种方法
		Class cls1=String.class;
		Class cls2=str1.getClass();
		Class cls3=Class.forName("java.lang.String");
		//三种方法获得的均是同一份字节码
		System.out.println(cls1==cls2);
		System.out.println(cls1==cls3);
		
		//九大预定义类型及isPrimitive()判断方法
		System.out.println(cls1.isPrimitive());
		System.out.println(int.class.isPrimitive());
		System.out.println(void.class.isPrimitive());
		System.out.println(int.class==Integer.class);
		System.out.println(int.class==Integer.TYPE);
		System.out.println(int[].class.isPrimitive());//数组也有Clss对象,但不是预定义类型
		System.out.println(int[].class.isArray());	
	}
}
输出结果:

true
true
false
true
true
false
true
false
true

二、反射概述

反射就是把java类中的各种成分映射成相应的java类。例如,一个java类用一个Class类的对象表示,那么一个类中的各个组成部分:成员变量、方法、构造方法、包等信息也可以用一个个的java类来表示,它们分别是Field、Method、Constructor、Package等。就像汽车是一个类,汽车中的发动机、变速箱等也是一个个的类。表示java类的Class类显然要提供一系列的方法来获得这些信息。

一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,并可以通过调用Class类的相应方法获得这些实例对象。那么得到这些实例对象后有什么用?怎么用?这正是学习和应用反射的要点。


三、Constructor类

Constructor类代表类中的构造方法。它可以获得构造方法的名字,获得所属于的类,也可以创建该类实例对象。

1、获得构造方法

获得某个类所有的构造方法:getConstructors()。例如,

Constructor[] constructors=Class.forName("java.lang.String").getConstructors();
获得某一个构造方法:getConstructor(Class<?>... parameterTypes)。一个类中有多个构造方法, 通过参数列表加以区分。其中, 参数类型用Class实例对象表示。例如,
Constructor constructor=Class.forName("java.lang.String").getConstructor(StringBuffer.class);//获得String(StringBuffer sb)构造方法。

2、创建实例对象

普通方式:

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

反射方式:

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

注意:利用反射创建实例对象时,传递的参数需是与获得构造方法时的参数相同类型的实例对象。例如,

String str=(String)constructor.newInstance("abc"));//错误,前面获得构造方法时参数类型为StringBuffer,现在创建实例对象时参数类型为String,不匹配。

3、Class.newInstance()方法

1)该方法内部先得到默认的无参构造方法,然后用该构造方法创建实例对象。例如,

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

2)该方法内部的代码用到了缓存机制来保存默认构造方法的实例对象,这说明反射比较消耗资源,会导致程序性能严重下降。


四、Field类

Field类代表类中的成员变量。它可以获得某个具体对象中某个成员变量的值,也可以设置它的值等。

1、获得成员变量

获得某个类所有可访问公共成员变量:getFields()。

获得某一个成员变量:getField(String name)。用成员变量的名字加以区别。

2、获得\设置某个具体实例对象的某个成员变量的值

获得某个具体实例对象的某个成员变量的值:get(Object obj)。

设置某个具体实例对象的某个成员变量的值:set(Object obj,Object value)。

例2:

package com.cn.itcast;

public class ReflectPoint {
	public int x;
	private int y;
	public String str1="ball";
	public String str2="basketball";
	public String str3="itcast";
	
	public ReflectPoint(int x,int y){
		super();
		this.x=x;
		this.y=y;
	}
	@Override
	public String toString(){
		return str1+":"+str2+":"+str3;
	}
}
package com.cn.itcast;

import java.lang.reflect.Field;

public class ReflectTest {

	public static void main(String[] args)throws Exception {
		ReflectPoint rp=new ReflectPoint(3, 5);
		//获得成员变量
		Field fieldX=rp.getClass().getField("x");//可获得可访问公共成员变量x
		System.out.println(fieldX.get(rp));
		
		/*Field fieldY=rp.getClass().getField("y");
		System.out.println(fieldY.get(rp));//错误,NoSuchFieldException: y。私有成员无法获得。
*/	
		/*Field fieldY=rp.getClass().getDeclaredField("y");
		System.out.println(fieldY.get(rp));//错误,IllegalAccessException。私有成员无法访问。
*/
		Field fieldY=rp.getClass().getDeclaredField("y");
		fieldY.setAccessible(true);				 //暴力反射,强制获取成员变量访问权
		System.out.println(fieldY.get(rp));	
		
		//将String成员变量的字符串内容中的'b'改为'a'
		changeStringValue(rp);
		System.out.println(rp);		
	}
	public static void changeStringValue(Object obj)throws Exception{
		Field[] fields=obj.getClass().getFields();//获取所有成员变量
		for(Field field:fields){
			//if(field.getType().equals(String.class))//equals不如==意义准确
			if(field.getType()==String.class){
				String oldValue=(String)field.get(obj);
				String newValue=oldValue.replace('b', 'a');
				field.set(obj, newValue);
			}
		}
	}

}
输出结果:
3
5
aall:aasketaall:itcast

五、Method
Method类代表类中的成员方法。它可以用于调用某个具体实例对象的某个方法。

1、获得成员方法

获得某个类所有公共成员方法:getMethods()。例如,

Method[] strMethods=Class.forName("java.lang.String").getMethod();
获得某一个成员方法:getMethod(String name,Class<?>... parameterTypes)。 通过方法名和参数列表确定方法。例如,

Method charAt=Class.forName("java.lang.String").getMethod("charAt",int.class);
2、调用方法:

普通方式:

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

1)静态方法

System.out.println(method.invoke(null,1));//该方法不需要对象,说明该方法是静态方法
2)JDK1.4与JDK1.5中invoke方法的区别:

JDK1.5:public Object invoke(Object obj,Object...args) 。采用可变参数。

JDK1.4:public Object invoke(Object obj,Object[] args) 。采用数组。

因为java需要向前兼容以前的老代码,所以当将一个数组作为参数传递给invoke方法时,java会按JDK1.4的语法进行处理,即将数组拆开,数组中的每个元素分别对应被调用方法的一个参数。所以,调用charAt()方法的代码也可以用JDK1.4语法改写为,

charAt.invoke("str",new Object[]{1});

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

package com.cn.itcast;

import java.lang.reflect.Method;

public class Test {

	public static void main(String[] args)throws Exception {
		//普通方式调用main方法
		ArgumentsTest.main(new String[]{"111","222","333"});
		//反射方式调用main方法
		String className=args[0];//运行时手动传入类名
		Method mainMethod=Class.forName(className).getMethod("main", String[].class);
		//mainMethod.invoke(null, new String[]{"111","222","333"});//IllegalArgumentException: wrong number of arguments
		
		//解决方法一
		mainMethod.invoke(null, (Object)new String[]{"111","222","333"});
		//解决方法二
		mainMethod.invoke(null, new Object[]{new String[]{"111","222","333"}});
	}

}
class ArgumentsTest{
	public static void main(String[] args){
		for(String arg:args){
			System.out.println(arg);
		}
	}
}
输出结果:

111
222
333
111
222
333
111
222
333

此例中,第一次反射调用时报错是因为invoke方法 按JDK1.4的语法处理传进来的字符串数组参数,即把数组打散成若干个单独的参数,所以运行后会报出“参数个数不匹配”的异常。解决方法一,将数组转为Object后,编译器就会作特殊处理,编译时不会把参数当做数组看待,也就不会把数组打散成若干个参数;解决方法二,则在字符串数组外再包了一个Object数组,它主动让编译器把外面那个Object数组拆掉,也同样实现了传递一个字符串数组的效果。


六、数组的反射

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

2、代表数组的Class实例对象的getSuperclass()方法返回的父类为Object.Class

3、基本类型的一维数组可以被当做Object类型使用,但不能当做Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。

4、Arrays.asList()方法处理int[]和String[]时的差异。

5、Array工具类用于完成对数组的反射操作。

例4:

package com.cn.itcast;

import java.lang.reflect.Array;
import java.util.Arrays;

public class ArrayReflect {

	public static void main(String[] args) throws Exception{
		int[] a1=new int[]{1,2,3};
		int[] a2=new int[]{1,2,3,4};
		int[][] a3=new int[][]{{1,2},{1,2,3}};
		String[] a4=new String[]{"a","b","c"};
		
		//具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象
		System.out.println(a1.getClass() == a2.getClass());
		//System.out.println(a1.getClass() == a3.getClass());//直接编译器报错,认为两者不相同
		//System.out.println(a1.getClass() == a4.getClass());//同上
		
		//代表数组的Class实例对象的getSuperclass()方法返回的父类为Object.Class
		System.out.println(a1.getClass().getSuperclass().getName());
		System.out.println(a4.getClass().getSuperclass().getName());
		
		//基本类型的一维数组可以被当做Object类型使用,但不能当做Object[]类型使用;
		//非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用
		Object aObj1=a1;
		Object aObj2=a4;
		//Object[] aObj3=a1;编译器报错,基本类型的一维数组不能看做Object[]
		Object[] aObj4=a3;
		Object[] aObj5=a4;
		
		//Arrays.asList()方法处理int[]和String[]时的差异
		System.out.println(a1);
		System.out.println(a4);
		System.out.println(Arrays.asList(a1));
		System.out.println(Arrays.asList(a4));
		
		//Array工具类用于完成对数组的反射操作
		printObject(a4);
		printObject("abc");
	}

	private static void printObject(Object obj) {
		Class clazz=obj.getClass();
		if(clazz.isArray()){
			int len=Array.getLength(obj);
			for(int i=0;i<len;i++){
				System.out.println(Array.get(obj, i));
			}
		}else{
			System.out.println(obj);
		}
		
	}

}
输出结果:

true
java.lang.Object
java.lang.Object
[I@2e739136
[Ljava.lang.String;@2542880d
[[I@2e739136]
[a, b, c]
a
b
c
abc

七:反射的作用:实现框架功能

1、框架与工具类
我做房子卖给用户住,由用户自己安装门窗。我做的房子就是框架,用户需要使用我的框架,把门窗插入我提供的框架中。用户自己又买了锁,锁被装在门上。

框架与工具类有区别:房子相当于框架,锁相当于工具类,门相当于用户自己的类。框架调用用户提供的类,而工具类被用户的类调用。

2、框架要解决的核心问题

我在写框架(房子)时,用户尚不知在何处。我写的框架程序怎样才能够调用到用户以后写的类(门窗)呢?

这就需要反射。因为在写程序时无法知道要被调用的类名,所以程序中无法直接new出某个类的实例对象。通过反射建立对象,类名由后来的用户传进来。例如,例3。



---------------------- ASP.Net+Unity开发.Net培训、期待与您交流! ----------------------详细请查看:www.itheima.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值