反射基础、原理、机制总结

1、反射的基础

什么是反射?

反射就是把Java类中的各种成分映射成相应的Java类[用这个类的实例对象来表示];

[例] 把汽车看成(Class)类,里面成分:发动机[method方法],车轮[Field成员变量] ,组合[constructor构造方法];而表示成分的方法:就是用这个类的实例对象,通过调用汽车(Class)类的方法得到这些实例对象; 例如:汽车.class.get发动机();//拿到汽车这个类里mothod方法;

Class类:

Java中各个Java类属于同一类事物,描述这一类事物的Java类名就是Class;

例如:描述人 Person p1 = new Person(); 描述Java类 Class cls = 字节码; //因为Class类没有构造方法(或者这个构造方法是私有的),是不能直接new的;

Class类代表Java类,我们知道Person类的对象可以是张三,李四;那么Class类它的具体实例对象又分别对应的是什么呢?

对应各个类内存中字节码,例如:Person类的字节码,ArrayList类的字节码等等;

当我们在源程序中用到Person这个类的时候,首先通过java编译器把编译好的.class文件放进硬盘上,就是一堆二进制代码,再把这些二进制代码加载到内存里,才可以创建一个个对象;硬盘上每一个字节码就是Class的实例对象;

一个类被类加载器加载到内存中,占用一片存储空间,这个空间里的内容就是类的字节码,不同的类的字节码是不同的,所以他们内存中的内容是不同的,这一个个空间可分别用一个个对象来表示,这些对象显然具有相同类型,这些类型是什么呢?

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

类名.class; 例如:Person.class,System.class等;

对象.getClass(); 例如:p1.getClass,new Date().getClass等;

Class.forName("类名"); 例如:Class.forName("java.util.Date")等;

下面我们用一段代码说明一下:

public class ReflectTest
{
	public static void main(String[] args) throws Exception
	{
		String str1 = "abc";
		
		Class cls1 = String.class;//类名.class得到String类的字节码,那么String.class就是一个对象,这个对象的类型就是Class
		Class cls2 = str1.getClass();//str1是String类的对象,同上得到String类的字节码
		Class cls3 = Class.forName("java.lang.String");//这个要抛异常
		
		//那么上边三个字节码是否属于同一类型呢?
		System.out.println(cls1 == cls2);//true
		System.out.println(cls1 == cls3);//true
	}
}

Java预定义的9个Class实例对象[参考Class.isPrimitive()方法的帮助]:

八种基本数据类型 + void 是Java预定义的九种原始类型;

int.class == Integer.TYPE; [Integer.TYPE 代表int的基本数据类型]

总之,只要在源程序中出现的类型,都有各自的Class实例对象;例如:int[] void ...等;

System.out.println(int.class.isPrimitive()); //true
System.out.println(int.class == Integer.class);//false Integer是包装类,不同的类有不同的字节码文件
System.out.println(float.class == Float.TYPE);//true TYPE代表这个Float包装类型所包装的基本类型字节码
System.out.println(void.class.isPrimitive());//true
System.out.println(int[].class.isPrimitive());//false 不在9种预定义类型范围之内都不属于原始类型
System.out.println(int[].class.isArray());//true 是否属于数组类型Class的实例对象

 

Constructor类:

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

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

import java.lang.reflect.Constructor;


public class Reflect
{
	public static void main(String[] args) throws Exception
	{
		/* new String(new StringBuilder("abc"));
		 * 用StringBuilder对象传给String构造方法,用这个构造方法在new String()
		 * 现在我们要用反射的方式,实现同样的效果:
		 * 先拿到构造方法:String.class代表一个字节码(对象),对象名.getConstructor拿到这个类的构造方法
		 * 再拿到这个构造方法里的参数StringBuilder类型,最后返回给这个对象的相对应的类型 Constructor
		 */
		Constructor cons = String.class.getConstructor(StringBuilder.class);
		/* 这俩行代码用了俩个StingBuilder,第一个表示:StringBuilder选择什么参数的构造方法[是类型],第二个表示:用这个构造方法时候还要传递与之对应的对象进去[是对象]
		 * newInstance是new一个构造方法,如果里面有参数一定不能忘了写![cons.newInstance("abc");错误示范]
		 */ 
		String str = (String)cons.newInstance(new StringBuilder("abc"));
		System.out.println(str.charAt(2)); //c
		/*
		 * 分析这三行代码执行:
		 * 在编译期间,严格进行语法检查,并没有执行"="号右边的代码,只是生成二进制代码(0101),放进内存里,并没有执行;
		 * 编译期间只知道是一个构造方法,并不知道是哪个类上的构造方法,因为newInstance返回类型是Object,而这个构造方法返回的是String类型,所以要强制转换成相应类型;
		 * 在运行期间,才会执行"="号右边的代码,
		 */
	}
}

 

