反射这些事

什么是反射?

在官方给的文档中,反射的概念为:反射允许对成员变量、成员方法和构造函数的信息进行编程访问。
但看这一句话可能是似懂非懂的。那么反射到底该怎么理解呢?
如果一个类相当于一个抽屉,而成员变量、成员方法和构造函数相当于抽屉里的各种东西的话。那么反射就可以看做是一个人,他可以提取任何一个抽屉的任何物品,即反射可以获取任何一个类的所有成员变量、成员方法和构造函数。(包括他们的修饰符、名字、类型、值、参数、对象、抛出的异常、注解)

由此可见,反射最主要的两块内容为:获取和解析

那么反射是如何获取和解析呢?

首先我们要知道Java是集编译与解释为一体的语言,具体过程大概为.java文件经过javac编译为.class(字节码文件),再利用解释器逐行解释执行。这是正常的过程,那么对于反射,当然是反着来。

在反射中,会从**.class(字节码文件)中获取成员变量、成员方法、构造函数**。知道从哪里获取后,那么如何获取呢?
这里则需要先获取一个class对象,利用这个class对象来获取成员变量、成员方法和构造函数。

如何获取class对象呢?

一般有三种方式:

  1. Class.forName(“全类名”);
  2. 类名.class
  3. 对象.getClass();

那么这三种方式该什么时候使用?
我们上面也说了Java的运行过程,那么其实还可以进一步分析:

  • 在Demo.java文件编译到Demo.class文件的过程中其实是在硬盘上执行的,此时并没有加载到内存,因此此时可用第一种方式。
  • 在编译后会把Demo.class加载到内存,此时处于加载阶段则可用第二种方式。
  • 在加载进内存之后,此时可以解释执行,例如可以创建对象Demo d = new Demo();,此时为运行阶段可用第三种方式

如果还不懂也不用着急,只用记着:

  • 知道类的全路径时使用第一种方法,这里假设一个类的路径为com.demo.haha,那么此时可以Class clz =
    Class.forName(“com.demo.haha”);
  • 在编译前就知道要操作的类使用第二种,此时已经被加入进了内存,因此如果知道要操作的类的话就无需定位整个类的路径,假设这里要操作Banana类,那么Class clz = Banana.class;
  • 运行时使用类的对象的getClass(),该方法一般处于运行时,即我们已经用该类创建了一个对象,再利用该对象得到class对象。例如Banana b = new Banana(“Hello”); Class clz = b.getClass();

如何利用Class对象获取成员变量,成员方法,构造函数?
首先,我们可以使用Class对象的 newInstance() 方法创建类的对象:

Class clz = Banana.class;
Banana banana = (Banana)clz.newInstance();

但是很多情况下构造函数都是有参数的,而利用Class对象的newInstance() 方法创建的对象只能使用默认的无参构造方法。所以这里反射机制还提供了一个 Constructor 对象,Constructor 对象的newInstance() 方法则可可以创建对象使用有参数的构造方法。

Class clz = Banana.class;
Constructor constructor = clz.getConstructor(String.class, int.class);//参数为构造方法参数的类型
Banana banana = (Banana)constructor.newInstance("非洲香蕉", 10);

还可以通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,当然这些属性是受保护的属性,如果要获取私有属性则可以使用 getDeclaredFields() 方法:

Class clz = Banana.class;
Field[] fields = clz.getFields();


Class clz = Banana.class;
Field[] fields = clz.getDeclaredFields();


这里给出了获取属性的方法,获取方法和构造函数的可以查看API进行调用不再多说

反射机制利用配置文件

这里还要解释一下如何利用反射机制去处理配置文件的东西,因为这一点比较常用。
假设此时有一个配置文件test.txt里面有内容:
className=com.csdn.Bababa
methodName=shop
这里有一个Bababa类,其内容如下:

public class Bababa {
	public void shop(){
		System.out.println("shop()");
	}
}

这里可以利用反射机制和配置文件来优化代码:

public class Demo {
	public static void main(String[] args) throws Exception {
		//获取Class对象
		Class clz = Class.forName(getValue("className"));//类的全路径
		//2获取show()方法
		Method m = clz.getMethod(getValue("methodName"));//文方法名  
		//3.调用show()方法
		m.invoke(clz.getConstructor().newInstance());
}

	//此方法接收一个key,在配置文件中获取相应的value
	public static String getValue(String key) throws IOException{
		Properties pro = new Properties();//获取配置文件的对象
		FileReader in = new FileReader("test.txt");//获取输入流
		pro.load(in);//将流加载到配置文件对象中
		in.close();
		return pro.getProperty(key);//返回根据key获取的value值
	}
}

此时当我们需要优化代码时,比如需要一个新的类Apple,此时如果不用反射就需要写一个Apple类后在后面手动实例化Apple类的对象,当代码量大时就不易管理,而利用反射机制,这里就可以写完Apple类后,只修改配置文件test.txt中的内容,className=com.csdn.Apple
methodName=shop。

注解与反射

最后,在Java中,注解的实现也是利用了反射,在使用Spring时,我们加入一个@Component注解时就能声明一个Spring Bean类,⼀个 @Value 注解就读取到配置⽂件中的值。这些都是因为动态代理,动态代理则是依赖于反射实现。
简单来说动态代理就是可以无侵入式的给代码增加额外的功能,例如一个歌唱家去演出,举办方虽然可以直接找歌唱家商量,但是场地和收费这些东西其实完全可以找人代理,因此就有了助理这个岗位,直接找歌唱家如果商量的事情多或者找的举办方比较多那么就比较麻烦,因此需要一个代理。
而动态代理则基于此原理,把一个公共的事情交给动态代理,举办方找助理商量收费了演出场地等事项,助理喊歌唱家来演出。 这里则是业务找到动态代理,动态代理这个模块在前面都运行好后调用待使用的模块(即调用歌唱家类的唱歌方法)
很明显动态代理是基于反射实现的!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值