黑马程序员——反射的理解与应用

------<ahref="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------

概述:

反射的基本功能:

Java反射机制是在运行状态中
1,对于任意一个类,都能够知道这个类中的所有属性和方法
2,对于任意一个对象,都能够调用它的任意一个方法和属性
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。简单一句话:反射技术可以对类进行解剖。

反射的基石:Class类

Class类的产生及基本特性

Class类的产生
所有的类文件都有共同属性,所以可以向上抽取,把这些共性内容封装成一个类,这个类就叫Class(描述字节码文件的对象)。
要想对一个类进行内容的获取,必须要先获取该字节码文件的对象。该对象是Class类型。
Class类中就包含属性:
field(字段)、method(方法)、construction(构造函数)
field中有修饰符、类型、变量名等复杂的描述内容,可将此字段封装为对象,获取类中field的内容,叫Field。(Method、Constructor)
Class类描述的信息:
类的名字,类的访问属性,类所属于的包名,字段名称的列表,方法名称的列表等。
字节码:
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码。
不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示。

Class和class的区别

class:Java中的类用于描述一类事物的共性,事物具体属性的值是什么,由此类的实例对象确定,不同的实例对象有不同的属性值。
Class:Java的类上之类。Class是Java程序中各个Java类的总称;它是反射的基石,通过Class类来使用反射。

获取Class对象的三种方式

加载XX.class文件进内存时就被封装成了对象,该对象就是字节码文件对象。如何获取Class对象呢?
方式一:通过Object类的getClass方法进行获取。
Class clazz = new Person().getClass();//Person是一个类名
//麻烦之处:每次都需要具体的类和该类的对象,以及调用getClass方法。
方式二:通过静态属性class来获取
Class clazz = Person.class;//Person是一个类名
//任何数据类型都具备着一个静态的属性class,这个属性直接获取到该类型的对应Class对象。
//比第一种简单,不用创建对象,不用调用getClass方法,但是还是要使用具体的类,和该类中的一个静态属性class完成。
方式三:通过Class类的forName来获取
Class clazz = Class.forName("包名.Person");//Person是一个类名
//这种方式较为简单,只要知道类的名称即可。不需要使用该类,也不需要去调用具体的属性和行为。就可以获取到Class对象了。
//这种方式仅知道类名就可以获取到该类字节码对象的方式,更有利于扩展。
注:
1、九个预定义的Class:
       1)八种基本类型(byte、short、int、long、float、double、char、boolean)的字节码对象和返回值为空类型的void.class。
       2)Integer.TYPE是Integer类的一个常量,它代表此包装类型包装的基本类型的字节码,所以和int.class是相等的。
基本数据类型的字节码都可以用与之对应的包装类中的TYPE常量表示
2、只要是在源程序中出现的类型都有各自的Class实例对象
如int[].class。数组类型的Class的实例对象,可以用Class.isArray()方法判断是否为数组类型的。

Class类中的方法

static Class forName(String className)
//返回与给定字符串名的类或接口的相关联的Class对象。
Class getClass()
//返回的是Object运行时的类,即返回Class对象即字节码对象
Constructor getConstructor()
//返回Constructor对象,它反映此Class对象所表示的类的指定公共构造方法。
Field getField(String name)
//返回一个Field对象,它表示此Class对象所代表的类或接口的指定公共成员字段。
Field[] getFields()
//返回包含某些Field对象的数组,表示所代表类中的成员字段。
Method getMethod(String name,Class… parameterTypes)
//返回一个Method对象,它表示的是此Class对象所代表的类的指定公共成员方法。
Method[] getMehtods()
//返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法。
String getName()
//以String形式返回此Class对象所表示的实体名称。
String getSuperclass()
//返回此Class所表示的类的超类的名称
boolean isArray()
//判定此Class对象是否表示一个数组
boolean isPrimitive()
//判断指定的Class对象是否是一个基本类型。
T newInstance()
//创建此Class对象所表示的类的一个新实例。

反射的应用:

获取并操作字节码实例