Field[字段或成员变量]:

//我们在ReflectPoint里面定义了public修饰的y,private修饰的x,俩个成员变量
public class ReflectPoint
{
	private int x;
	public int y;
	public ReflectPoint(int x, int y)
	{
		super();
		this.x = x;
		this.y = y;
	}
}

 

		/*
		 * 1:先new个ReflectPoint对象的引用rp出来
		 * 2:再用RellectPoint这个类的引用rp.getClass(),得到ReflectPoint类的字节码
		 * 3:这个字节码当做是一个对象看 .getField("<TYPE>");拿到成员变量
		 * 4:怎么区分具体拿到哪个成员变量,要用类型区分
		 * 5:返回类型Field
		 */
		ReflectPoint rp = new ReflectPoint(3,5);
		Field fieldY = rp.getClass().getField("y");
		//注意,现在fieldY的值是5吗?错!fieldY不是对象身上的变量,而是字节码(类)上,要用它去取某个对象上对应的值;
		System.out.println(fieldY.get(rp));//5
		
		/*
		 * 那么我们再来取私有化的x值
		 */
		Field fieldX = rp.getClass().getDeclaredField("x");
		//System.out.println(fieldX.get(rp));//报错,因为是private修饰的,不允许外界得到
		//我们可以用暴力反射,获取里面信息,把getField("x")设置成getDeclaredField("x");无视里面访问权限修饰的变量
		fieldX.setAccessible(true);//设置true可以访问
		System.out.println(fieldX.get(rp)); //3

下面我来来演示一个,用反射修改String类型的值
首先利用反射修改其他类里面被私有化的String str1,str2,str3的值,将字符‘b’改成’a‘;

//定义3个成员变量,把字符串中的字符b改成a;
private String str1 = "ball";
private String str2 = "bear";
private String str3 = "apple";

@Override
public String toString()
{
 return str1 + ":" + str2 + "::" + str3;
}

定义一个静态的方法,形参:Object obj

public static void ReplStringValue(Object obj) throws Exception
	{
		Field[] fields = obj.getClass().getDeclaredFields();
		for(Field field : fields)
		{
			/*
			 * 这里是field类型,与String类型进行比较
			 * 而比较用"=="比equals更合适,因为String字节码文件就一份,是同一份字节码;
			 */
			if(field.getType() == String.class)
			{
				field.setAccessible(true);
				String oldValue = (String)field.get(obj);
				String newValue = oldValue.replace('b', 'a');
				field.set(obj, newValue);//print: aall:aear::apple
			}
		}
	}


在main()方法里直接调用并且传递指定对象即可实现将String类型的字符串里面'b'改成'a';

 

Method:

//ReflectPoint是我们定义的类,里面有private String str2 = "aear";
//把ReflectPoint.class当做对象调用.getDeclaredField("/*这里传递参数类型*/");
Field f = ReflectPoint.class.getDeclaredField("str2");
f.setAccessible(true);

//get拿到str2里面的值;
String s = (String)f.get(rp);
System.out.println("str2的值是 ::" + s);

//getDeclaredMethod("charAt", int.class); "charAt":表示String类的方法; "int.class":方法的参数,int类型
Method methodCharAt = String.class.getDeclaredMethod("charAt", int.class);
methodCharAt.setAccessible(true);

//methodCharAt.invoke(s,2): "s":表示传递一个对象; "2":表示传递一个参数;如果传递的对象是静态的,写null即可;
//System.out.println(methodCharAt.invoke(s,2)); //print:a; 俩种输入方式,与下面结果一样;
System.out.println(methodCharAt.invoke(s, new Object[]{2}));//print:a; [拿到str2字符串,第2个位置的值]

静态方法调用练习:

import java.lang.reflect.Method;


