在上一篇文章中编写注解处理器的时候就有用到反射,故而在此梳理反射的内容,大体上包括Class的获取、Class对象使用、泛型数组和动态代理等,主流的框架大体上都用到了反射、动态代理一类的,例如spring。
最近遇到了一些事,所以有些懈怠了,这里警示一下。
每种类型都有class
对象,包括每个类、枚举、接口、注解、数组和基本数据类型,当然,还有一个关键字void
的class
对象,在反射中可以通过Class
对象可以拿到对应类的完整信息,源码请查看java.lang.Class
类。在java.lang.reflect
包下存在着一系列关于反射的类或接口,其中类或接口的关系如下所示:
在图中可以很清楚的看到三个类Field
、Method
和Constructor
都继承了类AccessibleObject
,其中有关于私有方法的权限设置,其它部分自己看吧~
Class对象
获取
Class
对象是反射的起点,它提供了操作类的工具,主要用于创建某种类型的对象(其中类型名称由字符串指定)和使用特殊技术实现类的加载(跨网络)。我们可以通过一下四种方式获得Class
对象:
1.通过使用对象的getClass()
方法获得对应的Class
对象。
//得到class对象的几种方式
String test = "test";
Class clazz = test.getClass();
2.使用类字面常量
Class clazz1 = String.class;
3.使用静态方法Class.forName()
查找类的完全限定名
try {
clazz2 = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
4.从某种能返回嵌套类或嵌套接口的反射方法中获取
clazz2.getClasses();
其中第三种方法因为通过字符串获取对象又有不同。通过Class
类的源码可知,它其实是Class<T>
泛型对象,例如String.class
的类型是Class<String>
、int.class
的类型是Class<Integer>
,需要注意的是基本数据类型的Class
对象和包装类型的Class
对象是同一个类的类型的两个不同实例,即int.class
与Integer.class
是同一个类型Class<Integer>
的不同实例。参数化类型共享的是其原始类型的Class
对象,如List<String>.class
与List.class
的Class
对象相同,即类型是Class<List>
。由此可知,上面前两种方式得到的Class
对象是对应的Class<String>
,但是第三种方式Class.forName("java.lang.String")
得到的则是通配符Class<?>
,如果想转换成确切的类型则需要方法asSubclass(Class clazz)
,如下所示:
Class<? extends String> clazz2 = Class.forName("java.lang.String").asSubclass(String.class);
使用
一个普通类从结构上看,主要可能会存在包名、类名、父类,接口、成员字段、构造方法、成员方法、内部类、注解、修饰符,所以也可以通过其对应Class
对象来判断获取这些元素,不止于此,通过Class
对象还可以用于获取类加载器、类标记、资源文件、定位类等。
分开来看,Class
对象存在几个检查自身的方法,如isInterface()
、isArray()
、isEnum()
,还有些如下所示:
//检查判断方法
/** class.isInstance(obj) 如果class obj1 = obj成立的话,返回true*/
public native boolean isInstance(Object obj);
/** class1.isAssignableFrom(class2) 如果class1对应的类与class2对应的类相同或超类*/
public native boolean isAssignableFrom(Class<?> cls);
/** 如果Class对象表示的是八大基本数据类型之一或者Void对象,则返回true*/
public native boolean isPrimitive();
/** 如果Class对象表示的是注解,则返回true*/
public boolean isAnnotation()
/** 如果Class对象表示的是由编译器引入的合成类型,则返回true。
* 合成程序类型指的是在源代码中不存在对应结构的元素*/
public boolean isSynthetic()
/** 如果Class对象表示的是匿名内部类,则返回true*/
public boolean isAnonymousClass()
/** 如果Class对象表示的是局部内部类,则返回true*/
public boolean isLocalClass()
/** 如果Class对象表示的是嵌套类,则返回true*/
public boolean isMemberClass()
/** 返回编码类型为整数的类型修饰符,可以通过Modifier常量和方法对这个数值解码*/
public native int getModifiers()
/** 返回由该类型实现的所有接口对应的Type对象的数组*/
public Type[] getGenericInterfaces()
/** 返回由该类型的超类对应的Type对象的数组*/
public Type getGenericSuperclass()
在上面检查方法中,isAssignableFrom(Class clazz)
方法并不怎么熟悉,故有如下示例:
//isAssignableFrom示例
System.out.println(String.class.isAssignableFrom(Object.class));
System.out.println(Object.class.isAssignableFrom(String.class));
System.out.println(Object.class.isAssignableFrom(Object.class));
输出:
false
true
true
其中类型修饰符Modifier
包括权限访问修饰符(public
、protected
、private
)以及 abstract
、final
、static
等,而基本数据类型修饰符一直是public
和final
的,数组类型的修饰符则是final
的。由于注解属于之后添加的部分,并不在Modifier
中。
除上述部分以外,还有一系列方法用来获取当前对象的成员,包括之前所说的包名、类名、父类,接口、成员字段、构造方法、成员方法、内部类、注解、修饰符等。在Class
类源码中可以看到,其中见的最多的当属
public String getName()
public Field[] getFields()
public Method[] getMethods()
public Constructor<?>[] getConstructors()
public Annotation[] getAnnotations()
public ClassLoader getClassLoader()
public InputStream getResourceAsStream(String name)
如上这几个方法。其中成员字段、方法、构造器返回的都是是public
修饰的字段,如果想获取所有的,则可以使用相应的Declared
方法。对于指定的字段、方法、构造器,都有相应的方法获取,如getField(String name)
,当然,也可以获取指定注解的。
需要说明的是,类名区分简单名和规范名,其中关系如:java.lang.Object
的简单名是Object
,规范名则是java.lang.Object
。
除了上面说的那部分之外,还有一个很重要的方法newInstance()
用来创建对象,其条件是被调用的对象需要拥有无参构造器,如下:
public static void main(String[] arg0) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
Class clazz = Class.forName("Test.Fruit");
Object obj = clazz.newInstance();
System.out.println(obj);
}
输出:
Test.Fruit@3ce53108
Field字段
在反射包下,存在Field
类用来操作对象的字段,如下所示:
Class clazz = Class.forName("Test.Fruit");
Field[] fields = clazz.getDeclaredFields();
for(Field field:fields){
System.out.print(field.getName()+",");
}
Fruit fruit = new Fruit();
Object obj = clazz.newInstance();
Field field = clazz.getDeclaredField("name");
Field field1 = clazz.getDeclaredField("age");
field1.setAccessible(true);
System.out.println("\n获取字段名:"+field.getName());
System.out.println("获取字段名:"+field1.getName());
Object val = field.get(fruit);
Object val1 = field.get(obj);
System.out.println("获取字段值:"+val);
System.out.println("获取字段值1:"+val1);
Object val2 = field1.get(obj);
System.out.println("获取字段1的值:"+val2);
field.set(fruit, "橘子");
field.set(obj, "香蕉");
System.out.println("更新后的字段值:"+fruit.name);
System.out.println("更新后的字段值1:"+((Fruit)obj).name);
输出:
name,age,
获取字段名:name
获取字段名:age
获取字段值:水果
获取字段值1:水果
获取字段1的值:null
更新后的字段值:橘子
更新后的字段值1:香蕉
在如上这个例子中Fruit
类中有两个字段name
和age
,其中age
是private
的,所以对它进行操作的时候需要调用方法field.setAccessible(true)
才可以进行操作否则就会报错,而公有字段则没有这个限制。不管怎么样,这样就将这个类的私有字段暴露出来,违反了封装特性同时带来安全问题,所以一般情况下都是不建议使用反射。
Constructor构造器
从名字就可以看出,这个类指代的是构造器,类中带有一些方法可以获取构造器名、修饰符、参数、参数类型等,当然其中最重要的当属 public T newInstance(Object ... initargs)
方法,这个方法是实现功能的核心。如下所示:
Class clazz = Class.forName("Test.Fruit");
Constructor[] constructors = clazz.getDeclaredConstructors();
System.out.println(constructors.length);
for(Constructor constructor:constructors){
System.out.println(constructor);
}
Constructor constructor = clazz.getDeclaredConstructor(String.class,String.class);
System.out.println(constructor);
Object obj = constructor.newInstance("香蕉","黄色");
Fruit fruit = (Fruit)obj;
System.out.println(fruit.name);
输出:
2
Test.Fruit(java.lang.String,java.lang.String)
Test.Fruit()
Test.Fruit(java.lang.String,java.lang.String)
香蕉
Method方法
很重要的一个部分Method
,其中的核心方法public Object invoke(Object obj, Object... args)
用来调用对象的当前方法,参数不定长,根据实际情况而定,其实目前编程来看很少使用不定参数,因为它带来很多不好的影响。Method
示例如下所示:
Class clazz = Class.forName("Test.Fruit");
Method[] methods = clazz.getDeclaredMethods();
for(Method method:methods){
System.out.println("method name :"+method.getName());
}
Method method = clazz.getDeclaredMethod("getName");
Object obj = clazz.newInstance();
Object ret = method.invoke(obj);
System.out.println("method return :"+ret);
输出:
method name :main
method name :getName
method return :水果
Annotation注解
在上一节已经说过一些注解的反射调用,在写注解处理器的时候通过反射来获取注解的相应信息。详情请看《Java梳理之理解注解》,当然文中说的比较浅,可以针对的去实践。
Modifier修饰符
之前说过,Modifier
类包含了除注解之外的所有修饰符,包括权限访问修饰,如下所示:
abstract
、final
、interface
、native
、private
、protected
、public
、static
、strict
、synchronized
、transient
、volatile
可以通过相应的查询方法查询是否存在某个修饰符,如下所示:
Class clazz = Class.forName("Test.Fruit");
int modifiers = clazz.getModifiers();
System.out.println("this is protected ?:"+Modifier.isProtected(modifiers));
System.out.println("this is public ?:"+Modifier.isPublic(modifiers));
输出:
this is protected ?:false
this is public ?:true
成员类
在Class
类中有如下这样一个方法:
public native Class<?> getDeclaringClass()
而且在Field
、Method
和Constructor
实现的接口Member
中也有这个方法getDeclaringClass()
,用来获取内部类。
类加载
在Java程序运行的时候,JVM
会根据需要加载类,其中用到的则是类加载器,虽然虚拟器实现的细节不同,但是使用还是没有什么问题的。在Class
类中有ClassLoader getClassLoader()
方法,通过该方法可以获取类加载器,通过这个加载器则可以实现类的加载,如下所示:
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class clazz1 = loader.loadClass("java.lang.String");
System.out.println(clazz1);
输出:
class java.lang.String
当然,类加载器还要一个非常有用的方法InputStream getResourceAsStream(String name)
,通过这个方法可以加载配置文件,如下所示:
static {
prop = new Properties();
try {
inputStream = RedisConstant.class.getClassLoader().getResourceAsStream("config.properties");
prop.load(inputStream);
inputStream.close();
inputStream = null;
//操作
...
}
数组
说完这个,可以看一下特殊的对象:数组,在数组中是没有字段和方法的,就算length
也并不是数组实际的字段,所以通过获取字段、方法和构造器来处理数组的时候,返回的都是null
。为了创建数组并对其进行操作,可以使用Array
的静态方法,如下:
//返回componentType类型的数组,长度为length
public static Object newInstance(Class<?> componentType, int length)
//返回componentType类型的多维数组,后面的则是维数dimensions
public static Object newInstance(Class<?> componentType, int... dimensions)
示例如下:
int[] ints = (int[])Array.newInstance(int.class, 10);
等价于
int[] ints1 = new int[10];
int[] dimensions ={4,4};
int[][] ints = (int[][])Array.newInstance(int.class, dimensions);
等价于
int[][] ints1 = new int[4][4];
当然,创建对象这样,那么操作数组的时候也是不同的。在Array
类中,存在Object get(Object array, int index)
和set(Object array, int index, Object value)
方法,通过这两个方法可以对数组进行操作。
代理Proxy
Proxy
类允许在运行时创建实现一个或多个接口的类。在这里可以这样理解:调用Proxy
类的静态方法getProxyClass
,调用时传入一个类加载器和一个接口数组,这样就可以得到对应的代理类。代理对象的构造器需要传递InvocationHandler
对象,如下所示:
Class clazz = Class.forName("Test.Fruit");
Class proxy = Proxy.getProxyClass(clazz.getClassLoader(), clazz.getInterfaces());
Constructor constructor = proxy.getDeclaredConstructor(InvocationHandler.class);
Object obj = constructor.newInstance(new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
}});
}
在invoke
方法中可以处理相应的操作,但是不仅仅是只有这一种方法,Proxy
类还提供了一种简单易懂的代理类创建方式,即,通过newProxyInstance
方法创建,创建时需要多传入一个实现了InvocationHandler
接口的实例即可,如下
public class reflectDemo {
public static void main(String[] arg0) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException{
Fruit fruit = new Fruit();
Proxy.newProxyInstance(fruit.getClass().getClassLoader(), fruit.getClass().getInterfaces(), new FruitInvocationHandler());
}
}
class FruitInvocationHandler implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
}
}
上例都只是简单的原理,并没有实现具体的功能,如果有需要,可以自行根据实际情况做处理。
到这里,大体就把反射给梳理的差不多了,这一部分是之后深入学习各种框架的基础,所以需要多多实践。本人能力有限,文中有误的地方请帮忙指出。
参考
《Java程序设计语言》16章
Java反射机制详解
网友评论