public class ReflectTest {
	public static void main(String[] args) throws Exception {//实际开发时要try...catch
	//创建一个对象
		String str1 = "abc";
	//获取字节码的三种方法
		Class cls1 = str1.getClass();
		Class cls2 = String.class;
		Class cls3 = Class.forName("java.lang.String");
		//cls3不处理异常,会发生Unhandled exception type ClassNotFoundException
	//对获取到的字节码进行处理
		System.out.println(cls1 == cls2);//true
		System.out.println(cls1 == cls3);//true
		//三种方式获取到的都是那一份字节码
		
		System.out.println(cls1.isPrimitive());//false,说明String是一个类,而不是基本类型
		System.out.println(int.class.isPrimitive());//true,说明int是基本类型
		
		System.out.println(int.class == Integer.class);//false,说明基本类型和包装类各有各的字节码,不相同
		System.out.println(int.class == Integer.TYPE);//true,Integer.TYPE就是包装类型包装的基本类型的字节码
		System.out.println(int[].class.isPrimitive());//false,说明数组的字节码不是基本类型
		System.out.println(int[].class.isArray());	//true,说明数组的字节码是Array
	}
}

Constructor的反射

import java.lang.reflect.Constructor;//导入reflect包

public class ReflectTest {
	public static void main(String[] args) throws Exception {//实际开发时要try...catch
	//想用反射的方式实现:new String(new StringBuffer("abc"));同样的效果	
		//1.拿到String类的构造方法中的String(StringBuffer buffer)构造方法的字节码
			Constructor constructor1 = String.class.getConstructor(StringBuffer.class);//用构造方法的参数类型来指定需要获得的是哪一个构造方法
		//2.得到字节码后,使用reflect的Constructor类中的newInstance方法去创建一个instance
			String str2 = (String)constructor1.newInstance(/*"abc"*/new StringBuffer("abc"));//用该SBf的时候,还需要传一个同类型SBf的对象进去
			//编译时期只知道这是一个Constructor,并不知道这是String的Constructor,只有在运行时才知道,所以要写(String)constructor1.newInstance
		//3.操作这个由反射建立的构造方法所初始化出来的实例
			System.out.println(str2.charAt(2));//输出:c
	}
}
 

Field的反射

import java.lang.reflect.*;//导入reflect包

public class ReflectTest {
	public static void main(String[] args) throws Exception {//实际开发时要try...catch
		//使用ReflectXY的构造方法初始化该类的两个成员变量
			ReflectXY pt1 = new ReflectXY(3,5);
		//得到字节码后,用Class类的getField方法得到ReflectXY中Field类变量
			Field fieldY = pt1.getClass().getField("y");//用变量的名字来告诉getField方法需要得到的是哪个成员变量
		//使用得到的Field类变量的get方法去取得其实例的值
			//得到public int y的值
				System.out.println("fieldY:" + fieldY);//输出:fieldY:public int collectionDemo.ReflectXY.y
				System.out.println("fieldY.get(pt1):" + fieldY.get(pt1));//输出:fieldY.get(pt1):5
				//fieldY不是对象身上的变量,而是类上,要用它去取某个对象上对应的值
			//得到private int x 的值
				Field fieldX = pt1.getClass().getDeclaredField("x");
				fieldX.setAccessible(true);//暴力反射
				System.out.println("fieldX.get(pt1):" + fieldX.get(pt1));	//输出:fieldX.get(pt1):3
	}
}

class ReflectXY {
	//先自定义两个成员变量,并通过Constructor赋值
		private int x;
		public int y;
		public ReflectXY(int x, int y) {//构造方法由Eclipse使用快捷键自动生成:Alt + Shift + S
			super();
			this.x = x;
			this.y = y;
		}
}

实际应用:
将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"。
import java.lang.reflect.*;//导入reflect包