public class GetMainMethod
{
	public static void main(String[] args) throws Exception
	{
		/*
		 * 我如何在GetMainMethod这个类里的main方法调用TestArgument里的main方法呢?
		 * 一般方法调用:
		 * TestArgument.main(new String[]{"1","a","hi"});
		 * 假设 这个类里有俩个main方法,而你只知道类名,通过main方法传递进来,启动xx类的main方法,要怎么办呢?
		 * 这里可以利用反射,来执行某个类中的main方法;
		 */
		
		String startingClassName = args[0];
		//当我要启动其他类里面的main方法时,通过当前类main方法传递一个参数,通过反射获取其main方法;
		Method methods = Class.forName(startingClassName).getMethod("main", String[].class);
		
		//执行这段代码时会报错俩个错误,因为此时当前main方法还没有可接收的字符串
		//1:我们可以再Run As-->Run Configu..-->Arguments里面加上要传递的类名;
		//2:jdk1.4特性,自动将new String[]数组解包,里面元素会超出范围,解决方案俩种如下:
		//第一种:可以当成对象来传递;第二种:外面在套一层数组,这样即使解包打开后还是一个数组来看待;
		//!methods.invoke(null, new String[]{"a","ab","abc"});
		methods.invoke(null, (Object)new String[]{"a","ab","abc"}); 
		methods.invoke(null, new Object[]{new String[]{"a","ab","abc"}});//null:静态没有对象
		
	}
}

class TestArgument
{
	public static void main(String[] args)
	{
		for(String arg : args)
		{
			System.out.println(arg);
		}
	}
}



import java.lang.reflect.Method;


public class GetMainMethod
{
	public static void main(String[] args) throws Exception
	{
		/*
		 * 我如何在GetMainMethod这个类里的main方法调用TestArgument里的main方法呢?
		 * 一般方法调用:
		 * TestArgument.main(new String[]{"1","a","hi"});
		 * 假设 这个类里有俩个main方法,而你只知道类名,通过main方法传递进来,启动xx类的main方法,要怎么办呢?
		 * 这里可以利用反射,来执行某个类中的main方法;
		 */
		
		String startingClassName = args[0];
		//当我要启动其他类里面的main方法时,通过当前类main方法传递一个参数,通过反射获取其main方法;
		Method methods = Class.forName(startingClassName).getMethod("main", String[].class);
		
		//执行这段代码时会报错俩个错误,因为此时当前main方法还没有可接收的字符串
		//1:我们可以再Run As-->Run Configu..-->Arguments里面加上要传递的类名;
		//2:jdk1.4特性,自动将new String[]数组解包,里面元素会超出范围,解决方案俩种如下:
		//第一种:可以当成对象来传递;第二种:外面在套一层数组,这样即使解包打开后还是一个数组来看待;
		//!methods.invoke(null, new String[]{"a","ab","abc"});
		methods.invoke(null, (Object)new String[]{"a","ab","abc"}); 
		methods.invoke(null, new Object[]{new String[]{"a","ab","abc"}});//null:静态没有对象
		
	}
}

class TestArgument
{
	public static void main(String[] args)
	{
		for(String arg : args)
		{
			System.out.println(arg);
		}
	}
}

 

数组反射应用:

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


public class ReflectArray
{
	public static void main(String[] args)
	{
		int[] a = new int[]{1,2};
		String[] s = new String[]{"a","b"};
		String str = "我不是数组";
		
		System.out.println(Arrays.asList(a));//只能打印出数组里,一个hashCode值 
		System.out.println(Arrays.asList(s));//[a,b] jdk1.4 ...asList(Object[] obj)
		
		printObject(str);//我不是数组
		printObject(a);//1 2
	}

	private static void printObject(Object obj)
	{
		//首先拿到obj字节码文件
		Class cls = obj.getClass();
		
		//判断拿到的obj.getClass是不是数组
		if(cls.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);
		}
	}
}


 

反射缺点:

1:程序性能下降,来看下面例子:

//该方法内部先得到默认的构造法,然后用该构造方法创建实例对象;
//该方法内部具体代码是怎样实现的呢?用到了缓存机制来保存默认构造方法的实例对象;
//如果你刚好选择不带参数的构造方法,那么直接newInstance()就可以了;
Sring obj = (String)Class.forName("java.lang.String").newInstance();

 

2. 反射的作用:

1、实现框架功能:

1)框架与框架要解决的核心问题:

举例说明:

假如我是开发商,我造房子卖给用户住,由用户自己安装门和窗,我做造房子就好比框架,用户需要使用我的框架,把门和窗插入框架中,框架和工具是有区别的,工具类被用户调用,用户自己安装的门调用锁,这是工具,由用户调用,用户造的门,被房子调用,房子是框架,框架则是调用用户提供的类;

2)框架要解决的核心问题:

举例说明:

我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?而我写的框架程序怎样才能调用你以后写的类(门窗)呢?

