Java反射
对象在运行时会有两种类型,编译时类型和运行时类型,例如:String a = new Name()
,编译时为String
,运行时为Name
。为了准确知道该对象的类型,可以通过instanceof()
方法,但是在什么都不知道的情况下,只能通过反射获取该对象的信息。
获取Class对象
每个类被加载后就会有Class
对象生成,通过该对象就可以访问JVM中的这个类了。这里介绍三种获取Class
对象的方法:
- 使用
Class
类的forName(String clazzName)
静态方法,参数为类的全限定类名。 - 调用
class
属性来获取Class
对象,例如:Human.class
。 - 调用类实例的
getClass()
方法。该方法在java.lang.Object
已定义。
从Class
对象中获取信息
Class类提供了大量实例方法来获取对应的类信息,一些如下:
- Constructor
getConstructor(Class<?> Types):返回对应类的带参的 public
构造器。 - Constructor<?>[ ] getDeclaredConstructors():返回所有构造器,不受访问权限限制。
- Method getDeclaredMethod(String name,Class<?> …parameterTypes):返回带指定形参的方法,访问权限无关。
- Field getField(String name):返回对应类的指定名称的
public
成员变量。
以下用于访问注解
:
A getAnnotation(Class annotationClass):用于获取指定类型的注解,不存在返回
null
Annotation[ ] getAnnotations():返回对应类上所有注解。
A[ ] getAnnoationByType(Class annotationClass):获取修饰该类的、指定的多个注解,例如重复注解。
如下方法访问内部类
:
Class<?>[] getDeclaredClasses():返回类中的全部内部类。
访问外部类
方法:
- Class<?> getDeclaringClass():返回对应类所在的外部类。
- Class<?>[] getInterfaces():返回对应类实现的全部接口。
- Class<? super T> getSuperclass():返回对应类的父类的
Class
对象。
以下用于获取对应类的修饰符、所在包、类名:
- int getModifiers():返回修饰符,返回的整数用
Modifier
工具类来解码。 - Package getPackage():获取类的包名。
- String getName():返回类名
- String getSimpleName():返回类名简称。
还有方法判断类是否为一个接口或注解的,如:boolean isAnnotation()
。上述大量方法都是分好的,很有规律。之所以有大量重复类名的方法,是因为在获取Method
的时候,类重载了许多方法,想要获取指定的方法必须给出相应的形参列表:
1 | clazz.getMethod("info",String.class); |
而获取构造器时无需传入构造器名,只需要给出形参列表即可。
Java8新增的方法参数反射
Java8在java.lang.reflect
包下新增一个Executable
抽象基类,该对象代表可执行的类成员,同时该类派生了Constructor
、Method
两个子类。抽象基类提供了获取修饰方法或构造器注解信息的方法,通过getModifiers()
方法获取该方法或构造器的修饰符。此外有两个方法来得到方法和形参名及个数:
- int getParameterCount():获取构造器或方法的形参个数。
- Parameter[] getParameters():获取该构造器或方法的所有形参。
同时在获取形参parameter
之后,还提供几个方法获取形参的参数信息:
- getModifiers():获取形参修饰符。
- String getName():获取形参名
- Type getParameterizedType():获取带泛型的形参。
- Class<?> getType():获取形参类型
- boolean isNamePresent():返回类的
class
文件中是否包含方法的形参名信息。 - boolean isVarArgs():判断参数是否为个数可变的形参
使用反射生成并操作对象
Class
对象可以获得类的方法(Method对象),构造器(Constructor对象),成员变量(Field对象),这三个类都位于java.lang.reflect
包下,并实现了java.lang.reflect.Member
接口,程序可以提供Method
调用方法,通过Constructor
调用构造器创建实例,能提供Field
对象访问并修改成员变量值。
创建对象
通过反射来生成对象有以下两种方式:
- 通过
Class
对象的newInstance()
方法来创建实例,前提是要有默认构造器。 - 先获取
Constructor
对象,再调用它的newInstance()
方法创建,特点是可以指定构造器创建。
1 | Class<?> clazz = Class.forName(clazzName); |
第二种方法获取指定构造器可以通过getConstructor()
方法来获取指定构造器。
调用方法
通过调用Class
对象的getMethod()
方法可以获取对应的Method
对象,每个对象对应一个方法。Method
对象有一个invoke()
方法,签名如下:
1 | //Object invoke(Object obj,args) |
当通过invoke
方法调用对应方法时,需要有调用该方法的权限。若是private
方法,可以先调用Method
的setAccessible(boolean f)
方法,f为true
,则该Method
使用时取消访问权限检查。
访问成员变量
通过Class
对象的getField()
可以获取该类的成员变量,Field
对象提供了如下方法来读取或设置成员变量值:
getXxx(Object obj)
:获取obj对象的该成员变量的值。Xxx
对应8种基本类型,引用类型则取消后面Xxx
。setXxx(Object obj,Xxx val)
:将obj对象的成员变量设置成val值,同理。
使用两个方法可以访问指定对象的所有成员变量,包括private
修饰的成员变量。
操作数组
java.lang.reflect
包下还提供一个Array
类,Array
对象可以代表数组,创建数组。
static Object
newInstance
(Class<?> componentType,int length):创建一个指定元素、长度的数组。static xxx
getXxx
(Object array,int index):返回array
数组中第index个元素。引用类型为get(Object array,int index)
static void
setXxx
(Object array,int index,Xxx val):将数组第index个元素的值设为val,如果数组元素为引用类型则方法变成set
。
1 | Object arr = Array.newInstance(String.class,10);//多维再添加数字 |
用反射生成JDK动态代理
在Java的java.lang.reflect
包下提供一个proxy
类和InvocationHandle
接口,通过使用代理类和接口可以生成JDK动态代理类和对象。
使用proxy
和InvocationHandle
创建动态代理
Proxy
提供了用于创建动态代理类和对象的静态方法,它是所有动态代理类的父类。如果在程序中为一个或多个接口动态生成实现类,就可以使用Proxy
来创建动态代理类;如果需要为一个或多个接口动态创建实例,也可以使用Proxy
来创建动态代理实例。
static Class<?> getProxyClass(ClassLoader loader,Class? >interface):创建一个代理类对应的
Class
对象,该代理类将实现interfces
所指定的多个接口。static Object newProxyInstance(ClassLoder loder,Class<>[] interfaces,InvocationHandler h):创建一个动态代理对象,该对象实现了一些接口,执行代理对象的每个方法时都会被替换执行
InvocationHandle
对象的invoke
方法。
采用第一个方法生成代理类的时候,如果需要通过代理类创建对象,依然需要传入一个InvocationHandler
对象,即一个代理对象关联一个InvocationHandler
对象。
1 | interface person{ |
动态代理和AOP
由于JDK动态代理只能为接口创建动态代理,所以下面先提供一个Dog接口:
1 | public interface Dog{ |
如果直接使用代理类为该接口创建动态代理对象,则动态代理对象的所有方法的执行效果将完全一样。下面提供一个DogUtil
类。
1 | public class DogUtil{ |
借助于Proxy
和InvocationHandler
就可以实现,当调用info方法和run方法时,系统将自动把两个通用方法插入info
和run
方法中执行。
1 | public class MyInvocationHandler implements InvocationHandler{ |
上面动态代理工厂类提供一个getProxy()
方法,该方法为target
对象生成一个动态代理对象,这个对象与target
实现了同样的接口。当调用动态代理对象的指定方法时,实际上将变为执行MyInvocationHandler
对象的invoke
方法。执行步骤为:
- 创建
DogUtil
实例 - 执行
DogUtil
实例的method1()
方法 - 使用反射以
target
作为调用者执行该方法 - 执行
DogUtil
实例的method2()
方法
当使用动态代理对象来代替target
对象时,代理对象的方法既能插入通用方法,但GDog
方法又没有像过去一样调用method1
和method2
方法。
1 | public class Test{ |
dog
为实际动态代理对象,实现了Dog接口,动态代理可以很容易实现解耦
,这种动态代理在AOP
中称为AOP
代理,AOP
代理包含了目标对象的全部方法。代理中的方法与目标对象的方法有差异:AOP
代理包含的方法可以在执行目标方法之前、之后插入一些通用处理。
反射与泛型
String.class
的类型实际上是Class<String>
,如果类型未知,则使用Class<?>
,反射中使用泛型可以避免生成的对象需要类型转换。前面介绍了Array
类创建数组,其实并不常用,newInstance()
返回一个数组,而不是Object
对象,如果要当String[]
数组使用则要强制类型转换。
1 | public static Object newInstance(Class<?> componentType,int .... dimension) |
如果改为:
1 | public static <T> T[] newInstance(Class<T> componentType,int length) |
则无需类型转换,但是得到参数可变的接收了。
使用反射获取泛型信息
得到成员变量对应的Field
对象后就可以获得具体的数据类型了,首先应该获得所含的成员变量:
1 | Class<?> a = f.getType();//获取成员变量的类型 |
如果成员变量是含有泛型类型,则使用如下方法获取:
1 | Type t = f.getGenericType(); |
之后就可以将Type
对象强制转换成ParametericedType
对象(被参数化的类型),它提供了两个方法:
getRawType()
:返回原始类型,没有泛型信息。getActualTypeArguments()
:返回泛型参数的类型。
getType()
方法只能获取普通类型的成员变量的数据类型,而带泛型的成员变量,应该使用getGenericType()
方法。Type
是java.lang.reflect
包下的接口,代表所有类型的高级接口,Class
是Type
接口的实现类。Type
包括原始类型、参数化类型、数组类型、类型变量和基本类型。