public class ReflectTest {
	public static void main(String[] args) throws Exception {//实际开发时要try...catch
	//使用ReflectXY的构造方法初始化该类的两个成员变量
		ReflectXY pt1 = new ReflectXY();
			//如果ReflectXY不定义成static类,会报No enclosing instance of type ReflectTest is accessible
			//开头以public class开头的内部类是动态的,而主程序是public static class main。
			//在Java中,类中的静态方法不能直接调用动态方法。只有将某个内部类修饰为静态类,然后才能够在静态类中调用该类的成员变量与成员方法。
	//调用changeStringValue方法,并输出
		changeStringValue(pt1);//输出:ReflectXY [str1=aall, str2=aasketaall, str3=itcast]
		System.out.println(pt1);//因为println底层调用的是toString方法,所以在ReflectXY中需要覆写
	}
	
	
	private static void changeStringValue(Object obj) throws Exception {
		//用Class类中的getFields方法,把所得到的Fileds放入到Field类型的数组中
			Field[] fields = obj.getClass().getFields();
		//遍历该Field类型数组
			for(Field field : fields){
			//判断field实例中属于String的字节码
				//if(field.getType().equals(String.class)),field.getType()也是String.class,equals比较好比是两个东西,看看是不是一样
				if(field.getType() == String.class){//对字节码的比较应该用等号比较,因为两者是同一份字节码,语意更准确
					String oldValue = (String)field.get(obj);//用Field类中的get方法得到实例
					String newValue = oldValue.replace('b', 'a');//可以直接使用得到的oldValue实例中的replace方法替换,并存在newValue中
					field.set(obj, newValue);//用Field类中的set方法,将传入的obj设置为newValue
				}
			}
	}
	
	static class ReflectXY {
		//定义三个String类型的成员变量
			public String str1 = "ball";
			public String str2 = "basketball";
			public String str3 = "itcast";	
		//Override toString method
			@Override
			public String toString() {//Eclipse自动生成的覆盖后的toString方法
				return "ReflectXY [str1=" + str1 + ", str2=" + str2 + ", str3=" + str3 + "]";
			}
	}
}

Method的反射

import java.lang.reflect.*;
public class ReflectDemo {
	public static void main(String[] args) throws Exception {//实际开发过程中要try..catch
		String str1 = "abc";
	//创建一个Method类,得到Class类中的getMethod方法得到的String类中的charAt方法
		Method methodCharAt = String.class.getMethod("charAt", int.class);
		//getMethod(方法名,参数类型),指定参数类型是为了确定使用的是哪一个重载方法
	//使用Method类中的invoke方法来唤醒得到的String的字节码中的charAt方法
		System.out.println(methodCharAt.invoke(str1, 1));//输出:b,invoke方法的第一个参数如果传的是null,说明是一个静态方法
		System.out.println(methodCharAt.invoke(str1, new Object[]{2}));//输出:c
	}
}

数组的反射

import java.lang.reflect.*;
import java.util.Arrays;
public class ReflectArray {
	public static void main(String[] args) {
	//不同类型的数组初始化
		int [] a1 = new int[]{1,2,3};
		int [] a2 = new int[4];
		int[][] a3 = new int[2][3];
		String [] a4 = new String[]{"a","b","c"};;
	//打印输出结果,看字节码是否相同
		System.out.println(a1.getClass().equals(a2.getClass()));//输出:true
		System.out.println(a1.getClass().equals(a4.getClass()));//输出:false
		System.out.println(a1.getClass().equals(a3.getClass()));//输出:false
		System.out.println(a1.getClass().getName());//输出:[I
		System.out.println(a1.getClass().getSuperclass().getName());//输出:java.lang.Object
		System.out.println(a4.getClass().getSuperclass().getName());//输出:java.lang.Object
	//转换为Object数组
		Object aObj1 = a1;
		Object aObj2 = a4;
		//Object[] aObj3 = a1;此处不可行,基本类型不是Object,不能存入Object数组
		Object[] aObj4 = a3;//可行,二维数组中装的是一维数组,一维数组是Object
		Object[] aObj5 = a4;//可行,String是Object
		
		System.out.println(a1);//[I@659e0bfd
		System.out.println(a4);//[Ljava.lang.String;@2a139a55
		System.out.println(Arrays.asList(a1));//[[I@659e0bfd],当成一个Object
		System.out.println(Arrays.asList(a4));	//[a, b, c]
		
		printObject(a4);
		/*程序输出结果:
			a
			b
			c
 		*/
		printObject("xyz");//输出:xyz
	}
	
	private static void printObject(Object obj) {
		Class clazz = obj.getClass();//得到字节码
		if(clazz.isArray()){//判断字节码是否属于数组:boolean java.lang.Class.isArray()
			int len = Array.getLength(obj);//得到数组的长度:java.lang.reflect.Array.getLength
			for(int i=0;i<len;i++){
				System.out.println(Array.get(obj, i));//java.lang.reflect.Array.get
			}
		}else{
			System.out.println(obj);
		}		
	}
}








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值