而当时我在写这个框架(房子)时,无法知道要被调用的类名,所以程序中无法直接new某个类的实例对象,而要用反射的方式去租,所以反射的作用就体现出来了;

 

采用配置文件加反射的方式创建ArrayList和HashSet的实例对象,观察运行后的结果,代码如下:

import java.io.*;
import java.util.*;


public class ReflectProperties
{	
	public static void main(String[] args) throws Exception
	{
		RelPoint n1 = new RelPoint(3,5);
		RelPoint n2 = new RelPoint(3,4);
		RelPoint n3 = new RelPoint(3,5);
		
		
 		//读取配置文件
  		//InputStream ips = new FileInputStream("config.Properties"); //这种方法可以提供OutputStream
  		//配置文件放在包下,如果放在子包下,修改: 包名/config.Properties
  		InputStream ips = ReflectProperties.class.getResourceAsStream("config.Properties");
  		//InputStream ips = ReflectProperties.class.getClassLoader().getResourceAsStream("config.Properties");		
		Properties props = new Properties();
		props.load(ips);
		ips.close();//防止内存泄露
		String className = props.getProperty("className");
		Collection collections = (Collection)Class.forName(className).newInstance();
		
		collections.add(n1);
		collections.add(n2);
		collections.add(n3);
		
		System.out.println(collections.size());//HashSet = 2
,	}
}

//创建这个类的目的,是为了重写HashCode方法,重写了HashCode方法,那么HashSet的size()不是3而是2!因为HashSet不可重复原则,且重写了HashCode并add以后不可修改[例如remove效果不起作用];
class RelPoint
{
	int x,y;

	public RelPoint(int x, int y)
	{
		super();
		this.x = x;
		this.y = y;
	}

	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}

	@Override
	public boolean equals(Object obj)
	{
		if (this == obj) return true;
		if (obj == null) return false;
		if (getClass() != obj.getClass()) return false;
		RelPoint other = (RelPoint) obj;
		if (x != other.x) return false;
		if (y != other.y) return false;
		return true;
	}
	
}

配置文件config.Properties:

className=java.util.ArrayList


3. 使用BeanUtils工具包

BeanUtils工具包是由Apache公司所开发,主要是方便程序员对Bean类能够进行简便的操作。

在使用BeanUtils工具包之前我们需要的Jar包有以下几种:

(1)   BeanUtils相关包

commons-beanutils-1.8.3.jar

commons-beanutils-1.8.3-javadoc.jar

commons-beanutils-1.8.3-javadoc.jar

commons-beanutils-bean-collections-1.8.3.jar

commons-beanutils-core-1.8.3.jar

(2)   Logic4j相关包

commons-logging.jar

log4j.jar


1. 测试类 

import java.util.Date;


public class ReflectPoint
{
	private int x;
	//java.utils.Date;
	private Date birthday = new Date();
	
	//为x,birthday:提供getter/setter方法
	public int getX()
	{
		return x;
	}
	public void setX(int x)
	{
		this.x = x;
	}
	public Date getBirthday()
	{
		return birthday;
	}
	public void setBirthday(Date birthday)
	{
		this.birthday = birthday;
	}
}

2. 使用BeanUtils修改测试类中变量

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;

public class Test
{
	public static void main(String[] args) throws Exception
	{
		//ReflectPoint类中有个private int x的变量,设置里面值为7,并打印
		ReflectPoint refp = new ReflectPoint();
		BeanUtils.setProperty(refp, "x", 7);
		System.out.println(BeanUtils.getProperty(refp, "x"));//7
		
		//利用PropertyUtils修改x值
		PropertyUtils.setProperty(refp, "x", 77);
		System.out.println(PropertyUtils.getProperty(refp, "x")); //77
		
		//以上俩种方法的区别BeanUtils返回类型String,PropertyUtils返回是传递者自身类型;
		//当需要类型转换的时候可以使用PropertyUtils;
		System.out.println("BeanUtils retType: " + BeanUtils.getProperty(refp, "x").getClass().getName());//String
		System.out.println("ProertyUtils retType: " + PropertyUtils.getProperty(refp, "x").getClass().getName());//Integer
		
		//private Date birthday = new Date(); Date里面有setTime()方法,重新设置并打印;
		System.out.println(BeanUtils.getProperty(refp, "birthday.time"));//原来14113...
		BeanUtils.setProperty(refp, "birthday.time", 999);
		System.out.println(BeanUtils.getProperty(refp, "birthday.time"));//999
	}
}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值