Java的反射

Class类

除了int等基本类型外,Java的其他类型全部都是class(包括interface)。

而class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存。

每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来。

由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。

这种通过Class实例获取class信息的方法称为反射(Reflection)。

获取一个class的Class实例,方法如下图;
在这里插入图片描述

1.Class实例比较和instanceof的差别

用instanceof不但匹配指定类型,还匹配指定类型的子类。而用==判断class实例可以精确地判断数据类型,但不能作子类型比较。

通常情况下,我们应该用instanceof判断数据类型,因为面向抽象编程的时候,我们不关心具体的子类型。只有在需要精确判断一个类型是不是某个class的时候,我们才使用==判断class实例。
在这里插入图片描述

2.获取Class实例基本信息

在这里插入图片描述

3.通过Class实例创建对应类

在这里插入图片描述
上述代码相当于new String()。通过Class.newInstance()可以创建类实例,它的局限是:只能调用public的无参数构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()被调用。

JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。例如:
在这里插入图片描述
当执行Main.java时,由于用到了Main,因此,JVM首先会把Main.class加载到内存。然而,并不会加载Person.class,除非程序执行到create()方法,JVM发现需要加载Person类时,才会首次加载Person.class。如果没有执行create()方法,那么Person.class根本就不会被加载。

访问字段

对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。
通过Class实例获取字段信息。

1.获取Field字段对象

Class类提供了以下几个方法来获取Field字段对象:

  • Field getField(name):根据字段名获取某个public的field(包括父类) Field

  • getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类) Field[]

  • getFields():获取所有public的field(包括父类) Field[]

  • getDeclaredFields():获取当前类的所有field(不包括父类)
    在这里插入图片描述
    在这里插入图片描述

2.获取Field字段对象信息

一个Field对象包含了一个字段的所有信息:

  • getName():返回字段名称,例如,“name”;

  • getType():返回字段类型,也是一个Class实例,例如,String.class;

  • getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。

  • 在这里插入图片描述

3.获取Field字段对象值

利用反射除了可以拿到字段的一个Field实例,还可以拿到一个实例对应的该字段的值;
在这里插入图片描述
上述代码先获取Class实例,再获取Field实例,然后,用Field.get(Object)获取指定实例的指定字段的值。

运行代码,如果不出意外,会得到一个IllegalAccessException,这是因为name被定义为一个private字段,正常情况下,Main类无法访问Person类的private字段。要修复错误,可以将private改为public,或者,在调用Object value = f.get§;前,先写一句f.setAccessible(true);

调用Field.setAccessible(true)的意思是,别管这个字段是不是public,一律允许访问。

有童鞋会问:如果使用反射可以获取private字段的值,那么类的封装还有什么意义?

答案是正常情况下,我们总是通过p.name来访问Person的name字段,编译器会根据public、protected和private决定是否允许访问字段,这样就达到了数据封装的目的。而反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。

此外,setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全。

4.设置Field字段对象值

通过Field实例既然可以获取到指定实例的字段值,自然也可以设置字段的值。如下图;
在这里插入图片描述

调用方法

1.获取Method方法对象。

Class类提供了以下几个方法来获取Method:

  • Method getMethod(name, Class…):获取某个public的Method(包括父类)
  • Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有public的Method(包括父类)
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

2.获取Method方法对象信息

一个Method对象包含一个方法的所有信息:

  • getName():返回方法名称,例如:“getScore”;
  • getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class;
  • getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class,
    int.class};
  • getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

调用构造方法

通过反射来创建新的实例,通常可以调用Class提供的newInstance()方法,但是它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。

1.获取Constructor方法对象

可以通过Class实例获取Constructor对象,它包含一个构造方法的所有信息,Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例;通过Class实例获取Constructor的方法如下:

  • getConstructor(Class…):获取某个public的Constructor;
  • getDeclaredConstructor(Class…):获取某个Constructor;
  • getConstructors():获取所有public的Constructor;
  • getDeclaredConstructors():获取所有Constructor。
    在这里插入图片描述

注意Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。

调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。

获取继承关系

有了Class实例,我们还可以获取它的父类的Class;
在这里插入图片描述
此外,对所有interface的Class调用getSuperclass()返回的是null,获取接口的父接口要用getInterfaces();

如果一个类没有实现任何interface,那么getInterfaces()返回空数组。

当我们判断一个实例是否是某个类型时,正常情况下,使用instanceof操作符:

	Object n = Integer.valueOf(123);
	boolean isDouble = n instanceof Double; // false
	boolean isInteger = n instanceof Integer; // true
	boolean isNumber = n instanceof Number; // true
	boolean isSerializable = n instanceof java.io.Serializable; // true

如果是两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom():

	// Integer i = ?
	// true,因为Integer可以赋值给Integer
	Integer.class.isAssignableFrom(Integer.class); 
	// Number n = ?
	// true,因为Integer可以赋值给Number
	Number.class.isAssignableFrom(Integer.class);
	// Object o = ?
	// true,因为Integer可以赋值给Object
	Object.class.isAssignableFrom(Integer.class); 
	// Integer i = ?
	// false,因为Number不能赋值给Integer
	Integer.class.isAssignableFrom(Number.class);

动态代理

动态代码,我们仍然先定义了接口Hello,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。

JDK提供的动态创建接口对象的方式,就叫动态代理。
在这里插入图片描述
在这里插入图片描述
在运行期动态创建一个interface实例的方法如下:

  1. 定义一个InvocationHandler实例,它负责实现接口的方法调用;
  2. 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
  3. 使用的ClassLoader,通常就是接口类的ClassLoader;
  4. 需要实现的接口数组,至少需要传入一个接口进去;
  5. 用来处理接口方法调用的InvocationHandler实例。
  6. 将返回的Object强制转型为接口。

动态代理实际上是JVM在运行期动态创建class字节码并加载的过程,它并没有什么黑魔法,把上面的动态代理改写为静态实现类大概长这样:

public class HelloDynamicProxy implements Hello {
	InvocationHandler handler;
	public HelloDynamicProxy(InvocationHandler handler) {
    	this.handler = handler;
	}
	public void morning(String name) {
    	handler.invoke(
	    	this,
 	    	Hello.class.getMethod("morning", String.class),
	    	new Object[] { name }
	    );
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

*『饶』*

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值